epoll 的accept , read, write
http://www.ccvita.com/515.html
在一個(gè)非阻塞(fcntl)的socket上調(diào)用read/write函數(shù), 返回EAGAIN或者EWOULDBLOCK(注: EAGAIN就是EWOULDBLOCK)
這個(gè)錯(cuò)誤表示資源暫時(shí)不夠, 可能read時(shí), 讀緩沖區(qū)沒有數(shù)據(jù), 或者, write時(shí),寫緩沖區(qū)滿了. ?(此處讀寫緩沖區(qū)指的是socket的緩沖區(qū),而不是read或write調(diào)用形參中的buffer)
遇到這種情況, 如果是阻塞socket, read/write就要阻塞掉.而如果是非阻塞socket, read/write立即返回-1, 同?時(shí)errno設(shè)置為EAGAIN.所以, 對(duì)于阻塞socket, read/write返回-1代表網(wǎng)絡(luò)出錯(cuò)了.但對(duì)于非阻塞socket, read/write返回-1不一定網(wǎng)絡(luò)真的出錯(cuò)了.可能是Resource temporarily unavailable. 這時(shí)你應(yīng)該再試, 直到Resource available.
綜上, 對(duì)于non-blocking的socket, ?正確的讀寫操作為:
讀: 忽略掉errno = EAGAIN的錯(cuò)誤, 下次繼續(xù)讀
寫:?忽略掉errno = EAGAIN的錯(cuò)誤, 下次繼續(xù)寫
對(duì)于select和epoll的LT模式, 這種讀寫方式是沒有問題的. 但對(duì)于epoll的ET模式, 這種方式還有漏洞.
epoll的兩種模式 LT 和 ET
二者的差異在于 level-trigger 模式下只要某個(gè) socket 處于 readable/writable 狀態(tài),無(wú)論什么時(shí)候調(diào)用epoll_wait 都會(huì)返回該 socket;而 edge-trigger 模式下只有某個(gè) socket 從 unreadable 變?yōu)?readable 或從unwritable 變?yōu)?writable 時(shí),epoll_wait 才會(huì)返回該 socket。
從socket讀數(shù)據(jù):
往socket寫數(shù)據(jù):
所以, 在epoll的ET模式下, 正確的讀寫方式為:
讀: 只要可讀, 就一直讀, 直到返回0, 或者 errno = EAGAIN
寫: 只要可寫, 就一直寫, 直到數(shù)據(jù)發(fā)送完, 或者 errno = EAGAIN
正確的讀:
n = 0; while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) { n += nread; } if (nread == -1 && errno != EAGAIN) { perror("read error"); }正確的寫:
int nwrite, data_size = strlen(buf); n = data_size; while (n > 0) { nwrite = write(fd, buf + data_size - n, n); if (nwrite < n) { if (nwrite == -1 && errno != EAGAIN) { perror("write error"); } break; } n -= nwrite; }我的理解:LT模式下,read、write不需要while循環(huán)了,即使這次系統(tǒng)調(diào)用沒有處理完所有數(shù)據(jù),下次調(diào)用epoll_wait時(shí)也會(huì)返回該就緒事件(上述write相關(guān)代碼感覺不對(duì))
讀函數(shù)read??ssize_t?read(int?fd,void?*buf,size_t?nbyte)? read函數(shù)是負(fù)責(zé)從fd中讀取內(nèi)容.成功時(shí),read返回實(shí)際所讀的字節(jié)數(shù),如果返回的值是0,表示已經(jīng)讀到文件的結(jié)束了. 小于0表示出現(xiàn)了錯(cuò)誤.如果錯(cuò)誤為EINTR說明讀是由中斷引起的,?如果是ECONNREST表示網(wǎng)絡(luò)連接出了問題. 寫函數(shù)write??
ssize_t?write(int?fd,const?void?*buf,size_t?nbytes)?
write函數(shù)將buf中的nbytes字節(jié)內(nèi)容寫入文件描述符fd.成功時(shí)返回寫的字節(jié)數(shù).失敗時(shí)返回-1.?并設(shè)置errno變量.?在網(wǎng)絡(luò)程序中,當(dāng)我們向套接字文件描述符寫時(shí)有倆種可能.??
1)write的返回值大于0,表示寫了部分或者是全部的數(shù)據(jù).??
2)返回的值小于0,此時(shí)出現(xiàn)了錯(cuò)誤.我們要根據(jù)錯(cuò)誤類型來(lái)處理.? 如果錯(cuò)誤為EINTR表示在寫的時(shí)候出現(xiàn)了中斷錯(cuò)誤.??
如果為EPIPE表示網(wǎng)絡(luò)連接出現(xiàn)了問題(對(duì)方已經(jīng)關(guān)閉了連接)
正確的accept,accept 要考慮 2 個(gè)問題
(1) 阻塞模式 accept 存在的問題
accept每次都是從已經(jīng)完成三次握手的tcp隊(duì)列中取出一個(gè)連接
考慮這種情況: TCP 連接被客戶端夭折,即在服務(wù)器調(diào)用 accept 之前,客戶端主動(dòng)發(fā)送 RST 終止連接,導(dǎo)致剛剛建立的連接從就緒隊(duì)列中移出,如果套接字被設(shè)置成阻塞模式,服務(wù)器就會(huì)一直阻塞在 accept 調(diào)用上(即此時(shí)沒有客戶端連接給accept),直到其他某個(gè)客戶建立一個(gè)新的連接為止(即accept接受新的連接,返回)。但是在此期間,服務(wù)器單純地阻塞在accept 調(diào)用上,就緒隊(duì)列中的其他描述符都得不到處理(我的理解:其他fd讀寫事件就緒但得不到處理).
解決辦法是把監(jiān)聽套接口設(shè)置為非阻塞,當(dāng)客戶在服務(wù)器調(diào)用 accept 之前中止某個(gè)連接時(shí),accept 調(diào)用可以立即返回 -1, 這時(shí)源自 Berkeley 的實(shí)現(xiàn)會(huì)在內(nèi)核中處理該事件,并不會(huì)將該事件通知給 epoll,而其他實(shí)現(xiàn)把 errno 設(shè)置為 ECONNABORTED 或者 EPROTO 錯(cuò)誤,我們應(yīng)該忽略這兩個(gè)錯(cuò)誤。
(2) ET 模式下 accept 存在的問題
考慮這種情況:多個(gè)連接同時(shí)到達(dá),服務(wù)器的 TCP 就緒隊(duì)列瞬間積累多個(gè)就緒連接,由于是邊緣觸發(fā)模式,epoll 只會(huì)通知一次,accept 只處理一個(gè)連接,導(dǎo)致 TCP 就緒隊(duì)列中剩下的連接都得不到處理。
解決辦法是用 while 循環(huán)抱住 accept 調(diào)用,處理完 TCP 就緒隊(duì)列中的所有連接后再退出循環(huán)。如何知道是否處理完就緒隊(duì)列中的所有連接呢? accept??返回 -1 并且 errno 設(shè)置為 EAGAIN 就表示所有連接都處理完。
綜合以上兩種情況,服務(wù)器應(yīng)該使用非阻塞地 accept, accept 在 ET 模式下 的正確使用方式為:
while ((conn_sock = accept(listenfd,(struct sockaddr *) &remote, (size_t *)&addrlen)) > 0) { handle_client(conn_sock); } if (conn_sock == -1) { if (errno != EAGAIN && errno != ECONNABORTED && errno != EPROTO && errno != EINTR) perror("accept"); }我的總結(jié):當(dāng)epoll在ET模式下,read、write、accept操作都在while循環(huán)中執(zhí)行,使得所有數(shù)據(jù)或者連接都處理完,因?yàn)镋T模式下,epoll_wait只返回一次
?
一道騰訊后臺(tái)開發(fā)的面試題
使用Linux epoll模型,水平觸發(fā)模式,當(dāng)socket可寫時(shí),會(huì)不停的觸發(fā) socket 可寫的事件,如何處理?
第一種最普遍的方式:
需要向 socket 寫數(shù)據(jù)的時(shí)候才把 socket 加入 epoll ,等待可寫事件。接受到可寫事件后,調(diào)用 write 或者 send 發(fā)送數(shù)據(jù)。當(dāng)所有數(shù)據(jù)都寫完后,把 socket 移出 epoll。
這種方式的缺點(diǎn)是,即使發(fā)送很少的數(shù)據(jù),也要把 socket 加入 epoll,寫完后在移出 epoll,有一定操作代價(jià)。
一種改進(jìn)的方式:
開始不把 socket 加入 epoll,需要向 socket 寫數(shù)據(jù)的時(shí)候,直接調(diào)用 write 或者 send 發(fā)送數(shù)據(jù)。如果返回 EAGAIN,把 socket 加入 epoll,在 epoll 的驅(qū)動(dòng)下寫數(shù)據(jù),全部數(shù)據(jù)發(fā)送完畢后,再移出 epoll。
這種方式的優(yōu)點(diǎn)是:數(shù)據(jù)不多的時(shí)候可以避免 epoll 的事件處理,提高效率。
轉(zhuǎn)載于:https://www.cnblogs.com/ljygoodgoodstudydaydayup/p/5655984.html
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的epoll 的accept , read, write的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SqlServer 跨服务器查询
- 下一篇: 军校提前批截止到几号?