epoll實現高并發聊天室

http://blog.csdn.net/qq_31564375/article/details/51581038

項目介紹

本項目是實現一個簡單的聊天室,聊天室分為服務端和客戶端。本項目將很多復雜的功能都去掉了,線程池、多線程編程、超時重傳、確認收包等等都不會涉及。總共300多行代碼,讓大家真正了解C/S模型,以及epoll的使用。為了方便查看,代碼已經改的很小白,絕對比nginx源碼好理解(當然大家有興趣的話,還是要拜讀下nginx源碼,絕對大有收獲)。希望本項目能為大家以后的工作或者學習提供一點幫助! 介紹如下:

1. 服務端

a. 支持多個用戶接入,實現聊天室的基本功能
b. 使用epoll機制實現并發,增加效率

2. 客戶端

a. 支持用戶輸入聊天消息
b. 顯示其他用戶輸入的信息
c. 使用fork創建兩個進程
子進程有兩個功能:
  1. 等待用戶輸入聊天信息
  2. 將聊天信息寫到管道(pipe),并發送給父進程
父進程有兩個功能
  1. 使用epoll機制接受服務端發來的信息,并顯示給用戶,使用戶看到其他用戶的聊天信息
  2. 將子進程發給的聊天信息從管道(pipe)中讀取, 并發送給服務端

3. 代碼說明

一共有3個文件, 即: server.cpp, client.cpp, utility.h

a. server.cpp是服務端程序
b. client.cpp是客戶端程序

c. utility.h是一個頭文件,包含服務端程序和客戶端程序都會用到的一些頭文件、變量聲明、函數、宏等。


1.1 TCP服務端通信的常規步驟

(1)使用socket()創建TCP套接字(socket)
(2)將創建的套接字綁定到一個本地地址和端口上(Bind)
(3)將套接字設為監聽模式,準備接收客戶端請求(listen)
(4)等待客戶請求到來: 當請求到來后,接受連接請求,返回一個對應于此次連接的新的套接字(accept)
(5)用accept返回的套接字和客戶端進行通信(使用write()/send()或send()/recv() )
(6)返回,等待另一個客戶請求
(7)關閉套接字
[cpp]?view plain?copy
  1. //server.cpp代碼(通信模塊):??
  2. ????//服務端地址?ip地址?+?端口號??
  3. ????struct?sockaddr_in?serverAddr;??
  4. ????serverAddr.sin_family?=?PF_INET;??
  5. ????serverAddr.sin_port?=?htons(SERVER_PORT);??
  6. ????serverAddr.sin_addr.s_addr?=?inet_addr(SERVER_HOST);??
  7. ??
  8. ????//服務端創建監聽socket??
  9. ????int?listener?=?socket(PF_INET,?SOCK_STREAM,?0);??
  10. ????if(listener?<?0)?{?perror("listener");?exit(-1);}??
  11. ????printf("listen?socket?created?\n");??
  12. ??
  13. ????//將服務端地址與監聽socket綁定??
  14. ????if(?bind(listener,?(struct?sockaddr?*)&serverAddr,?sizeof(serverAddr))?<?0)?{??
  15. ????????perror("bind?error");??
  16. ????????exit(-1);??
  17. ????}??
  18. ????//開始監聽??
  19. ????int?ret?=?listen(listener,?5);??
  20. ????if(ret?<?0)?{?perror("listen?error");?exit(-1);}??
  21. ????printf("Start?to?listen:?%s\n",?SERVER_HOST);??

2. 基本技術介紹

2.1 阻塞與非阻塞socket

通常的,對一個文件描述符指定的文件或設備, 有兩種工作方式: 阻塞與非阻塞方式。

(1). 阻塞方式是指: 當試圖對該文件描述符進行讀寫時,如果當時沒有數據可讀,或者暫時不可寫,程序就進入等待狀態,直到有東西可讀或者可寫為止。

(2). 非阻塞方式是指: 如果沒有數據可讀,或者不可寫,讀寫函數馬上返回,而不會等待。

(3). 舉個例子來說,比如說小明去找一個女神聊天,女神卻不在。 如果小明舍不得走,只能在女神大門口死等著,當然小明可以休息。當女 神來了,她會把你喚醒(囧,因為擋著她門了),這就是阻塞方式。如果小明發現女神不在,立即離開,以后每隔十分鐘回來看一下(采用輪詢方式),不在的話仍然立即離開,這就是非阻塞方式。

(4). 阻塞方式和非阻塞方式唯一的區別: 是否立即返回。本項目采用更高效的做法,所以應該將socket設置為非阻塞方式。這樣能充分利用服務器資源,效率得到了很大提高。

[cpp]?view plain?copy
  1. //utility.h代碼(設置非阻塞函數模塊):??
  2. //將文件描述符設置為非阻塞方式(利用fcntl函數)??
  3. int?setnonblocking(int?sockfd)??
  4. {??
  5. ????fcntl(sockfd,?F_SETFL,?fcntl(sockfd,?F_GETFD,?0)|?O_NONBLOCK);??
  6. ????return?0;??
  7. }??

2.2 epoll

前面介紹了阻塞和非阻塞方式,現在該介紹下epoll機制了。epoll真的是一個特別重要的概念,實驗的師兄們去bat任何一家面試后臺開發,或者系統開發等相關職位都會問epoll機制。當服務端的在線人數越來越多,會導致系統資源吃緊,I/O效率越來越慢,這時候就應該考慮epoll了。epoll是Linux內核為處理大批句柄而作改進的poll,是Linux特有的I/O函數。其特點如下:

a.
epoll是Linux下多路復用IO接口select/poll的增強版本。其實現和使用方式與select/poll有很多不同,epoll通過一組函數來完成有關任務,而不是一個函數。
b.
epoll之所以高效,是因為epoll將用戶關心的文件描述符放到內核里的一個事件表中,而不是像select/poll每次調用都需要重復傳入文件描述符集或事件集。比如當一個事件發生(比如說讀事件),epoll無須遍歷整個被偵聽的描述符集,只要遍歷那些被內核IO事件異步喚醒而加入就緒隊列的描述符集合就行了。
c.
epoll有兩種工作方式,LT(level triggered):水平觸發和ET(edge-triggered):邊沿觸發。LT是select/poll使用的觸發方式,比較低效;而ET是epoll的高速工作方式(本項目使用epoll的ET方式)。
d.
通俗理解就是,比如說有一堆女孩,有的很漂亮,有的很鳳姐。現在你想找漂亮的女孩聊天,LT就是你需要把這一堆女孩全都看一遍,才可以找到其中的漂亮的(就緒事件);而ET是你的小弟(內核)將N個漂亮的女孩編號告訴你,你直接去看就好,所以epoll很高效。另外,還記得小明找女神聊天的例子嗎?采用非阻塞方式,小明還需要每隔十分鐘回來看一下(select);如果小明有小弟(內核)幫他守在大門口,女神回來了,小弟會主動打電話,告訴小明女神回來了,快來處理吧!這就是epoll。
epoll 共3個函數,
1、int epoll_create(int size)創建一個epoll句柄,參數size用來告訴內核監聽的數目,size為epoll所支持的最大句柄數
2、int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)函數功能: epoll事件注冊函數參數epfd為epoll的句柄,即epoll_create返回值參數op表示動作,用3個宏來表示:  EPOLL_CTL_ADD(注冊新的fd到epfd), EPOLL_CTL_MOD(修改已經注冊的fd的監聽事件),EPOLL_CTL_DEL(從epfd刪除一個fd);其中參數fd為需要監聽的標示符;參數event告訴內核需要監聽的事件,event的結構如下:struct epoll_event {__uint32_t events; //Epoll eventsepoll_data_t data; //User data variable};其中介紹events是宏的集合,本項目主要使用EPOLLIN(表示對應的文件描述符可以讀,即讀事件發生),其他宏類型,可以google之!
3、 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)
等待事件的產生,函數返回需要處理的事件數目(該數目是就緒事件的數目,就是前面所說漂亮女孩的個數N)

因此服務端使用epoll的時候,步驟如下:

  1. 調用epoll_create函數在Linux內核中創建一個事件表;
  2. 然后將文件描述符(監聽套接字listener)添加到所創建的事件表中;
  3. 在主循環中,調用epoll_wait等待返回就緒的文件描述符集合;
  4. 分別處理就緒的事件集合,本項目中一共有兩類事件:新用戶連接事件和用戶發來消息事件(epoll還有很多其他事件,本項目為簡潔明了,不介紹)。

下面介紹下如何將一個socket添加到內核事件表中,如下:

[cpp]?view plain?copy
  1. //utility.h(添加socket模塊):??
  2. //將文件描述符fd添加到epollfd標示的內核事件表中,?并注冊EPOLLIN和EPOOLET事件,EPOLLIN是數據可讀事件;EPOOLET表明是ET工作方式。最后將文件描述符設置非阻塞方式??
  3. /**?
  4. ??*?@param?epollfd:?epoll句柄?
  5. ??*?@param?fd:?文件描述符?
  6. ??*?@param?enable_et?:?enable_et?=?true,??
  7. ?????采用epoll的ET工?作方式;否則采用LT工作方式?
  8. **/??
  9. void?addfd(?int?epollfd,?int?fd,?bool?enable_et?)??
  10. {??
  11. ????struct?epoll_event?ev;??
  12. ????ev.data.fd?=?fd;??
  13. ????ev.events?=?EPOLLIN;??
  14. ????if(?enable_et?)??
  15. ????????ev.events?=?EPOLLIN?|?EPOLLET;??
  16. ????epoll_ctl(epollfd,?EPOLL_CTL_ADD,?fd,?&ev);??
  17. ????setnonblocking(fd);??
  18. ????printf("fd?added?to?epoll!\n\n");??
  19. }??

3. 服務端實現

上面我們介紹了基本的模型和技術,現在該去實現服務端了。首先介紹下utility.h中一些變量和函數。

3.1 utility.h

[cpp]?view plain?copy
  1. /*?限于篇幅,這里先介紹下utility.h的主要構成。其中的頭文件和一些函數實現沒有顯示,完整源碼位于3.2節?*/??
  2. ????//服務端存儲所有在線用戶socket,?便于廣播信息??
  3. ????list<int>?clients_list;??
  4. ????//?服務器ip地址,為測試使用本地機地址,可以更改為其他服務端地址??
  5. ????#define?SERVER_IP?"127.0.0.1"??
  6. ????//?服務器端口號??
  7. ????#define?SERVER_PORT?8888??
  8. ????//int?epoll_create(int?size)中的size,為epoll支持的最大句柄數??
  9. ????#define?EPOLL_SIZE?5000??
  10. ??
  11. ????//?緩沖區大小65535??
  12. ????#define?BUF_SIZE?0xFFFF??
  13. ????//一些宏??
  14. ????#define?SERVER_WELCOME?"Welcome?you?join?to?the?chat?room!?Your?chat?ID?is:?Client?#%d"??
  15. ????#define?SERVER_MESSAGE?"ClientID?%d?say?>>?%s"??
  16. ????#define?EXIT?"EXIT"??
  17. ????#define?CAUTION?"There?is?only?one?int?the?char?room!"??
  18. ????/*?一些函數?*/??
  19. ????//設置非阻塞??
  20. ????int?setnonblocking(int?sockfd);??
  21. ????//將文件描述符fd添加到epollfd標示的內核事件表??
  22. ????void?addfd(?int?epollfd,?int?fd,?bool?enable_et?);??
  23. ????//服務端發送廣播信息,使所有用戶都能收到消息??
  24. ????int?sendBroadcastmessage(int?clientfd);??
  25. 3.1?utility.h完整源碼??
  26. ??
  27. #ifndef?UTILITY_H_INCLUDED??
  28. #define?UTILITY_H_INCLUDED??
  29. ??
  30. #include?<iostream>??
  31. #include?<list>??
  32. #include?<sys/types.h>??
  33. #include?<sys/socket.h>??
  34. #include?<netinet/in.h>??
  35. #include?<arpa/inet.h>??
  36. #include?<sys/epoll.h>??
  37. #include?<fcntl.h>??
  38. #include?<errno.h>??
  39. #include?<unistd.h>??
  40. #include?<stdio.h>??
  41. #include?<stdlib.h>??
  42. #include?<string.h>??
  43. ??
  44. using?namespace?std;??
  45. ??
  46. //?clients_list?save?all?the?clients's?socket??
  47. list<int>?clients_list;??
  48. ??
  49. /**********************???macro?defintion?**************************/??
  50. //?server?ip??
  51. #define?SERVER_IP?"127.0.0.1"??
  52. ??
  53. //?server?port??
  54. #define?SERVER_PORT?8888??
  55. ??
  56. //epoll?size??
  57. #define?EPOLL_SIZE?5000??
  58. ??
  59. //message?buffer?size??
  60. #define?BUF_SIZE?0xFFFF??
  61. ??
  62. #define?SERVER_WELCOME?"Welcome?you?join??to?the?chat?room!?Your?chat?ID?is:?Client?#%d"??
  63. ??
  64. #define?SERVER_MESSAGE?"ClientID?%d?say?>>?%s"??
  65. ??
  66. //?exit??
  67. #define?EXIT?"EXIT"??
  68. ??
  69. #define?CAUTION?"There?is?only?one?int?the?char?room!"??
  70. ??
  71. /**********************???some?function?**************************/??
  72. /**?
  73. ??*?@param?sockfd:?socket?descriptor?
  74. ??*?@return?0?
  75. **/??
  76. int?setnonblocking(int?sockfd)??
  77. {??
  78. ????fcntl(sockfd,?F_SETFL,?fcntl(sockfd,?F_GETFD,?0)|?O_NONBLOCK);??
  79. ????return?0;??
  80. }??
  81. ??
  82. /**?
  83. ??*?@param?epollfd:?epoll?handle?
  84. ??*?@param?fd:?socket?descriptor?
  85. ??*?@param?enable_et?:?enable_et?=?true,?epoll?use?ET;?otherwise?LT?
  86. **/??
  87. void?addfd(?int?epollfd,?int?fd,?bool?enable_et?)??
  88. {??
  89. ????struct?epoll_event?ev;??
  90. ????ev.data.fd?=?fd;??
  91. ????ev.events?=?EPOLLIN;??
  92. ????if(?enable_et?)??
  93. ????????ev.events?=?EPOLLIN?|?EPOLLET;??
  94. ????epoll_ctl(epollfd,?EPOLL_CTL_ADD,?fd,?&ev);??
  95. ????setnonblocking(fd);??
  96. ????printf("fd?added?to?epoll!\n\n");??
  97. }??
  98. ??
  99. /**?
  100. ??*?@param?clientfd:?socket?descriptor?
  101. ??*?@return?:?len?
  102. **/??
  103. int?sendBroadcastmessage(int?clientfd)??
  104. {??
  105. ????//?buf[BUF_SIZE]?receive?new?chat?message??
  106. ????//?message[BUF_SIZE]?save?format?message??
  107. ????char?buf[BUF_SIZE],?message[BUF_SIZE];??
  108. ????bzero(buf,?BUF_SIZE);??
  109. ????bzero(message,?BUF_SIZE);??
  110. ??
  111. ????//?receive?message??
  112. ????printf("read?from?client(clientID?=?%d)\n",?clientfd);??
  113. ????int?len?=?recv(clientfd,?buf,?BUF_SIZE,?0);??
  114. ??
  115. ????if(len?==?0)??//?len?=?0?means?the?client?closed?connection??
  116. ????{??
  117. ????????close(clientfd);??
  118. ????????clients_list.remove(clientfd);?//server?remove?the?client??
  119. ????????printf("ClientID?=?%d?closed.\n?now?there?are?%d?client?in?the?char?room\n",?clientfd,?(int)clients_list.size());??
  120. ??
  121. ????}??
  122. ????else??//broadcast?message???
  123. ????{??
  124. ????????if(clients_list.size()?==?1)?{?//?this?means?There?is?only?one?int?the?char?room??
  125. ????????????send(clientfd,?CAUTION,?strlen(CAUTION),?0);??
  126. ????????????return?len;??
  127. ????????}??
  128. ????????//?format?message?to?broadcast??
  129. ????????sprintf(message,?SERVER_MESSAGE,?clientfd,?buf);??
  130. ??
  131. ????????list<int>::iterator?it;??
  132. ????????for(it?=?clients_list.begin();?it?!=?clients_list.end();?++it)?{??
  133. ???????????if(*it?!=?clientfd){??
  134. ????????????????if(?send(*it,?message,?BUF_SIZE,?0)?<?0?)?{?perror("error");?exit(-1);}??
  135. ???????????}??
  136. ????????}??
  137. ????}??
  138. ????return?len;??
  139. }??
  140. #endif?//?UTILITY_H_INCLUDED??

3.3 服務端完整源碼

在上面的基礎上。服務端的代碼就很容易寫出了

[cpp]?view plain?copy
  1. #include?"utility.h"??
  2. ??
  3. int?main(int?argc,?char?*argv[])??
  4. {??
  5. ????//服務器IP?+?port??
  6. ????struct?sockaddr_in?serverAddr;??
  7. ????serverAddr.sin_family?=?PF_INET;??
  8. ????serverAddr.sin_port?=?htons(SERVER_PORT);??
  9. ????serverAddr.sin_addr.s_addr?=?inet_addr(SERVER_IP);??
  10. ????//創建監聽socket??
  11. ????int?listener?=?socket(PF_INET,?SOCK_STREAM,?0);??
  12. ????if(listener?<?0)?{?perror("listener");?exit(-1);}??
  13. ????printf("listen?socket?created?\n");??
  14. ????//綁定地址??
  15. ????if(?bind(listener,?(struct?sockaddr?*)&serverAddr,?sizeof(serverAddr))?<?0)?{??
  16. ????????perror("bind?error");??
  17. ????????exit(-1);??
  18. ????}??
  19. ????//監聽??
  20. ????int?ret?=?listen(listener,?5);??
  21. ????if(ret?<?0)?{?perror("listen?error");?exit(-1);}??
  22. ????printf("Start?to?listen:?%s\n",?SERVER_IP);??
  23. ????//在內核中創建事件表??
  24. ????int?epfd?=?epoll_create(EPOLL_SIZE);??
  25. ????if(epfd?<?0)?{?perror("epfd?error");?exit(-1);}??
  26. ????printf("epoll?created,?epollfd?=?%d\n",?epfd);??
  27. ????static?struct?epoll_event?events[EPOLL_SIZE];??
  28. ????//往內核事件表里添加事件??
  29. ????addfd(epfd,?listener,?true);??
  30. ????//主循環??
  31. ????while(1)??
  32. ????{??
  33. ????????//epoll_events_count表示就緒事件的數目??
  34. ????????int?epoll_events_count?=?epoll_wait(epfd,?events,?EPOLL_SIZE,?-1);??
  35. ????????if(epoll_events_count?<?0)?{??
  36. ????????????perror("epoll?failure");??
  37. ????????????break;??
  38. ????????}??
  39. ??
  40. ????????printf("epoll_events_count?=?%d\n",?epoll_events_count);??
  41. ????????//處理這epoll_events_count個就緒事件??
  42. ????????for(int?i?=?0;?i?<?epoll_events_count;?++i)??
  43. ????????{??
  44. ????????????int?sockfd?=?events[i].data.fd;??
  45. ????????????//新用戶連接??
  46. ????????????if(sockfd?==?listener)??
  47. ????????????{??
  48. ????????????????struct?sockaddr_in?client_address;??
  49. ????????????????socklen_t?client_addrLength?=?sizeof(struct?sockaddr_in);??
  50. ????????????????int?clientfd?=?accept(?listener,?(?struct?sockaddr*?)&client_address,?&client_addrLength?);??
  51. ??
  52. ????????????????printf("client?connection?from:?%s?:?%?d(IP?:?port),?clientfd?=?%d?\n",??
  53. ????????????????inet_ntoa(client_address.sin_addr),??
  54. ????????????????ntohs(client_address.sin_port),??
  55. ????????????????clientfd);??
  56. ??
  57. ????????????????addfd(epfd,?clientfd,?true);??
  58. ??
  59. ????????????????//?服務端用list保存用戶連接??
  60. ????????????????clients_list.push_back(clientfd);??
  61. ????????????????printf("Add?new?clientfd?=?%d?to?epoll\n",?clientfd);??
  62. ????????????????printf("Now?there?are?%d?clients?int?the?chat?room\n",?(int)clients_list.size());??
  63. ??
  64. ????????????????//?服務端發送歡迎信息????
  65. ????????????????printf("welcome?message\n");??????????????????
  66. ????????????????char?message[BUF_SIZE];??
  67. ????????????????bzero(message,?BUF_SIZE);??
  68. ????????????????sprintf(message,?SERVER_WELCOME,?clientfd);??
  69. ????????????????int?ret?=?send(clientfd,?message,?BUF_SIZE,?0);??
  70. ????????????????if(ret?<?0)?{?perror("send?error");?exit(-1);?}??
  71. ????????????}??
  72. ????????????//處理用戶發來的消息,并廣播,使其他用戶收到信息??
  73. ????????????else???
  74. ????????????{?????
  75. ????????????????int?ret?=?sendBroadcastmessage(sockfd);??
  76. ????????????????if(ret?<?0)?{?perror("error");exit(-1);?}??
  77. ????????????}??
  78. ????????}??
  79. ????}??
  80. ????close(listener);?//關閉socket??
  81. ????close(epfd);????//關閉內核??
  82. ????return?0;??
  83. }??
g++ server.cpp utility.h -o server
./server

4. 客戶端實現

4.1 子進程和父進程的通信

前面已經介紹了子進程和父進程的功能,他們之間用管道進行通信。如下圖所示,我們可以更直觀的了解子進程和父進程各自的功能。?

通過調用int pipe(int fd[2])函數創建管道, 其中fd[0]用于父進程讀, fd[1]用于子進程寫。
[cpp]?view plain?copy
  1. //client.cpp代碼(管道模塊)??
  2. ???//?創建管道.??
  3. ????int?pipe_fd[2];??
  4. ????if(pipe(pipe_fd)?<?0)?{?perror("pipe?error");?exit(-1);?}??

通過int pid = fork()函數,創建子進程,當pid < 0 錯誤;當pid = 0, 說明是子進程;當pid > 0說明是父進程。根據pid的值,我們可以父子進程,從而實現對應的功能!

4.2 客戶端完整源碼

根據上述介紹,我們可以寫出客戶端的源碼。如下:

[cpp]?view plain?copy
  1. #include?"utility.h"??
  2. ??
  3. int?main(int?argc,?char?*argv[])??
  4. {??
  5. ????//用戶連接的服務器?IP?+?port??
  6. ????struct?sockaddr_in?serverAddr;??
  7. ????serverAddr.sin_family?=?PF_INET;??
  8. ????serverAddr.sin_port?=?htons(SERVER_PORT);??
  9. ????serverAddr.sin_addr.s_addr?=?inet_addr(SERVER_IP);??
  10. ??
  11. ????//?創建socket??
  12. ????int?sock?=?socket(PF_INET,?SOCK_STREAM,?0);??
  13. ????if(sock?<?0)?{?perror("sock?error");?exit(-1);?}??
  14. ????//?連接服務端??
  15. ????if(connect(sock,?(struct?sockaddr?*)&serverAddr,?sizeof(serverAddr))?<?0)?{??
  16. ????????perror("connect?error");??
  17. ????????exit(-1);??
  18. ????}??
  19. ??
  20. ????//?創建管道,其中fd[0]用于父進程讀,fd[1]用于子進程寫??
  21. ????int?pipe_fd[2];??
  22. ????if(pipe(pipe_fd)?<?0)?{?perror("pipe?error");?exit(-1);?}??
  23. ??
  24. ????//?創建epoll??
  25. ????int?epfd?=?epoll_create(EPOLL_SIZE);??
  26. ????if(epfd?<?0)?{?perror("epfd?error");?exit(-1);?}??
  27. ????static?struct?epoll_event?events[2];???
  28. ????//將sock和管道讀端描述符都添加到內核事件表中??
  29. ????addfd(epfd,?sock,?true);??
  30. ????addfd(epfd,?pipe_fd[0],?true);??
  31. ????//?表示客戶端是否正常工作??
  32. ????bool?isClientwork?=?true;??
  33. ??
  34. ????//?聊天信息緩沖區??
  35. ????char?message[BUF_SIZE];??
  36. ??
  37. ????//?Fork??
  38. ????int?pid?=?fork();??
  39. ????if(pid?<?0)?{?perror("fork?error");?exit(-1);?}??
  40. ????else?if(pid?==?0)??????//?子進程??
  41. ????{??
  42. ????????//子進程負責寫入管道,因此先關閉讀端??
  43. ????????close(pipe_fd[0]);???
  44. ????????printf("Please?input?'exit'?to?exit?the?chat?room\n");??
  45. ??
  46. ????????while(isClientwork){??
  47. ????????????bzero(&message,?BUF_SIZE);??
  48. ????????????fgets(message,?BUF_SIZE,?stdin);??
  49. ??
  50. ????????????//?客戶輸出exit,退出??
  51. ????????????if(strncasecmp(message,?EXIT,?strlen(EXIT))?==?0){??
  52. ????????????????isClientwork?=?0;??
  53. ????????????}??
  54. ????????????//?子進程將信息寫入管道??
  55. ????????????else?{??
  56. ????????????????if(?write(pipe_fd[1],?message,?strlen(message)?-?1?)?<?0?)??
  57. ?????????????????{?perror("fork?error");?exit(-1);?}??
  58. ????????????}??
  59. ????????}??
  60. ????}??
  61. ????else??//pid?>?0?父進程??
  62. ????{??
  63. ????????//父進程負責讀管道數據,因此先關閉寫端??
  64. ????????close(pipe_fd[1]);???
  65. ??
  66. ????????//?主循環(epoll_wait)??
  67. ????????while(isClientwork)?{??
  68. ????????????int?epoll_events_count?=?epoll_wait(?epfd,?events,?2,?-1?);??
  69. ????????????//處理就緒事件??
  70. ????????????for(int?i?=?0;?i?<?epoll_events_count?;?++i)??
  71. ????????????{??
  72. ????????????????bzero(&message,?BUF_SIZE);??
  73. ??
  74. ????????????????//服務端發來消息??
  75. ????????????????if(events[i].data.fd?==?sock)??
  76. ????????????????{??
  77. ????????????????????//接受服務端消息??
  78. ????????????????????int?ret?=?recv(sock,?message,?BUF_SIZE,?0);??
  79. ??
  80. ????????????????????//?ret=?0?服務端關閉??
  81. ????????????????????if(ret?==?0)?{??
  82. ????????????????????????printf("Server?closed?connection:?%d\n",?sock);??
  83. ????????????????????????close(sock);??
  84. ????????????????????????isClientwork?=?0;??
  85. ????????????????????}??
  86. ????????????????????else?printf("%s\n",?message);??
  87. ??
  88. ????????????????}??
  89. ????????????????//子進程寫入事件發生,父進程處理并發送服務端??
  90. ????????????????else?{???
  91. ????????????????????//父進程從管道中讀取數據??
  92. ????????????????????int?ret?=?read(events[i].data.fd,?message,?BUF_SIZE);??
  93. ??
  94. ????????????????????//?ret?=?0??
  95. ????????????????????if(ret?==?0)?isClientwork?=?0;??
  96. ????????????????????else{???//?將信息發送給服務端??
  97. ??????????????????????send(sock,?message,?BUF_SIZE,?0);??
  98. ????????????????????}??
  99. ????????????????}??
  100. ????????????}//for??
  101. ????????}//while??
  102. ????}??
  103. ??
  104. ????if(pid){??
  105. ???????//關閉父進程和sock??
  106. ????????close(pipe_fd[0]);??
  107. ????????close(sock);??
  108. ????}else{??
  109. ????????//關閉子進程??
  110. ????????close(pipe_fd[1]);??
  111. ????}??
  112. ????return?0;??
  113. }??
同理建立一個client.cpp文件,并將上述完整源碼拷貝進去,然后啟動一個新的XFce終端,執行如下命令:
cd Desktop
g++ client.cpp utility.h -o client
./client
如圖所示,通過查看兩個終端界面,可以看到有一個用戶登陸服務端了。 同理,再點擊一下桌面上的XFce,開啟一個終端,運行同樣的命令(這里不用運行g++進行編譯了,因為前面已經生成了可執行文件client):
cd Desktop
./client
轉載自:https://www.shiyanlou.com/courses/315
很好的學習了epoll。


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

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

相關文章

BZOJ2809-左偏樹合并

Description 在一個忍者的幫派里&#xff0c;一些忍者們被選中派遣給顧客&#xff0c;然后依據自己的工作獲取報償。在這個幫派里&#xff0c;有一名忍者被稱之為 Master。除了 Master以外&#xff0c;每名忍者都有且僅有一個上級。為保密&#xff0c;同時增強忍者們的領導力&a…

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

http://blog.csdn.net/zhuxiaoping54532/article/details/56494313處理大并發之一對epoll的理解&#xff0c;epoll客戶端服務端代碼序言&#xff1a;該博客是一系列的博客&#xff0c;首先從最基礎的epoll說起&#xff0c;然后研究libevent源碼及使用方法&#xff0c;最后研究n…

epoll詳解

http://blog.csdn.net/majianfei1023/article/details/45772269歡迎轉載&#xff0c;轉載請注明原文地址&#xff1a;http://blog.csdn.net/majianfei1023/article/details/45772269一.基本概念&#xff1a;1.epoll是什么&#xff1a;epoll是Linux內核為處理大批量文件描述符而…

數據分割-并查集+set

小w來到百度之星的賽場上&#xff0c;準備開始實現一個程序自動分析系統。 這個程序接受一些形如xixj 或 xi≠xj 的相等/不等約束條件作為輸入&#xff0c;判定是否可以通過給每個 w 賦適當的值&#xff0c;來滿足這些條件。 輸入包含多組數據。 然而粗心的小w不幸地把每組數據…

linux c++線程池的實現

http://blog.csdn.net/zhoubl668/article/details/8927090?t1473221020107 線程池的原理大家都知道&#xff0c;直接上代碼了^_^ Thread.h [cpp] view plaincopy #ifndef __THREAD_H #define __THREAD_H #include <vector> #include <string> #inc…

樹啟發式合并入門

所謂啟發式合并&#xff0c;就是一種符合直覺的合并方法&#xff1a;將小的子樹合并在大的子樹上。 這些問題一般是相似的問題背景&#xff1a;都是樹上的計數問題&#xff0c;都不能直接從上往下進行暴力&#xff0c;都需要從下往上計數時對子樹信息進行運算從而得到父親節點的…

鏈棧基本操作

http://blog.csdn.net/jwentao01/article/details/46765517###;棧基本概念&#xff1a; 棧&#xff08;stack&#xff09;是限定在表尾進行插入和刪除操作的線性表&#xff08;或單鏈表&#xff09;。 //只能在一段進行插入和刪除&#xff0c;因此不存在&#xff0c;在中間進行…

Linux網絡編程---I/O復用模型之select

https://blog.csdn.net/men_wen/article/details/53456435Linux網絡編程—I/O復用模型之select 1. IO復用模型 IO復用能夠預先告知內核&#xff0c;一旦發現進程指定的一個或者多個IO條件就緒&#xff0c;它就通知進程。IO復用阻塞在select或poll系統調用上&#xff0c;而不是阻…

UVa12633-Super Rooks on Chessboard-容斥+FFT

題目大意就是給你一個R*C的棋盤&#xff0c;上面有超級兵&#xff0c;這種超級兵會攻擊 同一行、同一列、同一主對角線的所有元素&#xff0c;現在給你N個超級兵的坐標&#xff0c;需要你求出有多少方塊是不能被攻擊到的(R,C,N<50000) 遇到這種計數問題就要聯想到容斥&#…

Linux網絡編程---I/O復用模型之poll

https://blog.csdn.net/men_wen/article/details/53456474Linux網絡編程—I/O復用模型之poll 1.函數poll poll系統調用和select類似&#xff0c;也是在指定時間內輪詢一定數量的文件描述符&#xff0c;以測試其中是否有就緒者。 #include <poll.h>int poll(struct pollfd…

FFT模板

整理了一下&#xff0c;自己寫了一下模板 const double PIacos(-1.0); struct complex {double r,i;complex(double _r0,double _i0):r(_r),i(_i){}complex operator (const complex &b) {return complex(rb.r,ib.i);}complex operator -(const complex &b) {return c…

Linux網絡編程---I/O復用模型之epoll

https://blog.csdn.net/men_wen/article/details/53456474 Linux網絡編程—I/O復用模型之epoll 1. epoll模型簡介 epoll是Linux多路服用IO接口select/poll的加強版&#xff0c;e對應的英文單詞就是enhancement&#xff0c;中文翻譯為增強&#xff0c;加強&#xff0c;提高&…

POJ 1741tree-點分治入門

學習了一下點分治&#xff0c;如果理解有誤還請不吝賜教。 為了快速求得樹上任意兩點之間距離滿足某種關系的點對數&#xff0c;我們需要用到這種算法。 點分治是樹上的一種分治算法&#xff0c;依靠樹和子樹之間的關系進行分治從而降低復雜度。 和其他樹上的算法有一些區別…

基于單鏈表的生產者消費者問題

『生產者與消費者問題分析』「原理」生產者生產產品&#xff0c;消費者消費產品。產品如果被消費者消費完了&#xff0c;同時生產者又沒有生產出產品&#xff0c;消費者 就必須等待。同樣的&#xff0c;如果生產者生產了產品&#xff0c;而消費者沒有去消費&#x…

C++智能指針(一)智能指針的簡單介紹

https://blog.csdn.net/nou_camp/article/details/70176949C智能指針 在正式了解智能指針前先看一下下面的一段代碼 #include<iostream> using namespace std; class A { public:A():_ptr(NULL), _a(0){}~A(){} public:int* _ptr;int _a; };void test() {A a;int *p1 ne…

聰聰可可-點分治

聰聰和可可是兄弟倆&#xff0c;他們倆經常為了一些瑣事打起來&#xff0c;例如家中只剩下最后一根冰棍而兩人都想吃、兩個人都想玩兒電腦&#xff08;可是他們家只有一臺電腦&#xff09;……遇到這種問題&#xff0c;一般情況下石頭剪刀布就好了&#xff0c;可是他們已經玩兒…

C++智能指針(二)模擬實現三種智能指針

https://blog.csdn.net/nou_camp/article/details/70186721在上一篇博客中提到了Auto_ptr(C智能指針&#xff08;一&#xff09;)&#xff0c;下面進行模擬實現Auto_ptr 采用類模板實現 #include<iostream> using namespace std; template<class T> class Autoptr …

Prime Distance On Tree-樹分治+FFT

題目描述 Problem description. You are given a tree. If we select 2 distinct nodes uniformly at random, what’s the probability that the distance between these 2 nodes is a prime number? Input The first line contains a number N: the number of nodes in this…

C++智能指針(三)總結

https://blog.csdn.net/nou_camp/article/details/70195795 在上一篇博客中&#xff08;C智能指針&#xff08;二&#xff09;&#xff09;模擬實現了三種智能指針。 其中最好的就是shared_ptr,但是這并不代表它就是最完美的&#xff0c;它也有問題&#xff0c;這個問題就是循環…

POJ2114-Boatherds-樹分治

題目描述 Boatherds Inc. is a sailing company operating in the country of Trabantustan and offering boat trips on Trabantian rivers. All the rivers originate somewhere in the mountains and on their way down to the lowlands they gradually join and finally th…