Socket編程實踐(10) --select的限制與poll的使用

select的限制

用select實現的併發服務器,能達到的併發數一般受兩方面限制:

1)一個進程能打開的最大文件描述符限制。這可以通過調整內核參數。可以通過ulimit -n(number)來調整或者使用setrlimit函數設置,但一個系統所能打開的最大數也是有限的,跟內存大小有關,可以通過cat /proc/sys/fs/file-max 查看

  1. /**示例: getrlimit/setrlimit獲取/設置進程打開文件數目**/  
  2. int main()  
  3. {  
  4.     struct rlimit rl;  
  5.     if (getrlimit(RLIMIT_NOFILE, &rl) == -1)  
  6.         err_exit("getrlimit error");  
  7.     cout << "Soft limit: " << rl.rlim_cur << endl;  
  8.     cout << "Hard limit: " << rl.rlim_max << endl;  
  9.     cout << "------------------------->"  << endl;  
  10.   
  11.     rl.rlim_cur = 2048;  
  12.     rl.rlim_max = 2048;  
  13.     if (setrlimit(RLIMIT_NOFILE, &rl) == -1)  
  14.         err_exit("setrlimit error");  
  15.   
  16.     if (getrlimit(RLIMIT_NOFILE, &rl) == -1)  
  17.         err_exit("getrlimit error");  
  18.     cout << "Soft limit: " << rl.rlim_cur << endl;  
  19.     cout << "Hard limit: " << rl.rlim_max << endl;  
  20. }  

2)select中的fd_set集合容量的限制(FD_SETSIZE,1024),這需要重新編譯內核才能改變。

  1. /**測試: 測試服務器端最多能夠建立多少個連接 
  2. server端完整源代碼如下(注意使用的是<Socket編程實踐(7)中的TCPServer類實現>): 
  3. **/  
  4. int main()  
  5. {  
  6.     signal(SIGPIPE, sigHandlerForSigPipe);  
  7.     try  
  8.     {  
  9.         TCPServer server(8001);  
  10.         int listenfd = server.getfd();  
  11.   
  12.         struct sockaddr_in clientAddr;  
  13.         socklen_t addrLen;  
  14.         int maxfd = listenfd;  
  15.         fd_set rset;  
  16.         fd_set allset;  
  17.         FD_ZERO(&rset);  
  18.         FD_ZERO(&allset);  
  19.         FD_SET(listenfd, &allset);  
  20.   
  21.         //用於保存已連接的客戶端套接字  
  22.         int client[FD_SETSIZE];  
  23.         for (int i = 0; i < FD_SETSIZE; ++i)  
  24.             client[i] = -1;  
  25.         int maxi = 0;   //用於保存最大的不空閒的位置, 用於select返回之後遍歷數組  
  26.   
  27.         int count = 0;  
  28.         while (true)  
  29.         {  
  30.             rset = allset;  
  31.             int nReady = select(maxfd+1, &rset, NULL, NULL, NULL);  
  32.             if (nReady == -1)  
  33.             {  
  34.                 if (errno == EINTR)  
  35.                     continue;  
  36.                 err_exit("select error");  
  37.             }  
  38.   
  39.             if (FD_ISSET(listenfd, &rset))  
  40.             {  
  41.                 addrLen = sizeof(clientAddr);  
  42.                 int connfd = accept(listenfd, (struct sockaddr *)&clientAddr, &addrLen);  
  43.                 if (connfd == -1)  
  44.                     err_exit("accept error");  
  45.   
  46.                 int i;  
  47.                 for (i = 0; i < FD_SETSIZE; ++i)  
  48.                 {  
  49.                     if (client[i] < 0)  
  50.                     {  
  51.                         client[i] = connfd;  
  52.                         if (i > maxi)  
  53.                             maxi = i;  
  54.                         break;  
  55.                     }  
  56.                 }  
  57.                 if (i == FD_SETSIZE)  
  58.                 {  
  59.                     cerr << "too many clients" << endl;  
  60.                     exit(EXIT_FAILURE);  
  61.                 }  
  62.                 //打印客戶IP地址與端口號  
  63.                 cout << "Client information: " << inet_ntoa(clientAddr.sin_addr)  
  64.                      << ", " << ntohs(clientAddr.sin_port) << endl;  
  65.                 cout << "count = " << ++count << endl;  
  66.                 //將連接套接口放入allset, 並更新maxfd  
  67.                 FD_SET(connfd, &allset);  
  68.                 if (connfd > maxfd)  
  69.                     maxfd = connfd;  
  70.   
  71.                 if (--nReady <= 0)  
  72.                     continue;  
  73.             }  
  74.   
  75.             /**如果是已連接套接口發生了可讀事件**/  
  76.             for (int i = 0; i <= maxi; ++i)  
  77.                 if ((client[i] != -1) && FD_ISSET(client[i], &rset))  
  78.                 {  
  79.                     char buf[512] = {0};  
  80.                     int readBytes = readline(client[i], buf, sizeof(buf));  
  81.                     if (readBytes == -1)  
  82.                         err_exit("readline error");  
  83.                     else if (readBytes == 0)  
  84.                     {  
  85.                         cerr << "client connect closed..." << endl;  
  86.                         FD_CLR(client[i], &allset);  
  87.                         close(client[i]);  
  88.                         client[i] = -1;  
  89.                     }  
  90.                     cout << buf;  
  91.                     if (writen(client[i], buf, readBytes) == -1)  
  92.                         err_exit("writen error");  
  93.   
  94.                     if (--nReady <= 0)  
  95.                         break;  
  96.                 }  
  97.         }  
  98.     }  
  99.     catch (const SocketException &e)  
  100.     {  
  101.         cerr << e.what() << endl;  
  102.         err_exit("TCPServer error");  
  103.     }  
  104. }  

  1. /**高併發測試端代碼: contest完整源代碼如下**/  
  2. int main()  
  3. {  
  4.     //最好不要修改: 不然會產生段溢出(stack-overflow)  
  5. //    struct rlimit rlim;  
  6. //    rlim.rlim_cur = 2048;  
  7. //    rlim.rlim_max = 2048;  
  8. //    if (setrlimit(RLIMIT_NOFILE, &rlim) == -1)  
  9. //        err_exit("setrlimit error");  
  10.   
  11.     int count = 0;  
  12.     while (true)  
  13.     {  
  14.         int sockfd = socket(AF_INET, SOCK_STREAM, 0);  
  15.         if (sockfd == -1)  
  16.         {  
  17.             sleep(5);  
  18.             err_exit("socket error");  
  19.         }  
  20.   
  21.         struct sockaddr_in serverAddr;  
  22.         serverAddr.sin_family = AF_INET;  
  23.         serverAddr.sin_port = htons(8001);  
  24.         serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  
  25.         int ret = connect_timeout(sockfd, &serverAddr, 5);  
  26.         if (ret == -1 && errno == ETIMEDOUT)  
  27.         {  
  28.             cerr << "timeout..." << endl;  
  29.             err_exit("connect_timeout error");  
  30.         }  
  31.         else if (ret == -1)  
  32.             err_exit("connect_timeout error");  
  33.   
  34.         //獲取並打印對端信息  
  35.         struct sockaddr_in peerAddr;  
  36.         socklen_t peerLen = sizeof(peerAddr);  
  37.         if (getpeername(sockfd, (struct sockaddr *)&peerAddr, &peerLen) == -1)  
  38.             err_exit("getpeername");  
  39.         cout << "Server information: " << inet_ntoa(peerAddr.sin_addr)  
  40.              << ", " << ntohs(peerAddr.sin_port) << endl;  
  41.         cout << "count = " << ++count << endl;  
  42.     }  
  43. }  

Server端運行截圖如圖所示:


解析:對於客戶端,最多隻能開啓1021個連接套接字,因爲總共是在Linux中最多可以打開1024個文件描述如,其中還得除去0,1,2。而服務器端只能accept 返回1020個已連接套接字,因爲除了0,1,2之外還有一個監聽套接字listenfd,客戶端某一個套接字(不一定是最後一個)雖然已經建立了連接,在已完成連接隊列中,但accept返回時達到最大描述符限制,返回錯誤,打印提示信息。

 

client在socket()返回-1是調用sleep(5)解析

   當客戶端調用socket準備創建第1022個套接字時,如上所示也會提示錯誤,此時socket函數返回-1出錯,如果沒有睡眠4s後再退出進程會有什麼問題呢?如果直接退出進程,會將客戶端所打開的所有套接字關閉掉,即向服務器端發送了很多FIN段,而此時也許服務器端還一直在accept ,即還在從已連接隊列中返回已連接套接字,此時服務器端除了關心監聽套接字的可讀事件,也開始關心前面已建立連接的套接字的可讀事件,read 返回0,所以會有很多 client close 字段參雜在條目的輸出中,還有個問題就是,因爲read 返回0,服務器端會將自身的已連接套接字關閉掉,那麼也許剛纔說的客戶端某一個連接會被accept 返回,即測試不出服務器端真正的併發容量;

 

poll調用

poll沒有select第二個限制, 即FD_SETSIZE的限制, 但是第一個限制暫時還是無法避免的;

  1. #include <poll.h>  
  2. int poll(struct pollfd *fds, nfds_t nfds, int timeout);  

   參數nfds: 需要檢測事件的個數, 結構體數組大小(也可表示爲文件描述符個數)(The caller should specify the number of items in the fds array in nfds.)

   參數timeout: 超時時間(單位milliseconds, 毫秒),若爲-1,表示永不超時。

  1. //pollfd結構體  
  2. struct pollfd  
  3. {  
  4.     int   fd;         /* file descriptor */  
  5.     short events;     /* requested events: 請求的事件 */  
  6.     short revents;    /* returned events :  返回的事件*/  
  7. };  

events與revents取值(前3個最常用):


返回值:

   成功: 返回一個正整數(this is the number of  structures  which  have nonzero  revents  

fields  (in  other  words,  those  descriptors  with  events  or errors reported).  

   超時:  返回0(A value of 0 indicates that the call timed out and no file  descriptors  

were ready)  

   失敗: 返回-1(On error, -1 is returned, and errno is set appropriately.)

  1. /**poll-Server示例(將前面的select-server改造如下, 其沒有了FD_SETSIZE的限制, 關於第一個限制可以使用前文中的方法更改)(client端與測試端代碼如前)**/  
  2. const int SETSIZE = 2048;  
  3. int main()  
  4. {  
  5.     signal(SIGPIPE, sigHandlerForSigPipe);  
  6.     try  
  7.     {  
  8.         TCPServer server(8001);  
  9.         //用於保存已連接的客戶端套接字  
  10.         struct pollfd client[SETSIZE];  
  11.         //將client置空  
  12.         for (int i = 0; i < SETSIZE; ++i)  
  13.             client[i].fd = -1;  
  14.         int maxi = 0;   //用於保存最大的已佔用位置  
  15.         int count = 0;  
  16.         client[0].fd = server.getfd();  
  17.         client[0].events = POLLIN;  
  18.         while (true)  
  19.         {  
  20.             int nReady = poll(client, maxi+1, -1);  
  21.             if (nReady == -1)  
  22.             {  
  23.                 if (errno == EINTR)  
  24.                     continue;  
  25.                 err_exit("poll error");  
  26.             }  
  27.   
  28.             //如果是監聽套接口發生了可讀事件  
  29.             if (client[0].revents & POLLIN)  
  30.             {  
  31.                 int connfd = accept(server.getfd(), NULL, NULL);  
  32.                 if (connfd == -1)  
  33.                     err_exit("accept error");  
  34.   
  35.                 bool flags = false;  
  36.                 //略過client[0].fd(listenfd), 從1開始檢測  
  37.                 for (int i = 1; i < SETSIZE; ++i)  
  38.                 {  
  39.                     if (client[i].fd == -1)  
  40.                     {  
  41.                         client[i].fd = connfd;  
  42.                         client[i].events = POLLIN;  
  43.                         flags = true;  
  44.                         if (i > maxi)  
  45.                             maxi = i;  
  46.                         break;  
  47.                     }  
  48.                 }  
  49.                 //未找到一個合適的位置  
  50.                 if (!flags)  
  51.                 {  
  52.                     cerr << "too many clients" << endl;  
  53.                     exit(EXIT_FAILURE);  
  54.                 }  
  55.                 cout << "count = " << ++count << endl;  
  56.                 if (--nReady <= 0)  
  57.                     continue;  
  58.             }  
  59.   
  60.             /**如果是已連接套接口發生了可讀事件**/  
  61.             for (int i = 1; i <= maxi; ++i)  
  62.                 if (client[i].revents & POLLIN)  
  63.                 {  
  64.                     char buf[512] = {0};  
  65.                     int readBytes = readline(client[i].fd, buf, sizeof(buf));  
  66.                     if (readBytes == -1)  
  67.                         err_exit("readline error");  
  68.                     else if (readBytes == 0)  
  69.                     {  
  70.                         cerr << "client connect closed..." << endl;  
  71.                         close(client[i].fd);  
  72.                         client[i].fd = -1;  
  73.                     }  
  74.                     cout << buf;  
  75.                     if (writen(client[i].fd, buf, readBytes) == -1)  
  76.                         err_exit("writen error");  
  77.                     if (--nReady <= 0)  
  78.                         break;  
  79.                 }  
  80.         }  
  81.     }  
  82.     catch (const SocketException &e)  
  83.     {  
  84.         cerr << e.what() << endl;  
  85.         err_exit("TCPServer error");  
  86.     }  
  87. }  

附-getrlimit和setrlimit函數

每個進程都有一組資源限制,其中某一些可以用getrlimit和setrlimit函數查詢和更改。

  1. #include <sys/time.h>  
  2. #include <sys/resource.h>  
  3. int getrlimit(int resource, struct rlimit *rlim);  
  4. int setrlimit(int resource, const struct rlimit *rlim);  
  1. //rlimit結構體  
  2. struct rlimit  
  3. {  
  4.     rlim_t rlim_cur;  /* Soft limit */  
  5.     rlim_t rlim_max;  /* Hard limit (ceiling for rlim_cur) */  
  6. };  

軟限制是一個建議性的, 最好不要超越的限制, 如果超越的話, 系統可能向進程發送信號以終止其運行.

而硬限制一般是軟限制的上限;

resource可用值

RLIMIT_AS

進程可用的最大虛擬內存空間長度,包括堆棧、全局變量、動態內存

RLIMIT_CORE

內核生成的core文件的最大大小

RLIMIT_CPU

所用的全部cpu時間,以秒計算

RLIMIT_DATA

進程數據段(初始化DATA段, 未初始化BSS段和堆)限制(以B爲單位)

RLIMIT_FSIZE

文件大小限制

RLIMIT_SIGPENDING

用戶能夠掛起的信號數量限制

RLIMIT_NOFILE

打開文件的最大數目

RLIMIT_NPROC

用戶能夠創建的進程數限制

RLIMIT_STACK

進程棧內存限制, 超過會產生SIGSEGV信號

進程的資源限制通常是在系統初啓時由0#進程建立的,在更改資源限制時,須遵循下列三條規則:

  1.任何一個進程都可將一個軟限制更改爲小於或等於其硬限制。

  2.任何一個進程都可降低其硬限制值,但它必須大於或等於其軟限制值。這種降低,對普通用戶而言是不可逆反的。

  3.只有超級用戶可以提高硬限制。

發佈了52 篇原創文章 · 獲贊 10 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章