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还是会被重新拷贝挂载,然后被覆盖,每一次都会重复拷贝挂载。