2014-11-13 27 views
2

[我限制在Visual Studio 2010中,因此,C#4異步和等待都沒有提供給我。]套接字在C#中,我怎麼能異步通過的NetworkStream讀取和寫入數據

我爲我的項目開發網絡體系結構,它通過網絡在服務器和客戶端之間發送數據包,但客戶端和服務器必須在等待時繼續運行,所以代碼必須是非阻塞的,所以我想使用異步方法。但是,除了簡單的同步一次IO之外,我不太瞭解如何操作,特別是在使用NetworkStream時。我正在試圖做的是:

1)客戶端連接到服務器

2)服務器接受連接

3)數據服務器等待從客戶

4)服務器處理數據

5)服務器響應客戶端

6)當連接是開放的,從3

重複

我想使用NetworkStream來封裝套接字。但是我對異步I/O很陌生,我不確定如何在等待響應時阻止服務器/客戶端代碼的其他部分,尤其是使用NetworkStream時。在我的研究,我發現使用類似這樣的例子:

while(true){ 
    socket.BeginAccept(new AsyncCallback(AcceptCallback), socket); 
} 

但好像這個循環仍然會撐起這個應用程序。任何人都可以給我一些指示(哈)如何做到這一點?我一直無法找到許多保持連接打開的例子,只有Client Connect - > Client Send - > Server Recieve - > Server Send - > Disconnect。我並沒有要求完整的代碼,只是一些小片段的總體思路。

+0

[NetworkStream.BeginRead方法](http://msdn.microsoft.com/en-us/library/system.net.sockets.networkstream.beginread(v = vs.110).aspx)你檢查了這一點..? – MethodMan

+0

@DJKRAZE是的,我有。我知道我需要使用異步結果,以便我可以在不阻塞的情況下處理信息。但我無法弄清楚如何在沒有阻塞的情況下等待信息。 – AlphaModder

+0

你需要查看'Task await',因爲你正在做這個異步你可以顯示完整的方法簽名以及... – MethodMan

回答

3

下面是使用異步的一個非常簡單的例子/等待與NetworkStream

SocketServer.cs:

class SocketServer 
{ 
    private readonly Socket _listen; 

    public SocketServer(int port) 
    { 
     IPEndPoint listenEndPoint = new IPEndPoint(IPAddress.Loopback, port); 
     _listen = new Socket(SocketType.Stream, ProtocolType.Tcp); 
     _listen.Bind(listenEndPoint); 
     _listen.Listen(1); 
     _listen.BeginAccept(_Accept, null); 
    } 

    public void Stop() 
    { 
     _listen.Close(); 
    } 

    private async void _Accept(IAsyncResult result) 
    { 
     try 
     { 
      using (Socket client = _listen.EndAccept(result)) 
      using (NetworkStream stream = new NetworkStream(client)) 
      using (StreamReader reader = new StreamReader(stream)) 
      using (StreamWriter writer = new StreamWriter(stream)) 
      { 
       Console.WriteLine("SERVER: accepted new client"); 

       string text; 

       while ((text = await reader.ReadLineAsync()) != null) 
       { 
        Console.WriteLine("SERVER: received \"" + text + "\""); 
        writer.WriteLine(text); 
        writer.Flush(); 
       } 
      } 

      Console.WriteLine("SERVER: end-of-stream"); 

      // Don't accept a new client until the previous one is done 
      _listen.BeginAccept(_Accept, null); 
     } 
     catch (ObjectDisposedException) 
     { 
      Console.WriteLine("SERVER: server was closed"); 
     } 
     catch (SocketException e) 
     { 
      Console.WriteLine("SERVER: Exception: " + e); 
     } 
    } 
} 

方案。cs:

class Program 
{ 
    private const int _kport = 54321; 

    static void Main(string[] args) 
    { 
     SocketServer server = new SocketServer(_kport); 
     Socket remote = new Socket(SocketType.Stream, ProtocolType.Tcp); 
     IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Loopback, _kport); 

     remote.Connect(remoteEndPoint); 

     using (NetworkStream stream = new NetworkStream(remote)) 
     using (StreamReader reader = new StreamReader(stream)) 
     using (StreamWriter writer = new StreamWriter(stream)) 
     { 
      Task receiveTask = _Receive(reader); 
      string text; 

      Console.WriteLine("CLIENT: connected. Enter text to send..."); 

      while ((text = Console.ReadLine()) != "") 
      { 
       writer.WriteLine(text); 
       writer.Flush(); 
      } 

      remote.Shutdown(SocketShutdown.Send); 
      receiveTask.Wait(); 
     } 

     server.Stop(); 
    } 

    private static async Task _Receive(StreamReader reader) 
    { 
     string receiveText; 

     while ((receiveText = await reader.ReadLineAsync()) != null) 
     { 
      Console.WriteLine("CLIENT: received \"" + receiveText + "\""); 
     } 

     Console.WriteLine("CLIENT: end-of-stream"); 
    } 
} 

這是一個非常簡單的例子,在同一個進程中託管服務器和客戶端,一次只接受一個連接。這僅僅是爲了說明的目的。無疑,真實世界的場景無疑會包含其他功能以滿足他們的需求。

在這裏,我將NetworkStream s包裝在StreamReader s和StreamWriter s中。請注意,您必須致電Flush()以確保實際發送數據。爲了更好地控制I/O,您當然可以直接使用NetworkStream。只需使用Stream.ReadAsync()方法而不是StreamReader.ReadLineAsync()。還要注意,在我的例子中,寫入是同步的。如果你喜歡,你也可以使用與讀取所示相同的基本技術。

編輯:

的OP表示,他們無法使用async/await。下面是使用NetworkStream和舊式Begin/EndXXX() API客戶端的版本(類似的變化將作出當然服務器):

using System; 
using System.IO; 
using System.Net; 
using System.Net.Sockets; 
using System.Text; 
using System.Threading; 

namespace TestOldSchoolNetworkStream 
{ 
    class Program 
    { 
     private const int _kport = 54321; 

     static void Main(string[] args) 
     { 
      SocketServer server = new SocketServer(_kport); 
      Socket remote = new Socket(SocketType.Stream, ProtocolType.Tcp); 
      IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Loopback, _kport); 

      remote.Connect(remoteEndPoint); 

      using (NetworkStream stream = new NetworkStream(remote)) 
      { 
       // For convenience, These variables are local and captured by the 
       // anonymous method callback. A less-primitive implementation would 
       // encapsulate the client state in a separate class, where these objects 
       // would be kept. The instance of this object would be then passed to the 
       // completion callback, or the receive method itself would contain the 
       // completion callback itself. 
       ManualResetEvent receiveMonitor = new ManualResetEvent(false); 
       byte[] rgbReceive = new byte[8192]; 
       char[] rgch = new char[Encoding.UTF8.GetMaxCharCount(rgbReceive.Length)]; 
       Decoder decoder = Encoding.UTF8.GetDecoder(); 
       StringBuilder receiveBuffer = new StringBuilder(); 

       stream.BeginRead(rgbReceive, 0, rgbReceive.Length, result => 
       { 
        _Receive(stream, rgbReceive, rgch, decoder, receiveBuffer, receiveMonitor, result); 
       }, null); 

       string text; 

       Console.WriteLine("CLIENT: connected. Enter text to send..."); 

       while ((text = Console.ReadLine()) != "") 
       { 
        byte[] rgbSend = Encoding.UTF8.GetBytes(text + Environment.NewLine); 

        remote.BeginSend(rgbSend, 0, rgbSend.Length, SocketFlags.None, _Send, Tuple.Create(remote, rgbSend.Length)); 
       } 

       remote.Shutdown(SocketShutdown.Send); 
       receiveMonitor.WaitOne(); 
      } 

      server.Stop(); 
     } 

     private static void _Receive(NetworkStream stream, byte[] rgb, char[] rgch, Decoder decoder, StringBuilder receiveBuffer, EventWaitHandle monitor, IAsyncResult result) 
     { 
      try 
      { 
       int byteCount = stream.EndRead(result); 
       string fullLine = null; 

       if (byteCount > 0) 
       { 
        int charCount = decoder.GetChars(rgb, 0, byteCount, rgch, 0); 

        receiveBuffer.Append(rgch, 0, charCount); 

        int newLineIndex = IndexOf(receiveBuffer, Environment.NewLine); 

        if (newLineIndex >= 0) 
        { 
         fullLine = receiveBuffer.ToString(0, newLineIndex); 
         receiveBuffer.Remove(0, newLineIndex + Environment.NewLine.Length); 
        } 

        stream.BeginRead(rgb, 0, rgb.Length, result1 => 
        { 
         _Receive(stream, rgb, rgch, decoder, receiveBuffer, monitor, result1); 
        }, null); 
       } 
       else 
       { 
        Console.WriteLine("CLIENT: end-of-stream"); 
        fullLine = receiveBuffer.ToString(); 
        monitor.Set(); 
       } 

       if (!string.IsNullOrEmpty(fullLine)) 
       { 
        Console.WriteLine("CLIENT: received \"" + fullLine + "\""); 
       } 
      } 
      catch (IOException e) 
      { 
       Console.WriteLine("CLIENT: Exception: " + e); 
      } 
     } 

     private static int IndexOf(StringBuilder sb, string text) 
     { 
      for (int i = 0; i < sb.Length - text.Length + 1; i++) 
      { 
       bool match = true; 

       for (int j = 0; j < text.Length; j++) 
       { 
        if (sb[i + j] != text[j]) 
        { 
         match = false; 
         break; 
        } 
       } 

       if (match) 
       { 
        return i; 
       } 
      } 

      return -1; 
     } 

     private static void _Send(IAsyncResult result) 
     { 
      try 
      { 
       Tuple<Socket, int> state = (Tuple<Socket, int>)result.AsyncState; 
       int actualLength = state.Item1.EndSend(result); 

       if (state.Item2 != actualLength) 
       { 
        // Should never happen...the async operation should not complete until 
        // the full buffer has been successfully sent, 
        Console.WriteLine("CLIENT: send completed with only partial success"); 
       } 
      } 
      catch (IOException e) 
      { 
       Console.WriteLine("CLIENT: Exception: " + e); 
      } 
     } 
    } 
} 

注意,此代碼,甚至不顧留下了一堆的的異常處理邏輯相當長,至少部分原因是由於TextReader沒有內置的異步API,所以輸入數據的處理在這裏更加冗長。當然,這是一個簡單的基於行的文本交換協議。其他協議在數據解包方面可能或多或少是複雜的,但NetworkStream的底層讀寫元素將是相同的。

+0

就像這個答案一樣好,我僅限於VS2010,結果,C#4。所以我不能使用異步或等待。我會將其添加到我的問題。 – AlphaModder

+0

它只提供一個連接嗎? – gabba

+0

是的......這個例子一次只允許一個連接。但是,這只是爲了讓示例更簡單。很容易改變這個例子來支持多個連接:只需將'SocketServer._Accept()'中的'BeginAccept()'調用移到從當前套接字讀取循環之前。然後它將同時允許多個連接。 –

0

這是很好的例子示出在C#實現異步通信的總體思路

異步客戶端套接字示例: http://msdn.microsoft.com/en-us/library/bew39x2a(v=vs.110).aspx

異步服務器套接字示例: http://msdn.microsoft.com/en-us/library/fx6588te%28v=vs.110%29.aspx

在服務器例如結合碼插座。並開始接受客戶。當某個客戶端連接時,提供給BeginAccept的回調調用。在接受回調中,您可以管理客戶端套接字並開始讀取或寫入。在接受回調結束時,它發出allDone事件並且主循環開始接受新的客戶端。

採取注意:

public static ManualResetEvent allDone = new ManualResetEvent(false); 

這有助於在不浪費迴路CPU。

+0

關心評論? – gabba

+3

最有可能的,因爲你只是放在一些鏈接。將鏈接僅作爲註釋,將全功能,自成一體的解釋作爲答案。 –

+0

某些鏈接可能有用 – gabba