2014-01-19 100 views
1

我已經寫了一個服務器,它打開一個命名管道(阻塞,等到客戶端連接),然後定期寫入管道。客戶端打開管道,從中讀取並處理數據。然而,由於我無法控制的情況,客戶經常退出並在此後不久再次重啓。阻塞,直到讀取器連接到命名管道

當服務器想要在沒有閱讀器連接到管道的情況下在短時間間隔內寫入內容時會導致問題:服務器收到SIGPIPE並退出。我可以忽略這個信號,但我不想丟失數據:理想情況下,服務器將等待客戶端重新連接到管道,然後再寫入數據。在寫入期間服務器阻塞沒有問題。

使用write()我可以嘗試一個0字節的寫入並檢查EPIPE錯誤來檢測是否有客戶端連接。但是,如何才能阻塞,直到客戶端連接(除了休眠一下,並再次嘗試寫入)?

或者還有其他更好的方法來實現這個目標嗎?

+0

關閉並重新打開管道是否實用? – rici

+0

你試過[民意調查(2)](http://man7.org/linux/man-pages/man2/poll.2.html)嗎? –

+0

@BasileStarynkevitch:即使使用「poll()」,管道可能準備好寫入,然後,在寫入之前,讀者將死亡:數據丟失! – rodrigo

回答

2

事實證明,與我對上述原始問題的評論相反,有一個直接的解決方案。

該解決方案假定所有讀取器和所有同一FIFO的寫入器共享內核緩衝區。這應該是實現FIFO的最合理,最直接的方式(考慮它們的行爲),所以我確實希望提供FIFO的所有系統都以這種方式運行。但是,這只是我的假設,不是任何保證。我還沒有在相關的POSIX標準中發現任何支持或抵觸這一點的內容。如果您發現其他情況,請注意。

的過程很簡單:

當一個客戶端意外消失,筆者再次打開的FIFO,不首先關閉原來的描述符。這個open()將會阻塞,直到有新的讀取器可用,但由於原始文件描述符仍然是打開的,已經在FIFO中緩衝的數據將可供新讀取器使用。如果open()成功,那麼作者只需關閉原始描述符,然後切換爲使用新描述符。

如果內核結構是共享的,FIFO緩衝區狀態將在寫入程序描述符之間共享,新讀取程序將能夠讀取以前讀取程序未讀的內容。

(請注意,筆者不知道客戶的變化之間的緩衝的數據量,因此是不知道在交換機發生的數據流中的點。)

我已經驗證了這個簡單的策略,在Linux上工作Ubuntu中x86_64上的3.8.0-35-通用內核以及x86_64上的2.6.9-104.ELsmp。


然而,我仍然完全要麼接受數據丟失,或改變協議達成一致,如通過巴西萊Starynkevitch在原來的問題評論建議。個人而言,我發現Unix域套接字(綁定到路徑名,例如/var/run/yourservice/unix)是一個更好的選擇,因爲它允許多個併發客戶端沒有數據損壞(與FIFO不同)以及更加安全的協議。

我更喜歡Unix數據報套接字,每個數據報開始時的序列號和數據報長度。(這個長度有助於客戶端驗證它讀取整個數據報;我真的不希望任何操作系統截斷Unix數據報。)

通常,寫入程序向每個客戶端發送一些數據報,並在發送新消息之前等待確認那些。處理完數據報後,客戶端通過發送序列號給寫入器來確認數據報。 (請記住,這些是套接字,所以通信是雙向的。)這允許編寫者爲每個客戶端保留一些數據報,並且客戶端以正確的(序號)順序處理數據報或者亂序,使用多個線程。

重要的一點是每個數據報只有在它被處理後才被確認,而不是在它被接收後立即被確認。 (除了reader-> writer「acks」(確認),我還會支持「請重新發送」響應,以防客戶端使用太小的緩衝區來接收數據報。或者甚至可能是客戶不知道該怎麼處理的數據報「yuck」)

如果客戶端消失,作者知道所有未確認的數據報未被客戶端處理然而,並且可以將它們重新發送給另一個連接的客戶端或未來的客戶端。

有問題?

+0

謝謝。我認爲你是對的,所以經過一些考慮後,我選擇實現一個基於套接字的協議(這很好用)。 – Denvercoder9

+0

你提到多個客戶端的FIFO腐敗。你有沒有提及更多的信息? – lane

+0

@lane:我不記得(或者在重新閱讀我上面的答案後看到)與*腐敗*有關的任何連接,只是爲了數據丟失。正如我上面所解釋的那樣,我自己通過實驗驗證了這個解決方案(在檢查了命名管道的Linux內核實現之後),所以我不確定對您的意思或感興趣的更多信息有什麼樣的參考。 –