Socket编程实践(10) --select的限制与poll的使用
select的限制
用select實現的并發服務器,能達到的并發數一般受兩方面限制:
1)一個進程能打開的最大文件描述符限制。這可以通過調整內核參數。可以通過ulimit?-n(number)來調整或者使用setrlimit函數設置,但一個系統所能打開的最大數也是有限的,跟內存大小有關,可以通過cat?/proc/sys/fs/file-max?查看
/**示例: getrlimit/setrlimit獲取/設置進程打開文件數目**/ int main() {struct rlimit rl;if (getrlimit(RLIMIT_NOFILE, &rl) == -1)err_exit("getrlimit error");cout << "Soft limit: " << rl.rlim_cur << endl;cout << "Hard limit: " << rl.rlim_max << endl;cout << "------------------------->" << endl;rl.rlim_cur = 2048;rl.rlim_max = 2048;if (setrlimit(RLIMIT_NOFILE, &rl) == -1)err_exit("setrlimit error");if (getrlimit(RLIMIT_NOFILE, &rl) == -1)err_exit("getrlimit error");cout << "Soft limit: " << rl.rlim_cur << endl;cout << "Hard limit: " << rl.rlim_max << endl; }2)select中的fd_set集合容量的限制(FD_SETSIZE,1024),這需要重新編譯內核才能改變。
/**測試: 測試服務器端最多能夠建立多少個連接 server端完整源代碼如下(注意使用的是<Socket編程實踐(7)中的TCPServer類實現>): **/ int main() {signal(SIGPIPE, sigHandlerForSigPipe);try{TCPServer server(8001);int listenfd = server.getfd();struct sockaddr_in clientAddr;socklen_t addrLen;int maxfd = listenfd;fd_set rset;fd_set allset;FD_ZERO(&rset);FD_ZERO(&allset);FD_SET(listenfd, &allset);//用于保存已連接的客戶端套接字int client[FD_SETSIZE];for (int i = 0; i < FD_SETSIZE; ++i)client[i] = -1;int maxi = 0; //用于保存最大的不空閑的位置, 用于select返回之后遍歷數組int count = 0;while (true){rset = allset;int nReady = select(maxfd+1, &rset, NULL, NULL, NULL);if (nReady == -1){if (errno == EINTR)continue;err_exit("select error");}if (FD_ISSET(listenfd, &rset)){addrLen = sizeof(clientAddr);int connfd = accept(listenfd, (struct sockaddr *)&clientAddr, &addrLen);if (connfd == -1)err_exit("accept error");int i;for (i = 0; i < FD_SETSIZE; ++i){if (client[i] < 0){client[i] = connfd;if (i > maxi)maxi = i;break;}}if (i == FD_SETSIZE){cerr << "too many clients" << endl;exit(EXIT_FAILURE);}//打印客戶IP地址與端口號cout << "Client information: " << inet_ntoa(clientAddr.sin_addr)<< ", " << ntohs(clientAddr.sin_port) << endl;cout << "count = " << ++count << endl;//將連接套接口放入allset, 并更新maxfdFD_SET(connfd, &allset);if (connfd > maxfd)maxfd = connfd;if (--nReady <= 0)continue;}/**如果是已連接套接口發生了可讀事件**/for (int i = 0; i <= maxi; ++i)if ((client[i] != -1) && FD_ISSET(client[i], &rset)){char buf[512] = {0};int readBytes = readline(client[i], buf, sizeof(buf));if (readBytes == -1)err_exit("readline error");else if (readBytes == 0){cerr << "client connect closed..." << endl;FD_CLR(client[i], &allset);close(client[i]);client[i] = -1;}cout << buf;if (writen(client[i], buf, readBytes) == -1)err_exit("writen error");if (--nReady <= 0)break;}}}catch (const SocketException &e){cerr << e.what() << endl;err_exit("TCPServer error");} }/**高并發測試端代碼: contest完整源代碼如下**/ int main() {//最好不要修改: 不然會產生段溢出(stack-overflow) // struct rlimit rlim; // rlim.rlim_cur = 2048; // rlim.rlim_max = 2048; // if (setrlimit(RLIMIT_NOFILE, &rlim) == -1) // err_exit("setrlimit error");int count = 0;while (true){int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd == -1){sleep(5);err_exit("socket error");}struct sockaddr_in serverAddr;serverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(8001);serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");int ret = connect_timeout(sockfd, &serverAddr, 5);if (ret == -1 && errno == ETIMEDOUT){cerr << "timeout..." << endl;err_exit("connect_timeout error");}else if (ret == -1)err_exit("connect_timeout error");//獲取并打印對端信息struct sockaddr_in peerAddr;socklen_t peerLen = sizeof(peerAddr);if (getpeername(sockfd, (struct sockaddr *)&peerAddr, &peerLen) == -1)err_exit("getpeername");cout << "Server information: " << inet_ntoa(peerAddr.sin_addr)<< ", " << ntohs(peerAddr.sin_port) << endl;cout << "count = " << ++count << endl;} }
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的限制,?但是第一個限制暫時還是無法避免的;
#include <poll.h> 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,表示永不超時。
//pollfd結構體 struct pollfd {int fd; /* file descriptor */short events; /* requested events: 請求的事件 */short revents; /* returned events : 返回的事件*/ };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.)
/**poll-Server示例(將前面的select-server改造如下, 其沒有了FD_SETSIZE的限制, 關于第一個限制可以使用前文中的方法更改)(client端與測試端代碼如前)**/ const int SETSIZE = 2048; int main() {signal(SIGPIPE, sigHandlerForSigPipe);try{TCPServer server(8001);//用于保存已連接的客戶端套接字struct pollfd client[SETSIZE];//將client置空for (int i = 0; i < SETSIZE; ++i)client[i].fd = -1;int maxi = 0; //用于保存最大的已占用位置int count = 0;client[0].fd = server.getfd();client[0].events = POLLIN;while (true){int nReady = poll(client, maxi+1, -1);if (nReady == -1){if (errno == EINTR)continue;err_exit("poll error");}//如果是監聽套接口發生了可讀事件if (client[0].revents & POLLIN){int connfd = accept(server.getfd(), NULL, NULL);if (connfd == -1)err_exit("accept error");bool flags = false;//略過client[0].fd(listenfd), 從1開始檢測for (int i = 1; i < SETSIZE; ++i){if (client[i].fd == -1){client[i].fd = connfd;client[i].events = POLLIN;flags = true;if (i > maxi)maxi = i;break;}}//未找到一個合適的位置if (!flags){cerr << "too many clients" << endl;exit(EXIT_FAILURE);}cout << "count = " << ++count << endl;if (--nReady <= 0)continue;}/**如果是已連接套接口發生了可讀事件**/for (int i = 1; i <= maxi; ++i)if (client[i].revents & POLLIN){char buf[512] = {0};int readBytes = readline(client[i].fd, buf, sizeof(buf));if (readBytes == -1)err_exit("readline error");else if (readBytes == 0){cerr << "client connect closed..." << endl;close(client[i].fd);client[i].fd = -1;}cout << buf;if (writen(client[i].fd, buf, readBytes) == -1)err_exit("writen error");if (--nReady <= 0)break;}}}catch (const SocketException &e){cerr << e.what() << endl;err_exit("TCPServer error");} }附-getrlimit和setrlimit函數
每個進程都有一組資源限制,其中某一些可以用getrlimit和setrlimit函數查詢和更改。
#include <sys/time.h> #include <sys/resource.h> int getrlimit(int resource, struct rlimit *rlim); int setrlimit(int resource, const struct rlimit *rlim); //rlimit結構體 struct rlimit {rlim_t rlim_cur; /* Soft limit */rlim_t rlim_max; /* Hard limit (ceiling for rlim_cur) */ };軟限制是一個建議性的,?最好不要超越的限制,?如果超越的話,?系統可能向進程發送信號以終止其運行.
而硬限制一般是軟限制的上限;
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.只有超級用戶可以提高硬限制。
總結
以上是生活随笔為你收集整理的Socket编程实践(10) --select的限制与poll的使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Makefile学习(三)[第二版]
- 下一篇: POJ-1789 Truck Histo