2014-10-19 132 views
0

我想擴展我對.NET框架的體驗,並希望構建客戶端/服務器應用程序。實際上,客戶端/服務器是一個小型的銷售點系統,但首先我要關注服務器和客戶端之間的通信。 將來,我想讓它成爲一個WPF應用程序,但現在,我只是從一個控制檯應用程序開始。C#實現TCP客戶端服務器應用程序的最佳方式

2功能:

  • 客戶機接收(S)的數據集,每15/30分鐘的具有改變價格/新產品
    (因此,代碼將是一個異步方法與更新一個Thread.sleep 15/30分鐘)。關閉客戶端應用程序時,送樣的報告

  • (例如,XML)

在互聯網上,我發現很多的例子,但我不能決定哪一個是最好/最安全/高效的工作方式,所以我需要一些建議,我應該實施哪些技術。

CLIENT/SERVER

我想,處理最多6個1個客戶服務器應用程序。我讀過線程使用了很多MB,也許更好的方法是使用異步/等待功能的任務。

用ASYNC實施例/ AWAIT

http://bsmadhu.wordpress.com/2012/09/29/simplify-asynchronous-programming-with-c-5-asyncawait/

實施例有螺紋

mikeadev.net/2012/07/multi-threaded-tcp-server-in-csharp/

舉例與SOCKETS

codereview.stackexchange.com/questions/5306/tcp-socket-server

這似乎是插座的一個很好的例子,然而,revisioned代碼並非完全因爲不是所有的類都包含 MSDN工作.microsoft.com/en-us/library/fx6588te(v = vs.110).aspx MSDN的這個例子有更多的Buffersize和一個消息結尾的信號。我不知道這是否是一種「老辦法」,因爲在我之前的例子中,他們只是從客戶端向服務器發送一個字符串,就是這樣。

.NET Framework遠程處理/ WCF 我也發現了一些關於.NET和WCF但不要對遠程訪問的一部分」知道我是否需要實現這一點,因爲我覺得與異步/等待的例子也不錯。

序列化對象/ DATASET/XML

什麼是它之間發送數據的最佳方式?求一個XML序列化器或只是二進制?

實施例與數據集 - > XML

stackoverflow.com/questions/8384014/convert-dataset-to-xml

實施例與遠程處理

akadia.com/services/ dotnet_dataset_remoting.html

如果我應該使用Async/Await方法,在伺服應用程序中這樣做是否正確:

 while(true) 
     { 
      string input = Console.ReadLine(); 
      if(input == "products") 
       SendProductToClients(port); 
      if(input == "rapport") 
      { 
       string Example = Console.ReadLine(); 
      }         
     } 
+1

這是TCP練習嗎?如果沒有,請使用更高級別的協議,例如Web服務或HTTP。 TCP編程非常困難。 – usr 2014-10-19 13:34:52

+0

我只是想以有效的方式將數據從服務器發送到客戶端。 – PJDW 2014-10-19 15:12:39

+1

首先,這看起來像兩個完全不同的問題:「使用哪個網絡API?」和「如何序列化數據?」他們是相關的,但不是真正相同的問題。其次,在現代C#程序中,我將使用第一個選項:使用TcpClient/TcpListener進行異步/等待。你可能會也可能不想精確地遵循這個例子,但它似乎是一個很好的起點。更重要的一點是,異步/等待提供了一個非常乾淨和簡單的方法來處理這種確切的情況。絕對不要使用線程示例;這是效率最低,可擴展性最低的方法。 – 2014-10-20 00:59:49

回答

0

線程是不貴的要命,考慮到在現代系統可用內存量,所以我不認爲這是有幫助的,以優化低線程數。特別是如果我們正在討論1線程和2-5線程的區別。 (使用數百或數千個線程,線程的開銷就顯得很重要。)

但是您確實希望優化最小化任何線程的阻塞。因此,例如,不要使用Thread.Sleep以15分鐘的間隔完成工作,只需設置一個計時器,讓線程返回並在15分鐘後相信系統調用您的代碼。而不是阻止通過網絡讀取或寫入信息的操作,請使用非阻塞操作。

async/await模式是.Net上異步編程的新熱點,它比起.NET 1.0的Begin/End模式有了很大的改進。用async/await編寫的代碼仍在使用線程,它只是使用C#和.Net的特性來隱藏線程的複雜性 - 而且大多數情況下,它隱藏了應該隱藏的東西,所以您可以將注意力集中在應用程序的功能上,而不是多線程編程的細節。

因此,我的建議是對所有IO(網絡和磁盤)使用異步/等待方法,並使用計時器進行週期性瑣事,例如發送您提到的更新。

而關於系列化...

一個XML在二進制格式最大的優點是,您可以將XML的傳輸保存到磁盤,並利用現成可用的工具,以確認有效載荷的確不包含開起來你認爲的數據會在那裏。所以我傾向於避免使用二進制格式,除非帶寬稀少 - 即使如此,使用XML等文本友好格式開發大多數應用程序也是有用的,然後在發送和接收數據的基本機制已經完成後切換到二進制文件出。

所以我的投票是XML。

和關於你的代碼示例,以及療法沒有異步/在它等待...

但首先,請注意,一個典型的簡單的TCP服務器將有一個小的循環偵聽傳入的連接,並啓動一個線程來掌握每個新的連接。然後,連接線程的代碼將監聽傳入的數據,處理它併發送適當的響應。所以listen-for-new-connections代碼和handle-a-single-connection代碼是完全分離的。

所以無論如何,連接線程的代碼可能類似於你寫的,但不是隻是打電話的ReadLine你會做這樣的事「串線=等待的ReadLine();」 await關鍵字大約是代碼允許一個線程退出(在調用ReadLine之後)的地方,然後在另一個線程上恢復(當ReadLine的結果可用時)。除了等待方法應該有一個以Async結尾的名稱,例如ReadLineAsync。從網絡中讀取一行文本並不是一個壞主意,但是您必須自己編寫ReadLineAsync,並基於現有的網絡API。

我希望這會有所幫助。

1

這裏有幾件事情的人寫的客戶端/服務器應用程序應考慮:

  • 應用層報文可以跨越多個TCP數據包。
  • 多個應用層分組可以被包含在單個TCP數據包內。
  • 加密。
  • 認證。
  • 失落和反應遲鈍的客戶。
  • 數據序列化格式。
  • 基於線程或異步套接字閱讀器。

檢索正確的數據包需要在你的數據封裝協議。協議可以非常簡單。例如,它可能與指定有效負載長度的整數一樣簡單。我在下面提供的代碼片段直接來自GitHub上提供的開源客戶端/服務器應用程序框架項目DotNetOpenServer。注意:此代碼是由客戶端和服務器:

private byte[] buffer = new byte[8192]; 
private int payloadLength; 
private int payloadPosition; 
private MemoryStream packet = new MemoryStream(); 
private PacketReadTypes readState; 
private Stream stream; 

private void ReadCallback(IAsyncResult ar) 
{ 
    try 
    { 
     int available = stream.EndRead(ar); 
     int position = 0; 

     while (available > 0) 
     { 
      int lengthToRead; 
      if (readState == PacketReadTypes.Header) 
      { 
       lengthToRead = (int)packet.Position + available >= SessionLayerProtocol.HEADER_LENGTH ? 
         SessionLayerProtocol.HEADER_LENGTH - (int)packet.Position : 
         available; 

       packet.Write(buffer, position, lengthToRead); 
       position += lengthToRead; 
       available -= lengthToRead; 

       if (packet.Position >= SessionLayerProtocol.HEADER_LENGTH) 
        readState = PacketReadTypes.HeaderComplete; 
      } 

      if (readState == PacketReadTypes.HeaderComplete) 
      { 
       packet.Seek(0, SeekOrigin.Begin); 
       BinaryReader br = new BinaryReader(packet, Encoding.UTF8); 

       ushort protocolId = br.ReadUInt16(); 
       if (protocolId != SessionLayerProtocol.PROTOCAL_IDENTIFIER) 
        throw new Exception(ErrorTypes.INVALID_PROTOCOL); 

       payloadLength = br.ReadInt32(); 
       readState = PacketReadTypes.Payload; 
      } 

      if (readState == PacketReadTypes.Payload) 
      { 
       lengthToRead = available >= payloadLength - payloadPosition ? 
        payloadLength - payloadPosition : 
        available; 

       packet.Write(buffer, position, lengthToRead); 
       position += lengthToRead; 
       available -= lengthToRead; 
       payloadPosition += lengthToRead; 

       if (packet.Position >= SessionLayerProtocol.HEADER_LENGTH + payloadLength) 
       { 
        if (Logger.LogPackets) 
         Log(Level.Debug, "RECV: " + ToHexString(packet.ToArray(), 0, (int)packet.Length)); 

        MemoryStream handlerMS = new MemoryStream(packet.ToArray()); 
        handlerMS.Seek(SessionLayerProtocol.HEADER_LENGTH, SeekOrigin.Begin); 
        BinaryReader br = new BinaryReader(handlerMS, Encoding.UTF8); 

        if (!ThreadPool.QueueUserWorkItem(OnPacketReceivedThreadPoolCallback, br)) 
         throw new Exception(ErrorTypes.NO_MORE_THREADS_AVAILABLE); 

        Reset(); 
       } 
      } 
     } 

     stream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(ReadCallback), null); 
    } 
    catch (ObjectDisposedException) 
    { 
     Close(); 
    } 
    catch (Exception ex) 
    { 
     ConnectionLost(ex); 
    } 
} 


private void Reset() 
{ 
    readState = PacketReadTypes.Header; 
    packet = new MemoryStream(); 
    payloadLength = 0; 
    payloadPosition = 0; 
} 

如果你發送的銷售信息來看,它應該被加密。我建議通過.Net輕鬆啓用TLS。代碼非常簡單,並且有很多樣本,所以爲了簡潔起見,我不打算在這裏展示它。如果你有興趣,你可以在DotNetOpenServer中找到一個示例實現。

所有連接進行身份驗證。有很多方法可以做到這一點。我使用Windows身份驗證(NTLM)以及Basic。儘管NTLM功能強大且自動化,但僅限於特定的平臺。基本身份驗證只是在套接字加密後傳遞用戶名和密碼。然而,基本認證仍然可以;根據本地服務器或域控制器驗證用戶名/密碼組合,實質上模擬NTLM。後一種方法使開發人員能夠輕鬆創建在iOS,Mac,Unix/Linux和Java平臺上運行的非Windows客戶端應用程序(儘管一些Java實現支持NTLM)。您的服務器實現應該永遠不會允許應用程序數據傳輸,直到會話已通過身份驗證。

我們只能依靠幾件事:稅收,網絡故障和客戶端應用程序掛起。這只是事物的本質。您的服務器應該實施一種方法來清理丟失和掛起的客戶端會話。我通過保活(AKA心跳)協議在許多客戶端/服務器框架中完成了這項工作。在服務器端,我實現了一個定時器,每次客戶端發送數據包時都會重置該數據包。如果服務器在超時時間內沒有收到數據包,會話將關閉。當其他應用層協議空閒時,keep-alive協議用於發送數據包。由於您的應用程序每隔15分鐘只發送一次XML,每隔一分鐘發送一次保持活動數據包,因此服務器端在15分鐘時間間隔之前丟失連接時可以向管理員發出警報,可能會使IT部門解決網絡問題更及時。

接下來是數據格式。在你的情況下,XML很棒。 XML使您可以隨時更改有效負載,只要您想要。如果你真的需要速度,那麼二進制將永遠勝過字符串表示數據的膨脹本質。

最後,正如@ NSFW已經指出的,線程或異步在你的情況下並不重要。我已經編寫了基於線程和異步回調擴展到10000個連接的服務器。當涉及到它時,它們都是一樣的。正如@NSFW所說,我們大多數人現在都在使用異步回調,而且我寫的最新服務器實現也遵循這個模型。

相關問題