首先看原先《UNIX網絡編程——并發服務器(TCP)》的代碼,服務器代碼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>??
- ??
- #define?ERR_EXIT(m)?\??
- ????do?{?\??
- ????????perror(m);?\??
- ????????exit(EXIT_FAILURE);?\??
- ????}?while?(0)??
- ??
- void?do_service(int);??
- ??
- int?main(void)??
- {??
- ????signal(SIGCHLD,?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;???
- ??
- ????pid_t?pid;??
- ??
- ????while?(1)??
- ????{??
- ????????if?((conn?=?accept(listenfd,?(struct?sockaddr?*)&peeraddr,?&peerlen))?<?0)???
- ????????{??
- ????????????if(?errno?==?EINTR?)??????????????
- ???????????????continue;??
- ????????????else??
- ???????????????ERR_EXIT("accept?error");??
- ????????}????????
- ????????printf("recv?connect?ip=%s?port=%d\n",?inet_ntoa(peeraddr.sin_addr),??
- ???????????????ntohs(peeraddr.sin_port));??
- ??
- ????????pid?=?fork();??
- ????????if?(pid?==?-1)??
- ????????????ERR_EXIT("fork?error");??
- ????????if?(pid?==?0)??
- ????????{??
- ??????????????
- ????????????close(listenfd);??
- ????????????do_service(conn);??
- ????????????exit(EXIT_SUCCESS);??
- ????????}??
- ????????else??
- ????????????close(conn);???
- ????}??
- ??
- ????return?0;??
- }??
- ??
- void?do_service(int?conn)??
- {??
- ????char?recvbuf[1024];??
- ????while?(1)??
- ????{??
- ????????memset(recvbuf,?0,?sizeof(recvbuf));??
- ????????int?ret?=?read(conn,?recvbuf,?sizeof(recvbuf));??
- ????????if?(ret?==?0)?????
- ????????{??
- ????????????printf("client?close\n");??
- ????????????break;??
- ????????}??
- ????????else?if?(ret?==?-1)??
- ????????????ERR_EXIT("read?error");??
- ????????fputs(recvbuf,?stdout);??
- ????????write(conn,?recvbuf,?ret);??
- ????}??
- }??
客戶端代碼cli.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>??
- ??
- ??
- #define?ERR_EXIT(m)?\??
- ????do?{?\??
- ????????perror(m);?\??
- ????????exit(EXIT_FAILURE);?\??
- ????}?while?(0)??
- ??
- ??
- ??
- ??
- int?main(void)??
- {??
- ????int?sock;??
- ????if?((sock?=?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?=?inet_addr("127.0.0.1");??
- ??????
- ??
- ????if?(connect(sock,?(struct?sockaddr?*)&servaddr,?sizeof(servaddr))?<?0)??
- ????????ERR_EXIT("connect?error");??
- ????struct?sockaddr_in?localaddr;??
- ????char?cli_ip[20];??
- ????socklen_t?local_len?=?sizeof(localaddr);??
- ????memset(&localaddr,?0,?sizeof(localaddr));??
- ????if(?getsockname(sock,(struct?sockaddr?*)&localaddr,&local_len)?!=?0?)??
- ????????ERR_EXIT("getsockname?error");??
- ????inet_ntop(AF_INET,?&localaddr.sin_addr,?cli_ip,?sizeof(cli_ip));??
- ????printf("host?%s:%d\n",?cli_ip,?ntohs(localaddr.sin_port));???
- ??
- ????char?sendbuf[1024]?=?{0};??
- ????char?recvbuf[1024]?=?{0};??
- ????while?(fgets(sendbuf,?sizeof(sendbuf),?stdin)?!=?NULL)??
- ????{??
- ??
- ????????write(sock,?sendbuf,?strlen(sendbuf));??
- ????????read(sock,?recvbuf,?sizeof(recvbuf));??
- ??
- ??
- ????????fputs(recvbuf,?stdout);??
- ??
- ????????memset(sendbuf,?0,?sizeof(sendbuf));??
- ????????memset(recvbuf,?0,?sizeof(recvbuf));??
- ????}??
- ??
- ??
- ????close(sock);??
- ??
- ??
- ????return?0;??
- }??
?先運行服務器端,再運行客戶端:
- huangcheng@ubuntu:~$?./serv??
- huangcheng@ubuntu:~$?./cli??
先查看一下網絡狀態:
- huangcheng@ubuntu:~$?netstat?-anp?|?grep?5188??
- (并非所有進程都能被檢測到,所有非本用戶的進程信息將不會顯示,如果想看到所有信息,則必須切換到?root?用戶)??
- tcp????????0??????0?0.0.0.0:5188????????????0.0.0.0:*???????????????LISTEN??????2750/serv??
- tcp????????0??????0?127.0.0.1:49484?????????127.0.0.1:5188??????????ESTABLISHED?2751/cli??
- tcp????????0??????0?127.0.0.1:5188??????????127.0.0.1:49484?????????ESTABLISHED?2752/serv??
可以看出建立了連接,服務器端有兩個進程,一個父進程處于監聽狀態,另一子進程正在對客戶端進行服務。
服務器端的子進程的pid為2752,并kill掉它:
- huangcheng@ubuntu:~$?kill?-9?2752??
再查看一下網絡狀態: - huangcheng@ubuntu:~$?netstat?-anp?|?grep?5188??
- (并非所有進程都能被檢測到,所有非本用戶的進程信息將不會顯示,如果想看到所有信息,則必須切換到?root?用戶)??
- tcp????????0??????0?0.0.0.0:5188????????????0.0.0.0:*???????????????LISTEN??????2750/serv??
- tcp????????1??????0?127.0.0.1:49484?????????127.0.0.1:5188??????????CLOSE_WAIT??2751/cli??
- tcp????????0??????0?127.0.0.1:5188??????????127.0.0.1:49484?????????FIN_WAIT2???-??
? ? ?來分析一下,我們將server子進程 ?kill掉,則其終止時,socket描述符會自動關閉并發FIN段給client,client收到FIN后處于CLOSE_WAIT狀態,但是client并沒有終止,也沒有關閉socket描述符,因此不會發FIN給 server子進程,因此server 子進程的TCP連接處于FIN_WAIT2狀態。
為什么會出現這種情況呢,來看client的部分程序:
- char?sendbuf[1024]?=?{0};??
- char?recvbuf[1024]?=?{0};??
- while?(fgets(sendbuf,?sizeof(sendbuf),?stdin)?!=?NULL)??
- {??
- ??
- ????write(sock,?sendbuf,?strlen(sendbuf));??
- ????read(sock,?recvbuf,?sizeof(recvbuf));??
- ??
- ??
- ????fputs(recvbuf,?stdout);??
- ??
- ????memset(sendbuf,?0,?sizeof(sendbuf));??
- ????memset(recvbuf,?0,?sizeof(recvbuf));??
- }??
? ? ?客戶端程序阻塞在了fgets 那里,即從標準輸入讀取數據,所以不能執行到下面的read,也即不能返回0,不會退出循環,不會調用close關閉sock,所以出現上述的情況,即狀態停滯,不能向前推進。 ? ? ?出現上述問題的根本原因在于客戶端程序不能并發處理從標準輸入讀取數據和從套接字讀取數據兩個事件,我們可以使用前面講過的select函數來完善客戶端程序。
- int?select(int?nfds,?fd_set?*readfds,?fd_set?*writefds,?fd_set?*exceptfds,?struct?timeval?*timeout);??
參數1:讀寫異常集合中的文件描述符的最大值加1;
參數2:讀集合,關心可讀事件;
套接口緩沖區有數據可讀
連接的讀一半關閉,即接收到FIN段,讀操作將返回0
如果是監聽套接口,已完成連接隊列不為空時。
套接口上發生了一個錯誤待處理,錯誤可以通過getsockopt指定SO_ERROR選項來獲取。
參數3:寫集合,關心可寫事件;
套接口發送緩沖區有空間容納數據。
連接的寫一半關閉。即收到RST段之后,再次調用write操作。
套接口上發生了一個錯誤待處理,錯誤可以通過getsockopt指定SO_ERROR選項來獲取。
參數4:異常集合,關心異常事件;
套接口存在帶外數據(TCP頭部 URG標志,16位緊急指針字段)
參數5:超時時間結構體
對于參數2,3,4來說,如果不關心對應事件則設置為NULL即可。注意5個參數都是輸入輸出參數,即select返回時可能對其進行了修改,比如集合被修改以便標記哪些套接口發生了事件,時間結構體的傳出參數是剩余的時間,如果設置為NULL表示永不超時。用select管理多個I/O,select阻塞等待,一旦其中的一個或多個I/O檢測到我們所感興趣的事件,select函數返回,返回值為檢測到的事件個數,并且返回哪些I/O發送了事件,遍歷這些事件,進而處理事件。注意當select阻塞返回后,此時調用read/write 是不會阻塞的,因為正是有可讀可寫事件發生才導致select 返回,也可以認為是select 提前阻塞了。
下面是4個可以對集合進行操作的宏:
- void?FD_CLR(int?fd,?fd_set?*set);???
- int??FD_ISSET(int?fd,?fd_set?*set);???
- void?FD_SET(int?fd,?fd_set?*set);???
- void?FD_ZERO(fd_set?*set);???
下面是通過select函數改進的客戶端的代碼cli.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>??
- ??
- ??
- #define?ERR_EXIT(m)?\??
- ????do?{?\??
- ????????perror(m);?\??
- ????????exit(EXIT_FAILURE);?\??
- ????}?while?(0)??
- ??
- ??
- ??
- ??
- int?main(void)??
- {??
- ????int?sock;??
- ????if?((sock?=?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?=?inet_addr("127.0.0.1");??
- ??????
- ??
- ????if?(connect(sock,?(struct?sockaddr?*)&servaddr,?sizeof(servaddr))?<?0)??
- ????????ERR_EXIT("connect?error");??
- ????struct?sockaddr_in?localaddr;??
- ????char?cli_ip[20];??
- ????socklen_t?local_len?=?sizeof(localaddr);??
- ????memset(&localaddr,?0,?sizeof(localaddr));??
- ????if(?getsockname(sock,(struct?sockaddr?*)&localaddr,&local_len)?!=?0?)??
- ????????ERR_EXIT("getsockname?error");??
- ????inet_ntop(AF_INET,?&localaddr.sin_addr,?cli_ip,?sizeof(cli_ip));??
- ????printf("host?%s:%d\n",?cli_ip,?ntohs(localaddr.sin_port));???
- ??
- ????fd_set?rset;??
- ????FD_ZERO(&rset);??
- ????int?nready;??
- ????int?maxfd;??
- ????int?fd_stdin?=?fileno(stdin);???
- ????if?(fd_stdin?>?sock)??
- ????????maxfd?=?fd_stdin;??
- ????else??
- ????????maxfd?=?sock;??
- ????char?sendbuf[1024]?=?{0};??
- ????char?recvbuf[1024]?=?{0};??
- ??????
- ????while?(1)??
- ????{??
- ??
- ????????FD_SET(fd_stdin,?&rset);??
- ????????FD_SET(sock,?&rset);??
- ????????nready?=?select(maxfd?+?1,?&rset,?NULL,?NULL,?NULL);???
- ????????if?(nready?==?-1)??
- ????????????ERR_EXIT("select?error");??
- ??
- ????????if?(nready?==?0)??
- ????????????continue;??
- ??
- ????????if?(FD_ISSET(sock,?&rset))??
- ????????{??
- ??
- ????????????int?ret?=?read(sock,?recvbuf,?sizeof(recvbuf));???
- ????????????if?(ret?==?-1)??
- ????????????????ERR_EXIT("read?error");??
- ????????????else?if?(ret??==?0)?????
- ????????????{??
- ????????????????printf("server?close\n");??
- ????????????????break;??
- ????????????}??
- ??
- ????????????fputs(recvbuf,?stdout);??
- ????????????memset(recvbuf,?0,?sizeof(recvbuf));??
- ????????}??
- ??
- ????????if?(FD_ISSET(fd_stdin,?&rset))??
- ????????{??
- ??
- ????????????if?(fgets(sendbuf,?sizeof(sendbuf),?stdin)?==?NULL)??
- ????????????????break;??
- ??
- ????????????write(sock,?sendbuf,?strlen(sendbuf));??
- ????????????memset(sendbuf,?0,?sizeof(sendbuf));??
- ????????}??
- ????}??
- ??
- ????close(sock);??
- ????return?0;??
- }??
? ? ?即將兩個事件都添加進可讀事件集合,在while循環中,如果select返回說明有事件發生,依次判斷是哪些事件發生,如果是標準輸入有數據可讀,則讀取后再次回到循環開頭select阻塞等待事件發生,如果是套接口有數據可讀,且返回為0則說明對方已經關閉連接,退出循環并調用close關閉sock。
重復前面的操作:
(1)先運行服務器,再運行客戶端
- huangcheng@ubuntu:~$?./serv??
- huangcheng@ubuntu:~$?./cli??
(2)查看網絡狀態:
- huangcheng@ubuntu:~$?netstat?-anp?|?grep?5188??
- (并非所有進程都能被檢測到,所有非本用戶的進程信息將不會顯示,如果想看到所有信息,則必須切換到?root?用戶)??
- tcp????????0??????0?0.0.0.0:5188????????????0.0.0.0:*???????????????LISTEN??????2960/serv??
- tcp????????0??????0?127.0.0.1:49485?????????127.0.0.1:5188??????????ESTABLISHED?2963/cli??
- tcp????????0??????0?127.0.0.1:5188??????????127.0.0.1:49485?????????ESTABLISHED?2964/serv??
(3)kill掉服務器的子進程,再查看網絡狀態:
- huangcheng@ubuntu:~$?kill?-9?2964??
- huangcheng@ubuntu:~$?netstat?-anp?|?grep?5188??
- (并非所有進程都能被檢測到,所有非本用戶的進程信息將不會顯示,如果想看到所有信息,則必須切換到?root?用戶)??
- tcp????????0??????0?0.0.0.0:5188????????????0.0.0.0:*???????????????LISTEN??????2960/serv??
- tcp????????0??????0?127.0.0.1:5188??????????127.0.0.1:49485?????????TIME_WAIT???-??
? ? ?即 client 關閉socket描述符,server 子進程的TCP連接收到client發的FIN段后處于TIME_WAIT狀態,此時會再發生一個ACK段給client,client接收到之后就處于CLOSED狀態,這個狀態存在時間很短,所以看不到客戶端的輸出條目,TCP協議規定,主動關閉連接的一方要處于TIME_WAIT狀態,等待兩個MSL(maximumsegment lifetime)的時間后才能回到CLOSED狀態,需要有MSL 時間的主要原因是在這段時間內如果最后一個ack段沒有發送給對方,則可以重新發送。
? ? ?過一小會再次查看網絡狀態:
- huangcheng@ubuntu:~$?netstat?-anp?|?grep?5188??
- (并非所有進程都能被檢測到,所有非本用戶的進程信息將不會顯示,如果想看到所有信息,則必須切換到?root?用戶)??
- tcp????????0??????0?0.0.0.0:5188????????????0.0.0.0:*???????????????LISTEN??????2960/serv??
? ? ?可以發現只剩下服務器端父進程的監聽狀態了,由TIME_WAIT狀態轉入CLOSED狀態,也很快會消失。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
? ? ?前面我們實現的能夠并發服務的服務器端程序是使用fork出多個子進程來實現的,現在學習了select函數,可以用它來改進服務器端程序,實現單進程并發服務。先看如下程序,再來解釋:
- #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);??
- ??
- ????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");??
- ??????????????
- ????????????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("readline?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;??
- }??
- ??
- ?
- ?
- ?
- ?
- ??
? ? ?程序第一次進入while 循環,只把監聽套接字加入關心的事件,select返回說明監聽套接字有可讀事件,即已完成連接隊列不為空,這時調用accept不會阻塞,返回一個已連接套接字,將這個套接字加入allset,因為第一次運行則nready = 1,直接continue跳回到while 循環開頭,再次調用select,這次會關心監聽套接字和一個已連接套接字的可讀事件,如果繼續有客戶端連接上來則繼續將其加入allset,這次nready = 2,繼續執行下面的for 循環,然后對客戶端進行服務。服務完畢再次回到while 開頭調用select 阻塞時,就關心一個監聽套接字和2個已連接套接字的可讀事件了,一直循環下去。
? ? ?程序大概邏輯就這樣,一些細節就大家自己想想了,比如client數組是用來保存已連接套接字的,為了避免每次都得遍歷到FD_SETSIZE-1,保存一個最大不空閑下標maxi,每次遍歷到maxi就可以了。每次得到一個conn,要判斷一下conn與maxfd的大小。
當得知某個客戶端關閉,則需要將conn在allset中清除掉。之所以要有allset 和 rset 兩個變量是因為rset是傳入傳出參數,在select返回時rset可能被改變,故需要每次在回到while 循環開頭時需要將allset 重新賦予rset 。
