Xamarin и Azure App Service: улучшение согласованности в подключении WebSocket / ClientWebSocket и обмене сообщениями - PullRequest
0 голосов
/ 27 мая 2020

Я создаю многопользовательскую игру, используя ClientWebSockets и WebSockets между Xamarin и Azure App Service соответственно.

Проблема, с которой я сталкиваюсь, заключается в том, что время от времени соединение между двумя друзьями не происходит, потому что один из клиентов Xamarin, похоже, не подключается должным образом. По крайней мере, это кажется, что происходит, потому что точка останова удаленной отладки (на моем компьютере) не попадает (я отлаживаю фактическое приложение, работающее в Azure).

Примечание: я был в этом для дольше, чем хотелось бы, и я не хочу переходить на что-то вроде SignalR. Я хочу увидеть это в строке fini sh на данном этапе.

Environment

  1. Xamarin Emulator (P C)
  2. Pixel 3XL (Мой сотовый телефон)
  3. Azure Служба приложений (слот для развертывания разработки) с сокетами на

Xamarin

public void BeginMultiplayerGame()
{
    GameState = GameStateEnum.MULTIPLAYER_QUEUE;
    UserData = _multiplayerService.GetFreshCurrentUserMultiplayerSocketDataObject();

    if(IsFriendly && Friend != null)
    {
        UserData.IsFriendly = IsFriendly;
        UserData.Friend = Friend;
    }

    base.SetScoreInView(true, 0);
    base.SetScoreInView(false, 0);
    ConnectToServerAsync(); //Shown below
}

async void ConnectToServerAsync()
{
    var token = (await _persistenceService.GetPersistentLogin()).Token;
    var uri = _persistenceService.GetWebSocketUrl() + "?socket_token=" + token;

    client = new ClientWebSocket();
    client.Options.KeepAliveInterval = TimeSpan.FromMinutes(10);
    cts = new CancellationTokenSource();
    await client.ConnectAsync(new Uri(uri), cts.Token);

    if (OpponentData == null)
    {
        Broadcast();
    }

    await Task.Factory.StartNew(async () =>
    {
        while (client.State == WebSocketState.Open || client.State == WebSocketState.CloseReceived)
        {
           await ReceiveMessage();
        }

        if (client.State != WebSocketState.Open)
        {
            EndGame();
        }
    }, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}

Промежуточное ПО службы приложений

Промежуточное ПО извлекает сокет, авторизует его с помощью приложения IdentityServer в другом домене и использует фабрику для получения спецификаций. c процессор, необходимый для обработки / потребления потоков сокетов.

[Authorize]
public class SocketMiddleware
{
    private readonly RequestDelegate _nxt;

    private IUserService _userService;

    public SocketMiddleware(RequestDelegate next)
    {
        _nxt = next;
    }

    public async Task Invoke(HttpContext httpContext, IUserService userService)
    {
        _userService = userService;

        AuthenticateResult authenticateResult =
        await httpContext.AuthenticateAsync(IdentityServerAuthenticationDefaults.AuthenticationScheme);

        if (authenticateResult.Succeeded)
        {
            if (httpContext.WebSockets.IsWebSocketRequest)
            {
                var socket = await httpContext.WebSockets.AcceptWebSocketAsync();

                var token = CancellationToken.None;
                var buffer = WebSocket.CreateServerBuffer(8192);
                var received = await socket.ReceiveAsync(buffer, token);

                switch (received.MessageType)
                {
                    case WebSocketMessageType.Close:
                        break;
                    case WebSocketMessageType.Text:
                        {
                            await ProcessSocket(buffer, socket);
                        }
                        break;
                }
            }
            else
            {
                await _nxt.Invoke(httpContext);
            }
        }
        else
        {
            await _nxt.Invoke(httpContext);
        }
    }

    private async Task<object> ProcessSocket(ArraySegment<byte> buffer, WebSocket socket)
    {
        TaskCompletionSource<object> ts = new TaskCompletionSource<object>();
        ISocketProcessor processor = SocketProcessorFactory.GetSocketProcessor(buffer, socket);
        SocketData data = SocketProcessorFactory.GetSocketData(buffer);
        await processor.Store(data, socket, ts);
        return ts.Task;
    }

}

Служба приложений - GameSocketProcessor - Store (...) Метод

Для случайные игры с друзьями, я использую ConcurrentQueue, чтобы просто заполнять Queue и Dequeue всякий раз, когда счетчик становится больше 1 в отдельной задаче продолжения. На самом деле это работает очень хорошо, и я не испытываю особых проблем. Основная проблема заключается в c играх с друзьями. Я использую ConcurrentDictionary, чтобы держать друзей. Семафор, выделенный для каждой пары друзей, настроен таким образом, что один друг должен ждать, пока другой друг полностью не будет добавлен в зону ожидания (чтобы помочь в борьбе с условиями гонки там).

public async Task<object> Store(SocketData obj, WebSocket socket, TaskCompletionSource<object> ts)
    {
        var data = obj as MultiplayerSocketData;

        if (data.IsFriendly)
        {
            var semaphore = await GetFriendSemaphore(data); //SemaphoreSlim is (1,1)

            try
            {
                await semaphore.WaitAsync();

                _waitingFriends.TryAdd(data.UserName, new KeyValuePair<MultiplayerSocketData, WebSocket>(data, socket));

                KeyValuePair<MultiplayerSocketData, WebSocket> kv;
                _waitingFriends.TryGetValue(data.Friend.UserName, out kv);

                if (kv.Key != null)
                {
                    var p1Data = data;
                    var p1Socket = socket;
                    var p2Data = kv.Key;
                    var p2Socket = kv.Value;

                    KeyValuePair<MultiplayerSocketData, WebSocket> dummyKv;
                    _waitingFriends.TryRemove(kv.Key.UserName, out dummyKv);

                    if (!SocketUtils.IsSocketInClosedState(p1Socket) && !SocketUtils.IsSocketInClosedState(p2Socket))
                    {
                        var game = new MultiplayerSocketGame(p1Data, p1Socket, p2Data, p2Socket);
                        _friendlyGamesWaiting.Enqueue(game);
                    }
                    else
                    {
                        _waitingFriends.TryAdd(data.UserName, new KeyValuePair<MultiplayerSocketData, WebSocket>(data, socket));
                    }
                }
                else
                {
                    _waitingFriends.TryAdd(data.UserName, new KeyValuePair<MultiplayerSocketData, WebSocket>(data, socket));
                }
            }
            catch (Exception)
            {
                Console.WriteLine("Error in Game creation.");
            }
            finally
            {
                semaphore.Release();
            }

        }
        else
        {
            _playerWaitingQueue.Enqueue(new KeyValuePair<MultiplayerSocketData, WebSocket>(data, socket));
        }

        var result = await base.Consume<MultiplayerSocketData>(_socket, ts, "Game is complete.");

        Cleanup(data);

        return result;
    }
...