深入研究socket編程(3)——使用select函數編寫客戶端和服務器

http://blog.csdn.net/chenxun_2010/article/details/50488394

首先看原先《UNIX網絡編程——并發服務器(TCP)》的代碼,服務器代碼serv.c:

[cpp]?view plaincopy
  1. #include<stdio.h>??
  2. #include<sys/types.h>??
  3. #include<sys/socket.h>??
  4. #include<unistd.h>??
  5. #include<stdlib.h>??
  6. #include<errno.h>??
  7. #include<arpa/inet.h>??
  8. #include<netinet/in.h>??
  9. #include<string.h>??
  10. #include<signal.h>??
  11. ??
  12. #define?ERR_EXIT(m)?\??
  13. ????do?{?\??
  14. ????????perror(m);?\??
  15. ????????exit(EXIT_FAILURE);?\??
  16. ????}?while?(0)??
  17. ??
  18. void?do_service(int);??
  19. ??
  20. int?main(void)??
  21. {??
  22. ????signal(SIGCHLD,?SIG_IGN);??
  23. ????int?listenfd;?//被動套接字(文件描述符),即只可以accept,?監聽套接字??
  24. ????if?((listenfd?=?socket(PF_INET,?SOCK_STREAM,?IPPROTO_TCP))?<?0)??
  25. ????????//??listenfd?=?socket(AF_INET,?SOCK_STREAM,?0)??
  26. ????????ERR_EXIT("socket?error");??
  27. ??
  28. ????struct?sockaddr_in?servaddr;??
  29. ????memset(&servaddr,?0,?sizeof(servaddr));??
  30. ????servaddr.sin_family?=?AF_INET;??
  31. ????servaddr.sin_port?=?htons(5188);??
  32. ????servaddr.sin_addr.s_addr?=?htonl(INADDR_ANY);??
  33. ????/*?servaddr.sin_addr.s_addr?=?inet_addr("127.0.0.1");?*/??
  34. ????/*?inet_aton("127.0.0.1",?&servaddr.sin_addr);?*/??
  35. ??
  36. ????int?on?=?1;??
  37. ????if?(setsockopt(listenfd,?SOL_SOCKET,?SO_REUSEADDR,?&on,?sizeof(on))?<?0)??
  38. ????????ERR_EXIT("setsockopt?error");??
  39. ??
  40. ????if?(bind(listenfd,?(struct?sockaddr?*)&servaddr,?sizeof(servaddr))?<?0)??
  41. ????????ERR_EXIT("bind?error");??
  42. ??
  43. ????if?(listen(listenfd,?SOMAXCONN)?<?0)?//listen應在socket和bind之后,而在accept之前??
  44. ????????ERR_EXIT("listen?error");??
  45. ??
  46. ????struct?sockaddr_in?peeraddr;?//傳出參數??
  47. ????socklen_t?peerlen?=?sizeof(peeraddr);?//傳入傳出參數,必須有初始值??
  48. ????int?conn;?//?已連接套接字(變為主動套接字,即可以主動connect)??
  49. ??
  50. ????pid_t?pid;??
  51. ??
  52. ????while?(1)??
  53. ????{??
  54. ????????if?((conn?=?accept(listenfd,?(struct?sockaddr?*)&peeraddr,?&peerlen))?<?0)?//3次握手完成的序列??
  55. ????????{??
  56. ????????????if(?errno?==?EINTR?)????????????///必須處理被中斷的系統調用??
  57. ???????????????continue;??
  58. ????????????else??
  59. ???????????????ERR_EXIT("accept?error");??
  60. ????????}????????
  61. ????????printf("recv?connect?ip=%s?port=%d\n",?inet_ntoa(peeraddr.sin_addr),??
  62. ???????????????ntohs(peeraddr.sin_port));??
  63. ??
  64. ????????pid?=?fork();??
  65. ????????if?(pid?==?-1)??
  66. ????????????ERR_EXIT("fork?error");??
  67. ????????if?(pid?==?0)??
  68. ????????{??
  69. ????????????//?子進程??
  70. ????????????close(listenfd);??
  71. ????????????do_service(conn);??
  72. ????????????exit(EXIT_SUCCESS);??
  73. ????????}??
  74. ????????else??
  75. ????????????close(conn);?//父進程??
  76. ????}??
  77. ??
  78. ????return?0;??
  79. }??
  80. ??
  81. void?do_service(int?conn)??
  82. {??
  83. ????char?recvbuf[1024];??
  84. ????while?(1)??
  85. ????{??
  86. ????????memset(recvbuf,?0,?sizeof(recvbuf));??
  87. ????????int?ret?=?read(conn,?recvbuf,?sizeof(recvbuf));??
  88. ????????if?(ret?==?0)???//客戶端關閉了??
  89. ????????{??
  90. ????????????printf("client?close\n");??
  91. ????????????break;??
  92. ????????}??
  93. ????????else?if?(ret?==?-1)??
  94. ????????????ERR_EXIT("read?error");??
  95. ????????fputs(recvbuf,?stdout);??
  96. ????????write(conn,?recvbuf,?ret);??
  97. ????}??
  98. }??

客戶端代碼cli.c:

[cpp]?view plaincopy
  1. #include<stdio.h>??
  2. #include<sys/types.h>??
  3. #include<sys/socket.h>??
  4. #include<unistd.h>??
  5. #include<stdlib.h>??
  6. #include<errno.h>??
  7. #include<arpa/inet.h>??
  8. #include<netinet/in.h>??
  9. #include<string.h>??
  10. ??
  11. ??
  12. #define?ERR_EXIT(m)?\??
  13. ????do?{?\??
  14. ????????perror(m);?\??
  15. ????????exit(EXIT_FAILURE);?\??
  16. ????}?while?(0)??
  17. ??
  18. ??
  19. ??
  20. ??
  21. int?main(void)??
  22. {??
  23. ????int?sock;??
  24. ????if?((sock?=?socket(PF_INET,?SOCK_STREAM,?IPPROTO_TCP))?<?0)??
  25. ????????//??listenfd?=?socket(AF_INET,?SOCK_STREAM,?0)??
  26. ????????ERR_EXIT("socket?error");??
  27. ??
  28. ??
  29. ????struct?sockaddr_in?servaddr;??
  30. ????memset(&servaddr,?0,?sizeof(servaddr));??
  31. ????servaddr.sin_family?=?AF_INET;??
  32. ????servaddr.sin_port?=?htons(5188);??
  33. ????servaddr.sin_addr.s_addr?=?inet_addr("127.0.0.1");??
  34. ????/*?inet_aton("127.0.0.1",?&servaddr.sin_addr);?*/??
  35. ??
  36. ????if?(connect(sock,?(struct?sockaddr?*)&servaddr,?sizeof(servaddr))?<?0)??
  37. ????????ERR_EXIT("connect?error");??
  38. ????struct?sockaddr_in?localaddr;??
  39. ????char?cli_ip[20];??
  40. ????socklen_t?local_len?=?sizeof(localaddr);??
  41. ????memset(&localaddr,?0,?sizeof(localaddr));??
  42. ????if(?getsockname(sock,(struct?sockaddr?*)&localaddr,&local_len)?!=?0?)??
  43. ????????ERR_EXIT("getsockname?error");??
  44. ????inet_ntop(AF_INET,?&localaddr.sin_addr,?cli_ip,?sizeof(cli_ip));??
  45. ????printf("host?%s:%d\n",?cli_ip,?ntohs(localaddr.sin_port));???
  46. ??
  47. ????char?sendbuf[1024]?=?{0};??
  48. ????char?recvbuf[1024]?=?{0};??
  49. ????while?(fgets(sendbuf,?sizeof(sendbuf),?stdin)?!=?NULL)??
  50. ????{??
  51. ??
  52. ????????write(sock,?sendbuf,?strlen(sendbuf));??
  53. ????????read(sock,?recvbuf,?sizeof(recvbuf));??
  54. ??
  55. ??
  56. ????????fputs(recvbuf,?stdout);??
  57. ??
  58. ????????memset(sendbuf,?0,?sizeof(sendbuf));??
  59. ????????memset(recvbuf,?0,?sizeof(recvbuf));??
  60. ????}??
  61. ??
  62. ??
  63. ????close(sock);??
  64. ??
  65. ??
  66. ????return?0;??
  67. }??


?先運行服務器端,再運行客戶端:

[cpp]?view plaincopy
  1. huangcheng@ubuntu:~$?./serv??
  2. huangcheng@ubuntu:~$?./cli??


先查看一下網絡狀態:

[cpp]?view plaincopy
  1. huangcheng@ubuntu:~$?netstat?-anp?|?grep?5188??
  2. (并非所有進程都能被檢測到,所有非本用戶的進程信息將不會顯示,如果想看到所有信息,則必須切換到?root?用戶)??
  3. tcp????????0??????0?0.0.0.0:5188????????????0.0.0.0:*???????????????LISTEN??????2750/serv??
  4. tcp????????0??????0?127.0.0.1:49484?????????127.0.0.1:5188??????????ESTABLISHED?2751/cli??
  5. tcp????????0??????0?127.0.0.1:5188??????????127.0.0.1:49484?????????ESTABLISHED?2752/serv??

可以看出建立了連接,服務器端有兩個進程,一個父進程處于監聽狀態,另一子進程正在對客戶端進行服務。


服務器端的子進程的pid為2752,并kill掉它:

[cpp]?view plaincopy
  1. huangcheng@ubuntu:~$?kill?-9?2752??

再查看一下網絡狀態:
[cpp]?view plaincopy
  1. huangcheng@ubuntu:~$?netstat?-anp?|?grep?5188??
  2. (并非所有進程都能被檢測到,所有非本用戶的進程信息將不會顯示,如果想看到所有信息,則必須切換到?root?用戶)??
  3. tcp????????0??????0?0.0.0.0:5188????????????0.0.0.0:*???????????????LISTEN??????2750/serv??
  4. tcp????????1??????0?127.0.0.1:49484?????????127.0.0.1:5188??????????CLOSE_WAIT??2751/cli??
  5. 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的部分程序:

[cpp]?view plaincopy
  1. char?sendbuf[1024]?=?{0};??
  2. char?recvbuf[1024]?=?{0};??
  3. while?(fgets(sendbuf,?sizeof(sendbuf),?stdin)?!=?NULL)??
  4. {??
  5. ??
  6. ????write(sock,?sendbuf,?strlen(sendbuf));??
  7. ????read(sock,?recvbuf,?sizeof(recvbuf));??
  8. ??
  9. ??
  10. ????fputs(recvbuf,?stdout);??
  11. ??
  12. ????memset(sendbuf,?0,?sizeof(sendbuf));??
  13. ????memset(recvbuf,?0,?sizeof(recvbuf));??
  14. }??
? ? ?客戶端程序阻塞在了fgets 那里,即從標準輸入讀取數據,所以不能執行到下面的read,也即不能返回0,不會退出循環,不會調用close關閉sock,所以出現上述的情況,即狀態停滯,不能向前推進。
? ? ?出現上述問題的根本原因在于客戶端程序不能并發處理從標準輸入讀取數據和從套接字讀取數據兩個事件,我們可以使用前面講過的select函數來完善客戶端程序。


[cpp]?view plaincopy
  1. 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個可以對集合進行操作的宏:

[cpp]?view plaincopy
  1. void?FD_CLR(int?fd,?fd_set?*set);?//?清除出集合??
  2. int??FD_ISSET(int?fd,?fd_set?*set);?//?判斷是否在集合中??
  3. void?FD_SET(int?fd,?fd_set?*set);?//?添加進集合中??
  4. void?FD_ZERO(fd_set?*set);?//?將集合清零??

下面是通過select函數改進的客戶端的代碼cli.c:

[cpp]?view plaincopy
  1. #include<stdio.h>??
  2. #include<sys/types.h>??
  3. #include<sys/socket.h>??
  4. #include<unistd.h>??
  5. #include<stdlib.h>??
  6. #include<errno.h>??
  7. #include<arpa/inet.h>??
  8. #include<netinet/in.h>??
  9. #include<string.h>??
  10. ??
  11. ??
  12. #define?ERR_EXIT(m)?\??
  13. ????do?{?\??
  14. ????????perror(m);?\??
  15. ????????exit(EXIT_FAILURE);?\??
  16. ????}?while?(0)??
  17. ??
  18. ??
  19. ??
  20. ??
  21. int?main(void)??
  22. {??
  23. ????int?sock;??
  24. ????if?((sock?=?socket(PF_INET,?SOCK_STREAM,?IPPROTO_TCP))?<?0)??
  25. ????????//??listenfd?=?socket(AF_INET,?SOCK_STREAM,?0)??
  26. ????????ERR_EXIT("socket?error");??
  27. ??
  28. ??
  29. ????struct?sockaddr_in?servaddr;??
  30. ????memset(&servaddr,?0,?sizeof(servaddr));??
  31. ????servaddr.sin_family?=?AF_INET;??
  32. ????servaddr.sin_port?=?htons(5188);??
  33. ????servaddr.sin_addr.s_addr?=?inet_addr("127.0.0.1");??
  34. ????/*?inet_aton("127.0.0.1",?&servaddr.sin_addr);?*/??
  35. ??
  36. ????if?(connect(sock,?(struct?sockaddr?*)&servaddr,?sizeof(servaddr))?<?0)??
  37. ????????ERR_EXIT("connect?error");??
  38. ????struct?sockaddr_in?localaddr;??
  39. ????char?cli_ip[20];??
  40. ????socklen_t?local_len?=?sizeof(localaddr);??
  41. ????memset(&localaddr,?0,?sizeof(localaddr));??
  42. ????if(?getsockname(sock,(struct?sockaddr?*)&localaddr,&local_len)?!=?0?)??
  43. ????????ERR_EXIT("getsockname?error");??
  44. ????inet_ntop(AF_INET,?&localaddr.sin_addr,?cli_ip,?sizeof(cli_ip));??
  45. ????printf("host?%s:%d\n",?cli_ip,?ntohs(localaddr.sin_port));???
  46. ??
  47. ????fd_set?rset;??
  48. ????FD_ZERO(&rset);??
  49. ????int?nready;??
  50. ????int?maxfd;??
  51. ????int?fd_stdin?=?fileno(stdin);?//??
  52. ????if?(fd_stdin?>?sock)??
  53. ????????maxfd?=?fd_stdin;??
  54. ????else??
  55. ????????maxfd?=?sock;??
  56. ????char?sendbuf[1024]?=?{0};??
  57. ????char?recvbuf[1024]?=?{0};??
  58. ??????
  59. ????while?(1)??
  60. ????{??
  61. ??
  62. ????????FD_SET(fd_stdin,?&rset);??
  63. ????????FD_SET(sock,?&rset);??
  64. ????????nready?=?select(maxfd?+?1,?&rset,?NULL,?NULL,?NULL);?//select返回表示檢測到可讀事件??
  65. ????????if?(nready?==?-1)??
  66. ????????????ERR_EXIT("select?error");??
  67. ??
  68. ????????if?(nready?==?0)??
  69. ????????????continue;??
  70. ??
  71. ????????if?(FD_ISSET(sock,?&rset))??
  72. ????????{??
  73. ??
  74. ????????????int?ret?=?read(sock,?recvbuf,?sizeof(recvbuf));???
  75. ????????????if?(ret?==?-1)??
  76. ????????????????ERR_EXIT("read?error");??
  77. ????????????else?if?(ret??==?0)???//服務器關閉??
  78. ????????????{??
  79. ????????????????printf("server?close\n");??
  80. ????????????????break;??
  81. ????????????}??
  82. ??
  83. ????????????fputs(recvbuf,?stdout);??
  84. ????????????memset(recvbuf,?0,?sizeof(recvbuf));??
  85. ????????}??
  86. ??
  87. ????????if?(FD_ISSET(fd_stdin,?&rset))??
  88. ????????{??
  89. ??
  90. ????????????if?(fgets(sendbuf,?sizeof(sendbuf),?stdin)?==?NULL)??
  91. ????????????????break;??
  92. ??
  93. ????????????write(sock,?sendbuf,?strlen(sendbuf));??
  94. ????????????memset(sendbuf,?0,?sizeof(sendbuf));??
  95. ????????}??
  96. ????}??
  97. ??
  98. ????close(sock);??
  99. ????return?0;??
  100. }??

? ? ?即將兩個事件都添加進可讀事件集合,在while循環中,如果select返回說明有事件發生,依次判斷是哪些事件發生,如果是標準輸入有數據可讀,則讀取后再次回到循環開頭select阻塞等待事件發生,如果是套接口有數據可讀,且返回為0則說明對方已經關閉連接,退出循環并調用close關閉sock。

重復前面的操作:

(1)先運行服務器,再運行客戶端

[cpp]?view plaincopy
  1. huangcheng@ubuntu:~$?./serv??
  2. huangcheng@ubuntu:~$?./cli??
(2)查看網絡狀態:

[cpp]?view plaincopy
  1. huangcheng@ubuntu:~$?netstat?-anp?|?grep?5188??
  2. (并非所有進程都能被檢測到,所有非本用戶的進程信息將不會顯示,如果想看到所有信息,則必須切換到?root?用戶)??
  3. tcp????????0??????0?0.0.0.0:5188????????????0.0.0.0:*???????????????LISTEN??????2960/serv??
  4. tcp????????0??????0?127.0.0.1:49485?????????127.0.0.1:5188??????????ESTABLISHED?2963/cli??
  5. tcp????????0??????0?127.0.0.1:5188??????????127.0.0.1:49485?????????ESTABLISHED?2964/serv??
(3)kill掉服務器的子進程,再查看網絡狀態:

[cpp]?view plaincopy
  1. huangcheng@ubuntu:~$?kill?-9?2964??
  2. huangcheng@ubuntu:~$?netstat?-anp?|?grep?5188??
  3. (并非所有進程都能被檢測到,所有非本用戶的進程信息將不會顯示,如果想看到所有信息,則必須切換到?root?用戶)??
  4. tcp????????0??????0?0.0.0.0:5188????????????0.0.0.0:*???????????????LISTEN??????2960/serv??
  5. 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段沒有發送給對方,則可以重新發送。

? ? ?過一小會再次查看網絡狀態:

[cpp]?view plaincopy
  1. huangcheng@ubuntu:~$?netstat?-anp?|?grep?5188??
  2. (并非所有進程都能被檢測到,所有非本用戶的進程信息將不會顯示,如果想看到所有信息,則必須切換到?root?用戶)??
  3. tcp????????0??????0?0.0.0.0:5188????????????0.0.0.0:*???????????????LISTEN??????2960/serv??
? ? ?可以發現只剩下服務器端父進程的監聽狀態了,由TIME_WAIT狀態轉入CLOSED狀態,也很快會消失。


-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


? ? ?前面我們實現的能夠并發服務的服務器端程序是使用fork出多個子進程來實現的,現在學習了select函數,可以用它來改進服務器端程序,實現單進程并發服務。先看如下程序,再來解釋:

[cpp]?view plaincopy
  1. #include<stdio.h>??
  2. #include<sys/types.h>??
  3. #include<sys/socket.h>??
  4. #include<unistd.h>??
  5. #include<stdlib.h>??
  6. #include<errno.h>??
  7. #include<arpa/inet.h>??
  8. #include<netinet/in.h>??
  9. #include<string.h>??
  10. #include<signal.h>??
  11. #include<sys/wait.h>??
  12. ??
  13. #define?ERR_EXIT(m)?\??
  14. ????do?{?\??
  15. ????????perror(m);?\??
  16. ????????exit(EXIT_FAILURE);?\??
  17. ????}?while?(0)??
  18. ??
  19. ??
  20. int?main(void)??
  21. {??
  22. ??????
  23. ????signal(SIGPIPE,?SIG_IGN);??
  24. ????int?listenfd;?//被動套接字(文件描述符),即只可以accept,?監聽套接字??
  25. ????if?((listenfd?=?socket(PF_INET,?SOCK_STREAM,?IPPROTO_TCP))?<?0)??
  26. //??listenfd?=?socket(AF_INET,?SOCK_STREAM,?0)????
  27. ????????ERR_EXIT("socket?error");??
  28. ??
  29. ????struct?sockaddr_in?servaddr;??
  30. ????memset(&servaddr,?0,?sizeof(servaddr));??
  31. ????servaddr.sin_family?=?AF_INET;??
  32. ????servaddr.sin_port?=?htons(5188);??
  33. ????servaddr.sin_addr.s_addr?=?htonl(INADDR_ANY);???
  34. ????/*?servaddr.sin_addr.s_addr?=?inet_addr("127.0.0.1");?*/??
  35. ????/*?inet_aton("127.0.0.1",?&servaddr.sin_addr);?*/??
  36. ??????
  37. ????int?on?=?1;??
  38. ????if?(setsockopt(listenfd,?SOL_SOCKET,?SO_REUSEADDR,?&on,?sizeof(on))?<?0)??
  39. ????????ERR_EXIT("setsockopt?error");??
  40. ??
  41. ????if?(bind(listenfd,?(struct?sockaddr*)&servaddr,sizeof(servaddr))?<?0)??
  42. ????????ERR_EXIT("bind?error");??
  43. ??
  44. ????if?(listen(listenfd,?SOMAXCONN)?<?0)?//listen應在socket和bind之后,而在accept之前??
  45. ????????ERR_EXIT("listen?error");??
  46. ??????
  47. ????struct?sockaddr_in?peeraddr;?//傳出參數??
  48. ????socklen_t?peerlen?=?sizeof(peeraddr);?//傳入傳出參數,必須有初始值??
  49. ??????
  50. ????int?conn;?//?已連接套接字(變為主動套接字,即可以主動connect)??
  51. ????int?i;??
  52. ????int?client[FD_SETSIZE];??
  53. ????int?maxi?=?0;?//?client數組中最大不空閑位置的下標??
  54. ????for?(i?=?0;?i?<?FD_SETSIZE;?i++)??
  55. ????????client[i]?=?-1;??
  56. ??
  57. ????int?nready;??
  58. ????int?maxfd?=?listenfd;??
  59. ????fd_set?rset;??
  60. ????fd_set?allset;??
  61. ????FD_ZERO(&rset);??
  62. ????FD_ZERO(&allset);??
  63. ????FD_SET(listenfd,?&allset);??
  64. ??
  65. ????while?(1)?{??
  66. ????????rset?=?allset;??
  67. ????????nready?=?select(maxfd?+?1,?&rset,?NULL,?NULL,?NULL);??
  68. ????????if?(nready?==?-1)?{??
  69. ????????????if?(errno?==?EINTR)??
  70. ????????????????continue;??
  71. ????????????ERR_EXIT("select?error");??
  72. ????????}??
  73. ??
  74. ????????if?(nready?==?0)??
  75. ????????????continue;??
  76. ??
  77. ????????if?(FD_ISSET(listenfd,?&rset))?{??
  78. ??????????
  79. ????????????conn?=?accept(listenfd,?(struct?sockaddr*)&peeraddr,?&peerlen);??//accept不再阻塞??
  80. ????????????if?(conn?==?-1)??
  81. ????????????????ERR_EXIT("accept?error");??
  82. ??????????????
  83. ????????????for?(i?=?0;?i?<?FD_SETSIZE;?i++)?{??
  84. ????????????????if?(client[i]?<?0)?{??
  85. ????????????????????client[i]?=?conn;??
  86. ????????????????????if?(i?>?maxi)??
  87. ????????????????????????maxi?=?i;??
  88. ????????????????????break;??
  89. ????????????????}???
  90. ????????????}??
  91. ??????????????
  92. ????????????if?(i?==?FD_SETSIZE)?{??
  93. ????????????????fprintf(stderr,?"too?many?clients\n");??
  94. ????????????????exit(EXIT_FAILURE);??
  95. ????????????}??
  96. ??
  97. ????????????printf("recv?connect?ip=%s?port=%d\n",?inet_ntoa(peeraddr.sin_addr),??
  98. ????????????????ntohs(peeraddr.sin_port));??
  99. ??
  100. ????????????FD_SET(conn,?&allset);??
  101. ????????????if?(conn?>?maxfd)??
  102. ????????????????maxfd?=?conn;??
  103. ??
  104. ????????????if?(--nready?<=?0)??
  105. ????????????????continue;??
  106. ????????}??
  107. ??
  108. ????????for?(i?=?0;?i?<=?maxi;?i++)?{??
  109. ????????????conn?=?client[i];??
  110. ????????????if?(conn?==?-1)??
  111. ????????????????continue;??
  112. ??
  113. ????????????if?(FD_ISSET(conn,?&rset))?{??
  114. ??????????????????
  115. ????????????????char?recvbuf[1024]?=?{0};??
  116. ????????????????int?ret?=?read(conn,?recvbuf,?1024);??
  117. ????????????????if?(ret?==?-1)??
  118. ????????????????????ERR_EXIT("readline?error");??
  119. ????????????????else?if?(ret??==?0)?{?//客戶端關閉???
  120. ????????????????????printf("client?close?\n");??
  121. ????????????????????FD_CLR(conn,?&allset);??
  122. ????????????????????client[i]?=?-1;??
  123. ????????????????????close(conn);??
  124. ????????????????}??
  125. ??????????
  126. ????????????????fputs(recvbuf,?stdout);??
  127. ????????????????write(conn,?recvbuf,?strlen(recvbuf));??
  128. ??????????????????
  129. ????????????????if?(--nready?<=?0)??
  130. ????????????????????break;???
  131. ????????????}??
  132. ????????}??
  133. ??
  134. ??
  135. ????}??
  136. ??????????
  137. ????return?0;??
  138. }??
  139. ??
  140. /*?select所能承受的最大并發數受?
  141. ?*?1.一個進程所能打開的最大文件描述符數,可以通過ulimit?-n來調整?
  142. ?*???但一個系統所能打開的最大數也是有限的,跟內存有關,可以通過cat?/proc/sys/fs/file-max?查看?
  143. ?*?2.FD_SETSIZE(fd_set)的限制,這個需要重新編譯內核???????????????????????????????????????????????????????????????????????????
  144. ?*/??


? ? ?程序第一次進入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 。



本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/383845.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383845.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383845.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Java簡單輸入輸出

不同于面向過程中有直接的輸入輸出函數&#xff0c;Java中的輸入輸出只能通過類來實現。 比較常見的一種是使用Scanner類 需要引入java.util包&#xff0c;即在文件開始加上語句import java.util.*;創建Scanner類對象&#xff0c;屬于標準輸入流。 例如Scanner snew Scanner(S…

Ubuntu安裝搭建Clion環境

嗚嗚嗚&#xff0c;太辛苦了&#xff0c;我終于安裝好這個了。 大概過程就是先在官網下載安裝包&#xff0c;然后解壓以后用終端移動到對應文件夾下運行clin.sh 運行完以后會有一些窗口&#xff0c;第一個選擇don’t~~&#xff0c;然后點擊ok 接受&#xff08;你可以不接受…

UNIX網絡編程——select函數的并發限制和 poll 函數應用舉例

http://blog.csdn.net/chenxun_2010/article/details/50489577 一、用select實現的并發服務器&#xff0c;能達到的并發數&#xff0c;受兩方面限制 1、一個進程能打開的最大文件描述符限制。這可以通過調整內核參數。可以通過ulimit -n來調整或者使用setrlimit函數設置&#x…

【Java學習筆記二】繼承和多態

與C不同的是&#xff0c;在Java中&#xff0c;一個類只能直接繼承另一個類&#xff0c;而不允許繼承多個類&#xff0c;這個新類稱為繼承類、派生類或者子類&#xff0c;而被繼承的類稱為基類或者父類。 繼承類能夠繼承基類的群不屬性和行為。 面向對象程序設計的三大特點為&…

使用poll實現的io多路復用服務端和客戶端

http://blog.csdn.net/robertkun/article/details/52269313 參考&#xff1a;http://www.cnblogs.com/Anker/p/3261006.html 使用poll實現的io多路復用服務端和客戶端。 客戶端通過子進程創建多個客戶端連接。 客戶端每隔1秒向服務端發送一個時間戳&#xff0c; 服務端接收到時…

【Java學習筆記三】抽象類與接口

對象的類型轉換分為自動轉換和強制轉換兩種 派生類向基類轉換是自動轉換&#xff0c;因為派生類中包含基類基類向派生類的轉換是強制轉換 強制類型轉換是通過在轉換對象前面使用圓括號運算符來實現&#xff0c;圓括號內為要轉換的目標類型&#xff0c;格式為&#xff1a; (&…

Epoll 的tcp通信代碼(服務器+客戶端)

http://blog.csdn.net/libinbin_1014/article/details/50096187 Epoll 的tcp通信代碼&#xff08;服務器客戶端&#xff09; /* gcc -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS64 -I${ORACLE_HOME}/rdbms/public -I${ORACLE_HOME}/rdbms/demo -L${ORACLE_HOME}/lib -lclntsh …

【Java學習筆記四】Java中的包

包的聲明和引入&#xff1a;在Java語言系統中&#xff0c;Java編譯器為每一個類生成一個字節碼文件&#xff08;.class&#xff09;&#xff0c;為了對類文件進行分層和按用途分類管理&#xff0c;同時也為了解決相同類名的文件沖突的問題&#xff0c;Java提供了包機制來管理類…

Linux系統編程——線程池

http://blog.csdn.net/tennysonsky/article/details/46490099# 線程池基本原理 在傳統服務器結構中&#xff0c;常是有一個總的監聽線程監聽有沒有新的用戶連接服務器&#xff0c;每當有一個新的用戶進入&#xff0c;服務器就開啟一個新的線程用戶處理這 個用戶的數據包。這個線…

【Java學習筆記五】Java異常處理

異常通常分為三類&#xff1a; 程序可控制的異常&#xff1a;一般是可預見的錯誤&#xff0c;不是致命的。例如&#xff1a;除數為0&#xff0c;數組下標越界。程序不可控制的的異常&#xff1a;這種異常往往是致命的&#xff0c;但是系統可以預見的。例如&#xff1a;系統棧溢…

【C++學習筆記一】C++類和對象詳解

類定義是以關鍵字class開頭&#xff0c;后面跟類的名稱。主體是包含在一對花括號中。類定義后必須跟著一個分號或一個聲明列表。 類的對象的公共數據成員可以使用直接成員訪問運算符.來訪問。需要注意的是&#xff0c;私有的成員和受保護的成員不能直接使用成員訪問運算符來訪…

C語言實現的簡單的線程池

http://www.linuxidc.com/Linux/2013-01/77619.htm 有時我們會需要大量線程來處理一些相互獨立的任務&#xff0c;為了避免頻繁的申請釋放線程所帶來的開銷&#xff0c;我們可以使用線程池。下面是一個C語言實現的簡單的線程池。 頭文件&#xff1a; 1: #ifndef THREAD_POOL_H_…

C++獲取當前時間

可以使用windowsAPI直接獲取。 例如&#xff1a; #include<windows.h> #include<cstdio>using namespace std;int main() {SYSTEMTIME now;GetLocalTime(&now);printf("現在是%02d時%02d分%02d秒\n",now.wHour,now.wMinute,now.wSecond);printf(&…

成員函數后面加上const的作用

const表示成員函數不會修改類中的數據成員。 規則&#xff1a; 在類中被const 聲明的函數只能訪問const 函數&#xff0c;而非const 函數可以訪問任意成員函數。在成員函數中不管數據是否具有const 屬性&#xff0c;編譯器檢查的的是是否有修改&#xff08;賦值&#xff0c;自…

簡單Linux C線程池

http://www.cnblogs.com/venow/archive/2012/11/22/2779667.html 大多數的網絡服務器&#xff0c;包括Web服務器都具有一個特點&#xff0c;就是單位時間內必須處理數目巨大的連接請求&#xff0c;但是處理時間卻是比較短的。在傳統的多線程服務器模型中是這樣實現的&#xff1…

C++創建對象:棧和堆的區別

首先我們應該了解棧和堆的差別&#xff1a; 詳細信息&#xff1a;傳送門 棧相當于函數自帶的存儲空間&#xff0c;在windows下一般為2M,在Linux下一般為8M&#xff0c;存取速度稍微快一點。堆是系統的空間&#xff0c;相對較大&#xff0c;一般為2G&#xff0c;效率稍微慢一點…

IO多路復用之poll總結

http://www.cnblogs.com/Anker/p/3261006.html 1、基本知識 poll的機制與select類似&#xff0c;與select在本質上沒有多大差別&#xff0c;管理多個描述符也是進行輪詢&#xff0c;根據描述符的狀態進行處理&#xff0c;但是poll沒有最大文件描述符數量的限制。poll和select同…

【C++學習筆記二】C++繼承

繼承 繼承允許我們一句另一個類來定義一個類&#xff0c;這使得繼承和維護一個程序變得更加容易&#xff0c;也達到了重用代碼功能和提高執行效率的效果。 一般格式為&#xff1a; class 派生類名 :訪問修飾符 基類名{};其中訪問修飾符是public protected private中的一個&a…

處理大并發之二 對epoll的理解,epoll客戶端服務端代碼

http://blog.csdn.net/wzjking0929/article/details/51838370 序言&#xff1a; 該博客是一系列的博客&#xff0c;首先從最基礎的epoll說起&#xff0c;然后研究libevent源碼及使用方法&#xff0c;最后研究nginx和node.js&#xff0c;關于select,poll這里不做說明&#xff0c…

C++基類指針指向派生類(指針)

我們常用基類指針指向派生類對象來實現多態性。 私有繼承不允許基類指針指向派生類 基類指針只能訪問到基類中含有的公有成員。 當用基類指針指向派生類對象在動態分配堆上內存的時候&#xff0c;析構函數必須是虛函數! 成員如果是數據成員的話訪問的是基類的版本&#xff…