网络编程(四)

服务器模型

–》运行服务器s,运行一个客户端c1,此时连接成功且通信没有问题,再运行一个客户端c2,发现显示connect OK(说明客户端和服务器进行了三次握手,但不代表服务器就一定能处理客户端的业务—》除非服务器执行了accept后拿到与c2通信的新的套接字),但是c2与服务器的业务对接不了,此时c1选择结束结束业务,此时s也跟着退出了。
实际上:服务器是可以处理多个客户端的业务,其次服务器也不应该因为客户端的退出它自己也退出
因此:服务器模型应该为循环服务器或者并发服务器。

循环服务器

TCP服务器端运行后等待客户端的连接请求。
TCP服务器接受一个客户端的连接后开始处理,完成了客户的所有请求后断开连接
注意:TCP循环服务器同一时刻只能响应一个客户端的请求以及业务的处理。

只有在当前客户的所有请求都完成后,服务器才能处理下一个客户的连接/服务请求。
如果某个客户端一直占用服务器资源,那么其它的客户端都不能被处理。
故:TCP服务器一般很少采用循环服务器模型。

TCP循环服务器设计框架:

socket();
bind();
listen();
while(1)
{
	accept();
	while(1)
	{
		server_com();
	}
}
close();

并发服务器

概念:并发服务器同一时刻可以处理多个客户机的请求
设计思路:并发服务器是在循环服务器基础上优化过来的
如何优化的???
(1)每连接一个客户机,服务器立马创建子进程或者子线程来跟新的客户机通信
(accept之后的),服务器不会与客户端进行通信!!!
(2)IO多路复用技术

select 与epoll可连接最大客户端的数量:
在这里插入图片描述

实现并发服务器的方式:

  1. 多进程
  2. 多线程
  3. select函数
  4. poll和epoll(自己查询)

多进程实现并发服务器

最多可以创建1021个进程

思想:主进程专门用于连接多个客户端的请求,若有一个客户端连接进来,主进程就创建一个子进程,用该子进程来创建客户端的业务数据
在这里插入图片描述
在accept之后,创建子进程
server:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#define BUF_SIZE 20
//tcpserver
int main()
{
	//socket
	int serverfd = socket(AF_INET, SOCK_STREAM,0);
	//判断socket返回值
	if(-1 == serverfd)
	{
		//返回值为-1,代表创建软通道出错,打印出错原因并返回
		perror("socket error");
		return -1;
	}
	//创建软通道成功
	printf("socket ok------\r\n");
	//bind
	struct sockaddr_in stserver;
	//使用struct sockaddr_in结构体接受参数2的参数并强转为struct sockaddr结构体
	stserver.sin_family = AF_INET;
	stserver.sin_port = htons(8888);
	stserver.sin_addr.s_addr = inet_addr("127.0.0.1");
	int ret = bind(serverfd, (struct sockaddr*)&stserver, sizeof(struct sockaddr));
	//判断的返回值
	if(-1 == ret)
	{
		//失败打印失败原因并返回
		perror("bind error");
		return -1;
	}
	//绑定服务端主机成功
	printf("bind ok------\r\n");
	//listen
	ret = listen(serverfd, 5);
	//返回值判断
	if(-1 == ret)
	{
		//失败打印失败原因并返回
		perror("listen error");
		return -1;
	}
	//监听创建成功
	printf("listen ok------\r\n");
	//接受客户端的信息
	struct sockaddr_in stclient;
	//结构体大小
	socklen_t len = sizeof(struct sockaddr);
	//创建收发数据的缓冲区
	char buf[BUF_SIZE] = {0};
	while(1)
	{
		//清空接收数据的缓冲区
		memset(buf, 0, BUF_SIZE);
		//accept
		int newfd = accept(serverfd, (struct sockaddr*)&stclient, &len);
		//返回值为为客户端创建新的软通道
		if(-1 == newfd)
		{
			//创建失败,进行下一次侦听接收
			perror("accept error");
			continue;
		}
		//连接成功并软通道创建成功
		printf("accept ok-----newfd:%d\r\n", newfd);
		//fork
		pid_t pid = fork();
		if(-1 == pid)
		{
			perror("fork error");
			close(newfd);
			continue;
		}
		if(0 == pid)
		{
			while(1)
			{
				memset(buf, 0, BUF_SIZE);
				ret = recv(newfd, buf, BUF_SIZE, 0);
				if(ret <= 0)
				{
					perror("recv error");
					close(newfd);
					exit(0);
				}
				printf("recv%d data:%s\r\n", newfd, buf);
				//send
				send(newfd, buf, BUF_SIZE, 0);
			}
		}
	}
	return 0;
}

client:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BUF_SIZE 20
//功能:TCPclient
int main()
{
	//socket
	int clientfd = socket(AF_INET, SOCK_STREAM, 0);
	if(-1 == clientfd)
	{
		perror("socket error");
		return -1;
	}
	//创建软通道成功
	printf("socket ok----\r\n");
	//connect
	struct sockaddr_in serveraddr;
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(8888);
	serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	int ret = connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in));
	//判断connect的返回值
	if(-1 == ret)
	{
		//失败打印失败原因并返回
		perror("connect error----\r\n");
		return -1;
	}
	//连接服务端成功
	printf("connect ok....\r\n");
	//创建数据缓冲区
	char buf[BUF_SIZE] = {0};
	while(1)
	{
		memset(buf, 0, BUF_SIZE );
		printf("please write:\r\n");
		//从标准输入端口输入数据,存储在buf里
		fgets(buf, BUF_SIZE, stdin);
		//send为发送数据
		ret = send(clientfd, buf, BUF_SIZE, 0);
		//返回值判断
		if(-1 == ret)
		{
			//失败返回原因,不做返回,接受服务端的数据
			perror("send error");
		}
		//清空接收数据的缓冲区
		memset(buf, 0, BUF_SIZE );
		//recv接收数据
		ret = recv(clientfd, buf, BUF_SIZE, 0);
		//返回值判断
		if(-1 == ret)
		{
			//失败返回原因并返回
			perror("recv error");
			return -1;
		}
		//成功打印接受的数据
		printf("recv data : %s\r\n", buf);
	}
	//关闭软通道描述符
	close(clientfd);
	return 0;	
}

在这里插入图片描述

或注意:
子进程不管被杀死或者自己主动选择exit(0)退出,此时子进程的退出资源必须被回收!
如何回收?
思想:让父进程监测子进程的退出状态(信号:SIGCHLD)—》信号注册signal(信号名称,信号处理函数)
代码呈现:

//1、先注册信号
signal(SIGCHLD, handler);
//2、实现信号处理函数handler
void handler(int signum)
{
	//使用waitpid来回收子进程的退出资源
	while(waitpid(-1, NULL, WNOHANG) > 0)
	{
		printf("已成功回收子进程的退出资源!\n");
	}
}

在这里插入图片描述
关于子进程资源一定记得被回收的方法:
在这里插入图片描述
思考:
多进程并发服务器的缺点:每连接一个客户端,就为其创建子进程,客户端数量比较大时,服务器的运行效率就会变低。

多线程实现并发服务器

思想:主进程专门用于连接多个客户端的请求,若有一个客户端连接进来,主进程就创建一个子线程,用该子线程来创建客户端的业务数据

在accept创建子线程

server

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#define BUF_SIZE 20
//tcpserver

void* ThreadFunc(void * arg)
{
	int iClient = *(int *)arg;
	char buf[BUF_SIZE] = {0};
	int ret = -1;
	while(1)
	{
		ret = recv(iClient, buf, BUF_SIZE, 0);
		if(ret <= 0)
		{
			perror("recv error");
			close(iClient);
			pthread_exit("thread exit");
		}
		printf("recv data:%s\r\n", buf);
		send(iClient, buf, BUF_SIZE, 0);
	}
}


int main()
{
	//socket
	int serverfd = socket(AF_INET, SOCK_STREAM,0);
	//判断socket返回值
	if(-1 == serverfd)
	{
		//返回值为-1,代表创建软通道出错,打印出错原因并返回
		perror("socket error");
		return -1;
	}
	//创建软通道成功
	printf("socket ok------\r\n");
	//bind
	struct sockaddr_in stserver;
	//使用struct sockaddr_in结构体接受参数2的参数并强转为struct sockaddr结构体
	stserver.sin_family = AF_INET;
	stserver.sin_port = htons(8888);
	stserver.sin_addr.s_addr = inet_addr("127.0.0.1");
	int ret = bind(serverfd, (struct sockaddr*)&stserver, sizeof(struct sockaddr));
	//判断的返回值
	if(-1 == ret)
	{
		//失败打印失败原因并返回
		perror("bind error");
		return -1;
	}
	//绑定服务端主机成功
	printf("bind ok------\r\n");
	//listen
	ret = listen(serverfd, 5);
	//返回值判断
	if(-1 == ret)
	{
		//失败打印失败原因并返回
		perror("listen error");
		return -1;
	}
	//监听创建成功
	printf("listen ok------\r\n");
	//接受客户端的信息
	struct sockaddr_in stclient;
	//结构体大小
	socklen_t len = sizeof(struct sockaddr);
	//创建收发数据的缓冲区
	char buf[BUF_SIZE] = {0};
	while(1)
	{
		//清空接收数据的缓冲区
		memset(buf, 0, BUF_SIZE);
		//accept
		int newfd = accept(serverfd, (struct sockaddr*)&stclient, &len);
		//返回值为为客户端创建新的软通道
		if(-1 == newfd)
		{
			//创建失败,进行下一次侦听接收
			perror("accept error");
			continue;
		}
		//连接成功并软通道创建成功
		printf("accept ok-----newfd:%d\r\n", newfd);

		//thread
		pthread_t tID = -1;
		if(0 != pthread_create(&tID, NULL, ThreadFunc, &newfd))
		{
			perror("create thread error");
			close(newfd);
			continue;
		}
		printf("create thread ok newfd:%d\r\n", newfd);
	}
	return 0;
}

client:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BUF_SIZE 20
//功能:TCPclient
int main()
{
	//socket
	int clientfd = socket(AF_INET, SOCK_STREAM, 0);
	//判断socket返回值
	if(-1 == clientfd)
	{
		//返回值为-1,代表创建软通道出错,打印出错原因并返回
		perror("socket error");
		return -1;
	}
	//创建软通道成功
	printf("socket ok----\r\n");
	//connect
	struct sockaddr_in serveraddr;
	//使用struct sockaddr_in结构体接受参数2的参数并强转为struct sockaddr结构体
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(8888);
	serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	int ret = connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in));
	//判断connect的返回值
	if(-1 == ret)
	{
		//失败打印失败原因并返回
		perror("connect error----\r\n");
		return -1;
	}
	//连接服务端成功
	printf("connect ok....\r\n");
	//创建数据缓冲区
	char buf[BUF_SIZE] = {0};
	while(1)
	{
		memset(buf, 0, BUF_SIZE );
		printf("please write:\r\n");
		//从标准输入端口输入数据,存储在buf里
		fgets(buf, BUF_SIZE, stdin);
		//send为发送数据
		ret = send(clientfd, buf, BUF_SIZE, 0);
		//返回值判断
		if(-1 == ret)
		{
			//失败返回原因,不做返回,接受服务端的数据
			perror("send error");
		}
		//清空接收数据的缓冲区
		memset(buf, 0, BUF_SIZE );
		//recv接收数据
		ret = recv(clientfd, buf, BUF_SIZE, 0);
		//返回值判断
		if(-1 == ret)
		{
			//失败返回原因并返回
			perror("recv error");
			return -1;
		}
		//成功打印接受的数据
		printf("recv data : %s\r\n", buf);
	}
	//关闭软通道描述符
	close(clientfd);
	return 0;	
}

在这里插入图片描述

思考:
多线程并发服务器的缺点:主线程退出了,子线程也会跟着退出。

select函数实现并发服务器

IO多路复用实现并发

思想分三步:

  1. 构建一张文件描述符集合表,表的大小1024bit,每一个bit位表示一个文件描述符所对应的IO通道是否有数据,若有数据,该bit位为1,若没有数据,该bit位为0.
  2. 使用**select()**函数监控指定范围的文件描述符对应的IO通道,是否有数据发生;如果有数据,返回有几路通道有数据同时更新相应bit位置为1,其他bit位置为0;否则,一直阻塞等待.
  3. 对文件描述符集合表的置位结果做出判断和响应。使用宏函数FD_ISSET(fd, &stFdr);返回值如果是true,则要响应对应文件描述符的IO操作.如果没有被置位,则不处理。
    在这里插入图片描述
    访问顺序是:3435
    在这里插入图片描述
    server端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#define BUF_SIZE 20
//tcp multiplexing
int main()
{
	//socket
	int serverfd = socket(AF_INET, SOCK_STREAM,0);
	//判断socket返回值
	if(-1 == serverfd)
	{
		perror("socket error");
		return -1;
	}
	//创建软通道成功
	printf("socket ok------\r\n");
	//bind
	struct sockaddr_in stserver;
	stserver.sin_family = AF_INET;
	stserver.sin_port = htons(8888);
	stserver.sin_addr.s_addr = inet_addr("127.0.0.1");
	int ret = bind(serverfd, (struct sockaddr*)&stserver, sizeof(struct sockaddr));
	//判断的返回值
	if(-1 == ret)
	{
		//失败打印失败原因并返回
		perror("bind error");
		return -1;
	}
	//绑定服务端主机成功
	printf("bind ok------\r\n");
	//listen
	ret = listen(serverfd, 5);
	//返回值判断
	if(-1 == ret)
	{
		//失败打印失败原因并返回
		perror("listen error");
		return -1;
	}
	//监听创建成功
	printf("listen ok------\r\n");
	//接受客户端的信息
	struct sockaddr_in stclient;
	//结构体大小
	socklen_t len = sizeof(struct sockaddr);
	//创建收发数据的缓冲区
	char buf[BUF_SIZE] = {0};

	//文件描述集合表
	fd_set stFdr;
	//清零
	FD_ZERO(&stFdr);

	FD_SET(serverfd, &stFdr);
    //用来存储最大描述符
	int max = serverfd;

	while(1)
	{
		//select
		//临时文件描述符表
		fd_set stFdrTmp = stFdr;
        //监控所有文件描述符
		ret = select(max+1, &stFdrTmp, NULL, NULL, NULL);
		if(ret < 0)
		{
			perror("select error");
			continue;
		}
		printf("select ok ret=%d\r\n", ret);

		//FD_ISSET

		int i = 0;
		for(; i < max + 1; i++)
		{
			if(FD_ISSET(i, &stFdrTmp))
			{
				if(i == serverfd)
				{
					int newfd = accept(serverfd, (struct sockaddr*)&stclient, &len);
					if(-1 == newfd)
					{
						perror("accept error");
						continue;
					}
					printf("accept ok newfd:%d\r\n", newfd);
                    //将新的描述符,加入到原始文件描述符集合中
					FD_SET(newfd, &stFdr);
                    //更新max
					if(max < newfd) max = newfd;
				}
				else
				{
					ret = recv(i, buf, BUF_SIZE ,0);
					if(ret > 0)
					{
                        printf("recv data;%s\r\n", buf);
						send(i, buf, BUF_SIZE ,0);
					}
					else
					{
						close(i);
						FD_CLR(i, &stFdr);
					}
				}
			}
		}
	}
	return 0;
}

client端:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BUF_SIZE 20
//功能:TCPclient
int main()
{
	//socket
	int clientfd = socket(AF_INET, SOCK_STREAM, 0);
	//判断socket返回值
	if(-1 == clientfd)
	{
		//返回值为-1,代表创建软通道出错,打印出错原因并返回
		perror("socket error");
		return -1;
	}
	//创建软通道成功
	printf("socket ok----\r\n");
	//connect
	struct sockaddr_in serveraddr;
	//使用struct sockaddr_in结构体接受参数2的参数并强转为struct sockaddr结构体
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(8888);
	serveraddr.sin_addr.s_addr = inet_addr("192.168.15.71");
	int ret = connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in));
	//判断connect的返回值
	if(-1 == ret)
	{
		//失败打印失败原因并返回
		perror("connect error----\r\n");
		return -1;
	}
	//连接服务端成功
	printf("connect ok....\r\n");
	//创建数据缓冲区
	char buf[BUF_SIZE] = {0};
	while(1)
	{
		printf("please write:\r\n");
		//从标准输入端口输入数据,存储在buf里
		fgets(buf, BUF_SIZE, stdin);
		//send为发送数据
		ret = send(clientfd, buf, BUF_SIZE, 0);
		//返回值判断
		if(-1 == ret)
		{
			//失败返回原因,不做返回,接受服务端的数据
			perror("send error");
		}
		//清空接收数据的缓冲区
		memset(buf, 0, BUF_SIZE );
		//recv接收数据
		ret = recv(clientfd, buf, BUF_SIZE, 0);
		//返回值判断
		if(-1 == ret)
		{
			//失败返回原因并返回
			perror("recv error");
			return -1;
		}
		//成功打印接受的数据
		printf("recv data : %s\r\n", buf);
	}
	//关闭软通道描述符
	close(clientfd);
	return 0;	
}

在这里插入图片描述

  • 27
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值