1. 程式人生 > >linux非阻塞socket教程

linux非阻塞socket教程

from :http://blog.csdn.net/devday/article/details/5296621

        本文並非解釋什麼是非阻塞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. while(1) // put program in an infinite loop of reading and writing data
  3.  {  
  4.   waitd.tv_sec = 1;  // Make select wait up to 1 second for data
  5.   waitd.tv_usec = 0; // and 0 milliseconds.
  6.   FD_ZERO(&read_flags); // Zero the flags ready for using
  7.   FD_ZERO(&write_flags);  
  8.   // Set the sockets read flag, so when select is called it examines
  9.   // the read status of available data.
  10.   FD_SET(thefd, &read_flags);  
  11.   // If there is data in the output buffer to be sent then we
  12.   // need to also set the write flag to check the write status
  13.   // of the socket
  14.   if(strlen(outbuff)!=0) FD_SET(thefd, &write_flags);  
  15.   // Now call select
  16.   stat=select(s+1, &read_flags,&write_flags,(fd_set*)0,&waitv);  
  17.   if(stat < 0) {  // If select breaks then pause for 5 seconds
  18.      sleep(5);    // then continue
  19.      continue;  
  20.      }  
  21.   // Now select will have modified the flag sets to tell us
  22.   // what actions can be performed
  23.   // Check if data is available to read
  24.   if (FD_ISSET(thefd, &read_flags)) {  
  25.     FD_CLR(thefd, &read_flags);  
  26.     // here is where you use the read().
  27.     // If read returns an error then the socket
  28.     // must be dead so you must close it.
  29.     }  
  30.   //Check if the socket is prepared to accept data
  31.   if (FD_ISSET(thefd, &write_flags)) {  
  32.     FD_CLR(thefd, &write_flags);  
  33.     // this means the socket is ready for you to use write()
  34.     }  
  35.   // Now here you can put in any of the precedures that you want
  36.   // to happen every 1 second or so.
  37.   // 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. // this routine simply converts the address into an
  12. // internet ip
  13. unsigned long name_resolve(char *host_name)  
  14. {  
  15. struct in_addr addr;  
  16. struct hostent *host_ent;  
  17.   if((addr.s_addr=inet_addr(host_name))==(unsigned)-1) {  
  18.     host_ent=gethostbyname(host_name);  
  19.     if(host_ent==NULL) return(-1);  
  20.     memcpy(host_ent->h_addr, (char *)&addr.s_addr, host_ent->h_length);  
  21.     }  
  22.   return (addr.s_addr);  
  23. }  
  24. // The connect routine including the command to set
  25. // the socket non-blocking.
  26. int doconnect(char *address, int port)  
  27. {  
  28. int x,s;  
  29. struct sockaddr_in sin;  
  30.   s=socket(AF_INET, SOCK_STREAM, 0);  
  31.   x=fcntl(s,F_GETFL,0);              // Get socket flags
  32.   fcntl(s,F_SETFL,x | O_NONBLOCK);   // Add non-blocking flag
  33.   memset(&sin, 0, sizeof(struct sockaddr_in));  
  34.   sin.sin_family=AF_INET;  
  35.   sin.sin_port=htons(port);  
  36.   sin.sin_addr.s_addr=name_resolve(address);  
  37.   if(sin.sin_addr.s_addr==NULL) return(-1);  
  38.   printf("ip: %s/n",inet_ntoa(sin.sin_addr));  
  39.   x=connect(s, (struct sockaddr *)&sin, sizeof(sin));  
  40.   if(x<0) return(-1);  
  41. return(s);  
  42. }  
  43. int main (void)  
  44. {  
  45. fd_set read_flags,write_flags; // you know what these are
  46. struct timeval waitd;            
  47. int thefd;             // The socket
  48. char outbuff[512];     // Buffer to hold outgoing data
  49. char inbuff[512];      // Buffer to read incoming data into
  50. int err;           // holds return values
  51.   memset(&outbuff,0,sizeof(outbuff)); // memset used for portability
  52.   thefd=doconnect("203.1.1.1",79); // Connect to the finger port
  53.   if(thefd==-1) {  
  54.     printf("Could not connect to finger server/n");  
  55.     exit(0);  
  56.     }  
  57.   strcat(outbuff,"jarjam/n"); //Add the string jarjam to the output
  58.                               //buffer
  59.   while(1) {  
  60.     waitd.tv_sec = 1;     // Make select wait up to 1 second for data
  61.     waitd.tv_usec = 0;    // and 0 milliseconds.
  62.     FD_ZERO(&read_flags); // Zero the flags ready for using
  63.     FD_ZERO(&write_flags);  
  64.     FD_SET(thefd, &read_flags);  
  65.     if(strlen(outbuff)!=0) FD_SET(thefd, &write_flags);  
  66.     err=select(thefd+1, &read_flags,&write_flags,  
  67.                (fd_set*)0,&waitd);  
  68.     if(err < 0) continue;  
  69.     if(FD_ISSET(thefd, &read_flags)) { //Socket ready for reading
  70.       FD_CLR(thefd, &read_flags);  
  71.       memset(&inbuff,0,sizeof(inbuff));  
  72.       if (read(thefd, inbuff, sizeof(inbuff)-1) <= 0) {  
  73.         close(thefd);  
  74.         break;  
  75.         }  
  76.       else printf("%s",inbuff);  
  77.       }  
  78.     if(FD_ISSET(thefd, &write_flags)) { //Socket ready for writing
  79.       FD_CLR(thefd, &write_flags);  
  80.       write(thefd,outbuff,strlen(outbuff));  
  81.       memset(&outbuff,0,sizeof(outbuff));  
  82.       }  
  83.     // now the loop repeats over again
  84.     }  
  85. }  

7.下一步

        其它更多的示例程式碼從此教程中分離,以zip檔案的方式給出。為了更好的理解所學, 你最好參考一些結構更復雜,技術更強的程式碼: