select多路复用感觉比之前学的多进程多线程复杂多了
不过还好最终还是理解了。这里做个笔记
代码实现步骤:
1、首先是socket初始化并返回监听套接字(文件描述符)函数,用到socket、bind、listen。
int socket_init(void)
2、然后第二步是先清理select函数监听的集合(描述字的集合),并把监听套接字加入到集合rdset中,因为一开始集合中只有监听套接字的描述字,所以最大值maxfd就是listenfd,但是我们要清楚,不管最大值是谁、是多大,select函数都是在内核里中从0到maxfd-1轮询所有的描述字,是否有发生相关事件。
72 listenfd = socket_init();
73 FD_ZERO(&rdset); //清空集合
74
75 FD_SET(listenfd, &rdset); //将listen监听套接字加入集合
76 maxfd = listenfd;
3、然后进入while(1)循环中
一开始阻塞于select(select等待某个事件发生):或是新客户连接的建立,或是数据、FIN或RST的到达。
如果有客户端请求连接,那么通过FD_ISSET(i, &cur_rdset)就能发现是监听套接字描述字,就执行accept,成功则返回客户端与服务端的通信套接字connfd,最后把这个通信套接字加入到集合rdset中,则下一次循环的时候集合rdset中除了listenfd之外,也有通信套接字connfd了,如果有多个客户端请求连接,则不断循环不断的把各自的通信套接字connfd加入集合rdset中,这样select大妈就能够监视所以的客户端。
while(1)
79 { cur_rdset = rdset;
80 if(select(maxfd+1, &cur_rdset, NULL, NULL, NULL)<0)
81 {
82 printf("select failure:%s\n", strerror(errno));
83 return -1;
84 }
85 printf("select successfully\n");
86 for(i=0;i<=maxfd;i++)
87 {
88 if(FD_ISSET(i, &cur_rdset))//判断指定描述符是否在集合中
89 {
90 if(listenfd == i) //判断是不是监听套接字,如果是,就执行接收客户端的连接(accept),得到通信套接字connfd
91 {
92 if((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) < 0)
93 {
94 printf("accept new client failure:%s\n",strerror(errno));
95 return -1;
96 }
97 printf("accept new client[%d][%s:%d]successfully!\n", connfd, inet_ntoa(client.sin_addr),ntohs(client.sin_port));
98
99 //每接收一个客户端的连接,都要比较一下当前最大的描述符和刚接收到的客户端描述符,取其中大的当作集合rdset中的最大描述符
100 maxfd = maxfd > connfd ? maxfd : connfd;
101 //每接收一个客户端都要把其描述符加到集合中,以便让select大妈监视它/它们。
102 FD_SET(connfd, &rdset);
103 }
4、如果select大妈监听到的是通信套接字发生相关事件,这里只监听有数据到来可读;那么就进行读或写数据操作(客户端与服务端通信),如果客户端断开连接,则把其对应的通信套接字从集合人rdset中移除。就是叫select大妈不要再监视她了。
//如果不是监听套接字,那么就是已经建立连接的通信套接字了,所以就可以直接执行读写操作了,通信结束了之后还要把此通信套接字清理掉
105 else
106 {
107 memset(buff, 0, sizeof(buff));
108 rv = read(i, buff, sizeof(buff));
109 if(rv < 0)
110 {
111 printf("read data from socket[%d]failure:%s\n",i, strerror(errno));
112 return -1;
113 }
114 if(rv == 0)
115 {
116 printf("the socket[%d]get disconnection\n",i);
117 FD_CLR(i, &rdset);
118 close(i);
119 continue;
120 }
121 printf("read data from socket[%d]successfully:%s\n", i, buff);
122 rv = write(i, BUFF, strlen(BUFF));
123 if(rv < 0)
124 {
125 printf("send data to socket[%d]failure:%s\n", i, strerror(errno));
126 close(i);
127 continue;
128 }
129 printf("send data to socket[%d]successfully!\n",i);
130 }
下面是全部代码
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <string.h>
4 #include <arpa/inet.h>
5 #include <netinet/in.h>
6 #include <errno.h>
7 #include <sys/types.h>
8 #include <sys/socket.h>
9
10 #define MAX_LISTEN_QUE 13
11 #define BUFF "hello client , i'm server!"
12
13 //初始化并返回套接字函数,socket、bind、listen
14 int socket_init(void)
15 {
16 int ser_fd = -1;
17 int rv = -1;
18 int on = 1;
19 int port = 9998;
20 struct sockaddr_in ser_addr;
21
22 ser_fd = socket(AF_INET, SOCK_STREAM, 0);
23 if(ser_fd < 0)
24 {
25 printf("create socket failure:%s\n", strerror(errno));
26 return -1;
27 }
28 printf("\ncreate socket[%d]successfully!\n",ser_fd);
29
30 if((rv = setsockopt(ser_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) < 0)
31 {
32 printf("\n设置地址重用失败:%s\n",strerror(errno));
33 return -1;
34 }
35
36 memset(&ser_addr, 0, sizeof(ser_addr));
37 ser_addr.sin_family = AF_INET;
38 ser_addr.sin_port = htons(port);
39 ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
40 rv = bind(ser_fd, (struct sockaddr *)&ser_addr, sizeof(ser_addr));
41 if(rv < 0)
42 {
43 printf("ser_fd[%d]bind port[%d]failure:%s\n", ser_fd, port, strerror(errno));
44 return -1;
45 }
46 printf("\nser_fd[%d]bind port[%d]successfully!\n", ser_fd, port);
47
48 rv = listen(ser_fd, MAX_LISTEN_QUE);
49 if(rv < 0)
50 {
51 printf("\nser_fd[%dlisten port[%d]failure:%s\n", ser_fd, port, strerror(errno));
52 return -1;
53 }
54 printf("\nser_fd[%dlisten port[%d]successfully!\n", ser_fd, port);
55
56 return ser_fd;
57 }
58
59 int main(int argc, char **argv)
60 {
61 int listenfd;
62 int connfd;
63 struct sockaddr_in client;
64 socklen_t len;
65 int i;
66 int maxfd = 0;
67 int rv;
68 int found;
69 char buff[1024] = {};
70 fd_set rdset, cur_rdset;
71
72 listenfd = socket_init();
73 FD_ZERO(&rdset); //清空集合
74
75 FD_SET(listenfd, &rdset); //将listen监听套接字加入集合
76 maxfd = listenfd;
77
78 while(1)
79 { cur_rdset = rdset;
80 if(select(maxfd+1, &cur_rdset, NULL, NULL, NULL)<0)
81 {
82 printf("select failure:%s\n", strerror(errno));
83 return -1;
84 }
85 printf("select successfully\n");
86 for(i=0;i<=maxfd;i++)
87 {
88 if(FD_ISSET(i, &cur_rdset))//判断指定描述符是否在集合中
89 {
90 if(listenfd == i) //判断是不是监听套接字,如果是,就执行接收客户端的连接(accept),得到通信套接字connfd
91 {
92 if((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) < 0)
93 {
94 printf("accept new client failure:%s\n",strerror(errno));
95 return -1;
96 }
97 printf("accept new client[%d][%s:%d]successfully!\n", connfd, inet_ntoa(client.sin_addr),ntohs(client.sin_port));
98
99 //每接收一个客户端的连接,都要比较一下当前最大的描述符和刚接收到的客户端描述符,取其中大的当作集合rdset中的最大描述符
100 maxfd = maxfd > connfd ? maxfd : connfd;
101 //每接收一个客户端都要把其描述符加到集合中,以便让select大妈监视它/它们。
102 FD_SET(connfd, &rdset);
103 }
104 //如果不是监听套接字,那么就是已经建立连接的通信套接字了,所以就可以直接执行读写操作了,通信结束了之后还要把此通信套接字清理掉
105 else
106 {
107 memset(buff, 0, sizeof(buff));
108 rv = read(i, buff, sizeof(buff));
109 if(rv < 0)
110 {
111 printf("read data from socket[%d]failure:%s\n",i, strerror(errno));
112 return -1;
113 }
114 if(rv == 0)
115 {
116 printf("the socket[%d]get disconnection\n",i);
117 FD_CLR(i, &rdset);
118 close(i);
119 continue;
120 }
121 printf("read data from socket[%d]successfully:%s\n", i, buff);
122 rv = write(i, BUFF, strlen(BUFF));
123 if(rv < 0)
124 {
125 printf("send data to socket[%d]failure:%s\n", i, strerror(errno));
126 close(i);
127 continue;
128 }
129 printf("send data to socket[%d]successfully!\n",i);
130 }
131 }
132
133 }
134 }
135
136 }
我觉得用数组来实现的话更复杂一点,但是不用数组的话目前还不知道如何实现判断连接客户端是否达到最大数量
版权声明:本文为weixin_45506125原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。