【发布时间】:2017-05-12 19:06:58
【问题描述】:
我正在创建一个自定义 TCP/IP 客户端/服务器应用程序,但在尝试停止服务器时遇到了障碍。最初,我的代码使用一个 TcpListener 来监听指定端口,而我(为方便起见)启动和停止服务器的代码如下:
private bool state;
private TcpListener listener;
private CancellationTokenSource tokenSource;
private Dictionary<string, ConnectedClient> clients;
private List<Task> clientTasks;
private ConnectedClient command_client;
public async Task RunServer() {
if (!state) {
state = true;
tokenSource = new CancellationTokenSource();
listener = new TcpListener(IPAddress.Any, 55001);
listener.Start();
while (true) {
try {
TcpClient socketClient = await listener.AcceptTcpClientAsync();
ConnectedClient client = new ConnectedClient(socketClient);
clients.Add(client.id, client);
client.task = ProcessClientAsync(client, tokenSource.Token);
clientTasks.Add(client.task);
}
catch (ObjectDisposedException) {
//Server stopped by user
//exit while
break;
}
}
/* Server has been stopped; close all connections */
CloseAll();
}
else {
/* Stop the server */
tokenSource.Cancel();
listener.Stop();
/* Clean up of currently connected clients is handled in CloseAll, handled upon ObjectDisposedException above */
}
}
ConnectedClient 是我编写的一个类,用于保存有关单个客户端的一些信息以方便使用,并且具有处理接收数据时发生的情况的函数。我意识到我遗漏了一些东西来简化,但是这段代码完全符合我的要求:服务器等待连接,创建一个 ConnectedClient 对象来处理接收到的连接,然后返回等待。当服务器已经在监听的时候调用这个函数,监听器就会停止,这会导致监听器抛出异常,从而打破循环并关闭所有连接。
当我尝试创建一个侦听两个不同端口的服务器时出现了问题,这两个端口需要区别对待。
这是我的代码(尝试):
private bool state;
private Dictionary<string, ConnectedClient> clients;
private TcpListener command_listener;
private TcpListener query_listener;
private CancellationTokenSource tokenSource;
private List<Task> clientTasks;
public async Task RunServer() {
if (!state) {
state = true;
tokenSource = new CancellationTokenSource();
command_listener = new TcpListener(IPAddress.Any, 55001);
command_listener.Start();
query_listener = new TcpListener(IPAddress.Any, 55002);
query_listener.Start();
Task prevCommand = null;
Task prevQuery = null;
while (true) {
try {
if (prevCommand == null || prevCommand.IsCompleted) {
prevCommand = waitForConnections(command_listener);
}
if (prevQuery == null || prevQuery.IsCompleted) {
prevQuery = waitForConnections(query_listener);
}
await Task.WhenAny(prevCommand, prevQuery);
}
catch (ObjectDisposedException) {
//Server stopped by user
//exit while
break;
}
}
/* Server has been stopped; close all connections */
CloseAll();
}
else {
/* Stop the server */
tokenSource.Cancel();
command_listener.Stop();
query_listener.Stop();
/* Clean up of currently connected clients is handled in CloseAll, handled upon ObjectDisposedException above */
}
}
waitForConnections 的目的是处理连接请求,以便在一个端口上等待连接不会阻塞另一个端口上的连接,同时确保在端口 55001 上只能建立一个连接。
public async Task waitForConnections(TcpListener listener) {
TcpClient socketClient = await listener.AcceptTcpClientAsync();
if (((IPEndPoint)listener.LocalEndpoint).Port == 55001 ) {
if (command_client == null) {
command_client = new ConnectedClient(socketClient, onClientUpdate, onResend);
clients.Add(command_client.id, command_client);
command_client.task = ProcessClientAsync(command_client, tokenSource.Token);
clientTasks.Add(command_client.task);
}
else {
//only one client allowed on this port, reject the connection
socketClient.Close();
}
}
else {
ConnectedClient client = new ConnectedClient(socketClient, onClientUpdate, onResend);
clients.Add(client.id, client);
client.task = ProcessClientAsync(client, tokenSource.Token);
clientTasks.Add(client.task);
}
}
有了这个,我可以在没有阻塞的情况下连接两个端口上的客户端,但是再次调用这个函数并停止侦听器似乎不会导致 ObjectDisposedException 按预期抛出,这会导致整个程序挂起而不是做任何事情。我怀疑这是由于不负责任地使用异步函数造成的,但我该如何解决呢?
【问题讨论】:
-
您打算强制(和滥用)ObjectDisposedExceptions 以指示某种形式的连接终止的方法在我看来完全错误。无论如何,停止 TcpListener 对象并不会释放它。
-
公平,我认为使用异常来跟踪状态是不好的做法。就处置而言,为了简洁起见,我将其省略了,因为我认为它与问题无关。我想我真正的问题是,为什么在第一种情况下会抛出异常,而在第二种情况下却没有?
-
对不起,我还是不清楚。为什么你不能读懂我的心思? ;) 在第一种情况下,它只使用 TcpListener.Stop() 调用抛出了 ObjectDisposedException,但在第二种情况下,它没有。如果停止侦听器不会释放它,那为什么它首先会起作用?
-
不,不,我想我会慢慢明白你想做什么。所以基本上,你第一次调用
RunServer(),不知何故抛出了一个ObjectDisposedException。然后再次调用RunServer(),而state 仍然为真,这将导致调用TcpListner.Stop() 方法。你也期望 Stop() 抛出,对吧? -
是的,没错。但是直到调用 TcpListener.Stop( ) 才抛出异常,该异常被捕获并打破负责侦听连接的无限 while 循环。也许 Stop() 会导致一些其他对象被释放,这就是引发异常的原因?
标签: c# asynchronous tcp deadlock