UDPClient ошибка 10052
Есть задача опроса многих (тысячи) устройств по UDP (устройства в GPRS). Решил воспользоваться классом UDPClient. Использую функции асинхронного чтения и записи ReceiveAsync, SendAsync, которые возвращают Task-и, через которые можно ждать завершения операции и читать результат.
Алгоритм простой: есть фоновый поток, который в бесконечном цикле рассылает запросы из входящей очереди, а так же читает сокет на предмет входящих данных и выпихивает их в выходную очередь.
После отсылки пакетов на ряд адресов вызов ReceiveAsync завершается Task-ом c выставленным IsFaulted и в Task.Exception у него лежит такая вот гадость:
System.AggregateException: One or more errors occurred. ---> System.Net.Sockets.SocketException: The connection has been broken due to keep-alive activity detecting a failure while the operation was in progress at System.Net.Sockets.Socket.EndReceiveFrom(IAsyncResult asyncResult, EndPoint& endPoint) at System.Net.Sockets.UdpClient.EndReceive(IAsyncResult asyncResult, IPEndPoint& remoteEP) at System.Net.Sockets.UdpClient.b__4(IAsyncResult ar) at System.Threading.Tasks.TaskFactory
1.FromAsyncCoreLogic(IAsyncResult iar, Func
2 endFunction, Action1 endAction, Task
1 promise, Boolean requiresSynchronization)
В MSDNнаписано, что:
For a datagram socket, this error indicates that the time to live has expired.
Причем, если после этого вызвать ReceiveAsync, то уже при вызове вылетает точно такое же исключение, приходятся пересоздавать UDPClient.
Собственно вопроса 3:
Можно ли избежать таких ошибок? Дело в том, что я такую же программу писал на Delphi, на компоненте UDPServer из Indy 9 и там было все замечательно, при опросе того же самого оборудования в той же сети. Я собственно даже никаких ошибок не видел. От отвалившихся устройств просто ничего не приходило.
Как понять для какого пакета (IPEndPoint) вышел TTL. Запросы одновременно могут посылаться в сотни адресов.
В случае ошибки, можно ли продолжить работу, не не переоткрывая Socket?
Проблемно значимый код выглядит приблизительно так:
/// <summary>
/// Основаная процедура ввода/вывода
/// </summary>
private void DoIO()
{
Task<UdpReceiveResult> readResult = null;
Task<int> writeResult = null;
//0 - чтение
//1 - запись или сигнал о поступлениях во входящей очереди
WaitHandle[] waitedEvents = new WaitHandle[2];
RequestInfo? request = null;
while (true)
try
{
// Чтение всегда должно быть
if (readResult == null)
{
readResult = _udpClient.ReceiveAsync();
waitedEvents[0] = (readResult as IAsyncResult).AsyncWaitHandle;
}
// Достаем очередной запрос
if (!request.HasValue)
{
request = ExtractRequest();
writeResult = null;
}
// Отправка запроса, с паралельной задачей чтения
if (request.HasValue)
{
if (writeResult == null)
writeResult = _udpClient.SendAsync(request.Value.Data,
request.Value.Data.Length,
request.Value.Connection.IpEndPoint);
waitedEvents[1] = (writeResult as IAsyncResult).AsyncWaitHandle;
if (WaitHandle.WaitAny(waitedEvents) == 1) // Завершилась задача записи
{
if (writeResult.IsFaulted)
{
request.Value.Connection.AddMessage(writeResult.Exception.Message);
throw new Exception("Ошибка записи", writeResult.Exception);
}
writeResult.Dispose();
writeResult = null;
request = null;
}
}
else // Чтение и ожидание нового пакета на отправку
{
waitedEvents[1] = eventNewRequest;
WaitHandle.WaitAny(waitedEvents);
}
//чтение завершено
if (readResult.IsCompleted)
{
if (readResult.IsFaulted)
throw new Exception("Ошибка чтения", readResult.Exception);
else
SendResponse(readResult);
readResult.Dispose();
readResult = null;
}
}
catch (Exception e)
{
AppendDiagnosticMessage(null, DiagnosticMessageKindData.Error, e.Message);
if (readResult != null)
{
if (readResult.IsCompleted)
readResult.Dispose();
readResult = null;
}
if (writeResult != null)
{
if (writeResult.IsCompleted)
writeResult.Dispose();
writeResult = null;
}
DisposeUdpClient(true);
}
}
}
/// <summary>
/// Запускает поток ввода/вывода если еще не запущен
/// </summary>
private void StartIOThreadIfNeed()
{
lock (this)
if (_udpClient == null)
{
_udpClient = CreateUdpClient();
new Thread(DoIO) {IsBackground = true}.Start();
}
}
/// <summary>
/// Создает объект обмен по UDP
/// </summary>
private UdpClient CreateUdpClient()
{
//Пытаемся занять ранее используемый порт, если не выходит, то занимаем какой то новый
UdpClient result = null;
for (var i = 0; i < 2; i++)
try
{
result = new UdpClient(_localPort);
_localPort = ((IPEndPoint)result.Client.LocalEndPoint).Port;
break;
}
catch (SocketException e)
{
if (e.SocketErrorCode == SocketError.AddressAlreadyInUse && _localPort != 0)
_localPort = 0;
else
throw;
}
var socket = result.Client;
socket.SendBufferSize = 0xFFFF;
socket.ReceiveBufferSize = 0xFFFF;
result.Ttl = 255;
return result;
}
/// <summary>
/// Удаляет UDPClient
/// </summary>
/// <param name="recreate">Пересоздать объект</param>
private void DisposeUdpClient(bool recreate = false)
{
lock (this)
if (_udpClient != null)
try
{
(_udpClient as IDisposable).Dispose();
_udpClient = null;
if (recreate)
_udpClient = CreateUdpClient();
}
catch
{
};
}