生活随笔
收集整理的這篇文章主要介紹了
UNIX网络编程——select函数的并发限制和 poll 函数应用举例
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
http://blog.csdn.net/chenxun_2010/article/details/50489577
一、用select實(shí)現(xiàn)的并發(fā)服務(wù)器,能達(dá)到的并發(fā)數(shù),受兩方面限制
? ? ? ?1、一個(gè)進(jìn)程能打開的最大文件描述符限制。這可以通過調(diào)整內(nèi)核參數(shù)。可以通過ulimit -n來調(diào)整或者使用setrlimit函數(shù)設(shè)置,?但一個(gè)系統(tǒng)所能打開的最大數(shù)也是有限的,跟內(nèi)存大小有關(guān),可以通過cat /proc/sys/fs/file-max?查看
? ? ? ?2、select中的fd_set集合容量的限制(FD_SETSIZE,一般為1024) ,這需要重新編譯內(nèi)核。
可以寫個(gè)測(cè)試程序,只建立連接,看看最多能夠建立多少個(gè)連接,客戶端程序如下:
[cpp]?view plaincopy
#include?<sys/types.h>?? #include?<sys/socket.h>?? #include?<netinet/in.h>?? #include?<arpa/inet.h>?? #include?<signal.h>?? #include?<stdlib.h>?? #include?<stdio.h>?? #include?<errno.h>?? #include?<string.h>?? ?? #define?ERR_EXIT(m)?\?? ????????do?\?? ????????{?\?? ????????????????perror(m);?\?? ????????????????exit(EXIT_FAILURE);?\?? ????????}?while(0)?? ?? ?? int?main(void)?? {?? ????int?count?=?0;?? ????while(1)?? ????{?? ????????int?sock;?? ????????if?((sock?=?socket(PF_INET,?SOCK_STREAM,?IPPROTO_TCP))?<?0)?? ????????{?? ????????????sleep(4);?? ????????????ERR_EXIT("socket");?? ????????}?? ?? ????????struct?sockaddr_in?servaddr;?? ????????memset(&servaddr,?0,?sizeof(servaddr));?? ????????servaddr.sin_family?=?AF_INET;?? ????????servaddr.sin_port?=?htons(5188);?? ????????servaddr.sin_addr.s_addr?=?inet_addr("127.0.0.1");?? ?? ????????if?(connect(sock,?(struct?sockaddr?*)&servaddr,?sizeof(servaddr))?<?0)?? ????????????ERR_EXIT("connect");?? ?? ????????struct?sockaddr_in?localaddr;?? ????????socklen_t?addrlen?=?sizeof(localaddr);?? ????????if?(getsockname(sock,?(struct?sockaddr?*)&localaddr,?&addrlen)?<?0)?? ????????????ERR_EXIT("getsockname");?? ?? ????????printf("ip=%s?port=%d\n",?inet_ntoa(localaddr.sin_addr),?ntohs(localaddr.sin_port));?? ????????printf("count?=?%d\n",?++count);?? ?? ????}?? ?? ????return?0;?? }??
服務(wù)器的代碼serv.c
[cpp]?view plaincopy
#include<stdio.h>?? #include<sys/types.h>?? #include<sys/socket.h>?? #include<unistd.h>?? #include<stdlib.h>?? #include<errno.h>?? #include<arpa/inet.h>?? #include<netinet/in.h>?? #include<string.h>?? #include<signal.h>?? #include<sys/wait.h>?? ?? ?? ?? #define?ERR_EXIT(m)?\?? ????do?{?\?? ????????perror(m);?\?? ????????exit(EXIT_FAILURE);?\?? ????}?while?(0)?? ?? ?? int?main(void)?? {?? ?????? ????signal(SIGPIPE,?SIG_IGN);?? ????int?listenfd;??? ????if?((listenfd?=?socket(PF_INET,?SOCK_STREAM,?IPPROTO_TCP))?<?0)?? ?? ????????ERR_EXIT("socket?error");?? ?? ????struct?sockaddr_in?servaddr;?? ????memset(&servaddr,?0,?sizeof(servaddr));?? ????servaddr.sin_family?=?AF_INET;?? ????servaddr.sin_port?=?htons(5188);?? ????servaddr.sin_addr.s_addr?=?htonl(INADDR_ANY);??? ?????? ?????? ?????? ????int?on?=?1;?? ????if?(setsockopt(listenfd,?SOL_SOCKET,?SO_REUSEADDR,?&on,?sizeof(on))?<?0)?? ????????ERR_EXIT("setsockopt?error");?? ?? ????if?(bind(listenfd,?(struct?sockaddr*)&servaddr,sizeof(servaddr))?<?0)?? ????????ERR_EXIT("bind?error");?? ?? ????if?(listen(listenfd,?SOMAXCONN)?<?0)??? ????????ERR_EXIT("listen?error");?? ?????? ????struct?sockaddr_in?peeraddr;??? ????socklen_t?peerlen?=?sizeof(peeraddr);??? ?????? ????int?conn;??? ????int?i;?? ????int?client[FD_SETSIZE];?? ????int?maxi?=?0;??? ????for?(i?=?0;?i?<?FD_SETSIZE;?i++)?? ????????client[i]?=?-1;?? ?? ????int?nready;?? ????int?maxfd?=?listenfd;?? ????fd_set?rset;?? ????fd_set?allset;?? ????FD_ZERO(&rset);?? ????FD_ZERO(&allset);?? ????FD_SET(listenfd,?&allset);?? ?? ????int?count?=?0;?? ????while?(1)?{?? ????????rset?=?allset;?? ????????nready?=?select(maxfd?+?1,?&rset,?NULL,?NULL,?NULL);?? ????????if?(nready?==?-1)?{?? ????????????if?(errno?==?EINTR)?? ????????????????continue;?? ????????????ERR_EXIT("select?error");?? ????????}?? ?? ????????if?(nready?==?0)?? ????????????continue;?? ?? ????????if?(FD_ISSET(listenfd,?&rset))?{?? ?????????? ????????????conn?=?accept(listenfd,?(struct?sockaddr*)&peeraddr,?&peerlen);???? ????????????if?(conn?==?-1)?? ????????????????ERR_EXIT("accept?error");?? ????????????printf("count?=?%d\n",?++count);?? ????????????for?(i?=?0;?i?<?FD_SETSIZE;?i++)?{?? ????????????????if?(client[i]?<?0)?{?? ????????????????????client[i]?=?conn;?? ????????????????????if?(i?>?maxi)?? ????????????????????????maxi?=?i;?? ????????????????????break;?? ????????????????}??? ????????????}?? ?????????????? ????????????if?(i?==?FD_SETSIZE)?{?? ????????????????fprintf(stderr,?"too?many?clients\n");?? ????????????????exit(EXIT_FAILURE);?? ????????????}?? ?? ????????????printf("recv?connect?ip=%s?port=%d\n",?inet_ntoa(peeraddr.sin_addr),?? ????????????????ntohs(peeraddr.sin_port));?? ?? ????????????FD_SET(conn,?&allset);?? ????????????if?(conn?>?maxfd)?? ????????????????maxfd?=?conn;?? ?? ????????????if?(--nready?<=?0)?? ????????????????continue;?? ????????}?? ?? ????????for?(i?=?0;?i?<=?maxi;?i++)?{?? ????????????conn?=?client[i];?? ????????????if?(conn?==?-1)?? ????????????????continue;?? ?? ????????????if?(FD_ISSET(conn,?&rset))?{?? ?????????????????? ????????????????char?recvbuf[1024]?=?{0};?? ????????????????int?ret?=?read(conn,?recvbuf,?1024);?? ????????????????if?(ret?==?-1)?? ????????????????????ERR_EXIT("read?error");?? ????????????????else?if?(ret??==?0)?{??? ????????????????????printf("client?close?\n");?? ????????????????????FD_CLR(conn,?&allset);?? ????????????????????client[i]?=?-1;?? ????????????????????close(conn);?? ????????????????}?? ?????????? ????????????????fputs(recvbuf,?stdout);?? ????????????????write(conn,?recvbuf,?strlen(recvbuf));?? ?????????????????? ????????????????if?(--nready?<=?0)?? ????????????????????break;??? ????????????}?? ????????}?? ?? ?? ????}?? ?????????? ????return?0;?? }?? ?? ? ? ? ? ??
huangcheng@ubuntu:~$ ./serv ?
count = 1 ?
recv connect ip=127.0.0.1 port=48370 ?
count = 2 ?
recv connect ip=127.0.0.1 port=48371 ?
count = 3 ?
recv connect ip=127.0.0.1 port=48372 ?
count = 4 ?
recv connect ip=127.0.0.1 port=48373 ?
.................................... ?
recv connect ip=127.0.0.1 port=49389 ?
count = 1020 ?
recv connect ip=127.0.0.1 port=49390 ?
accept error: Too many open files ?
[cpp] view plaincopyprint?
huangcheng@ubuntu:~$ ./cli ?
ip=127.0.0.1 port=46327 ?
count = 1 ?
ip=127.0.0.1 port=46328 ?
count = 2 ?
ip=127.0.0.1 port=46329 ?
count = 3 ?
ip=127.0.0.1 port=46330 ?
count = 4 ?
ip=127.0.0.1 port=46331 ?
count = 5 ?
ip=127.0.0.1 port=46332 ?
count = 6 ?
ip=127.0.0.1 port=46333 ?
....................... ?
ip=127.0.0.1 port=47345 ?
count = 1020 ?
ip=127.0.0.1 port=47346 ?
count = 1021 ?
socket: Too many open files ?
輸出太多條目,上面只截取最后幾條,從中可以看出對(duì)于客戶端,最多只能開啟1021個(gè)連接套接字,因?yàn)榭偣彩?024個(gè),還得除去0、1、2。而服務(wù)器端只能accept 返回1020個(gè)已連接套接字,因?yàn)槌?、1、2之外還有一個(gè)監(jiān)聽套接字,客戶端某一個(gè)套接字(不一定是最后一個(gè))雖然已經(jīng)建立了連接,在已完成連接隊(duì)列中,但accept 返回時(shí)達(dá)到最大描述符限制,返回錯(cuò)誤,打印提示信息。
? ? ? ?也許有人會(huì)注意到上面有一行 sleep(4);當(dāng)客戶端調(diào)用socket準(zhǔn)備創(chuàng)建第1022個(gè)套接字時(shí),如上所示也會(huì)提示錯(cuò)誤,此時(shí)socket函數(shù)返回-1出錯(cuò),如果沒有睡眠4s后再退出進(jìn)程會(huì)有什么問題呢?如果直接退出進(jìn)程,會(huì)將客戶端所打開的所有套接字關(guān)閉掉,即向服務(wù)器端發(fā)送了很多FIN段,而此時(shí)也許服務(wù)器端還一直在accept ,即還在從已連接隊(duì)列中返回已連接套接字,此時(shí)服務(wù)器端除了關(guān)心監(jiān)聽套接字的可讀事件,也開始關(guān)心前面已建立連接的套接字的可讀事件,read 返回0,所以會(huì)有很多 client close 字段 參雜在條目的輸出中,還有個(gè)問題就是,因?yàn)閞ead 返回0,服務(wù)器端會(huì)將自身的已連接套接字關(guān)閉掉,那么也許剛才說的客戶端某一個(gè)連接會(huì)被accept 返回,即測(cè)試不出服務(wù)器端真正的并發(fā)容量。
huangcheng@ubuntu:~$?./serv?? count?=?1?? recv?connect?ip=127.0.0.1?port=50413?? count?=?2?? ....................................?? client?close?? client?close?? client?close?? client?close?? ...................................?? recv?connect?ip=127.0.0.1?port=51433?? client?close?? count?=?1021?? recv?connect?ip=127.0.0.1?port=51364?? client?close?? client?close??
? ? ? 可以看到輸出參雜著client close,且這次的count 達(dá)到了1021,原因就是服務(wù)器端前面已經(jīng)有些套接字關(guān)閉了,所以accept 創(chuàng)建套接字不會(huì)出錯(cuò),服務(wù)器進(jìn)程也不會(huì)因?yàn)槌鲥e(cuò)而退出,可以看到最后接收到的一個(gè)連接端口是51364,即不一定是客戶端的最后一個(gè)連接。
二、poll 函數(shù)應(yīng)用舉例
[cpp]?view plaincopy
#include?<poll.h>?? int?poll(struct?pollfd?*fds,?nfds_t?nfds,?int?timeout);??
參數(shù)1:結(jié)構(gòu)體數(shù)組指針
[cpp]?view plaincopy
struct?pollfd?{?? ????int???fd;??????????? ????short?events;??????? ????short?revents;?????? };??
結(jié)構(gòu)體中的fd 即套接字描述符,events 即感興趣的事件,如下圖所示,revents 即返回的事件。
參數(shù)2:結(jié)構(gòu)體數(shù)組的成員個(gè)數(shù),即文件描述符個(gè)數(shù)。
參數(shù)3:即超時(shí)時(shí)間,若為-1,表示永不超時(shí)。
? ? ? ?poll 跟 select 還是很相似的,比較重要的區(qū)別在于poll 所能并發(fā)的個(gè)數(shù)跟FD_SETSIZE無關(guān),只跟一個(gè)進(jìn)程所能打開的文件描述符個(gè)數(shù)有關(guān),可以在select 程序的基礎(chǔ)上修改成poll 程序,在運(yùn)行服務(wù)器端程序之前,使用ulimit -n 2048?將限制改成2048個(gè),注意在運(yùn)行客戶端進(jìn)程的終端也需更改,因?yàn)榭蛻舳艘矔?huì)有所限制,這只是臨時(shí)性的更改,因?yàn)樽舆M(jìn)程會(huì)繼承這個(gè)環(huán)境參數(shù),而我們是在bash命令行啟動(dòng)程序的,故在進(jìn)程運(yùn)行期間,文件描述符的限制為2048個(gè)。
使用poll 函數(shù)的服務(wù)器端程序如下:
[cpp]?view plaincopy
#include<stdio.h>?? #include<sys/types.h>?? #include<sys/socket.h>?? #include<unistd.h>?? #include<stdlib.h>?? #include<errno.h>?? #include<arpa/inet.h>?? #include<netinet/in.h>?? #include<string.h>?? #include<signal.h>?? #include<sys/wait.h>?? #include<poll.h>?? ?? #define?ERR_EXIT(m)?\?? ????do?{?\?? ????????perror(m);?\?? ????????exit(EXIT_FAILURE);?\?? ????}?while?(0)?? ?? ?? int?main(void)?? {?? ????int?count?=?0;?? ????signal(SIGPIPE,?SIG_IGN);?? ????int?listenfd;??? ????if?((listenfd?=?socket(PF_INET,?SOCK_STREAM,?IPPROTO_TCP))?<?0)?? ?????????? ????????ERR_EXIT("socket?error");?? ?? ????struct?sockaddr_in?servaddr;?? ????memset(&servaddr,?0,?sizeof(servaddr));?? ????servaddr.sin_family?=?AF_INET;?? ????servaddr.sin_port?=?htons(5188);?? ????servaddr.sin_addr.s_addr?=?htonl(INADDR_ANY);?? ?????? ?????? ?? ????int?on?=?1;?? ????if?(setsockopt(listenfd,?SOL_SOCKET,?SO_REUSEADDR,?&on,?sizeof(on))?<?0)?? ????????ERR_EXIT("setsockopt?error");?? ?? ????if?(bind(listenfd,?(struct?sockaddr?*)&servaddr,?sizeof(servaddr))?<?0)?? ????????ERR_EXIT("bind?error");?? ?? ????if?(listen(listenfd,?SOMAXCONN)?<?0)??? ????????ERR_EXIT("listen?error");?? ?? ????struct?sockaddr_in?peeraddr;??? ????socklen_t?peerlen?=?sizeof(peeraddr);??? ?? ????int?conn;??? ????int?i;?? ?? ????struct?pollfd?client[2048];?? ????int?maxi?=?0;??? ?? ????for?(i?=?0;?i?<?2048;?i++)?? ????????client[i].fd?=?-1;?? ?? ????int?nready;?? ????client[0].fd?=?listenfd;?? ????client[0].events?=?POLLIN;?? ?? ????while?(1)?? ????{?? ?????????? ????????nready?=?poll(client,?maxi?+?1,?-1);?? ????????if?(nready?==?-1)?? ????????{?? ????????????if?(errno?==?EINTR)?? ????????????????continue;?? ????????????ERR_EXIT("poll?error");?? ????????}?? ?? ????????if?(nready?==?0)?? ????????????continue;?? ?? ????????if?(client[0].revents?&?POLLIN)?? ????????{?? ?? ????????????conn?=?accept(listenfd,?(struct?sockaddr?*)&peeraddr,?&peerlen);??? ????????????if?(conn?==?-1)?? ????????????????ERR_EXIT("accept?error");?? ?? ????????????for?(i?=?1;?i?<?2048;?i++)?? ????????????{?? ????????????????if?(client[i].fd?<?0)?? ????????????????{?? ????????????????????client[i].fd?=?conn;?? ????????????????????if?(i?>?maxi)?? ????????????????????????maxi?=?i;?? ????????????????????break;?? ????????????????}?? ????????????}?? ?? ????????????if?(i?==?2048)?? ????????????{?? ????????????????fprintf(stderr,?"too?many?clients\n");?? ????????????????exit(EXIT_FAILURE);?? ????????????}?? ?? ????????????printf("count?=?%d\n",?++count);?? ????????????printf("recv?connect?ip=%s?port=%d\n",?inet_ntoa(peeraddr.sin_addr),?? ???????????????????ntohs(peeraddr.sin_port));?? ?? ????????????client[i].events?=?POLLIN;?? ?? ????????????if?(--nready?<=?0)?? ????????????????continue;?? ????????}?? ?? ????????for?(i?=?1;?i?<=?maxi;?i++)?? ????????{?? ????????????conn?=?client[i].fd;?? ????????????if?(conn?==?-1)?? ????????????????continue;?? ????????????if?(client[i].revents?&?POLLIN)?? ????????????{?? ?? ????????????????char?recvbuf[1024]?=?{0};?? ????????????????int?ret?=?read(conn,?recvbuf,?1024);?? ????????????????if?(ret?==?-1)?? ????????????????????ERR_EXIT("readline?error");?? ????????????????else?if?(ret??==?0)????? ????????????????{?? ????????????????????printf("client??close?\n");?? ????????????????????client[i].fd?=?-1;?? ????????????????????close(conn);?? ????????????????}?? ?? ????????????????fputs(recvbuf,?stdout);?? ????????????????write(conn,?recvbuf,?strlen(recvbuf));?? ?? ????????????????if?(--nready?<=?0)?? ????????????????????break;?? ????????????}?? ????????}?? ?? ?? ????}?? ?? ????return?0;?? }?? ?? ??
參照前面對(duì)
select 函數(shù)
的解釋不難理解上面的程序,就不再贅述了。來看一下輸出:
[cpp]?view plaincopy
root@ubuntu:/home/huangcheng#?ulimit?-n?2048?? root@ubuntu:/home/huangcheng#?su?-?huangcheng?? huangcheng@ubuntu:~$?ulimit?-n?? 2048?? huangcheng@ubuntu:~$?./serv?? ...........................?? count?=?2042?? recv?connect?ip=127.0.0.1?port=54499?? count?=?2043?? recv?connect?ip=127.0.0.1?port=54500?? count?=?2044?? recv?connect?ip=127.0.0.1?port=54501?? accept?error:?Too?many?open?files??
[cpp]?view plaincopy
root@ubuntu:/home/huangcheng#?ulimit?-n?2048?? root@ubuntu:/home/huangcheng#?su?-?huangcheng?? huangcheng@ubuntu:~$?ulimit?-n?? 2048?? huangcheng@ubuntu:~$./cli?? ..........................?? ip=127.0.0.1?port=54499?? count?=?2043?? ip=127.0.0.1?port=54500?? count?=?2044?? ip=127.0.0.1?port=54501?? count?=?2045?? socket:?Too?many?open?files??
? ? ? ?可以看到現(xiàn)在最大的連接數(shù)已經(jīng)是2045個(gè)了,雖然服務(wù)器端有某個(gè)連接沒有accept 返回。即poll 比 select 能夠承受更多的并發(fā)連接,只受一個(gè)進(jìn)程所能打開的最大文件描述符個(gè)數(shù)限制。可以通過ulimit -n ?修改,但一個(gè)系統(tǒng)所能打開的文件描述符個(gè)數(shù)也是有限的,這跟系統(tǒng)的內(nèi)存大小有關(guān)系,所以說也不是可以無限地并
發(fā),可以查看一下本機(jī)的容量:
[cpp]?view plaincopy
huangcheng@ubuntu:~$?cat?/proc/sys/fs/file-max?? 101598??
本機(jī)是虛擬機(jī),內(nèi)存2G,能夠打開的文件描述符個(gè)數(shù)大約在10w個(gè)左右。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)
總結(jié)
以上是生活随笔為你收集整理的UNIX网络编程——select函数的并发限制和 poll 函数应用举例的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。