linux下用select实现非阻塞socket

  本文并非解释什么是非阻塞socket,也不是介绍socket API的用法, 取而代替的是让你感受实际工作中的代码编写。虽然很简陋,但你可以通过man手册与其它资源非富你的代码。请注意本教程所说的主题,如果细说,内容可以达到一本书内容,你会发现本教程很有用。

 

本教程内容如下:

 

        1. 改变一个阻塞的socket为非阻塞模式。

        2. select模型

        3. FD宏

        4. 读写函数

        5. 写一个非阻塞socket代码片

        6. 整个代码

        7.下一步

 

        如果你在此有许多问题,那么恭喜你,在初级阶段,任何人都没有剥夺你发现问题的权利。关于你所发现的问题,请不要犹豫email我。

        你可以自由发表本教程到任何WWW或FTP网部,但无论如何也要保持原教程的原型。这样我将会非常感谢你。

 

1.改变一个阻塞的socket为非阻塞模式

 

        简单的几行代码就可以创建一个socket 并连接,看起来如此简单。(你可以自己加入错误处理)

  1. s = socket(AF_INET, SOCK_STREAM, 0);  
  2. memset(&sin, 0, sizeof(struct sockaddr_in));  
  3. sin.sin_family = AF_INET;  
  4. sin.sin_port = htons(port);  
  5. sin.sin_addr.s_addr = inet_addr(hstname);  
  6. if(sin.sin_addr.s_addr == INADDR_NONE) {  
  7. connect(s, (struct sockaddr *)&sin, sizeof(sin))  

 

        有很多种方法可以设置socket为非阻塞模式, 我在unix下常用的方法如下:

  1. int x;  
  2. x=fcntl(s,F_GETFL,0);  
  3. fcntl(s,F_SETFL,x | O_NONBLOCK);  

 

        到现在为此, 这个socket已为非阻塞模式了,我们可以把焦点放在如何用它了。 但是在接着写代码之前,我们要看看我们将要用到的命令。

 

2.选择模型

 

        select这个方法用来检测一个socket是否有数据到来或是是否有准备好的数据要发送。声明如下:

  1. select(s, &read_flags, &write_flags, &exec_flags, timer);  

 

s                               socket的句柄

read_flags                读描述字集合。检查socket上是否有数据可读。

write_flags               写描述字集合。检查socket上是否已有数据可发送。

exec_flags                错误描述字集合。(本教程这儿不介绍)

timer                         等待某个条件为真时超时时间。

 

在本教程中,我们将如下用:

  1. select(s, &read_flags, &write_flags, NULL, timer);  

exec_flags参数设为null,因为在我们的程序中我们不需要关心exec事件。(解释它,超出了本教程的范围)

 

3.FD宏

 

        在select方法中的描述字集合是用来检测socket上发生的读取事件的,它用法如下:

  1. FD_ZERO(s, &write_flags)      sets all associated flags  
  2.                               in the socket to 0  
  3. FD_SET(s, &write_flags)       used to set a socket for checking  
  4. FD_CLR (s, &write_flags)      used to clear a socket from  being checked  
  5. FD_ISSET(s, &write_flags)     used to query as to if the socket is ready  
  6.                               for reading or writing.  

 

        如何用,我们在后面的代码中展示。

 

4.读取方法

 

       你应知道如何用下面的两个方法,但是在非阻塞模式下,它们的用法有一点点不同。

  1. write(s,buffer,sizeof(buffer))   send the text in "buffer"  
  2. read(s,buffer,sizeof(buffer))    read available data into "buffer"  

 

        当一个socket用非阻塞模式时,当调用这两个方法的时候,它们将立即返回,比如,如果没有数据的时候,它们将不会阻塞等待数据,而是返回一个错误。从现在开始,我们就要看代码了。

 

5.写一个非阻塞socket代码片

   

       首先声明要用到的变量:

  1. fd_set read_flags,write_flags; // the flag sets to be used  
  2. struct timeval waitd;          // the max wait time for an event  
  3. char buffer[8196];             // input holding buffer  
  4. int stat;                      // holds return value for select();  

       

        我们程序运行的大部份时间都花费在不断调用select(它将花费我们大部份CPU时间),至到有数据准备好读或取。此时,timer就体现了它的意义。它决定select将等待多久。下面就是用法,请仔细分析:

  1. // Insert Code to create a socket  
  2.   
  3. while(1) // put program in an infinite loop of reading and writing data  
  4.  {  
  5.   waitd.tv_sec = 1;  // Make select wait up to 1 second for data  
  6.   waitd.tv_usec = 0; // and 0 milliseconds.  
  7.   
  8.   FD_ZERO(&read_flags); // Zero the flags ready for using  
  9.   FD_ZERO(&write_flags);  
  10.   
  11.   // Set the sockets read flag, so when select is called it examines  
  12.   // the read status of available data.  
  13.   FD_SET(thefd, &read_flags);  
  14.                                       
  15.   // If there is data in the output buffer to be sent then we  
  16.   // need to also set the write flag to check the write status  
  17.   // of the socket  
  18.   if(strlen(outbuff)!=0) FD_SET(thefd, &write_flags);  
  19.   
  20.   // Now call select  
  21.   stat=select(s+1, &read_flags,&write_flags,(fd_set*)0,&waitv);  
  22.   if(stat < 0) {  // If select breaks then pause for 5 seconds  
  23.      sleep(5);    // then continue  
  24.      continue;  
  25.      }  
  26.   // Now select will have modified the flag sets to tell us  
  27.   // what actions can be performed  
  28.   
  29.   // Check if data is available to read  
  30.   if (FD_ISSET(thefd, &read_flags)) {  
  31.     FD_CLR(thefd, &read_flags);  
  32.     // here is where you use the read().  
  33.     // If read returns an error then the socket  
  34.     // must be dead so you must close it.  
  35.     }  
  36.   
  37.   //Check if the socket is prepared to accept data  
  38.   if (FD_ISSET(thefd, &write_flags)) {  
  39.     FD_CLR(thefd, &write_flags);  
  40.     // this means the socket is ready for you to use write()  
  41.     }  
  42.   
  43.   // Now here you can put in any of the precedures that you want  
  44.   // to happen every 1 second or so.  
  45.   
  46.   // now the loop repeats over again  

 

补充:请确保只有在你有数据发送的情况下才设置write_flag这个描述字集合,因为socket一量创建总是可写的。也就是说,如果你设置了这个参数,select将不会等待,而是马上返回并一直循环,它将抢占CPU99%的利用率,这是不允许的。

 

6.整个代码

 

      最后利用我们所学,写一个简单的客户端。当然用非阻塞模式写一个客户端有点大采小用,这儿我们只是为了展示用法。更多示例请看第7节内容。

 

  1. #include <sys/types.h>  
  2. #include <sys/socket.h>  
  3. #include <sys/time.h>  
  4. #include <netinet/in.h>  
  5. #include <netdb.h>  
  6. #include <stdio.h>  
  7. #include <string.h>  
  8. #include <unistd.h>  
  9. #include <stdlib.h>  
  10. #include <fcntl.h>  
  11.   
  12. // this routine simply converts the address into an  
  13. // internet ip  
  14. unsigned long name_resolve(char *host_name)  
  15. {  
  16. struct in_addr addr;  
  17. struct hostent *host_ent;  
  18.   if((addr.s_addr=inet_addr(host_name))==(unsigned)-1) {  
  19.     host_ent=gethostbyname(host_name);  
  20.     if(host_ent==NULL) return(-1);  
  21.     memcpy(host_ent->h_addr, (char *)&addr.s_addr, host_ent->h_length);  
  22.     }  
  23.   return (addr.s_addr);  
  24. }  
  25.   
  26. // The connect routine including the command to set  
  27. // the socket non-blocking.  
  28. int doconnect(char *address, int port)  
  29. {  
  30. int x,s;  
  31. struct sockaddr_in sin;  
  32.   
  33.   s=socket(AF_INET, SOCK_STREAM, 0);  
  34.   x=fcntl(s,F_GETFL,0);              // Get socket flags  
  35.   fcntl(s,F_SETFL,x | O_NONBLOCK);   // Add non-blocking flag  
  36.   memset(&sin, 0, sizeof(struct sockaddr_in));  
  37.   sin.sin_family=AF_INET;  
  38.   sin.sin_port=htons(port);  
  39.   sin.sin_addr.s_addr=name_resolve(address);  
  40.   if(sin.sin_addr.s_addr==NULL) return(-1);  
  41.   printf("ip: %s/n",inet_ntoa(sin.sin_addr));  
  42.   x=connect(s, (struct sockaddr *)&sin, sizeof(sin));  
  43.   if(x<0) return(-1);  
  44. return(s);  
  45. }  
  46.   
  47. int main (void)  
  48. {  
  49. fd_set read_flags,write_flags; // you know what these are  
  50. struct timeval waitd;            
  51. int thefd;             // The socket  
  52. char outbuff[512];     // Buffer to hold outgoing data  
  53. char inbuff[512];      // Buffer to read incoming data into  
  54. int err;           // holds return values  
  55.   
  56.   memset(&outbuff,0,sizeof(outbuff)); // memset used for portability  
  57.   thefd=doconnect("203.1.1.1",79); // Connect to the finger port  
  58.   if(thefd==-1) {  
  59.     printf("Could not connect to finger server/n");  
  60.     exit(0);  
  61.     }  
  62.   strcat(outbuff,"jarjam/n"); //Add the string jarjam to the output  
  63.                               //buffer  
  64.   while(1) {  
  65.     waitd.tv_sec = 1;     // Make select wait up to 1 second for data  
  66.     waitd.tv_usec = 0;    // and 0 milliseconds.  
  67.     FD_ZERO(&read_flags); // Zero the flags ready for using  
  68.     FD_ZERO(&write_flags);  
  69.     FD_SET(thefd, &read_flags);  
  70.     if(strlen(outbuff)!=0) FD_SET(thefd, &write_flags);  
  71.     err=select(thefd+1, &read_flags,&write_flags,  
  72.                (fd_set*)0,&waitd);  
  73.     if(err < 0) continue;  
  74.     if(FD_ISSET(thefd, &read_flags)) { //Socket ready for reading  
  75.       FD_CLR(thefd, &read_flags);  
  76.       memset(&inbuff,0,sizeof(inbuff));  
  77.       if (read(thefd, inbuff, sizeof(inbuff)-1) <= 0) {  
  78.         close(thefd);  
  79.         break;  
  80.         }  
  81.       else printf("%s",inbuff);  
  82.       }  
  83.     if(FD_ISSET(thefd, &write_flags)) { //Socket ready for writing  
  84.       FD_CLR(thefd, &write_flags);  
  85.       write(thefd,outbuff,strlen(outbuff));  
  86.       memset(&outbuff,0,sizeof(outbuff));  
  87.       }  
  88.     // now the loop repeats over again  
  89.     }  
  90. }  

 

7.下一步

 

        其它更多的示例代码从此教程中分离,以zip文件的方式给出。为了更好的理解所学, 你最好参考一些结构更复杂,技术更强的代码:

 http://users.cybernex.net.au/jj/sock.zip

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值