2013-01-07 46 views
10

我使用tee()複製了一個「主」管道,以使用splice()寫入多個套接字。自然,這些管道將以不同的速率清空,具體取決於我可以拼接()到目標套接字的多少。所以,當我下一步將數據添加到「主」管道然後再次發球時,我可能會遇到一種情況,我可以向管道寫入64KB,但只能向其中一個「從」管道發送4KB。我在猜測,如果我將所有「主」管道拼接到套接字,我將永遠無法將剩餘的60KB發送到該管道。真的嗎?我想我可以跟蹤一個tee_offset(從0開始),我將它設置爲「不受保證」數據的開始,然後不經過它的拼接()。所以在這種情況下,我會將tee_offset設置爲4096,並且不能拼接更多,直到我能夠將其全部轉換爲其他管道。我在正確的軌道上嗎?任何提示/警告對我來說?使用管道,tee()和splice()將數據發送到多個套接字

回答

20

如果我理解正確,那麼您已經獲得了一些想要複用到多個套接字的實時數據源。你有一個單一的「源代碼」管道連接到生成你的數據的任何東西,並且你想要發送數據的每個套接字都有一個「目標」管道。你在做什麼是使用tee()將數據從源管道複製到每個目標管道,並將splice()從目標管道複製到套接字本身。

你打算在這裏打的基本問題是,如果其中一個套接字根本無法跟上 - 如果你的數據製作速度比你發送數據的速度快,那麼你將遇到問題。這與您使用管道無關,這只是一個基本問題。所以,在這種情況下,你需要選擇一種策略來應對 - 即使你不希望它很常見,我也建議你處理這個問題,因爲這些事情往往會在以後出現。您的基本選擇是關閉違規的套接字,或者在數據清除到輸出緩衝區之前跳過數據 - 例如,後一種選擇可能更適合音頻/視頻流。

然而,與使用管道相關的的問題是,在Linux上,管道緩衝區的大小有點不靈活。自Linux 2.6.11(在2.6.17中添加tee()調用)以來,它默認爲64K - 請參閱pipe manpage。自2.6.35以來,此值可通過F_SETPIPE_SZ選項更改爲fcntl()(請參閱fcntl manpage),最大爲/proc/sys/fs/pipe-size-max指定的限制,但緩衝對於按需更改比用戶空間中的動態分配方案更難以實現是。這意味着你處理緩慢套接字的能力會受到一定的限制 - 這是否可以接受取決於你希望接收和發送數據的速率。

假設這個緩衝策略是可以接受的,那麼假設你需要跟蹤每個目標管道從源消耗了多少數據,並且放棄所有目標管道消耗的數據是安全的。這是因爲tee()沒有偏移的概念 - 你只能從管道的開頭複製。這樣做的結果是,您只能以最慢的套接字速度進行復制,因爲您無法使用tee()複製到目標管道,直到某些數據已從源中消耗完,並且您無法執行這個直到所有套接字都有你將要消耗的數據。

你如何處理這取決於你的數據的重要性。如果你真的需要tee()splice()的速度,並且你確信慢速套接字將是一件非常罕見的事情,你可以做這樣的事情(我假設你正在使用非阻塞IO和單線程,但類似還將與多線程工作)的東西:

  1. 確保所有管道無阻塞(使用fcntl(d, F_SETFL, O_NONBLOCK)使每個文件描述符非阻塞)。
  2. 初始化每個目標管道的read_counter變量爲零。
  3. 使用諸如epoll()之類的東西來等待,直到源管道中有東西。
  4. 遍歷所有目的地管道其中read_counter爲零,主叫tee()將數據傳輸到每一個。確保你通過標誌中的SPLICE_F_NONBLOCK
  5. 遞增read_counter爲每個目標管道按金額轉移tee()。跟蹤最低的結果值。
  6. 查找read_counter的最小結果值 - 如果它不爲零,則從源管道丟棄該數據量(例如,使用splice()調用,該調用的目的地打開在/dev/null上)。丟棄數據後,減去從read_counter全部管道丟棄的數量(因爲這是最低值,那麼這不會導致它們中的任何一個變爲負值)。
  7. 重複步驟。

注:這是我絆倒了,在過去的一件事是,SPLICE_F_NONBLOCK會影響管道的tee()splice()操作是否無阻塞,和你fnctl()設置O_NONBLOCK影響是否與其他調用的相互作用(例如read()write())是非阻塞的。如果您希望所有內容都是非阻塞的,請同時設置。另外請記住,要使您的套接字非阻塞,或調用傳輸數據給它們可能會阻塞(除非這是你想要的,如果你使用的是線程方法)。

正如你所看到的,這個策略存在一個主要問題 - 只要一個套接字阻塞,一切都暫停 - 該套接字的目標管道將填滿,然後源管道將停滯。所以,如果你到達階段,tee()回報EAGAIN步驟那麼你會想要麼關閉套接字,或者至少是「斷開」它(即把它拿出你的循環),使得你不寫直到它的輸出緩衝區爲空時爲止。你選擇哪一個取決於你的數據流是否可以通過跳過它的位來恢復。

如果要應付網絡延遲更優雅,那麼你會需要做更多的緩衝,這將涉及任何用戶空間的緩衝區(其中相當否定的tee()splice()的優勢),或者是基於磁盤的緩衝區。基於磁盤的緩衝幾乎肯定會比用戶空間緩衝慢得多,因此不適合,因爲假設您想要很高的速度,因爲您首先選擇了tee()splice(),但我提到它的完整性。

有一點值得注意的是,如果最終在用戶空間插入數據的任何一點是vmsplice()調用,它可以執行用戶空間到用戶空間的「收集輸出」,類似於調用writev()的方式。如果你做了足夠的緩衝,你可以將數據分割到多個不同的分配緩衝區中(例如,如果使用池分配器方法),這可能會很有用。最後,你可以想象在使用tee()splice()的「快速」方案之間交換套接字,如果它們跟不上,就將它們移動到較慢的用戶空間緩衝區。這會讓你的實現複雜化,但是如果你處理大量的連接,並且只有很小的一部分連接很慢,那麼你仍然會減少複製到用戶空間的複製量,這在一定程度上涉及到了。但是,這只是一個短暫的措施來應對短暫的網絡問題 - 正如我原先所說的,如果你的套接字比源代碼慢,你就會遇到一個基本問題。你最終會達到一些緩衝限制,並且需要跳過數據或關閉連接。總的來說,我會仔細考慮爲什麼你需要的速度爲tee()splice(),並且對於你的使用情況來說,在內存中還是在磁盤上進行簡單的用戶空間緩衝會更合適。但是,如果你確信速度總是很高,並且有限的緩衝是可以接受的,那麼我上面概述的方法應該可行。

另外,我應該提到的一件事是,這將使您的代碼非常特定於Linux - 我不知道這些調用在其他Unix變體中得到支持。 sendfile()呼叫比splice()更受限制,但可能更便於攜帶。如果你真的希望事情是可移植的,堅持用戶空間緩衝。

讓我知道是否有什麼我已經涵蓋了你想了解更多的細節。

+3

我希望我能+10你的答案。是的,你已經很好地描述了我的問題,如果一個套接字無法跟上,那麼你是對的。除非一個收件人失敗,否則每個套接字應該以相同的速度隨時間推移。在這種情況下,唯一明智的做法是將其從複製集中刪除。但是你錯過的是,儘管缺省情況下管道是64KB,但你可以將它們設置爲1MB(通過修改/ proc/sys/fs/pipe-max-size可以提高限制本身)。足夠的內存,我可以分配多達64MB到每個管道。你怎麼看? – Eloff

+0

我從來不知道'F_SETPIPE_SZ',謝謝!我編輯了我的答案。請記住,2.6.35仍然有點新(例如Ubuntu 10.04 LTS是2.6.32 AFAIK)。只要你不介意Linux的特殊性,這種方法看起來很好。爲了以防萬一,我會盡量限制Linux特有的代碼範圍。還有一件事要記住,這只是解決方案的一個方面 - 如果性能至關重要,我會建議您使用非阻塞IO與線程與流程進行比較,以查看哪種方法最適合您。關於pipe的好處之一是如果你需要的話,它們可以在fork()中很好地工作。 – Cartroo

+0

有一件事我忘了提及 - 多進程方法似乎是提高當今多核系統性能的顯而易見的方法,但要記住將會有大量的內存共享,因此它不是直截了當的。例如,在[NUMA](http://en.wikipedia.org/wiki/Non-Uniform_Memory_Access)體系結構(例如AMD Opteron)上,經常訪問相同內存的多個內核可以創建性能優勢。即使在[SMP](http://en.wikipedia.org/wiki/Symmetric_multiprocessing)系統上,如果您的瓶頸是內存總線,多進程是否會爲您購買任何東西還不清楚。 – Cartroo

相關問題