?
一、關于socket通信
服務器端工作流程:
- 調用 socket() 函數創建套接字 用 bind() 函數將創建的套接字與服務端IP地址綁定
- 調用listen()函數監聽socket() 函數創建的套接字,等待客戶端連接 當客戶端請求到來之后
- 調用 accept()函數接受連接請求,返回一個對應于此連接的新的套接字,做好通信準備
- 調用 write()/read() 函數和 send()/recv()函數進行數據的讀寫,通過 accept() 返回的套接字和客戶端進行通信 關閉socket(close)
客戶端工作流程:
- 調用 socket() 函數創建套接字
- 調用 connect() 函數連接服務端
- 調用write()/read() 函數或者 send()/recv() 函數進行數據的讀寫
- 關閉socket(close)
?
二、用select實現服務器端編程:
select函數樓主在之前文章中(select函數用法)已經提及,不在多做綴述。下面貼上服務器端代碼servce.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
?
?
?
?
? ?
?
? ?
?
?
?
? ?
?
?
?
?
?
?
?
?
?
?
?
?
? ?
?
?
?
?
?
?
?
|
select實現多路復用,多路復用,顧名思義,就是說各做各的事,標準輸入事件到來,有相關函數處理。服務器處理服務器的事件,客戶端到來時有相關函數對其進行處理,通過select遍歷各fd的讀寫情況,就不用擔心阻塞了。
三、用epoll實現客戶端編程:
1、客戶端程序(epoll_client.c):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
|
2、關于epoll函數:
相比于select,epoll最大的好處在于它不會隨著監聽fd數目的增長而降低效率。因為在內核中的select實現中,它是采用輪詢來處理的,輪詢的fd數目越多,自然耗時越多。并且,在linux/posix_types.h頭文件有這樣的聲明:?
#define __FD_SETSIZE 1024?
表示select最多同時監聽1024個fd
一共三個函數:
1 2 |
|
size用來告訴內核這個監聽的數目一共有多大。這個參數不同于select()中的第一個參數,給出最大監聽的fd+1的值。需要注意的是,當創建好epoll句柄后,它就是會占用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll后,必須調用close()關閉,否則可能導致fd被耗盡。
1 |
|
epoll的事件注冊函數,它不同與select()是在監聽事件時告訴內核要監聽什么類型的事件,而是在這里先注冊要監聽的事件類型。第一個參數是epoll_create()的返回值,第二個參數表示動作,用三個宏來表示:
EPOLL_CTL_ADD:注冊新的fd到epfd中;
EPOLL_CTL_MOD:修改已經注冊的fd的監聽事件;
EPOLL_CTL_DEL:從epfd中刪除一個fd;
第三個參數是需要監聽的fd
第四個參數是告訴內核需要監聽什么事,struct epoll_event結構如下:
1 2 3 4 5 |
|
1 2 3 4 5 6 7 |
|
events可以是以下幾個宏的集合:
- EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
- EPOLLOUT:表示對應的文件描述符可以寫;
- EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來);
- EPOLLERR:表示對應的文件描述符發生錯誤;
- EPOLLHUP:表示對應的文件描述符被掛斷;
- EPOLLET: 將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對于水平觸發(Level Triggered)來說的。
- EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里
1 |
|
等待事件的產生,類似于select()調用。
參數events用來從內核得到事件的集合,
maxevents告之內核這個events有多大,這個 maxevents的值不能大于創建epoll_create()時的size,
參數timeout是超時時間(毫秒,0會立即返回,-1將不確定,也有說法說是永久阻塞)。該函數返回需要處理的事件數目,如返回0表示已超時。
使用步驟:
<1>首先通過create_epoll(int maxfds)來創建一個epoll的句柄,其中maxfds為你epoll所支持的最大句柄數。這個函數會返回一個新的epoll句柄,之后的所有操作將通過這個句柄來進行操作。在用完之后,記得用close()來關閉這個創建出來的epoll句柄。
<2>然后每一幀的調用epoll_wait (int epfd, epoll_event events, int max events, int timeout) 來查詢所有的網絡接口。
<3>kdpfd為用epoll_create創建之后的句柄,events是一個epoll_event*的指針,當epoll_wait這個函數操作成功之后,epoll_events里面將儲存所有的讀寫事件。max_events是當前需要監聽的所有socket句柄數。最后一個timeout是 epoll_wait的超時,為0的時候表示馬上返回,為-1的時候表示一直等下去,直到有事件范圍,為任意正整數的時候表示等這么長的時間,如果一直沒有事件,則返回。一般如果網絡主循環是單獨的線程的話,可以用-1來等,這樣可以保證一些效率,如果是和主邏輯在同一個線程的話,則可以用0來保證主循環的效率。 epoll_wait返回之后應該是一個循環,遍歷所有的事件。
基本上都是如下的框架:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
?
|