2014-11-13 28 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