我一直在考虑使用异步编程设置TCP服务器的正确方法。
通常我会为每个传入的请求生成一个线程,但是我想完成ThreadPool的大部分操作,所以当连接空闲时,没有被阻塞的线程。
首先,我将创建监听器并开始接受客户端,在本例中,是在控制台应用程序中:
static void Main(string[] args)
{
CancellationTokenSource cancellation = new CancellationTokenSource();
var endpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8001);
TcpListener server = new TcpListener(endpoint);
server.Start();
var task = AcceptTcpClients(server, cancellation.Token);
Console.ReadKey(true);
cancellation.Cancel();
await task;
Console.ReadKey(true);
}
在该方法中,我将循环接受传入请求并生成一个新任务来处理连接,因此循环可以返回以接受更多客户端:
static async Task AcceptTcpClients(TcpListener server, CancellationToken token)
{
while (!token.IsCancellationRequested)
{
var ws = await server.AcceptTcpClientAsync();
Task.Factory.StartNew(async () =>
{
while (ws.IsConnected && !token.IsCancellationRequested)
{
String msg = await ws.ReadAsync();
if (msg != null)
await ws.WriteAsync(ProcessResponse(msg));
}
}, token);
}
}
创建新任务并不一定意味着新线程,但这是正确的方法吗?我是否正在利用ThreadPool或者我还能做什么呢?
这种方法有潜在的缺陷吗?
答案 0 :(得分:17)
await task;
中的Main
将无法编译;如果你想阻止它,你将不得不使用task.Wait();
。
此外,您应该在异步编程中使用Task.Run
而不是Task.Factory.StartNew
。
创建新任务并不一定意味着新线程,但这是正确的方法吗?
您当然可以启动单独的任务(使用Task.Run
)。虽然你没有拥有。您可以轻松地调用单独的async
方法来处理各个套接字连接。
但实际的套接字处理存在一些问题。 Connected
属性几乎没用。即使在写入时,也应始终连续读取已连接的套接字。此外,您应该编写“keepalive”消息或读取超时,以便您可以检测半开放情况。我保留了TCP/IP .NET FAQ来解释这些常见问题。
我真的,强烈建议人们不编写TCP / IP服务器或客户端。有吨的陷阱。如果可能的话,自我托管WebAPI和/或SignalR会好得多。
答案 1 :(得分:3)
为了优雅地停止服务器接受循环,我注册了一个在取消cancelToken(cancellationToken.Register(listener.Stop);
)时停止监听的回调。
这将在await listener.AcceptTcpClientAsync();
上抛出一个容易捕获的ObjectDisposedException。
不需要Task.Run(HandleClient()),因为调用异步方法会返回并行运行的任务。
public async Task Run(CancellationToken cancellationToken)
{
TcpListener listener = new TcpListener(address, port);
listener.Start();
cancellationToken.Register(listener.Stop);
while (!cancellationToken.IsCancellationRequested)
{
try
{
TcpClient client = await listener.AcceptTcpClientAsync();
var clientTask = protocol.HandleClient(client, cancellationToken)
.ContinueWith((antecedent) => client.Dispose())
.ContinueWith((antecedent)=> logger.LogInformation("Client disposed."));
}
catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested)
{
logger.LogInformation("TcpListener stopped listening because cancellation was requested.");
}
catch (Exception ex)
{
logger.LogError(new EventId(), ex, $"Error handling client: {ex.Message}");
}
}
}