<menu id="w8yyk"><menu id="w8yyk"></menu></menu>
  • <dd id="w8yyk"><nav id="w8yyk"></nav></dd>
    <menu id="w8yyk"></menu>
    <menu id="w8yyk"><code id="w8yyk"></code></menu>
    <menu id="w8yyk"></menu>
    <xmp id="w8yyk">
    <xmp id="w8yyk"><nav id="w8yyk"></nav>
  • 網站首頁 > 物聯資訊 > 技術分享

    windows和linux套接字中的select機制淺析

    2016-09-28 00:00:00 廣州睿豐德信息科技有限公司 閱讀
    睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接

    先來談談為什么會出現select函數,也就是select是解決什么問題的?

    平常使用的recv函數時阻塞的,也就是如果沒有數據可讀,recv就會一直阻塞在那里,這是如果有另外一個連接過來,就得一直等待,這樣實時性就不是太好。

    這個問題的幾個解決方法:1. 使用ioctlsocket函數,將recv函數設置成非阻塞的,這樣不管套接字上有沒有數據都會立刻返回,可以重復調用recv函數,這種方式叫做輪詢(polling),但是這樣效率很是問題,因為,大多數時間實際上是無數據可讀的,花費時間不斷反復執行read系統調用,這樣就比較浪費CPU的時間。并且循環之間的間隔不好確定。2. 使用fork,使用多進程來解決,這里終止會比較復雜(待研究)。 3.使用多線程來解決,這樣避免了終止的復雜性,但卻要求處理線程之間的同步,在減少復雜性方面這可能會得不償失。4. 使用異步IO(待研究)。5. 就是本文所使用的I/O多路轉接(多路復用)--其實就是在套接字阻塞和非阻塞之間做了一個均衡,我們稱之為半阻塞。

    經過對select的初步了解,在windows和linux下的實現小有區別,所以分開來寫。這里先寫windows下的select機制。

    select的大概思想:將多個套接字放在一個集合里,然后統一檢查這些套接字的狀態(可讀、可寫、異常等),調用select后,會更新這些套接字的狀態,然后做判斷,如果套接字可讀,就執行read操作。這樣就巧妙地避免了阻塞,達到同時處理多個連接的目的。當然如果沒有事件發生,select會一直阻塞,如果不想一直讓它等待,想去處理其它事情,可以設置一個最大的等待時間。

    /***********************************************************************************************************/

    下面具體講講函數的參數,參見MSDN的解釋http://msdn.microsoft.com/en-us/library/windows/desktop/ms740141(v=vs.85).aspx

     

    [cpp] view plaincopy  
    1. int select(  
    2.   _In_     int nfds,  
    3.   _Inout_  fd_set *readfds,  
    4.   _Inout_  fd_set *writefds,  
    5.   _Inout_  fd_set *exceptfds,  
    6.   _In_     const struct timeval *timeout  
    7. );  


    函數的返回值,表示準備好的套接字的個數,如果是0,則表示沒有一個準備好(超時就是一種情況),如果是-1(SOCKET_ERROR),表示有錯誤發生,可以使用WSAGetLastError()函數來得到錯誤代碼,從而知道是什么錯誤。

     

    函數的參數,第一個是輸入參數nfds,表示滿足條件的套接字的個數,windows下可以設置為0,因為fd_set結構體中已經包含了這個參數,這個參數已經是多余的了,之所以還存在,只是是為了與FreeBSD兼容。

    第二三四參數都是輸入輸出參數(值-結果參數,輸入和輸出會不一樣),表示套接字的可讀、可寫和異常三種狀態的集合。調用select之后,如果指定套接字不可讀或者不可寫,就會從相應隊列中清除,這樣就可以判斷哪些套接字可讀或者可寫。 

    說明一下,這里的可讀性是指:如果有客戶的連接請求到達,套接口就是可讀的,調用accept能夠立即完成,而不發生阻塞;如果套接口接收隊列緩沖區中的字節數大于0,調用recv或者recvfrom就不會阻塞。可寫性是指,可以向套接字發送數據(套接字創建成功后,就是可寫的)。當然不是套接字可寫就會去發送數據,就像不是看到電話就去打電話一樣,而是由打電話的需求了,才去看電話是否可打;可讀就不一樣了,電話響了,自然要去接電話(除非,你有事忙或者不想接,一般都是要接的)。可讀已經包含了緩沖區中有數據可以讀取,可寫只是說明了緩沖區有空間讓你寫,你需不需要寫就要看你有沒有數據要寫了.關于異常,就是指一些意外情況,自己用的比較少,以后用到了,再過來補上。

    第五個參數是等待的最大時間,是一個結構體:struct timeval,它的定義是:

     

    [cpp] view plaincopy  
    1. /* 
    2. * Structure used in select() call, taken from the BSD file sys/time.h. 
    3. */  
    4. struct timeval {  
    5.         long    tv_sec;         /* seconds */  
    6.         long    tv_usec;        /* and microseconds */  
    7. };  

    具體到秒和微妙,按照等待的時間長短可以分為不等待、等待一定時間、一直等待。對應的設置分別為,(0,0)是不等待,這是select是非阻塞的,(x,y)最大等待時間x秒y微妙(如果有事件就會提前返回,而不繼續等待),NULL表示一直等待,直到有事件發生。這里可以將timeout分別設置成0(不阻塞)或者1微妙(阻塞很短的時間),然后觀察CPU的使用率,會發現設置成非阻塞后,CPU的使用率已下載就上升到了50%左右,這樣可以看出非阻塞占用CPU很多,但利用率不高。

     

    /***********************************************************************************************************/

    跟select配合使用的幾個宏和fd_set結構體介紹:

    套接字描述符為了方便管理是放在一個集合里的,這個集合是fd_set,它的具體定義是:

     

    [cpp] view plaincopy  
    1. typedef struct fd_set {  
    2.         u_int   fd_count;               /* how many are SET? */  
    3.         SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */  
    4. } fd_set;  

    fd_count是集合中已經設置的套接口描述符的數量。fd_array數組保存已經設置的套接口描述符,其中FD_SETSIZE的定義是:

     

     

    [cpp] view plaincopy  
    1. #ifndef FD_SETSIZE  
    2. #define FD_SETSIZE      64  
    3. #endif /* FD_SETSIZE */  

    這個默認值在一般的程序中已經夠用,如果需要,可以將其更改為更大的值。

     

    集合的管理操作,比如元素的清空、加入、刪除以及判斷元素是否在集合中都是用宏來完成的。四個宏是:

     

    [html] view plaincopy  
    1. FD_ZERO(*set)  
    2. FD_SET(s, *set)  
    3. FD_ISSET(s, *set)  
    4. FD_CLR(s, *set)  

    下面一一介紹這些宏的作用和定義:

     

    FD_ZERO(*set),是把集合清空(初始化為0,確切的說,是把集合中的元素個數初始化為0,并不修改描述符數組).使用集合前,必須用FD_ZERO初始化,否則集合在棧上作為自動變量分配時,fd_set分配的將是隨機值,導致不可預測的問題。它的宏定義如下:

     

    [cpp] view plaincopy  
    1. #define FD_ZERO(set) (((fd_set FAR *)(set))->fd_count=0)  

    FD_SET(s,*set),向集合中加入一個套接口描述符(如果該套接口描述符s沒在集合中,并且數組中已經設置的個數小于最大個數時,就把該描述符加入到集合中,集合元素個數加1)。這里是將s的值直接放入數組中。它的宏定義如下:

     

     

    [cpp] view plaincopy  
    1. #define FD_SET(fd, set) do { \  
    2.     if (((fd_set FAR *)(set))->fd_count < FD_SETSIZE) \  
    3.         ((fd_set FAR *)(set))->fd_array[((fd_set FAR *)(set))->fd_count++]=(fd);\  
    4. while(0)  

    FD_ISSET(s,*set),檢查描述符是否在集合中,如果在集合中返回非0值,否則返回0. 它的宏定義并沒有給出具體實現,但實現的思路很簡單,就是搜索集合,判斷套接字s是否在數組中。它的宏定義是:

     

     

    [cpp] view plaincopy  
    1. #define FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set FAR *)(set))  

    FD_CLR(s,*set),從集合中移出一個套接口描述符(比如一個套接字連接中斷后,就應該移除它)。實現思路是,在數組集合中找到對應的描述符,然后把后面的描述依次前移一個位置,最后把描述符的個數減1. 它的宏定義是:

     

     

    [cpp] view plaincopy  
    1. #define FD_CLR(fd, set) do { \  
    2.     u_int __i; \  
    3.     for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count ; __i++) { \  
    4.         if (((fd_set FAR *)(set))->fd_array[__i] == fd) { \  
    5.             while (__i < ((fd_set FAR *)(set))->fd_count-1) { \  
    6.                 ((fd_set FAR *)(set))->fd_array[__i] = \  
    7.                     ((fd_set FAR *)(set))->fd_array[__i+1]; \  
    8.                 __i++; \  
    9.             } \  
    10.             ((fd_set FAR *)(set))->fd_count--; \  
    11.             break; \  
    12.         } \  
    13.     } \  
    14. while(0)  


    /***********************************************************************************************************/

     

    至此,一些基礎的點基本就講完了,然后給出大概流程和一個示例:

    1.調用FD_ZERO來初始化套接字狀態;

    2.調用FD_SET將感興趣的套接字描述符加入集合中(每次循環都要重新加入,因為select更新后,會將一些沒有滿足條件的套接字移除隊列);

    3.設置等待時間后,調用select函數--更新套接字的狀態;

    4.調用FD_ISSET,來判斷套接字是否有相應狀態,然后做相應操作,比如,如果套接字可讀,就調用recv函數去接收數據。

    關鍵技術:套接字隊列和狀態的表示與處理。

    server端得程序如下(套接字管理隊列一個很重要的作用就是保存套接字描述符,因為accept得到的套接字描述符會覆蓋掉原來的套接字描述符,而readfs中的描述符在select后會刪除這些套接字描述符):

     

    [cpp] view plaincopy  
    1. // server.cpp :   
    2. //程序中加入了套接字管理隊列,這樣管理起來更加清晰、方便,當然也可以不用這個東西  
    3.   
    4. #include "winsock.h"  
    5. #include "stdio.h"  
    6. #pragma comment (lib,"wsock32.lib")  
    7. struct socket_list{  
    8.     SOCKET MainSock;  
    9.     int num;  
    10.     SOCKET sock_array[64];  
    11. };  
    12. void init_list(socket_list *list)  
    13. {  
    14.     int i;  
    15.     list->MainSock = 0;  
    16.     list->num = 0;  
    17.     for(i = 0;i < 64;i ++){  
    18.         list->sock_array[i] = 0;  
    19.     }  
    20. }  
    21. void insert_list(SOCKET s,socket_list *list)  
    22. {  
    23.     int i;  
    24.     for(i = 0;i < 64; i++){  
    25.         if(list->sock_array[i] == 0){  
    26.             list->sock_array[i] = s;  
    27.             list->num += 1;  
    28.             break;  
    29.         }  
    30.     }  
    31. }  
    32. void delete_list(SOCKET s,socket_list *list)  
    33. {  
    34.     int i;  
    35.     for(i = 0;i < 64; i++){  
    36.         if(list->sock_array[i] == s){  
    37.             list->sock_array[i] = 0;  
    38.             list->num -= 1;  
    39.             break;  
    40.         }  
    41.     }  
    42. }  
    43. void make_fdlist(socket_list *list,fd_set *fd_list)  
    44. {  
    45.     int i;  
    46.     FD_SET(list->MainSock,fd_list);  
    47.     for(i = 0;i < 64;i++){  
    48.         if(list->sock_array[i] > 0){  
    49.             FD_SET(list->sock_array[i],fd_list);  
    50.         }  
    51.     }  
    52. }  
    53. int main(int argc, char* argv[])  
    54. {  
    55.     SOCKET s,sock;  
    56.     struct sockaddr_in ser_addr,remote_addr;  
    57.     int len;  
    58.     char buf[128];  
    59.     WSAData wsa;  
    60.     int retval;  
    61.     struct socket_list sock_list;  
    62.     fd_set readfds,writefds,exceptfds;  
    63.     timeval timeout;        //select的最多等待時間,防止一直等待  
    64.     int i;  
    65.     unsigned long arg;  
    66.   
    67.     WSAStartup(0x101,&wsa);  
    68.     s = socket(AF_INET,SOCK_STREAM,0);  
    69.     ser_addr.sin_family = AF_INET;  
    70.     ser_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);  
    71.     ser_addr.sin_port = htons(0x1234);  
    72.     bind(s,(sockaddr*)&ser_addr,sizeof(ser_addr));  
    73.   
    74.     listen(s,5);  
    75.     timeout.tv_sec = 5;     //如果套接字集合中在1s內沒有數據,select就會返回,超時select返回0  
    76.     timeout.tv_usec = 0;  
    77.     init_list(&sock_list);  
    78.     FD_ZERO(&readfds);  
    79.     FD_ZERO(&writefds);  
    80.     FD_ZERO(&exceptfds);  
    81.     sock_list.MainSock = s;  
    82.     arg = 1;  
    83.     ioctlsocket(sock_list.MainSock,FIONBIO,&arg);  
    84.     while(1){  
    85.         make_fdlist(&sock_list,&readfds);  
    86.         //make_fdlist(&sock_list,&writefds);  
    87.         //make_fdlist(&sock_list,&exceptfds);  
    88.   
    89.         retval = select(0,&readfds,&writefds,&exceptfds,&timeout);     //超過這個時間,就不阻塞在這里,返回一個0值。  
    90.         if(retval == SOCKET_ERROR){  
    91.             retval = WSAGetLastError();  
    92.             break;  
    93.         }  
    94.         else if(retval == 0) {  
    95.             printf("select() is time-out! There is no data or new-connect coming!\n");  
    96.             continue;  
    97.         }  
    98.         if(FD_ISSET(sock_list.MainSock,&readfds)){  
    99.             len = sizeof(remote_addr);  
    100.             sock = accept(sock_list.MainSock,(sockaddr*)&remote_addr,&len);  
    101.             if(sock == SOCKET_ERROR)  
    102.                 continue;  
    103.             printf("accept a connection\n");  
    104.             insert_list(sock,&sock_list);  
    105.         }  
    106.         for(i = 0;i < 64;i++){  
    107.             if(sock_list.sock_array[i] == 0)  
    108.                 continue;  
    109.             sock = sock_list.sock_array[i];  
    110.             if(FD_ISSET(sock,&readfds)){  
    111.                 retval = recv(sock,buf,128,0);  
    112.                 if(retval == 0){  
    113.                     closesocket(sock);  
    114.                     printf("close a socket\n");  
    115.                     delete_list(sock,&sock_list);  
    116.                     continue;  
    117.                 }else if(retval == -1){  
    118.                     retval = WSAGetLastError();  
    119.                     if(retval == WSAEWOULDBLOCK)  
    120.                         continue;  
    121.                     closesocket(sock);  
    122.                     printf("close a socket\n");  
    123.                     delete_list(sock,&sock_list);   //連接斷開后,從隊列中移除該套接字  
    124.                     continue;  
    125.                 }  
    126.                 buf[retval] = 0;  
    127.                 printf("->%s\n",buf);  
    128.                 send(sock,"ACK by server",13,0);  
    129.             }  
    130.             //if(FD_ISSET(sock,&writefds)){  
    131.             //}  
    132.             //if(FD_ISSET(sock,&exceptfds)){  
    133.               
    134.         }  
    135.         FD_ZERO(&readfds);  
    136.         FD_ZERO(&writefds);  
    137.         FD_ZERO(&exceptfds);  
    138.     }  
    139.     closesocket(sock_list.MainSock);  
    140.     WSACleanup();  
    141.     return 0;  
    142. }  

     

    關于linux下的select跟windows下的區別還有待學習。

     

    參考書籍:

    《WinSock網絡編程經絡》第19章

    《UNIX環境高級編程》

    RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成
    最近免费观看高清韩国日本大全