簡體   English   中英

select() - writefds 和 exceptfds 與非阻塞 TCP 套接字的實際使用?

[英]select() - practical use of writefds and exceptfds with non-blocking TCP sockets?

根據Linux man pagesselect支持三種喚醒事件:

  • readfds將被監視以查看字符是否可供閱讀
  • 將監視writefds以查看是否有空間可用於寫入
  • 將監視exceptfds是否有異常

在網上和網絡書籍中尋找 TCP 套接字的實際使用示例時,我大多只看到使用readfds ,即使代碼稍后嘗試寫入套接字。

但是套接字可能還沒有准備好寫入,因為我們可能只在readfs集中而不是在writefds集中接收到它。 為了避免寫阻塞,我通常將套接字的 fd 設置為非阻塞模式。 然后,如果send失敗,我可以將數據排入某個內部緩沖區並稍后將其發送出去(這意味着 - 下次帶有readfs select()喚醒時)。 但這似乎很危險——如果下一次readfs喚醒來得晚得多並且要寫入的數據只是坐在我們的緩沖區中等待,理論上,永遠?

Apple 的文檔還建議使用writefdsUsing Sockets and Socket Streams ,請參閱“使用純 POSIX 代碼處理事件”部分,引用:

在循環中調用 select,為讀取和寫入描述符集傳遞該文件描述符集(通過調用 FD_COPY 創建)的兩個單獨副本

所以問題是:

  1. Apple 是否建議使用writefds只是因為它是“正確的官方方式”,或者也許還有其他方法可以在沒有writefds情況下處理套接字寫入? 蘋果的建議對我來說似乎很可疑。 如果我們從一開始就將套接字放入writefds然后一段時間不寫入它,則select()不會因為套接字可寫而立即喚醒(那是因為我們還沒有寫入它) )?

  2. 關於exceptfds -我還沒有看到任何使用它與TCP套接字的例子。 我讀過它用於帶外數據。 這是否意味着如果我只處理主流 Internet 流量,例如 HTTP、音頻/視頻流、游戲服務器等,我可以忽略 TCP 套接字的exceptfds

Apple 是否建議使用 writefds 只是因為它是“正確的官方方式”,或者也許還有其他方法可以在沒有 writefds 的情況下處理套接字寫入?

另一種方法(你在你看過的教程中看到的)是假設寫緩沖區總是足夠大,可以立即保存你想發送給它的任何數據,只要你需要就盲目地調用 send()到。

它簡化了代碼,但它不是一個很好的方法——也許它對於玩具/示例程序來說已經足夠好了,但我不想在生產質量的代碼中做出這樣的假設,因為這意味着如果/當您的程序一次生成足夠的數據來填充套接字的輸出緩沖區時。 根據您(錯誤)如何處理對 send() 的調用,您的程序將進入自旋循環(調用 send() 並獲得 EWOULDBLOCK,一遍又一遍,直到最終有足夠的空間來放置所有數據) ,或錯誤輸出(如果您將 EWOULDBLOCK/short-send() 視為致命錯誤條件),或刪除一些傳出數據字節(如果您只是完全忽略了 send() 的返回值)。 這些都不是處理全輸出緩沖區情況的優雅方式。

如果我們從一開始就將套接字放入 writefds 然后一段時間不寫入它,則 select() 不會因為套接字可寫而立即喚醒(那是因為我們還沒有寫入它) )?

是的,絕對 - 這就是為什么如果您當前有一些要寫入 socket 的數據,您只會將 socket 放入 writefds 集。 如果您當前沒有要寫入套接字的數據,則可以將套接字排除在 writefds 之外,以便 select() 不會立即返回。

關於exceptfds -我還沒有看到任何使用它與TCP套接字的例子。 我讀過它用於帶外數據。

通常,exceptfds 的用途並不多(TCP 的帶外數據功能也不是,AFAIK)。 我見過它的唯一一次使用是在 Windows 下進行異步/非阻塞 TCP 連接時——當異步/非阻塞 TCP 連接嘗試失敗時,Windows 使用 exceptfds 喚醒 select()。

然后,如果發送失敗,我可以將數據排入某個內部緩沖區並稍后將其發送出去(這意味着 - 下次帶有 readfs 的 select() 喚醒時)。 但這似乎很危險——如果下一次 readfs 喚醒來得晚得多並且要寫入的數據只是坐在我們的緩沖區中等待,理論上,永遠?

由於 TCP 會自動減慢發送方的傳輸速度,以大致以接收方接收的速率進行傳輸,因此接收程序可能會簡單地停止調用 recv(),最終將發送方的傳輸速率降低到零。 或者,發送方和接收方之間的網絡可能會開始丟棄如此多的數據包,以至於傳輸速率實際上為零,即使接收方像預期的那樣調用 recv() 也是如此。 在任何一種情況下,這都意味着您的排隊數據很可能會在您的傳出數據緩沖區中停留很長時間——在后一種情況下可能不會永遠存在,因為完全陷入困境的 TCP 連接最終會出錯; 在前一種情況下,您需要調試接收方而不是發送方。

真正的問題是當您的發送方生成數據的速度比接收方接收數據的速度快(或者,換句話說,比網絡傳輸數據的速度快)——在這種情況下,如果您正在排隊“多余”的數據進入發送方的 FIFO,該 FIFO 可以無限增長,直到最終您的發送進程因內存耗盡而崩潰——這絕對不是理想的行為。

有幾種方法可以處理; 一種方法是簡單地監控當前保存在 FIFO 中的字節數,當它達到某個閾值(例如一兆字節或其他什么;什么構成“合理”閾值將取決於您的應用程序正在做什么)時,服務器可以決定客戶端根本不能很好地執行並關閉發送套接字以進行自衛(當然並釋放相關聯的 FIFO 隊列)。 這在很多情況下都很有效,盡管如果您的服務器瞬間生成/排隊的數據量超過該數量,它可能會出現誤報,並最終以不當方式斷開實際運行良好的客戶端。

另一種方法(我喜歡,如果可能)是設計服務器,使得其在插座產生更多的輸出數據時,目前還沒有輸出數據排隊用於該套接字。 即當套接字選擇為准備寫入時,盡可能多地將現有數據從 FIFO 隊列中排出到套接字中。 當 FIFO 隊列為空並且您有要從中生成傳出字節的數據並且套接字准備好寫入時,是生成更多輸出數據字節並將它們放入 FIFO 隊列的唯一時間。 永遠重復這個過程,無論客戶端有多慢,您的 FIFO 隊列的大小永遠不會大於您在一次生成更多數據字節步驟迭代中生成的數據量。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM