Пишу приложение-сервис, который запрашивает с сервера по
UDP данные о состоянии и выводит оповещения при необходимости.
Задача в том, что сервер живет своей жизнью. Он может быть работающим в момент старта приложения, а может и нет. А может и вообще упасть/перезапуститься/etc, и сервису требуется ждать и переодически проверять, не поднялся ли сервер и можно ли снова подключаться.
Реализовал я это таким образом.
Много кода
public Server(string name, string host, int? queryPort = null, int? rConPort = null)
{
Name = name;
Host = Dns.GetHostAddresses(host)[0];
QueryPort = queryPort;
RConPort = rConPort;
if (QueryPort == null && RConPort == null)
{
throw new IncorrectServerEntryPoint(Host);
}
}
public async void Watch()
{
if (QueryPort == null) return;
_statusWatcherClient = new UdpClient(Host.ToString(), QueryPort.Value);
UpdateChallengeTokenTimer = new Timer(async obj =>
{
Console.WriteLine($"[INFO] [{Name}] Send handshake request");
Request handshakeRequest = Request.GetHandshakeRequest();
byte[] response = null;
try
{
response = await SendResponseService.SendReceive(_statusWatcherClient, handshakeRequest.Data, ReceiveAwaitIntervalSeconds);
IsOnline = true;
}
catch (SocketException)
{
WaitForServerAlive(QueryPort.Value);
}
if (response == null) return;
var challengeTokenRaw = Response.ParseHandshake(response);
lock (_challengeTokenLock)
{
SetChallengeToken(challengeTokenRaw);
}
Console.WriteLine($"[INFO] [{Name}] ChallengeToken is set up: " + BitConverter.ToString(challengeTokenRaw));
}, null, 0, GettingChallengeTokenInterval);
UpdateServerStatusTimer = new Timer(async obj =>
{
Console.WriteLine($"[INFO] [{Name}] Send full status request");
var challengeToken = new byte[4];
lock (_challengeTokenLock)
{
Buffer.BlockCopy(_challengeToken, 0, challengeToken, 0, 4);
}
var fullStatusRequest = Request.GetFullStatusRequest(challengeToken);
byte[] response = null;
try
{
response = await SendResponseService.SendReceive(_statusWatcherClient, fullStatusRequest.Data, ReceiveAwaitIntervalSeconds);
IsOnline = true;
}
catch (SocketException)
{
WaitForServerAlive(QueryPort.Value);
}
if (response == null) return;
ServerFullState fullState = Response.ParseFullState(response);
Console.WriteLine($"[INFO] [{Name}] Full status is received");
OnFullStatusUpdated?.Invoke(this, new ServerStateEventArgs(Name, fullState));
}, null, 500, GettingStatusInterval);
}
public async Task Unwatch()
{
await UpdateChallengeTokenTimer.DisposeAsync();
await UpdateServerStatusTimer.DisposeAsync();
_statusWatcherClient.Dispose();
_statusWatcherClient = null;
}
public async void WaitForServerAlive(int port)
{
Console.WriteLine($"[WARNING] [{Name}] Server is unavailable. Waiting for reconnection...");
IsOnline = false;
await Unwatch();
Timer waitTimer = null;
waitTimer = new Timer(async obj => {
try
{
using TcpClient tcpClient = new TcpClient();
await tcpClient.ConnectAsync(Host, port);
if (waitTimer == null) return;
await waitTimer.DisposeAsync();
Console.WriteLine($"[INFO] [{Name}] Server is available again");
Watch();
} catch (SocketException) { }
}, null, 500, 5000);
}
public static int ReceiveAwaitIntervalSeconds = 10;
public static int GettingChallengeTokenInterval = 30000;
public static int GettingStatusInterval = 5000;
Здесь мы создаём экземпляр сервера и запускаем его отслеживание через Watch(), который занимается тем, что обновляет токены запросов в актуальном состоянии (каждые 30 секунд) и запрашивает статус сервера каждые 5 секунд.
Если же соединение прерывается или не доступно с самого начала в дело вступает WaitForServerAlive(), который останавливает таймеры запросов к серверу, уничтожает старый сокет и начинает каждые 5 секунд стучаться к серверу по порту в попытке получить подключение. Если подключение проходит, то возобновляет мониторинг сервера вызовом Watch().
Код отправки и полуения пакетов по UDP выглядит так
public static async Task<byte[]> SendReceive(UdpClient client, byte[] data, int receiveAwaitIntervalSeconds)
{
if (client == null)
{
throw new NullReferenceException("UdpClient client is null");
}
IPEndPoint ipEndPoint = null;
byte[] response = null;
await client.SendAsync(data, data.Length);
var responseToken = client.BeginReceive(null, null);
responseToken.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(receiveAwaitIntervalSeconds));
if (responseToken.IsCompleted)
{
try
{
response = client.EndReceive(responseToken, ref ipEndPoint);
}
catch (Exception)
{
// can't end receive
}
}
if (response == null)
throw new SocketException();
return response;
}
Собственно, пока сервер стабильно работает, то всё идет гладко, но стоит сервису запуститься до старта сервера или серверу упасть посередине работы, как в момент "возвращения" сервера приложение начинает выдавать что-то вроде
Логи работы
[INFO] [ML_VDS] Send handshake request
[WARNING] [ML_VDS] Server is unavailable. Waiting for reconnection...
[INFO] [ML_VDS] Server is available again
[INFO] [ML_VDS] Send handshake request
[WARNING] [ML_VDS] Server is unavailable. Waiting for reconnection...
[INFO] [ML_VDS] Server is available again
...
[INFO] [ML_VDS] Send handshake request
[WARNING] [ML_VDS] Server is unavailable. Waiting for reconnection...
[INFO] [ML_VDS] Server is available again
[INFO] [ML_VDS] Send handshake request
[INFO] [ML_VDS] Send full status request
[WARNING] [ML_VDS] Server is unavailable. Waiting for reconnection...
[WARNING] [ML_VDS] Server is unavailable. Waiting for reconnection...
[INFO] [ML_VDS] Server is available again
[INFO] [ML_VDS] Send handshake request
[INFO] [ML_VDS] Server is available again
[INFO] [ML_VDS] Send handshake request
[ML_VDS] Server is online
[INFO] [ML_VDS] ChallengeToken is set up: 00-93-95-1E
[INFO] [ML_VDS] ChallengeToken is set up: 00-98-D8-8B
[INFO] [ML_VDS] Send full status request
[INFO] [ML_VDS] Send full status request
[INFO] [ML_VDS] Send full status request
[WARNING] [ML_VDS] Server is unavailable. Waiting for reconnection...
[ML_VDS] Server is offline
[WARNING] [ML_VDS] Server is unavailable. Waiting for reconnection...
[INFO] [ML_VDS] Send full status request
[WARNING] [ML_VDS] Server is unavailable. Waiting for reconnection...
[WARNING] [ML_VDS] Server is unavailable. Waiting for reconnection...
[WARNING] [ML_VDS] Server is unavailable. Waiting for reconnection...
Unhandled exception. [WARNING] [ML_VDS] Server is unavailable. Waiting for reconnection...
Unhandled exception. Unhandled exception. Unhandled exception. Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
at MCServerNotifier.Server.Unwatch() in /Users/maxlevs/RiderProjects/MCServerNotifier/MCServerNotifier/Server.cs:line 142
at MCServerNotifier.Server.WaitForServerAlive(Int32 port) in /Users/maxlevs/RiderProjects/MCServerNotifier/MCServerNotifier/Server.cs:line 150
at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__140_1(Object state)
...
at System.Threading.ThreadPoolWorkQueue.Dispatch()
System.NullReferenceException: Object reference not set to an instance of an object.
at MCServerNotifier.Server.Unwatch() in
...
at System.Threading.ThreadPoolWorkQueue.Dispatch()
System.NullReferenceException: Object reference not set to an instance of an object.
at MCServerNotifier.Server.Unwatch() in
...
at System.Threading.ThreadPoolWorkQueue.Dispatch()
System.NullReferenceException: Object reference not set to an instance of an object.
at MCServerNotifier.Server.Unwatch() in
...
at System.Threading.ThreadPoolWorkQueue.Dispatch()
System.NullReferenceException: Object reference not set to an instance of an object.
at MCServerNotifier.Server.Unwatch() in
...
at System.Threading.ThreadPoolWorkQueue.Dispatch()
Прошу знающих людей помочь разобраться в причине проблемы. Как правильно подходить к попыткам возобновить соединение после разрыва?