linux socket select非阻塞模式多台客户端与服务器通信

时间:2021-11-15 23:59:03

转自:http://blog.csdn.net/tingyuanss/article/details/45189861

select函数原型如下:

int select (int maxfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select系统调用是用来让我们的程序监视多个文件句柄(socket 句柄)的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变

有一片博文写得非常详细易理解http://blog.csdn.net/lingfengtengfei/article/details/12392449。推荐大家看看,这里就不说了。

主要贴代码,参考的也是别人的代码,但是发现有BUG,努力修正后实现多台客户段与一台服务器通信:在非阻塞模式下,服务器和客户端可以*发消息,不必等待回答,目前服务器发的消息,所有客户端都会收到此消息。读者可以自己改一下,让服务器与指定的客户端通信(可以通过键盘输入要通信的客户端编号来控制,或者用栈或队列来保存客户端编号,服务器在分别发送消息):

服务器端代码:

[cpp] view plain copy linux socket select非阻塞模式多台客户端与服务器通信linux socket select非阻塞模式多台客户端与服务器通信
  1. #include<stdio.h>  
  2. #include<stdlib.h>  
  3. #include<netinet/in.h>  
  4. #include<sys/socket.h>  
  5. #include<arpa/inet.h>  
  6. #include<string.h>  
  7. #include<unistd.h>  
  8. #define BACKLOG 5     //完成三次握手但没有accept的队列的长度  
  9. #define CONCURRENT_MAX 8   //应用层同时可以处理的连接  
  10. #define SERVER_PORT 11332  
  11. #define BUFFER_SIZE 1024  
  12. #define QUIT_CMD ".quit"  
  13. int client_fds[CONCURRENT_MAX];  
  14. int main(int argc, const char * argv[])  
  15. {  
  16.     char input_msg[BUFFER_SIZE];  
  17.     char recv_msg[BUFFER_SIZE];  
  18.     //本地地址  
  19.     struct sockaddr_in server_addr;  
  20.     server_addr.sin_family = AF_INET;  
  21.     server_addr.sin_port = htons(SERVER_PORT);  
  22.     server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  
  23.     bzero(&(server_addr.sin_zero), 8);  
  24.     //创建socket  
  25.     int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);  
  26.     if(server_sock_fd == -1)  
  27.     {  
  28.         perror("socket error");  
  29.         return 1;  
  30.     }  
  31.     //绑定socket  
  32.     int bind_result = bind(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));  
  33.     if(bind_result == -1)  
  34.     {  
  35.         perror("bind error");  
  36.         return 1;  
  37.     }  
  38.     //listen  
  39.     if(listen(server_sock_fd, BACKLOG) == -1)  
  40.     {  
  41.         perror("listen error");  
  42.         return 1;  
  43.     }  
  44.     //fd_set  
  45.     fd_set server_fd_set;  
  46.     int max_fd = -1;  
  47.     struct timeval tv;  //超时时间设置  
  48.     while(1)  
  49.     {  
  50.         tv.tv_sec = 20;  
  51.         tv.tv_usec = 0;  
  52.         FD_ZERO(&server_fd_set);  
  53.         FD_SET(STDIN_FILENO, &server_fd_set);  
  54.         if(max_fd <STDIN_FILENO)  
  55.         {  
  56.             max_fd = STDIN_FILENO;  
  57.         }  
  58.         //printf("STDIN_FILENO=%d\n", STDIN_FILENO);  
  59.     //服务器端socket  
  60.         FD_SET(server_sock_fd, &server_fd_set);  
  61.        // printf("server_sock_fd=%d\n", server_sock_fd);  
  62.         if(max_fd < server_sock_fd)  
  63.         {  
  64.             max_fd = server_sock_fd;  
  65.         }  
  66.     //客户端连接  
  67.         for(int i =0; i < CONCURRENT_MAX; i++)  
  68.         {  
  69.             //printf("client_fds[%d]=%d\n", i, client_fds[i]);  
  70.             if(client_fds[i] != 0)  
  71.             {  
  72.                 FD_SET(client_fds[i], &server_fd_set);  
  73.                 if(max_fd < client_fds[i])  
  74.                 {  
  75.                     max_fd = client_fds[i];  
  76.                 }  
  77.             }  
  78.         }  
  79.         int ret = select(max_fd + 1, &server_fd_set, NULL, NULL, &tv);  
  80.         if(ret < 0)  
  81.         {  
  82.             perror("select 出错\n");  
  83.             continue;  
  84.         }  
  85.         else if(ret == 0)  
  86.         {  
  87.             printf("select 超时\n");  
  88.             continue;  
  89.         }  
  90.         else  
  91.         {  
  92.             //ret 为未状态发生变化的文件描述符的个数  
  93.             if(FD_ISSET(STDIN_FILENO, &server_fd_set))  
  94.             {  
  95.                 printf("发送消息:\n");  
  96.                 bzero(input_msg, BUFFER_SIZE);  
  97.                 fgets(input_msg, BUFFER_SIZE, stdin);  
  98.                 //输入“.quit"则退出服务器  
  99.                 if(strcmp(input_msg, QUIT_CMD) == 0)  
  100.                 {  
  101.                     exit(0);  
  102.                 }  
  103.                 for(int i = 0; i < CONCURRENT_MAX; i++)  
  104.                 {  
  105.                     if(client_fds[i] != 0)  
  106.                     {  
  107.                         printf("client_fds[%d]=%d\n", i, client_fds[i]);  
  108.                         send(client_fds[i], input_msg, BUFFER_SIZE, 0);  
  109.                     }  
  110.                 }  
  111.             }  
  112.             if(FD_ISSET(server_sock_fd, &server_fd_set))  
  113.             {  
  114.                 //有新的连接请求  
  115.                 struct sockaddr_in client_address;  
  116.                 socklen_t address_len;  
  117.                 int client_sock_fd = accept(server_sock_fd, (struct sockaddr *)&client_address, &address_len);  
  118.                 printf("new connection client_sock_fd = %d\n", client_sock_fd);  
  119.                 if(client_sock_fd > 0)  
  120.                 {  
  121.                     int index = -1;  
  122.                     for(int i = 0; i < CONCURRENT_MAX; i++)  
  123.                     {  
  124.                         if(client_fds[i] == 0)  
  125.                         {  
  126.                             index = i;  
  127.                             client_fds[i] = client_sock_fd;  
  128.                             break;  
  129.                         }  
  130.                     }  
  131.                     if(index >= 0)  
  132.                     {  
  133.                         printf("新客户端(%d)加入成功 %s:%d\n", index, inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));  
  134.                     }  
  135.                     else  
  136.                     {  
  137.                         bzero(input_msg, BUFFER_SIZE);  
  138.                         strcpy(input_msg, "服务器加入的客户端数达到最大值,无法加入!\n");  
  139.                         send(client_sock_fd, input_msg, BUFFER_SIZE, 0);  
  140.                         printf("客户端连接数达到最大值,新客户端加入失败 %s:%d\n", inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));  
  141.                     }  
  142.                 }  
  143.             }  
  144.             for(int i =0; i < CONCURRENT_MAX; i++)  
  145.             {  
  146.                 if(client_fds[i] !=0)  
  147.                 {  
  148.                     if(FD_ISSET(client_fds[i], &server_fd_set))  
  149.                     {  
  150.                         //处理某个客户端过来的消息  
  151.                         bzero(recv_msg, BUFFER_SIZE);  
  152.                         long byte_num = recv(client_fds[i], recv_msg, BUFFER_SIZE, 0);  
  153.                         if (byte_num > 0)  
  154.                         {  
  155.                             if(byte_num > BUFFER_SIZE)  
  156.                             {  
  157.                                 byte_num = BUFFER_SIZE;  
  158.                             }  
  159.                             recv_msg[byte_num] = '\0';  
  160.                             printf("客户端(%d):%s\n", i, recv_msg);  
  161.                         }  
  162.                         else if(byte_num < 0)  
  163.                         {  
  164.                             printf("从客户端(%d)接受消息出错.\n", i);  
  165.                         }  
  166.                         else  
  167.                         {  
  168.                             FD_CLR(client_fds[i], &server_fd_set);  
  169.                             client_fds[i] = 0;  
  170.                             printf("客户端(%d)退出了\n", i);  
  171.                         }  
  172.                     }  
  173.                 }  
  174.             }  
  175.         }  
  176.     }  
  177.     return 0;  
  178. }  

客户端代码:

[cpp] view plain copy linux socket select非阻塞模式多台客户端与服务器通信linux socket select非阻塞模式多台客户端与服务器通信
  1. #include<stdio.h>  
  2. #include<stdlib.h>  
  3. #include<netinet/in.h>  
  4. #include<sys/socket.h>  
  5. #include<arpa/inet.h>  
  6. #include<string.h>  
  7. #include<unistd.h>  
  8. #define BUFFER_SIZE 1024  
  9.   
  10. int main(int argc, const char * argv[])  
  11. {  
  12.     struct sockaddr_in server_addr;  
  13.     server_addr.sin_family = AF_INET;  
  14.     server_addr.sin_port = htons(11332);  
  15.     server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  
  16.     bzero(&(server_addr.sin_zero), 8);  
  17.   
  18.     int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);  
  19.     if(server_sock_fd == -1)  
  20.     {  
  21.     perror("socket error");  
  22.     return 1;  
  23.     }  
  24.     char recv_msg[BUFFER_SIZE];  
  25.     char input_msg[BUFFER_SIZE];  
  26.   
  27.     if(connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)) == 0)  
  28.     {  
  29.     fd_set client_fd_set;  
  30.     struct timeval tv;  
  31.   
  32.     while(1)  
  33.     {  
  34.         tv.tv_sec = 20;  
  35.         tv.tv_usec = 0;  
  36.         FD_ZERO(&client_fd_set);  
  37.         FD_SET(STDIN_FILENO, &client_fd_set);  
  38.         FD_SET(server_sock_fd, &client_fd_set);  
  39.   
  40.        select(server_sock_fd + 1, &client_fd_set, NULL, NULL, &tv);  
  41.         if(FD_ISSET(STDIN_FILENO, &client_fd_set))  
  42.         {  
  43.             bzero(input_msg, BUFFER_SIZE);  
  44.             fgets(input_msg, BUFFER_SIZE, stdin);  
  45.             if(send(server_sock_fd, input_msg, BUFFER_SIZE, 0) == -1)  
  46.             {  
  47.                 perror("发送消息出错!\n");  
  48.             }  
  49.         }  
  50.         if(FD_ISSET(server_sock_fd, &client_fd_set))  
  51.         {  
  52.             bzero(recv_msg, BUFFER_SIZE);  
  53.             long byte_num = recv(server_sock_fd, recv_msg, BUFFER_SIZE, 0);  
  54.             if(byte_num > 0)  
  55.             {  
  56.             if(byte_num > BUFFER_SIZE)  
  57.             {  
  58.                 byte_num = BUFFER_SIZE;  
  59.             }  
  60.             recv_msg[byte_num] = '\0';  
  61.             printf("服务器:%s\n", recv_msg);  
  62.             }  
  63.             else if(byte_num < 0)  
  64.             {  
  65.             printf("接受消息出错!\n");  
  66.             }  
  67.             else  
  68.             {  
  69.             printf("服务器端退出!\n");  
  70.             exit(0);  
  71.             }  
  72.         }  
  73.         }  
  74.     //}  
  75.     }  
  76.     return 0;  
  77. }  

调试时发现,select函数每次调用后,如果超时,都会把

[cpp] view plain copy linux socket select非阻塞模式多台客户端与服务器通信linux socket select非阻塞模式多台客户端与服务器通信
  1. struct timeval tv 设置为0,这样再次调用select时它会立即返回,根本不会监视socket句柄,导致一个超时的死循环。  
  2. 所以每次调用select之后都要重新给struct timeval tv 赋值。