Ошибка при разрыве соединения TcpClient
Попытался реализовать класс подключения клиентов к серверам, выводить сообщения, информировать о подключении и ошибках, выполнять автоматическое переподключение в случае разрыва соединения.
При удачном подключении вроде бы всё ок и сообщения я получаю, но возникает ошибка при разрыве соединения с сервером или попытке выполнить Disconnect() + бесконечно выводятся пустые сообщения. Скорее всего, я что-то не правильно реализовал, но не могу понять в чем проблема.
Подскажите пожалуйста, в чем я допустил ошибку или может уже существуют готовые решения моей задачи?
Ошибка: System.ObjectDisposedException: "CancellationTokenSource был удален."
Код программы:
public SocketClient(Setting setting)
{
_setting = setting;
Thread thread = new Thread((o) =>
{
while (true)
{
Connect();
while (_tcpClient != null && _tcpClient.Connected)
{
// Проверка соединения каждые 5сек
if (_tcpClient.Client.Poll(0, SelectMode.SelectRead))
{
byte[] buff = new byte[1];
if (_tcpClient.Client.Receive(buff, SocketFlags.Peek) == 0)
{
NLogger.Info("Соединение закрыто");
Disconnect();
}
}
Thread.Sleep(5000);
}
// Переподключение каждые 10 сек
Thread.Sleep(10_000);
}
});
thread.IsBackground = true;
thread.Start();
}
Подключение:
public async void Connect()
{
try
{
using (_tcpClient = new TcpClient())
{
await _tcpClient.ConnectAsync(IPAddress.Parse(_setting.Address), _setting.Port);
if (_tcpClient.Connected)
{
using (_cancelToken = new CancellationTokenSource())
{
await ReceiveMessage(_cancelToken.Token);
}
}
}
}
catch (SocketException)
{
NLogger.Info("Ошибка подключения");
}
catch (Exception ex)
{
NLogger.Error(ex, "Ошибка программы: Connect");
Disconnect();
}
}
Отключение:
public void Disconnect()
{
_cancelToken.Cancel();
_cancelToken.Dispose();
_tcpClient.Close();
_tcpClient.Dispose();
}
Чтение сообщения:
private async Task ReceiveMessage(CancellationToken token)
{
try
{
using (NetworkStream stream = _tcpClient.GetStream())
{
while (!token.IsCancellationRequested)
{
if (stream.CanRead)
{
byte[] responceData = new byte[1024];
int bytes = 0;
StringBuilder sb = new StringBuilder();
do
{
bytes = await stream.ReadAsync(responceData, 0, responceData.Length);
sb.Append(Encoding.ASCII.GetString(responceData, 0, bytes));
_message = sb.ToString();
}
while (stream.DataAvailable);
}
}
}
}
catch (ObjectDisposedException ex)
{
NLogger.Info(ex, "Отключен");
Disconnect();
}
catch (Exception ex)
{
NLogger.Error(ex, "Ошибка программы: ReceiveMessage");
Disconnect();
}
}
Ответы (1 шт):
У вас процессор не кипит?
while (!token.IsCancellationRequested)
{
if (stream.CanRead)
{
}
}
Если stream.CanRead
возвращает false
, цикл просто выдаст максимальную нагрузку на процессор. К тому же синхронно повесит код.
private async Task ReceiveMessage(CancellationToken token)
{
try
{
using (NetworkStream stream = _tcpClient.GetStream())
{
byte[] buffer = new byte[1024];
StringBuilder sb = new StringBuilder();
while (true)
{
do
{
int bytes = await stream.ReadAsync(buffer, 0, buffer.Length, token);
sb.Append(Encoding.ASCII.GetString(buffer, 0, bytes));
}
while (stream.DataAvailable);
_message = sb.ToString();
sb.Clear();
}
}
}
catch (ObjectDisposedException ex)
{
NLogger.Info(ex, "Отключен");
}
catch (Exception ex)
{
NLogger.Error(ex, "Ошибка программы: ReceiveMessage");
}
}
Как-то так оно должно выглядеть.
Вот теперь будет работать, так как этот код не будет вешать родительский метод.
убрал
using
уtcpClient
исancelToken
. ВDisconnect()
сделал, еслисancelToken != null
, то.Close()
,Dispose()
,сancelToken = null
. Так же и с_tcpClient
. Вроде бы всё стало работать
Возвращайте using
назад. А метод Disconnect()
уберите совсем везде, он не нужен.
Ещё вот этот код можно немного упростить
public SocketClient(Setting setting)
{
_setting = setting;
Task.Run(() =>
{
while (true)
{
Connect();
while (_tcpClient?.Connected ?? false)
{
// Проверка соединения каждые 5сек
if (_tcpClient.Client.Poll(0, SelectMode.SelectRead))
{
byte[] buff = new byte[1];
if (_tcpClient.Client.Receive(buff, SocketFlags.Peek) == 0)
{
NLogger.Info("Соединение закрыто");
Disconnect();
}
}
Thread.Sleep(5000);
}
// Переподключение каждые 10 сек
Thread.Sleep(10_000);
}
});
}
И вот здесь ещё небольшая правочка
if (_tcpClient.Connected)
{
using (_cancelToken = new CancellationTokenSource())
{
await ReceiveMessage(_cancelToken.Token);
}
_cancelToken = null;
}
Если вручную надо закрыть подключение, просто вызовите _cancelToken?.Cancel();
. Больше ничего делать не нужно.
Ну или тот же Disconnect
public void Disconnect()
{
_cancelToken?.Cancel();
}
Но вызывать его только вручную, не из методов Connect
или ReceiveMessage
.
По-хорошему, вам нужен протокол передачи данных. То есть вы сейчас не знаете, какого размера будет принимаемое сообщение. Нужно передавать длину сообщения, затем само сообщение.
Вот пример: TCP клиент-сервер как реализовать передачу данных без краша сервера?