您好,登錄后才能下訂單哦!
這篇文章主要介紹了怎么處理Accept出現Emfile的問題,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
通常情況下,服務端調用 accept 函數會返回一個新的文件描述符,用于和客戶端之間的數據傳輸
在服務器的開發中,有時會遇到這種情況:當調用 accept 函數接受客戶端連接,函數返回失敗,對應的錯誤碼是 EMFILE, 它表示當前進程打開的文件描述符已達上限,此時,服務器不能再接受客戶端連接
當遇到上述問題,怎么合理的處理呢,下面就來分析一下
建立連接的流程
先簡單回顧下客戶端和服務器建立連接的流程,具體的如下圖所示:
1. 客戶端發起 SYN 請求
2. 服務器收到客戶端的 SYN 請求后,內核把連接放入半連接隊列,同時給客戶端返回一個 SYN + ACK
3. 客戶端向服務器返回一個確認的 ACK, 服務器收到本次 ACK 之后,三次握手完成,同時,內核把連接從半連接隊列中移除,創建新完全連接,加入到全連接隊列中
4. 應用層調用 accept 函數從全連接隊列中取出連接
上面的第 1、第 2、第 3 步是 TCP 的三次握手,它是由內核中TCP協議完成的, 第 4 步是應用層調用 accept 接口
在 epoll 中的問題
epoll 是 Linux中IO多路復用模型,在服務器的開發中有廣泛的應用,下面就以 epoll 為例來詳細說明
服務器端創建偵聽文件描述符 listenfd 之后, 向 epoll 注冊讀事件
當 epoll 檢測到 listenfd 上有讀事件發生,會立即通知應用層,應用層調用 accept 接受新連接,而此時進程打開的文件描述符數量已經達到上限了,所以每次 accept 都是失敗的
這里會出現以下幾個問題
鴻蒙官方戰略合作共建——HarmonyOS技術社區
由于 每次 accept 都失敗了,相當于 listenfd 上的可讀事件沒有處理,epoll 會不停的觸發 listenfd 上的可讀事件,應用層也就會不停的調用 accept,然后又出現 accept 調用失敗,如此這般不停的執行無效的循環,白白浪費了CPU的資源
上面提到服務器在不停的執行無效的循環, 將會引發另一個問題,如果此時有新客戶端連接到來,建立連接的過程會很慢
前面說的 epoll 默認是使用了水平觸發模式,如果使用垂直觸發模式會出現什么問題呢?
垂直觸發模式下,listenfd 從無讀事件狀態到有讀事件狀態時,才會通知到應用層,在應用層處理完 listenfd 上所有的讀事件之前,epoll 不會再通知應用層
也就是說,應用層收到 listenfd 上讀事件通知之后,需要把 listenfd 上所有的讀事件全部處理完,下次listenfd 上再有讀事件時,才會通知應用層
回到 accept 的問題上,在垂直觸發模式下,當 epoll 通知應用層 listenfd 上有可讀事件時,應用層調用 accept, 由于此時進程打開的文件描述符數量已經達到上限了,所以 accept 調用失敗
也即 listenfd 上的可讀事件還沒有處理,在應用層處理完 listenfd 上可讀事件之前,epoll 不會再通知應用層 listenfd 上有可讀事件
如果在應用層處理完 listenfd 上可讀事件之前,有新的客戶端連接到來,這個時候 epoll 是不會通知應用層 listenfd 上有可讀事件,這會導致一個嚴重的問題:accept 只要出現了 EMFILE的錯誤碼,就再也無法接受客戶端的連接了
所以,當出現 EMFILE 時,不管使用 epoll 的水平觸發模式還是垂直觸發模式都會存在問題
如何解決
EMFILE 表示進程打開的文件描述符數量達到上限了,可以把這個值調大些,但這治標不治本
本來系統設置文件描述符數量上限是為了限制進程對系統資源的過度占用,況且,這個值調整到多大合適呢,總不能無限大吧,所以調整上限值的方式不是最合適的方式
accept 成功時會返回一個新的文件描述符,如果此時進程打開的文件描述符數量已經達到上限了,就會返回失敗
假如此時能關閉一個空閑的文件描述符,讓出一個名額,再調用 accept 就會創建成功,這種方式具體的處理步驟如下:
1、事先準備一個空閑的文件描述符 idlefd,相當于先占一個"坑"位
2、調用 close 關閉 idlefd,關閉之后,進程就會獲得一個文件描述符名額
3、再次調用 accept 函數, 此時就會返回新的文件描述符 clientfd, 立刻調用 close 函數,關閉 clientfd
4、重新創建空閑文件描述符 idlefd,重新占領 "坑" 位,再出現這種情況的時候又可以使用
由于測試代碼比較長,這里就不貼了,感興趣可以通過文末的方式獲取,下面是處理 EMFILE 的偽代碼:
int ret = accept( listenfd, (struct sockaddr*)&addr, sizeof(addr) ); if (-1 == ret) { if ( errno == EMFILE ) { //關閉空閑文件描述符,釋放 "坑"位 close(idlefd); //接受 clientfd clientfd = accept( listenfd, nullptr, nullptr); //關閉 clientfd,防止一直觸發 listenfd 上的可讀事件 close(clientfd); //重新占領 "坑"位 idlefd = ::open("/dev/null", O_RDONLY | O_CLOEXEC); } }
感謝你能夠認真閱讀完這篇文章,希望小編分享的“怎么處理Accept出現Emfile的問題”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。