http://blog.csdn.net/chenxun_2010/article/details/50489577
一、用select實現的并發服務器,能達到的并發數,受兩方面限制
? ? ? ?1、一個進程能打開的最大文件描述符限制。這可以通過調整內核參數。可以通過ulimit -n來調整或者使用setrlimit函數設置,?但一個系統所能打開的最大數也是有限的,跟內存大小有關,可以通過cat /proc/sys/fs/file-max?查看
? ? ? ?2、select中的fd_set集合容量的限制(FD_SETSIZE,一般為1024) ,這需要重新編譯內核。
可以寫個測試程序,只建立連接,看看最多能夠建立多少個連接,客戶端程序如下:
- #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;??
- }??
服務器的代碼serv.c
- #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 ?
輸出太多條目,上面只截取最后幾條,從中可以看出對于客戶端,最多只能開啟1021個連接套接字,因為總共是1024個,還得除去0、1、2。而服務器端只能accept 返回1020個已連接套接字,因為除了0、1、2之外還有一個監聽套接字,客戶端某一個套接字(不一定是最后一個)雖然已經建立了連接,在已完成連接隊列中,但accept 返回時達到最大描述符限制,返回錯誤,打印提示信息。
? ? ? ?也許有人會注意到上面有一行 sleep(4);當客戶端調用socket準備創建第1022個套接字時,如上所示也會提示錯誤,此時socket函數返回-1出錯,如果沒有睡眠4s后再退出進程會有什么問題呢?如果直接退出進程,會將客戶端所打開的所有套接字關閉掉,即向服務器端發送了很多FIN段,而此時也許服務器端還一直在accept ,即還在從已連接隊列中返回已連接套接字,此時服務器端除了關心監聽套接字的可讀事件,也開始關心前面已建立連接的套接字的可讀事件,read 返回0,所以會有很多 client close 字段 參雜在條目的輸出中,還有個問題就是,因為read 返回0,服務器端會將自身的已連接套接字關閉掉,那么也許剛才說的客戶端某一個連接會被accept 返回,即測試不出服務器端真正的并發容量。
- 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 達到了1021,原因就是服務器端前面已經有些套接字關閉了,所以accept 創建套接字不會出錯,服務器進程也不會因為出錯而退出,可以看到最后接收到的一個連接端口是51364,即不一定是客戶端的最后一個連接。
二、poll 函數應用舉例
- #include?<poll.h>??
- int?poll(struct?pollfd?*fds,?nfds_t?nfds,?int?timeout);??
參數1:結構體數組指針
- struct?pollfd?{??
- ????int???fd;???????????
- ????short?events;???????
- ????short?revents;??????
- };??
結構體中的fd 即套接字描述符,events 即感興趣的事件,如下圖所示,revents 即返回的事件。

參數2:結構體數組的成員個數,即文件描述符個數。
參數3:即超時時間,若為-1,表示永不超時。
? ? ? ?poll 跟 select 還是很相似的,比較重要的區別在于poll 所能并發的個數跟FD_SETSIZE無關,只跟一個進程所能打開的文件描述符個數有關,可以在select 程序的基礎上修改成poll 程序,在運行服務器端程序之前,使用ulimit -n 2048?將限制改成2048個,注意在運行客戶端進程的終端也需更改,因為客戶端也會有所限制,這只是臨時性的更改,因為子進程會繼承這個環境參數,而我們是在bash命令行啟動程序的,故在進程運行期間,文件描述符的限制為2048個。
使用poll 函數的服務器端程序如下:
- #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;??
- }??
- ??
- ??
參照前面對
select 函數
的解釋不難理解上面的程序,就不再贅述了。來看一下輸出:
- 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??
- 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??
? ? ? ?可以看到現在最大的連接數已經是2045個了,雖然服務器端有某個連接沒有accept 返回。即poll 比 select 能夠承受更多的并發連接,只受一個進程所能打開的最大文件描述符個數限制。可以通過ulimit -n ?修改,但一個系統所能打開的文件描述符個數也是有限的,這跟系統的內存大小有關系,所以說也不是可以無限地并
發,可以查看一下本機的容量:
- huangcheng@ubuntu:~$?cat?/proc/sys/fs/file-max??
- 101598??
本機是虛擬機,內存2G,能夠打開的文件描述符個數大約在10w個左右。