I'm having trouble closing a tcpClient
connection when calling Application.exit()
from a windows form. The problem I'm having is when the form closes the application is still running due to TCP connected devices and I want to close these TCP connections that the app has. I'm using threads per advice on here and I'm successfully connecting devices. When I call server.Stop()
I get the following error:
System.Net.Sockets.SocketException (0x80004005): A blocking operation was interrupted by a call to WSACancelBlockingCall
I've found that the error is linked to this line of code TcpClient client = server.AcceptTcpClient();
which I think keeps connections open when I use threads per each TCP connection.
I've tried using server.Shutdown()
and also using tokens (the program class has one that calls StartServer()
on launch but is blocked by the same error as the TCPService, it's code added below.
Also, If I close the connection from a connected device the application does close.
The full TCP class code is below:
public class TCPService
{
private const int port = 8045;
private bool running;
private static TcpListener server = null;
private Thread t = null;
public ammendDeviceCountDelegate handleDeviceCount;
public void StartServer()
{
string hostName = Dns.GetHostName();
IPAddress myIP = GetIPAddress(hostName);
IPEndPoint localEndPoint = new IPEndPoint(myIP, port);
server = new TcpListener(myIP, port);
server.Start();
running = true;
StartListener();
}
public void stopServer()
{
running = false;
server.Stop();
}
private void StartListener()
{
try
{
while (running)
{
TcpClient client = server.AcceptTcpClient();
Console.WriteLine("Connected!");
t = new Thread(new ParameterizedThreadStart(HandleDevice));
t.Start(client);
}
}
catch (SocketException e)
{
Console.WriteLine("Exception: {0}", e);
}
}
private void HandleDevice(Object obj)
{
TcpClient client = (TcpClient)obj;
var stream = client.GetStream();
string imei = String.Empty;
string data = null;
Byte[] bytes = new Byte[256];
int i;
handleDeviceCount(false);
try
{
while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
{
string hex = BitConverter.ToString(bytes);
data = Encoding.ASCII.GetString(bytes, 0, i);
Console.WriteLine("{1}: Received: {0}", data, Thread.CurrentThread.ManagedThreadId);
string str = "handshaky";
Byte[] reply = System.Text.Encoding.ASCII.GetBytes(str);
stream.Write(reply, 0, reply.Length);
Console.WriteLine("{1}: Sent: {0}", str, Thread.CurrentThread.ManagedThreadId);
}
client.Close();
}
catch (Exception e)
{
Console.WriteLine("Exception: {0}", e.ToString());
client.Close();
}
}
private static IPAddress GetIPAddress(string hostname)
{
IPHostEntry host;
host = Dns.GetHostEntry(hostname);
foreach (IPAddress ip in host.AddressList)
{
if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
return ip;
}
}
return IPAddress.None;
}
}
Program start method:
private static void startTCPServer()
{
tokenSource = new CancellationTokenSource();
Task.Run(() =>
{
while (running)
{
if (!tokenSource.Token.IsCancellationRequested)
{
tcp.StartServer();
break;
}
}
}, tokenSource.Token);
}
A separate thread for each listener is a bad idea if you have lots of listeners anyway. You should transition this completely to async
await
and use the CancellationToken
when you close the app.
You just need to use token also in the handler of the client, then it will bail out.
public class TCPService
{
private const int port = 8045;
public CancellationToken Token {get; set;}
public ammendDeviceCountDelegate handleDeviceCount;
public async Task StartServer()
{
string hostName = Dns.GetHostName();
IPAddress myIP = await GetIPAddress(hostName);
IPEndPoint localEndPoint = new IPEndPoint(myIP, port);
TcpListener server = null; // server is local variable
try
{
var server = new TcpListener(myIP, port);
server.Start();
await Listen;
}
finally
{
server?.Stop();
}
}
private async Task Listen()
{
try
{
while (true)
{
Token.ThrowIfCancellationRequested();
TcpClient client = await server.AcceptTcpClientAsync(Token);
Console.WriteLine("Connected!");
Task.Run(async () => await HandleDevice(client), Token);
}
}
catch (SocketException e)
{
Console.WriteLine("Exception: {0}", e);
}
}
private async Task HandleDevice(TcpClient client)
{
string imei = String.Empty;
string data = null;
Byte[] bytes = new Byte[256];
int i;
handleDeviceCount(false);
try
{
using (var stream = client.GetStream())
{
while ((i = await stream.ReadAsync(bytes, 0, bytes.Length, _token)) != 0)
{
Token.ThrowIfCancellationRequested();
string hex = BitConverter.ToString(bytes);
data = Encoding.ASCII.GetString(bytes, 0, i);
Console.WriteLine("{1}: Received: {0}", data, Thread.CurrentThread.ManagedThreadId);
string str = "handshaky";
Byte[] reply = System.Text.Encoding.ASCII.GetBytes(str);
await stream.WriteAsync(reply, 0, reply.Length, Token);
Console.WriteLine("{1}: Sent: {0}", str, Thread.CurrentThread.ManagedThreadId);
}
}
}
catch (OperationCanceledException) { }
catch (Exception e)
{
Console.WriteLine("Exception: {0}", e.ToString());
}
finally
{
client.Close();
}
}
private async Task<IPAddress> GetIPAddress(string hostname)
{
IPHostEntry host;
host = await Dns.GetHostEntryAsync(hostname, Token);
foreach (IPAddress ip in host.AddressList)
{
if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
return ip;
}
}
return IPAddress.None;
}
}
You then start and stop it like this
CancellationTokenSource _tokenSource;
private static void startTCPServer()
{
_tokenSource = new CancellationTokenSource();
tcp.Token = tokenSource.Token;
Task.Run(tcp.StartServer, tokenSource.Token);
}
private static void stopTCPServer()
{
_tokenSource.Cancel();
}
Call stopTCPServer
when your app closes.