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