多路IO转接服务器:不由应用程序自己监听客户端连接,取而代之由内核替应用程序监听文件
- select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024
- 超过限制值后,select采用轮询模型,会降低服务器响应效率
1 /*
2 头文件:#include <sys/select.h>
3 原型:
4 int select(int nfds, fd_set *restrict readfds,fd_set *restrict writefds, fd_set *restrict errorfds,struct timeval *restrict timeout);
5 nfds: 最大描述符+1
6 readfds: 可读事件
7 writefds: 可写事件
8 errorfds: 异常事件
9 timeout: 设置监听的时间
10
11 返回值:成功返回满足条件的总数;失败返回-1,设置errno
12 作用:判断集合中是否有对应的文件描述符发送数据
13
14
15 fd_set set; 设置文件描述符集
16 void FD_CLR(int fd,fd_set *set); 将fd从set中清除
17 void FD_ZERO(fd_set *set); 将set清空
18 void FD_SET(int fd,fd_set *set); 将将fd设置到set集合中去
19
20 int FD_ISSET(int fd,fd_set *set); 判断fd是否在集合中
21 返回值:成功返回0;失败返回-1,设置errno
22 作用:设置文件描述符集合内的成员
23
24 struct timespec {
25 time_t tv_sec; seconds
26 long tv_nsec; nanoseconds
27 };
28 struct timeval {
29 time_t tv_sec; seconds
30 suseconds_t tv_usec; microseconds
31 };
32 */
服务器监听、对可读事件的操作:
34 #include<stdio.h>
35 #include<sys/socket.h>
36 #include<arpa/inet.h>
37 #include<strings.h>
38 #include<unistd.h>
39 #include<ctype.h>
40 #include"fork.h"
41 #define SERV_PORT 8888
42 #define SERV_ADDR "127.0.0.1"
43 void main(void)
44 {
45 //定义listennfd套接字
46 int nfds,listenfd,connfd,sockfd;
47 listenfd=socket(AF_INET,SOCK_STREAM,0);
48 char buf[BUFSIZ];
49
50 struct sockaddr_in serv_sockaddr,clie_sockaddr;
51 socklen_t clie_sockaddr_len=sizeof(clie_sockaddr);
52 bzero(&clie_sockaddr,sizeof(clie_sockaddr));
53 bzero(&serv_sockaddr,sizeof(serv_sockaddr));
54 serv_sockaddr.sin_family=AF_INET;
55 serv_sockaddr.sin_port=htons(SERV_PORT);
56 serv_sockaddr.sin_addr.s_addr=htonl(INADDR_ANY);
57 //绑定listenfd套接字
58 bind(listenfd,(struct sockaddr *)&serv_sockaddr,sizeof(serv_sockaddr));
59 //进行监听
60 listen(listenfd,128);
61 //select转接内核负责监听,并返回请求连接的信息
62 //client[]将所添加的监听文件描述符放入,以便搜索可连接的sock文件描述符
63 int client[FD_SETSIZE];
64 //readset用于添加读事件文件描述符oldset用于保存readset的设置
65 //(调用select会将readset不满足的描述符删除)
66 fd_set readset,oldset;
67 //起初listenfd即为最大文件描述符
68 nfds=listenfd;
69 //maxno作为client[]的下标
70 int maxno=-1;
71 //用-1初始化client[]
72 int i;
73 for(i=0;i<FD_SETSIZE;i++){
74 client[i]=-1;
75 }
76 //初始化oldset,并添加listenfd文件描述符到oldset保存
77 FD_ZERO(&oldset);
78 FD_SET(listenfd,oldset);
79 //每次监听都重新设置readset内的所需监听的文件描述符
80 //request=select();查看readset中是否有我监听的那个客户端请求连接
81 int request;
82 while(1){
83 readset=oldset;
84 request=select(nfds+1,&readset,NULL,NULL,NULL);
85 if(request<0){//出错打印,退出
86 perror("select error\n");
87 exit(1);
88 }
89 //判断是否有客户端发送请求,有就把客户端的文件描述符放到oldset中
90 if(FD_ISSET(listenfd,&readset)){
91 //当listenfd在readset集合中则表示有对应的客户端请求连接,存在才接收,所以accpet不阻塞
92 connfd=accept(listenfd,(struct sockaddr *)&clie_sockaddr,&clie_sockaddr_len);
93 printf("connect sucessful\n");
94 for(i=0;i<FD_SETSIZE;i++){
95 //将接收到对应的客户端的文件描述符放到自己定义的数组中,方便找到对应的文件描述符
96 if(client[i]<0){
97 client[i]=connfd;
98 break;
99 }
100 }
101 FD_SET(connfd,&oldset);//将connfd放到oldset保存
102 if(connfd>nfds){ //因为nfds只存最大的文件描述符号
103 nfds=connfd;
104 }
105 if(i>maxno){//保证maxno是clinet[]最后一个元素的下标
106 maxno=i;
107 }
108 if(--requset==0){
109 continue;
110 }
111 }
112 for(i=0;i<=maxno;i++){//检查client中文件描述符是否有发送数据
113 if((sockfd=client[i])<0){
114 continue;
115 }
116 if(FD_ISSET(sockfd,&readset)){
117 if((n=read(sockfd,buf,sizeof(buf)))==0){
118 close(sockfd);
119 FD_CLR(sockfd,&oldset);//清除oldset中的connfd文件描述符
120 }else if(n>0){
121 printf("%s",buf);
122 }
123 //如果后续没有需要执行操作的文件描述符,着退回select去,继续监听
124 if(--request==0){
125 break;
126 }
127 }
128 }
129 }
130 close(listenfd);
131 return 0;
132 }
select特点:
- 定义字符集,并设置监听的sock文件描述符,传入select()
- 如果自定义的字符集没有接收到客户端的请求,则相应的fd位翻转为0
- 因此用户需要在传入时,保存原先设置的文件描述集
- 得到传出的集合,需要从文件描述集中自己判断,哪个满足条件
- 因此用户需要再自定义对应的数组,自己进行判断