Linux socket编程——select多路复用

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版权协议,转载请附上原文出处链接和本声明。