private void ClientSendConnectRequest()
    {
        if (connectRequestCount > 0)
        {
            // Last attempt failed
            connectEventStream.OnNext(new ConnectEvent
            {
                IsSuccess           = false,
                ConnectRequestCount = connectRequestCount,
            });
        }

        connectRequestCount++;
        try
        {
            ConnectRequestDto connectRequestDto = new ConnectRequestDto
            {
                ProtocolVersion      = ProtocolVersion,
                ClientName           = settings.ClientName,
                ClientId             = settings.ClientId,
                MicrophoneSampleRate = clientSideMicSampleRecorder.SampleRate.Value,
            };
            byte[] requestBytes = Encoding.UTF8.GetBytes(connectRequestDto.ToJson());
            // UDP Broadcast (255.255.255.255)
            clientUdpClient.Send(requestBytes, requestBytes.Length, "255.255.255.255", ConnectPortOnServer);
            Debug.Log($"Client has sent ConnectRequest as broadcast. Request: {connectRequestDto.ToJson()}");
        }
        catch (Exception e)
        {
            Debug.LogException(e);
        }
    }
    private void HandleClientMessageWithNoMicrophone(IPEndPoint clientIpEndPoint, ConnectRequestDto connectRequestDto)
    {
        ConnectResponseDto connectResponseDto = new ConnectResponseDto
        {
            ClientName     = connectRequestDto.ClientName,
            ClientId       = connectRequestDto.ClientId,
            HttpServerPort = httpServer.port,
        };

        serverUdpClient.Send(connectResponseDto.ToJson(), clientIpEndPoint);
    }
    private void HandleClientMessageWithMicrophone(IPEndPoint clientIpEndPoint, ConnectRequestDto connectRequestDto)
    {
        ConnectedClientHandler newConnectedClientHandler = RegisterClient(clientIpEndPoint, connectRequestDto.ClientName, connectRequestDto.ClientId, connectRequestDto.MicrophoneSampleRate);

        clientConnectedEventQueue.Enqueue(new ClientConnectionEvent(newConnectedClientHandler, true));

        ConnectResponseDto connectResponseDto = new ConnectResponseDto
        {
            ClientName     = connectRequestDto.ClientName,
            ClientId       = connectRequestDto.ClientId,
            HttpServerPort = httpServer.port,
            MicrophonePort = newConnectedClientHandler.MicTcpListener.GetPort(),
        };

        Debug.Log("Sending ConnectResponse to " + clientIpEndPoint.Address + ":" + clientIpEndPoint.Port);
        serverUdpClient.Send(connectResponseDto.ToJson(), clientIpEndPoint);
    }
    private void HandleClientMessage(IPEndPoint clientIpEndPoint, string message)
    {
        Debug.Log($"Received message from client {clientIpEndPoint} ({clientIpEndPoint.Address}): '{message}'");
        try
        {
            ConnectRequestDto connectRequestDto = JsonConverter.FromJson <ConnectRequestDto>(message);
            if (connectRequestDto.ProtocolVersion != ProtocolVersion)
            {
                throw new ConnectRequestException($"Malformed ConnectRequest: protocolVersion does not match"
                                                  + $" (server (main game): {ProtocolVersion}, client (companion app): {connectRequestDto.ProtocolVersion}).");
            }
            if (connectRequestDto.ClientName.IsNullOrEmpty())
            {
                throw new ConnectRequestException("Malformed ConnectRequest: missing ClientName.");
            }
            if (connectRequestDto.ClientId.IsNullOrEmpty())
            {
                throw new ConnectRequestException("Malformed ConnectRequest: missing ClientId.");
            }

            if (connectRequestDto.MicrophoneSampleRate > 0)
            {
                HandleClientMessageWithMicrophone(clientIpEndPoint, connectRequestDto);
            }
            else
            {
                HandleClientMessageWithNoMicrophone(clientIpEndPoint, connectRequestDto);
            }
        }
        catch (Exception e)
        {
            Debug.LogException(e);
            serverUdpClient.Send(new ConnectResponseDto
            {
                ErrorMessage = e.Message
            }.ToJson(), clientIpEndPoint);
        }
    }