[我仅限于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 - >客户端发送 - > Server Recieve - >服务器发送 - >断开。我不是要求提供完整的代码,只是提出几个片段的一般想法。
答案 0 :(得分:3)
以下是使用as {/ 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");
}
}
这是一个非常简单的示例,在同一进程中托管服务器和客户端,并且一次只接受一个连接。这只是为了说明目的。毫无疑问,真实场景将包含其他功能以满足他们的需求。
在这里,我将NetworkStream
包裹在StreamReader
和StreamWriter
中。请注意,您必须调用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
的基础读写元素将是相同的。
答案 1 :(得分: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
服务器示例中的代码绑定到socket。并开始接受客户。当某个客户端连接时,提供给BeginAccept的回调被调用。在接受回调中,您可以管理客户端套接字并开始读取或写入。在接受回调结束时,它发出allDone事件的信号,主循环开始接受新的客户端。
注意:
public static ManualResetEvent allDone = new ManualResetEvent(false);
这有助于不浪费cpu循环。