2010-04-24 178 views
4

這不是一個問題,說真的,我只是在尋找一些準則:) 我目前正在寫這應該使用的線程低數量,因爲它可以一些抽象的TCP服務器。非阻塞TCP服務器

目前它的工作方式如下。我有一個線程正在監聽和一些工作線程。 Listener線程只是坐等客戶端連接我希望每個服務器實例有一個單一的監聽器線程。工作線程正在客戶端套接字上執行所有讀/寫/處理任務。

所以我的問題是建立有效的工作進程。我遇到了一些我仍然無法解決的問題。工人代碼是類似的東西(代碼是非常簡單的只是爲了顯示一個地方,我有我的問題):

List<Socket> readSockets = new List<Socket>(); 
List<Socket> writeSockets = new List<Socket>(); 
List<Socket> errorSockets = new List<Socket>(); 

while(true){ 
    Socket.Select(readSockets, writeSockets, errorSockets, 10); 

    foreach(readSocket in readSockets){ 
     // do reading here 
    } 

    foreach(writeSocket in writeSockets){ 
     // do writing here 
    } 

    // POINT2 and here's the problem i will describe below 
} 

它的工作原理都smothly接受,因爲while循環是循環的100%的CPU使用率一遍,如果我讓我的客戶端執行send-> receive-> disconnect例程,這並不是那麼痛苦,但是如果我試着保持活動,再次send-> receive-> send->再接收一遍,它確實會吃掉所有的CPU。所以我的第一個想法是在那裏安排睡眠,我檢查是否所有的套接字都有數據發送,然後將Thread.Sleep放在POINT2中,持續10ms,但是這10ms之後,當我想要接收下一個時間時,會產生10ms的巨大延遲命令從客戶端套接字。例如,如果我不試圖「保持活着」命令正在執行10-15毫秒內,並保持活着它變得更糟的至少10毫秒:(

也許這只是一個糟糕的架構?有什麼辦法可以讓我的處理器無法獲得100%的利用率,而我的服務器會盡快對客戶端插座上出現的問題做出反應?也許有人可以指出一個非阻塞服務器和它應該維護的體系結構的一個好例子嗎?

回答

3

先看看TcpListener課程,它有一個BeginAccept方法不會阻止,並且會在有人連接時調用您的某個函數。

而且看一看的Socket類及其Begin方法。這些工作方式相同。每當某個事件觸發時,就會調用其中一個函數(callback function),然後您可以處理該事件。所有的Begin方法都是異步的,所以它們不會被阻塞,它們也不應該使用100%的CPU。基本上你想BeginReceive閱讀和BeginSend寫我相信。

您可以通過搜索這些方法和異步插座教程找到更多關於谷歌。例如,用這種方式實現一個TCP客戶端,例如:Here's how。即使對於您的服務器,它的工作方式也基本相同。

這樣,你不需要任何的無限循環,它的所有事件驅動。

+2

它會產生大量的線程,不是嗎?我使用循環,所以我可以通過讀/寫一個小緩衝區來處理一個線程中的大量套接字。 – hoodoos 2010-04-25 18:38:26

+0

我想它會的。你確定這是一個問題嗎?在任何情況下,你可以發佈你的確切代碼,導致100%的CPU?避免它的一個想法是有一個特殊的「保持活着」的消息,像服務器發送「ping?」的東西。給客戶**每x秒**並期待「pong!」在下一個「平?」之前?應該發送。如果它不來,則假設連接斷開。這樣你就沒有睡覺了。保持活力只是每60秒完成一次,而不是等。例如,您可以使用AutoResetEvent來指示何時應發送保持活動狀態,具體取決於代碼我認爲 – IVlad 2010-04-25 18:55:40

+0

@hoodoos您可以在單個線程上使用TcpListener BeginAccept,在下一次調用BeginAccept()你的AsyncCallback。在聽另一個連接之前,您可以完全處理一個連接。 – khargoosh 2016-08-16 01:23:59

1

您正在創建點對點應用程序還是客戶端服務器應用程序?你必須考慮你通過套接字輸入的數據量。

異步BeginSend和BeginReceive是去,你將需要實現事件的方式,但它的速度快,一旦你得到它的權利。

也許不希望將您的發送和接收超時設置得太高,但應該有一個超時,以便如果在一段時間後沒有收到任何內容,它將從該塊中出來並且您可以處理它那裏。

+0

我正在創建服務器應用程序。最後,我實現了一些抽象工作者,這些工作人員正在分階段地開發抽象協議鏈,並且實際上我設法使其工作得很好。現在我需要讓它處理活着的連接,而不用吃那麼多的CPU。目前它可以處理250個併發連接。我想我會在谷歌上發佈代碼,所以你可以查看它。謝謝。在我的情況下,開始接收/發送將不起作用,因爲我想讓我的應用程序不是線程餓,嘗試處理2個核心CPU上的100個併發客戶端。你會發現它在上下文切換時吃了很多CPU – hoodoos 2010-04-29 19:14:56

0

微軟有一個不錯的異步TCP服務器的例子。它需要一點點圍繞它。在我能夠基於這個例子爲我自己的程序創建基本的TCP框架之前的幾個小時。

http://msdn.microsoft.com/en-us/library/fx6588te.aspx

程序邏輯是有點像這樣。有一個線程調用listener.BeginAccept,然後在allDone.WaitOne上阻塞。 BeginAccept是一個異步調用,它被卸載到線程池並由OS處理。當新連接進來時,操作系統調用從BeginAccept傳入的回調方法。該方法翻轉allDone讓主聽線程知道它可以再次聽。回調方法只是一個過渡方法,並繼續調用另一個異步調用來接收數據。

提供的回調方法ReadCallback是異步調用的主要工作「循環」(有效遞歸異步調用)。我寬泛地使用術語「循環」,因爲每個方法調用實際上完成,但不是在調用下一個異步方法之前。實際上,你有大量的異步調用都會互相調用,並傳遞你的「狀態」對象。這個對象是你自己的對象,你可以隨心所欲地做任何事情。當操作系統調用你的方法

逢回調方法將只能得到兩件事情返回:

1)Socket對象代表的連接與你使用你的邏輯

2)State對象

使用狀態對象和套接字對象,可以有效地異步處理「連接」。操作系統非常擅長。

另外,因爲您的主循環會阻塞等待連接,並通過異步調用將這些連接卸載到線程池,因此它大部分時間都保持空閒狀態。您的套接字的線程池由操作系統通過完成端口進行處理,因此在數據進入之前它們不會執行任何實際工作。使用很少的CPU並且通過線程池進行有效線程化。

P.S.據我所知,你不想用這些方法做任何艱苦的工作,只是處理數據的移動。由於線程池是網絡IO的池,並且被其他程序共享,所以您應該通過線程/任務/異步卸載任何艱苦工作,以免導致套接字線程池陷入停滯狀態。

P.P.S.我沒有找到關閉監聽連接的方法,而不是僅僅處理「監聽者」。由於調用了beginListen的異步調用,所以在連接進入之前,該方法永遠不會返回,這意味着,我無法告訴它停止,直到它返回。我想我會在MSDN上發佈一個關於它的問題。如果我得到了很好的迴應,我會聯繫。

0

一切都很好是你的代碼exept超時值。你將它設置爲10微秒(10 * 10^-6),這樣你的while例程經常迭代。你應該設置和足夠的價值(例如10秒),你的代碼不會吃100%的CPU。

List<Socket> readSockets = new List<Socket>(); 
List<Socket> writeSockets = new List<Socket>(); 
List<Socket> errorSockets = new List<Socket>(); 

while(true){ 
    Socket.Select(readSockets, writeSockets, errorSockets, 10*1000*1000); 

    foreach(readSocket in readSockets){ 
     // do reading here 
    } 

    foreach(writeSocket in writeSockets){ 
     // do writing here 
    } 

    // POINT2 and here's the problem i will describe below 
}