linux非阻塞socket教程

http://blog.csdn.net/favormm/article/details/5296621

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

 

本教程内容如下:

 

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

        2. select模型

        3. FD宏

        4. 读写函数

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

        6. 整个代码

        7.下一步

 

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

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

 

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

 

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

[cpp] view plain copy print ?
  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))  

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

 

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

[cpp] view plain copy print ?
  1. int x;  
  2. x=fcntl(s,F_GETFL,0);  
  3. fcntl(s,F_SETFL,x | O_NONBLOCK);  

int x; x=fcntl(s,F_GETFL,0); fcntl(s,F_SETFL,x | O_NONBLOCK);

 

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

 

2.选择模型

 

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

[cpp] view plain copy print ?
  1. select(s, &read_flags, &write_flags, &exec_flags, timer);  

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

 

s                               socket的句柄

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

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

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

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

 

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

[cpp] view plain copy print ?
  1. select(s, &read_flags, &write_flags, NULL, timer);  

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

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

 

3.FD宏

 

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

[cpp] view plain copy print ?
  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.  

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

 

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

 

4.读取方法

 

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

[cpp] view plain copy print ?
  1. write(s,buffer,sizeof(buffer))   send the text in "buffer"  
  2. read(s,buffer,sizeof(buffer))    read available data into "buffer"  

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

 

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

 

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

   

       首先声明要用到的变量:

[cpp] view plain copy print ?
  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();  

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

       

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

[cpp] view plain copy print ?
  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  

// Insert Code to create a socket while(1) // put program in an infinite loop of reading and writing data { waitd.tv_sec = 1; // Make select wait up to 1 second for data waitd.tv_usec = 0; // and 0 milliseconds. FD_ZERO(&read_flags); // Zero the flags ready for using FD_ZERO(&write_flags); // Set the sockets read flag, so when select is called it examines // the read status of available data. FD_SET(thefd, &read_flags); // If there is data in the output buffer to be sent then we // need to also set the write flag to check the write status // of the socket if(strlen(outbuff)!=0) FD_SET(thefd, &write_flags); // Now call select stat=select(s+1, &read_flags,&write_flags,(fd_set*)0,&waitv); if(stat < 0) { // If select breaks then pause for 5 seconds sleep(5); // then continue continue; } // Now select will have modified the flag sets to tell us // what actions can be performed // Check if data is available to read if (FD_ISSET(thefd, &read_flags)) { FD_CLR(thefd, &read_flags); // here is where you use the read(). // If read returns an error then the socket // must be dead so you must close it. } //Check if the socket is prepared to accept data if (FD_ISSET(thefd, &write_flags)) { FD_CLR(thefd, &write_flags); // this means the socket is ready for you to use write() } // Now here you can put in any of the precedures that you want // to happen every 1 second or so. // now the loop repeats over again

 

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

 

6.整个代码

 

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

 

[cpp] view plain copy print ?
  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. }  

#include <sys/types.h> #include <sys/socket.h> #include <sys/time.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> // this routine simply converts the address into an // internet ip unsigned long name_resolve(char *host_name) { struct in_addr addr; struct hostent *host_ent; if((addr.s_addr=inet_addr(host_name))==(unsigned)-1) { host_ent=gethostbyname(host_name); if(host_ent==NULL) return(-1); memcpy(host_ent->h_addr, (char *)&addr.s_addr, host_ent->h_length); } return (addr.s_addr); } // The connect routine including the command to set // the socket non-blocking. int doconnect(char *address, int port) { int x,s; struct sockaddr_in sin; s=socket(AF_INET, SOCK_STREAM, 0); x=fcntl(s,F_GETFL,0); // Get socket flags fcntl(s,F_SETFL,x | O_NONBLOCK); // Add non-blocking flag memset(&sin, 0, sizeof(struct sockaddr_in)); sin.sin_family=AF_INET; sin.sin_port=htons(port); sin.sin_addr.s_addr=name_resolve(address); if(sin.sin_addr.s_addr==NULL) return(-1); printf("ip: %s/n",inet_ntoa(sin.sin_addr)); x=connect(s, (struct sockaddr *)&sin, sizeof(sin)); if(x<0) return(-1); return(s); } int main (void) { fd_set read_flags,write_flags; // you know what these are struct timeval waitd; int thefd; // The socket char outbuff[512]; // Buffer to hold outgoing data char inbuff[512]; // Buffer to read incoming data into int err; // holds return values memset(&outbuff,0,sizeof(outbuff)); // memset used for portability thefd=doconnect("203.1.1.1",79); // Connect to the finger port if(thefd==-1) { printf("Could not connect to finger server/n"); exit(0); } strcat(outbuff,"jarjam/n"); //Add the string jarjam to the output //buffer while(1) { waitd.tv_sec = 1; // Make select wait up to 1 second for data waitd.tv_usec = 0; // and 0 milliseconds. FD_ZERO(&read_flags); // Zero the flags ready for using FD_ZERO(&write_flags); FD_SET(thefd, &read_flags); if(strlen(outbuff)!=0) FD_SET(thefd, &write_flags); err=select(thefd+1, &read_flags,&write_flags, (fd_set*)0,&waitd); if(err < 0) continue; if(FD_ISSET(thefd, &read_flags)) { //Socket ready for reading FD_CLR(thefd, &read_flags); memset(&inbuff,0,sizeof(inbuff)); if (read(thefd, inbuff, sizeof(inbuff)-1) <= 0) { close(thefd); break; } else printf("%s",inbuff); } if(FD_ISSET(thefd, &write_flags)) { //Socket ready for writing FD_CLR(thefd, &write_flags); write(thefd,outbuff,strlen(outbuff)); memset(&outbuff,0,sizeof(outbuff)); } // now the loop repeats over again } }

 

7.下一步

 

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

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

 

你可能感兴趣的:(linux非阻塞socket教程)