Linux服务器4 --- select模型服务端代码及select模型的利弊

1、服务端代码

#include<SOCKET_API.h> //在SOCKET_API.h中添加select头文件

/*宏定义*/
#define SERVER_IP "192.168.27.128"
#define SERVER_PORT 8000
#define BUFSIZE 1500
#define TIMEOUT 1
#define BACKLOG 128

int main()
{
	/*网络初始化*/
	struct sockaddr_in serveraddr,clientaddr;
	bzero(&serveraddr,sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(SERVER_PORT);
	inet_pton(AF_INET,SERVER_IP,&serveraddr.sin_addr.s_addr);
	int serverfd = SOCKET(AF_INET,SOCK_STREAM,0);
	BIND(serverfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
	LISTEN(serverfd,BACKLOG);

	/*业务处理数据*/
	int recvsize ,sendsize;
	char buffer[BUFSIZE];
	bzero(buffer,sizeof(buffer));
	int flags; //记录小写转大写数量(业务)
	int clientfd;
	socklen_t addrlen;

	/*SELECT初始化*/
	int maxfd;
	int readycode;
	int clientfd_arry[1023];  //保存客户端clientfd
	fd_set listen_set ,ready_set;  //监听集合与就绪集合

	maxfd = serverfd;
	for(int i=0;i<1023;i++) //初始化数组 方便使用
		clientfd_arry[i]= -1;
	FD_ZERO(&listen_set);//初始化select监听集合
	FD_SET(serverfd,&listen_set);//设置监听serverfd

	printf("Select Server Waiting ...\n");
	while(TIMEOUT){
		ready_set = listen_set; //传入传出分离
		readycode = select(maxfd+1,&ready_set,NULL,NULL,NULL);

		while(readycode) //处理所有就绪
		{
			if(FD_ISSET(serverfd,&ready_set))
			{
				//serverfd就绪?建立连接
				addrlen = sizeof(clientaddr);
				clientfd = ACCEPT(serverfd,(struct sockaddr*)&clientaddr,&addrlen);
				if(maxfd < clientfd)
					maxfd = clientfd;
				for(int i = 0;i < 1023;i++)//add-01
					if(clientfd_arry[i] == -1)
					{
						clientfd_arry[i] = clientfd;
						break;
					}
					FD_SET(clientfd,&listen_set);//add-02
					FD_CLR(serverfd,&ready_set);//此次事件处理完毕后将就绪集合对应位清零
			}
			else
			{
				//clientfd就绪?处理业务
				for(int i = 0;i < 1023;i++)
					if(clientfd_arry[i]!=-1)
						if(FD_ISSET(clientfd_arry[i],&ready_set))
						{
							if((recvsize = RECV(clientfd_arry[i],buffer,sizeof(buffer),0))>0)
							{
								printf("select server recv request size %d\n",recvsize);
								flags = 0;
								while(recvsize > flags)
								{
									buffer[flags] = toupper(buffer[flags]);
									flags++;
								}
								sendsize = SEND(clientfd_arry[i],buffer,recvsize,0);
								printf("select server send response size %d\n",sendsize);
								bzero(buffer,sizeof(buffer));
								FD_CLR(clientfd_arry[i],&ready_set);//此次事件处理完毕后将就绪集合对应位清零
							}
							else if(recvsize == 0)
							{
								//客户端退出
								FD_CLR(clientfd_arry[i],&ready_set);//此次事件处理完毕后将就绪集合对应位清零

								FD_CLR(clientfd_arry[i],&listen_set);//delete-01
								close(clientfd_arry[i]);
								clientfd_arry[i] = -1;//delete-02
							}
							break;
						}
			}
			--readycode;
		}
	}
	close(serverfd);
	return 0;
}

可以处理多个客户端的请求业务,由于我们是本机测试,虽然我们开启多个客户端,但是其实一轮监听下来的就绪数一直是1,(在这个客户端中连接就绪数1,然后发送就绪数1,再发送就绪数还是1,每一轮监听的就绪数都是1)因为我们自己一次只能操作一个客户端,所以造成一种可以一直给一个客户端处理多次的假象,但其实它是每次就绪,处理一次,下轮监听再就绪,再处理,由于单进程限制,不能循环持续处理一个客户端,否则其他客户端无法处理。可以处理多次任务,但是不能持续处理,一次请求,一次就绪,一次处理 ,不能请求一次,多次处理。

在这里插入图片描述

在这里插入图片描述
2、SELECT模型的利弊:
优点:
1)单进程让服务端拥有基本的一对多响应能力。
2)实现较为简单。(模型比较轻量)
3)SELECT跨平台能力较强。(各个系统兼容)
4)如果网络IO监听模型对时间精度有要求,select可以满足需要,支持微妙级别定时监听。
缺点:
1)SELECT受fd_set监听集合类型的影响, 最大监听数为1024。(不能满足服务端高并发需求)
2)SELECT监听采用的轮询方式,(随着轮询数量的增加 ,IO处理性能呈线性下降),轮询模型可能导致事件处理不及时。
3)SELECT启动监听时传入监听集合, 监听到就绪后内核修改为就绪集合,该就绪集合无法作为监听集合再次使用,所以用户必须将传入传出进行分离,比较麻烦。
4)SELECT监听到就绪后只返回就绪的数量,没有返回谁就绪,开发者需要自行遍历查找就绪的socket并处理, 开销较大,比较耗时。
5)SELECT每轮监听,都要向内核空间重新拷贝监听集合, 将集合中设置的socket,挂载到等待队列中设置监听, 这种做法会导致大量重复的拷贝开销与挂载开销。
在用户空间定义监听集合,设置需要监听的socket,将监听集合从用户空间拷贝到内核空间,内核空间有一个内核设备叫做IO设备等待队列。select监听网络IO依赖于这个设备,这个设备轮询监听,将监听集合中设置为1的socket挂载到设备上,socket上发生读事件就绪,内核根据socket是否就绪修改监听集合,将监听集合变为就绪集合,select函数传出。假设第一轮监听两个socket,为A和B,这轮拷贝挂载2个socket,假设第二轮监听了又增加了3个socket,那么第二轮拷贝挂载是5个socket,以前拷贝挂载过的socket还是会被重新拷贝挂载,然后被覆盖,每一次都会重复拷贝挂载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值