我正在创建一个简单的服务器来侦听客户端,这些客户端将读取客户端请求,进行一些计算,将响应发送回客户端并再次关闭ASAP(有点类似于HTTP)。
每秒可能有很多连接,所以我希望它尽可能快速有效。
到目前为止,我能想到这样做的最佳方式,如下图所示:
private static ManualResetEvent gate = new ManualResetEvent(false);
static async void ListenToClient(TcpListener listener)
{
Console.WriteLine("Waiting for connection");
TcpClient client = await listener.AcceptTcpClientAsync();
Console.WriteLine("Connection accepted & establised");
gate.Set(); //Unblocks the mainthread
Stream stream = client.GetStream();
byte[] requestBuffer = new byte[1024];
int size = await stream.ReadAsync(requestBuffer, 0, requestBuffer.Length);
//PSEUDO CODE: Do some calculations
byte[] responseBuffer = Encoding.ASCII.GetBytes("Ok");
await stream.WriteAsync(responseBuffer, 0, responseBuffer.Length);
stream.Close();
client.Close();
}
static void Main(string[] args)
{
TcpListener listener = new TcpListener(IPAddress.Any, 8888);
listener.Start();
while (true)
{
gate.Reset();
ListenToClient(listener);
gate.WaitOne(); //Blocks the main thread and waits until the gate.Set() is called
}
}
注意:对于这个示例和简单性,我没有像try-catch那样进行任何错误处理,我知道这里的响应总是“Ok”
这里的代码只是等待连接,当它到达await listener.AcceptTcpClientAsync()
时,它会跳回到while循环并等待直到建立连接并且调用gate.Set()以便它可以监听新的联系再次。因此,这将同时允许多个客户端(特别是如果计算可能需要很长时间)
但是我应该使用stream.ReadAsync()还是stream.Read()?我很好奇,如果它甚至重要,因为我已经在一个不会阻止主线程的异步函数。
所以我最后的问题是:
更新新的改进
由于答案,我已将代码更新为:
private static ManualResetEvent gate = new ManualResetEvent(false);
static async Task ListenToClient(TcpListener listener)
{
//Same Code
}
static void Main(string[] args)
{
TcpListener listener = new TcpListener(IPAddress.Any, 8888);
listener.Start();
while (true)
{
gate.Reset();
Task task = ListenToClient(listener);
task.ContinueWith((Task paramTask) =>
{
//Inspect the paramTask
});
gate.WaitOne(); //Blocks the main thread and waits until the gate.Set() is called
}
}
答案 0 :(得分:3)
马上我发现了两个常见的async
错误:
async void
不要这样做。编译器甚至支持 async void
的唯一原因是处理现有的事件驱动接口。这不是其中之一,所以这里是反模式。 async void
实际上会导致失去任何响应该任务或对其执行任何操作的方式,例如处理错误。
说到回应任务......
ListenToClient(listener);
你正在产生一个任务,但从未检查过它的状态。如果在该任务中有异常,你会怎么做?无论如何它都没有被捕获,它只会被默默地忽略。至少,您应该在任务完成后为该任务提供顶级回调。甚至像这样简单:
ListenToClient(listener).ContinueWith(t =>
{
// t is the task. Examine it for errors, cancelations, etc.
// Respond to error conditions here.
});
答案 1 :(得分:2)
这是完成此任务的最佳/正确方法(也是使用ManualResetEvent类)吗?
没有。您启动异步操作,然后立即等待它。出于某种原因,我经常看到这种疯狂的舞蹈。让它同步:
while (true) {
var clientSocket = Accept();
ProcessClientAsync(clientSocket);
}
这么简单。
在这种情况下,在读取和写入流时使用异步或非异步操作会有什么不同吗?
如果您有很多客户端,那么在套接字上使用异步IO很有用。对于几十个,您可以只使用带线程的同步IO。 Async IO不会阻塞线程(每个线程使用1MB的堆栈空间)。
如果您决定使用异步IO,ProcessClientAsync
应该是您现在拥有的异步功能。
如果您决定使用同步IO,请在新线程上启动ProcessClientAsync
,以便能够同时处理多个客户端。
如果它滞后,发送/接收数据需要1-2秒,那么在异步和非同步操作之间进行选择仍然很重要吗?
只要您独立处理个人客户,您就可以了。同步和异步之间的选择只能在高规模发挥作用(同时打开多个连接)。
通过在不需要的情况下执行async来使事情过于复杂是一个常见的错误。基本上所有教程都会犯这个错误。