select函数用法

来源:互联网 发布:数据库管理员是青春饭 编辑:程序博客网 时间:2024/06/08 00:50
首先,看看这个程序服务端设计的基本逻辑,其实非常简单,就在一个while(1)循环里面不停地轮询 accept 和 select函数。
有人可能问,accept不是会阻塞,直到有客户端连接进来的吗?
其实当你的socket套接字设置成非阻塞模式,那么accept也不会阻塞。

1.那怎么弄非阻塞呢?
这就涉及到 fcntl函数。fcntl能改变文件的属性。看看他的原型:int fcntl(int fd, int cmd);失败返回-1(当然这个函数还有很多用途,但这里只谈谈他如何实现非阻塞)
三行代码:
[cpp] view plain copy
  1. long val = fcntl(sockfd,F_GETFL); //把sockfd套接字的属性拿出来给val  
  2. val|=O_NONBLOCK;          //把非阻塞的属性O_NONBLOCK加进去,用或运算  
  3. fcntl(sockfd,F_SETFL,val);    //再把做好的属性val,再加到sockfd中去  
这时候 把sockfd 作为参数给 accept,accept就不会再阻塞。


2.select()函数:select函数能够同时监听多个文件描述符,若其中一个或多个文件描述符有反应(读或写),select就会返回。
原型:int select(nfds, readfds, writefds, errfds, timeout)  

fd_set *readfds, *writefds, *errfds;   //第2,3,4个参数都是文件描述符集,对Socket编程比较有用的是 readfds。  
struct timeval *timeout;               //控制select()如何返回,是非阻塞,还是阻塞一定时间返回,还是直接有文件描述符有响应才返回。

函数参数:
    1.nfs:最大的文件描述符+1,这个不能错,若不能确定,写一个算法找出来,下面提供的代码有
    2.可读文件描述符集。什么意思呢,就是这个集中的文件描述符随时可能给服务器写数据,那作为服务器,就要监测这些文件描述符,若不关心可读,填NULL。
    3.可写文件描述符集。这里不用到,就不说了。若不关心,可填NULL
    4.异常文件描述符集。若不关心,可填NULL。
    5.时间控制结构体。这个结构体里面有2个成员。一个代表秒,一个代表毫秒。两个都设成0表示select将会非阻塞返回。

对此,还有一些列的宏提供给select用:
     1.FD_SET();  用来把文件描述符加到文件描述符集中
     2.FD_ZERO(); 清空文件描述符集中的所有描述符
     3.FD_ISSET();判断某个文件描述符有没有响应。
     注意:select每返回一次后,都要重新清空文件描述符集,和重新把文件描述符加到文件描述符集中。

下面给出服务端的代码:
[cpp] view plain copy
  1. //TCP服务端  
  2. #include"myhead.h"  
  3.   
  4. struct client_list  
  5. {  
  6.     int sock;  
  7.     struct client_list *next;  
  8. };  
  9.   
  10.   
  11. struct client_list *head = NULL;  
  12.   
  13. struct client_list *init_list(struct client_list*head)  
  14. {  
  15.     head = malloc(sizeof(struct client_list));  
  16.     head->sock = -1;  
  17.     head->next = NULL;  
  18.     return head;  
  19. }  
  20.   
  21. //新的客户端加到客户端队列中  
  22. int add_sock(struct client_list*head,int new_sock)  
  23. {  
  24.     struct client_list *p = head;  
  25.     struct client_list *new_node = malloc(sizeof(struct client_list));  
  26.     new_node->sock = new_sock;  
  27.     new_node->next = NULL;  
  28.   
  29.     while(p->next!=NULL)  
  30.     {  
  31.         p = p->next;  
  32.     }  
  33.     p->next = new_node;  
  34.     return 0;  
  35. }  
  36.   
  37. //找出最大的文件描述符  
  38. int find_max(struct client_list*head)  
  39. {  
  40.     struct client_list *p = head->next;  
  41.     if(p==NULL)  
  42.         return 0;  
  43.     int max_sd = p->sock;  
  44.     for(p;p!=NULL;p=p->next)  
  45.     {  
  46.         if(max_sd < p->sock)  
  47.             max_sd = p->sock;  
  48.     }  
  49.     return max_sd;  
  50. }  
  51.   
  52. //信息转发给其他客户端  
  53. int write_to_client(struct client_list*head,char *wbuf,int size)  
  54. {  
  55.     struct client_list *p=head;  
  56.     for(p=head->next;p!=NULL;p=p->next)  
  57.     {  
  58.         write(p->sock,wbuf,size);  
  59.     }  
  60.     return 0;  
  61. }  
  62.   
  63. //当有新的客户端作为新的文件描述符加进来时,显示客户端列表中的所有客户端文件描述符  
  64. void show_client_list(struct client_list*head)  
  65. {  
  66.     struct client_list *p = head;  
  67.     if(p->next == NULL)  
  68.     {  
  69.         printf("IS A EMPTY LIST!\n");  
  70.         return ;  
  71.     }  
  72.     else  
  73.     {  
  74.         puts("client_list is :");  
  75.         for(p =head->next; p!=NULL;p = p->next)  
  76.         {  
  77.             printf("%d ",p->sock);  
  78.         }  
  79.         printf("\n");  
  80.     }  
  81. }  
  82. <pre class="cpp" name="code">//取消退出客户端的结点。  
  83. int del_node(struct client_list*head,int sock)  
  84. {  
  85.     struct client_list *p = head->next;  
  86.     struct client_list *q = head;  
  87.     while(p!=NULL)  
  88.     {  
  89.         if(p->sock == sock)  
  90.         {  
  91.             q->next = p->next;  
  92.             free(p);  
  93.             p = NULL;  
  94.         }  
  95.         else  
  96.         {  
  97.             p = p->next;  
  98.             q = q->next;  
  99.         }  
  100.     }  
  101.     return 0;  
  102. }  
  103.   
  104. int main(int argc, char const *argv[])  
  105. {  
  106.     char rbuf[50]={0};  
  107.     char wbuf[50]={0};  
  108.   
  109.     int sockfd,size,on=1;  
  110.     int new_sock;  
  111.     int max_sd;  
  112.     struct client_list *pos;  
  113.     struct timeval timeout = {0,0};  //设置select为非阻塞返回  
  114.     fd_set fdset;  
  115.     long val;  
  116.   
  117.     head = init_list(head);   //初始化客户端链表。  
  118.     pos = head;  
  119.   
  120.     struct sockaddr_in saddr;  
  121.     struct sockaddr_in caddr;  
  122.     size = sizeof(struct sockaddr_in);  
  123.     bzero(&saddr,size);  
  124.   
  125.     saddr.sin_family = AF_INET;  
  126.     saddr.sin_port = htons(8888);  
  127.     saddr.sin_addr.s_addr = htonl(INADDR_ANY);  
  128.   
  129.     sockfd = socket(AF_INET,SOCK_STREAM,0);  
  130.     setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));//设置socket套接字为复用,不设也可以  
  131.       
  132.     //把sockfd设置为非阻塞  
  133.     val = fcntl(sockfd,F_GETFL);  
  134.     val|=O_NONBLOCK;  
  135.     fcntl(sockfd,F_SETFL,val);  
  136.   
  137.     bind(sockfd,(struct sockaddr*)&saddr,size);  
  138.     listen(sockfd,10);  
  139.     while(1)  
  140.     {  
  141.         new_sock = accept(sockfd,(struct sockaddr*)&caddr,&size);//循环接受新连接的客户端  
  142.         if (new_sock!= -1)  
  143.         {  
  144.             puts("new node come!\n");  
  145.             printf("new_sock = %d\n",new_sock);  
  146.             add_sock(head,new_sock);  
  147.   
  148.             show_client_list(head);  
  149.         }  
  150.   
  151.         max_sd = find_max(head);     //从客户端队列中,找出最大的文件描述符  
  152.         FD_ZERO(&fdset);             //清空文件描述符集  
  153.         pos = head;  
  154.         //把每个套接字加入到集合中  
  155.         if(pos->next != NULL)   //若套接字列表不是空      
  156.         {  
  157.             for(pos=head->next;pos!=NULL;pos=pos->next)  
  158.             {  
  159.                 FD_SET(pos->sock,&fdset);  
  160.             }  
  161.         }  
  162.   
  163.         select(max_sd+1,&fdset,NULL,NULL,&timeout); //等待描述符  
  164.   
  165.         for(pos=head->next;pos!=NULL;pos = pos->next) //检查哪个套接字有响应  
  166.         {  
  167.             if(FD_ISSET(pos->sock,&fdset))           //判断pos->sock这个文件描述符指向的客户端有没有数据写过来  
  168.             {  
  169.                 bzero(rbuf,50);  
  170.                 read(pos->sock,rbuf,50);  
  171.                 printf("%s\n",rbuf);  
  172.                 if(strcmp(rbuf,"quit")==0)  //若客户端发来的信息为quit,则取消这个客户端的结点。  
  173.                 {  
  174.                     del_node(head,pos->sock);      
  175.                 }  
  176.                 write_to_client(head,rbuf,50);     //把写过来的信息转发给队列中的其他客户端。         
  177.   
  178.             }  
  179.         }  
  180.     }  
  181.     return 0;  
  182. }</pre>  
  183. <pre></pre>  
  184. <br>  

客户端代码:
[cpp] view plain copy
  1. //客户端  
  2. #include"myhead.h"  
  3.   
  4. int main(int argc, char const *argv[])  
  5. {  
  6.     int sockfd;  
  7.     char rbuf[50]={0};  
  8.     char wbuf[50]={0};  
  9.     char ipbuf[50]={0};  
  10.     int port;  
  11.     int max_sd;  
  12.   
  13.     int size,on=1;  
  14.     int ret;  
  15.     fd_set fdset;  
  16.     struct sockaddr_in saddr;  
  17.     size = sizeof(struct sockaddr_in);  
  18.   
  19.     saddr.sin_family = AF_INET;  
  20.     saddr.sin_port = htons(8888);  
  21.     saddr.sin_addr.s_addr = inet_addr("192.168.152.128");  
  22.   
  23.     sockfd = socket(AF_INET,SOCK_STREAM,0);  
  24.     setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));  
  25.   
  26.     ret = connect(sockfd,(struct sockaddr*)&saddr,size);  
  27.     if(ret ==0)  
  28.     {  
  29.         printf("connect sucess\n");  
  30.         inet_ntop(AF_INET,(void*)&saddr.sin_addr.s_addr,ipbuf,50);  
  31.         port = ntohs(saddr.sin_port);  
  32.         printf("ip:%s,port:%d\n",ipbuf,port);  
  33.     }  
  34.     else if(ret == -1)  
  35.     {  
  36.         printf("failed to connect\n");  
  37.         return -1;  
  38.     }  
  39.       
  40.     while(1)  
  41.     {  
  42.         FD_ZERO(&fdset);  
  43.         FD_SET(sockfd,&fdset); //把监测服务端的描述符集放到集合中  
  44.         FD_SET(STDIN_FILENO,&fdset); //STDIN_FILENO这个文件描述符用于监测标准输入(键盘)  
  45.         max_sd = sockfd>STDIN_FILENO?sockfd:STDIN_FILENO;  
  46.         select(max_sd+1,&fdset,NULL,NULL,NULL); //这里的select是设置为一直阻塞到有文件描述符发生响应  
  47.   
  48.         if(FD_ISSET(sockfd,&fdset)) //判断是否服务端有数据发过来  
  49.         {  
  50.             bzero(rbuf,50);  
  51.             read(sockfd,rbuf,50);  
  52.             printf("%s\n",rbuf);  
  53.         }  
  54.         if(FD_ISSET(STDIN_FILENO,&fdset))  //判断键盘是否有数据传过来  
  55.         {  
  56.             bzero(wbuf,50);  
  57.             scanf("%s",wbuf);  
  58.             write(sockfd,wbuf,50);         //把键盘传过来的数据发给服务端  
  59.             if(strcmp(wbuf,"quit")==0)    //若键盘过来的数据为quit,则关闭这个客户端  
  60.             {  
  61.                 printf("quit!\n");  
  62.                 return 0;  
  63.             }  
  64.         }  
  65.     }  
  66.     return 0;  
  67. }