Linux网络编程——tcp并发服务器(I/O复用之select

时间:2021-11-30 17:58:21

http://blog.csdn.net/lianghe_work/article/details/46519633

多线程、多进程相比,I/O复用最大的优势是系统开销小,系统不需要建立新的进程或者线程,也不必维护这些线程和进程。


代码示例:

[csharp]  view plain  copy
  1. #include <stdio.h>   
  2. #include <unistd.h>  
  3. #include <stdlib.h>  
  4. #include <errno.h>  
  5. #include <string.h>  
  6. #include <sys/socket.h>  
  7. #include <sys/types.h>  
  8. #include <netinet/in.h>  
  9. #include <arpa/inet.h>  
  10. #include <sys/select.h>  
  11.  
  12. #define SERV_PORT 8080  
  13. #define LIST 20                //服务器最大接受连接  
  14. #define MAX_FD 10              //FD_SET支持描述符数量  
  15.   
  16.   
  17. int main(int argc, char *argv[])  
  18. {  
  19.     int sockfd;  
  20.     int err;  
  21.     int i;  
  22.     int connfd;  
  23.     int fd_all[MAX_FD]; //保存所有描述符,用于select调用后,判断哪个可读  
  24.       
  25.     //下面两个备份原因是select调用后,会发生变化,再次调用select前,需要重新赋值  
  26.     fd_set fd_read;    //FD_SET数据备份  
  27.     fd_set fd_select;  //用于select  
  28.   
  29.     struct timeval timeout;         //超时时间备份  
  30.     struct timeval timeout_select;  //用于select  
  31.       
  32.     struct sockaddr_in serv_addr;   //服务器地址  
  33.     struct sockaddr_in cli_addr;    //客户端地址  
  34.     socklen_t serv_len;  
  35.     socklen_t cli_len;  
  36.       
  37.     //超时时间设置  
  38.     timeout.tv_sec = 10;  
  39.     timeout.tv_usec = 0;  
  40.       
  41.     //创建TCP套接字  
  42.     sockfd = socket(AF_INET, SOCK_STREAM, 0);  
  43.     if(sockfd < 0)  
  44.     {  
  45.         perror("fail to socket");  
  46.         exit(1);  
  47.     }  
  48.       
  49.     // 配置本地地址  
  50.     memset(&serv_addr, 0, sizeof(serv_addr));  
  51.     serv_addr.sin_family = AF_INET;         // ipv4  
  52.     serv_addr.sin_port = htons(SERV_PORT);  // 端口, 8080  
  53.     serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // ip  
  54.   
  55.     serv_len = sizeof(serv_addr);  
  56.       
  57.     // 绑定  
  58.     err = bind(sockfd, (struct sockaddr *)&serv_addr, serv_len);  
  59.     if(err < 0)  
  60.     {  
  61.         perror("fail to bind");  
  62.         exit(1);  
  63.     }  
  64.   
  65.     // 监听  
  66.     err = listen(sockfd, LIST);  
  67.     if(err < 0)  
  68.     {  
  69.         perror("fail to listen");  
  70.         exit(1);  
  71.     }  
  72.       
  73.     //初始化fd_all数组  
  74.     memset(fd_all, -1, sizeof(fd_all));  
  75.   
  76.     fd_all[0] = sockfd;   //第一个为监听套接字  
  77.       
  78.     FD_ZERO(&fd_read);  // 清空  
  79.     FD_SET(sockfd, &fd_read);  //将监听套接字加入fd_read  
  80.   
  81.     int maxfd = fd_all[0];  //监听的最大套接字  
  82.       
  83.     while(1){  
  84.       
  85.         // 每次都需要重新赋值,fd_select,timeout_select每次都会变  
  86.         fd_select = fd_read;  
  87.         timeout_select = timeout;  
  88.           
  89.         // 检测监听套接字是否可读,没有可读,此函数会阻塞  
  90.         // 只要有客户连接,或断开连接,select()都会往下执行  
  91.         err = select(maxfd+1, &fd_select, NULL, NULL, NULL);  
  92.         //err = select(maxfd+1, &fd_select, NULL, NULL, (struct timeval *)&timeout_select);  
  93.         if(err < 0)  
  94.         {  
  95.                 perror("fail to select");  
  96.                 exit(1);  
  97.         }  
  98.   
  99.         if(err == 0){  
  100.             printf("timeout\n");  
  101.         }  
  102.           
  103.         // 检测监听套接字是否可读  
  104.         if( FD_ISSET(sockfd, &fd_select) ){//可读,证明有新客户端连接服务器  
  105.               
  106.             cli_len = sizeof(cli_addr);  
  107.               
  108.             // 取出已经完成的连接  
  109.             connfd = accept(sockfd, (struct sockaddr *)&cli_addr, &cli_len);  
  110.             if(connfd < 0)  
  111.             {  
  112.                 perror("fail to accept");  
  113.                 exit(1);  
  114.             }  
  115.               
  116.             // 打印客户端的 ip 和端口  
  117.             char cli_ip[INET_ADDRSTRLEN] = {0};  
  118.             inet_ntop(AF_INET, &cli_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);  
  119.             printf("----------------------------------------------\n");  
  120.             printf("client ip=%s,port=%d\n", cli_ip,ntohs(cli_addr.sin_port));  
  121.               
  122.             // 将新连接套接字加入 fd_all 及 fd_read  
  123.             for(i=0; i < MAX_FD; i++){  
  124.                 if(fd_all[i] != -1){  
  125.                     continue;  
  126.                 }else{  
  127.                     fd_all[i] = connfd;  
  128.                     printf("client fd_all[%d] join\n", i);  
  129.                     break;  
  130.                 }  
  131.             }  
  132.               
  133.             FD_SET(connfd, &fd_read);  
  134.               
  135.             if(maxfd < connfd)  
  136.             {  
  137.                 maxfd = connfd;  //更新maxfd  
  138.             }  
  139.           
  140.         }  
  141.           
  142.         //从1开始查看连接套接字是否可读,因为上面已经处理过0(sockfd)  
  143.         for(i=1; i < maxfd; i++){  
  144.             if(FD_ISSET(fd_all[i], &fd_select)){  
  145.                 printf("fd_all[%d] is ok\n", i);  
  146.                   
  147.                 char buf[1024]={0};  //读写缓冲区  
  148.                 int num = read(fd_all[i], buf, 1024);  
  149.                 if(num > 0){  
  150.   
  151.                     //收到 客户端数据并打印  
  152.                     printf("receive buf from client fd_all[%d] is: %s\n", i, buf);  
  153.                       
  154.                     //回复客户端  
  155.                     num = write(fd_all[i], buf, num);  
  156.                     if(num < 0){  
  157.                         perror("fail to write ");  
  158.                         exit(1);  
  159.                     }else{  
  160.                         //printf("send reply\n");  
  161.                     }  
  162.                       
  163.                 }  
  164.                 else if(0 == num){ // 客户端断开时  
  165.                       
  166.                     //客户端退出,关闭套接字,并从监听集合清除  
  167.                     printf("client:fd_all[%d] exit\n", i);  
  168.                     FD_CLR(fd_all[i], &fd_read);  
  169.                     close(fd_all[i]);  
  170.                     fd_all[i] = -1;  
  171.                       
  172.                     continue;  
  173.                 }  
  174.                   
  175.             }else {  
  176.                 //printf("no data\n");                    
  177.             }  
  178.         }  
  179.     }  
  180.       
  181.     return 0;  
  182. }  

运行结果:

Linux网络编程——tcp并发服务器(I/O复用之select