C#中的套接字,如何通过NetworkStream异步读写数据

[我仅限于Visual Studio 2010,因此,我不能使用C#4 async和await。]

我正在研究我的项目的网络架构,它通过网络在服务器和客户端之间发送数据包,但客户端和服务器必须在等待时继续运行,因此代码必须是非阻塞的,所以我想过要使用异步方法。 但是,除了简单的同步一次性IO,我不知道该怎么做,特别是在使用NetworkStream时。 我想要做的是:

1)客户端连接到服务器

2)服务器接受连接

3)服务器等待来自客户端的数据

4)服务器处理数据

5)服务器响应客户端

6)连接打开时,从3开始重复。

我想使用NetworkStream来包装套接字。 但我是异步I / O的新手,我不确定在等待响应时如何在不阻塞服务器/客户端代码的其他部分的情况下执行此操作,尤其是对于NetworkStream。 在我的研究中,我看到使用类似这样的例子:

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

但似乎循环仍然会阻碍应用程序。 谁能给我一些关于如何做到这一点的指针(ha)? 我无法找到许多保持连接打开的示例,只有Client Connect – > Client Send – > Server Recieve – > Server Send – > Disconnect。 我不是要求完整的代码,只是提供一些片段的一般想法。

以下是使用async / await和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); } } } 

Program.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"); } } 

这是一个非常简单的示例,在同一进程中托管服务器和客户端,并且一次只接受一个连接。 这只是为了说明目的。 毫无疑问,真实场景将包含其他function以满足他们的需求。

在这里,我将NetworkStreamStreamReaderStreamWriter 。 请注意,您必须调用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 state = (Tuple)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); } } } } 

请注意,即使不考虑一堆exception处理逻辑,这段代码也要长得多,至少部分原因是TextReader没有内置的异步API,因此输入数据的处理是这里更加冗长。 当然,这是一个简单的基于行的文本交换协议。 就数据解包方面而言,其他协议可能或多或少复杂,但NetworkStream的基础读取和写入元素将是相同的。

这是一个很好的例子,展示了在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循环。