Example #1
0
        /// <summary>
        /// Check if other machine can connect to this machine with the port for game without semaphore.
        /// </summary>
        /// <param name="protocol">The protocol which is used for accept TCP connection of game</param>
        /// <param name="portNumber">The port number which is used for accept TCP connection of game</param>
        /// <exception cref="ClientErrorException"></exception>
        /// <exception cref="InvalidOperationException">The port is already used by other connection or listener</exception>
        /// <exception cref="SocketException">Failed to create a socket with the port.</exception>
        /// <returns></returns>
        private async Task <bool> ConnectionTestCoreAsync(TransportProtocol protocol, ushort portNumber)
        {
            TcpListener tcpListener       = null;
            UdpClient   udpClient         = null;
            Task        task              = null;
            var         cancelTokenSource = new CancellationTokenSource();

            try
            {
                if (!Connected)
                {
                    throw new ClientErrorException(ClientErrorCode.NotConnected);
                }

                if (!NetworkHelper.CheckPortAvailability(protocol, portNumber))
                {
                    throw new InvalidOperationException(
                              $"The port \"{portNumber}\" is already used by other {portNumber} connection or listener.");
                }

                Logger.Log(LogLevel.Info,
                           $"Start {protocol} connectable test to my port {portNumber} from the server.");

                // Accept both IPv4 and IPv6
                if (protocol == TransportProtocol.Tcp)
                {
                    tcpListener = new TcpListener(IPAddress.IPv6Any, portNumber);
                    tcpListener.Server.SetSocketOption(
                        SocketOptionLevel.IPv6,
                        SocketOptionName.IPv6Only,
                        0);
                    tcpListener.Start();
                    Logger.Log(LogLevel.Debug, "TCP listener for connectable test is started.");

                    async Task Func(CancellationToken cancellationToken)
                    {
                        try
                        {
                            using (var socket = await Task.Run(tcpListener.AcceptSocketAsync, cancellationToken)
                                                .ConfigureAwait(false))
                            {
                                // Echo received message
                                var buffer = new ArraySegment <byte>(new byte[64]);
                                var size   = await Task
                                             .Run(
                                    async() => await socket.ReceiveAsync(buffer, SocketFlags.None)
                                    .ConfigureAwait(false), cancellationToken).ConfigureAwait(false);

                                Logger.Log(LogLevel.Debug,
                                           $"A test message from the server is received: \"{System.Text.Encoding.ASCII.GetString(buffer.ToArray())}\"");

                                var replyData = new ArraySegment <byte>(buffer.ToArray(), 0, size);
                                await Task.Run(async() =>
                                {
                                    await socket.SendAsync(replyData, SocketFlags.None).ConfigureAwait(false);
                                }, cancellationToken).ConfigureAwait(false);

                                Logger.Log(LogLevel.Debug, "The received message is sent to the server.");

                                socket.Shutdown(SocketShutdown.Both);
                                socket.Disconnect(false);
                                Logger.Log(LogLevel.Debug, "TCP listener for connectable test is shut-downed.");
                            }
                        }
                        // ObjectDisposedException is thrown when we cancel accepting after we started accepting.
                        catch (ObjectDisposedException)
                        {
                            Logger.Log(LogLevel.Debug, "TCP listener for connectable test is disposed.");
                        }
                    }

                    task = Func(cancelTokenSource.Token);
                }
                else
                {
                    udpClient = new UdpClient(portNumber);
                    Logger.Log(LogLevel.Debug, "TCP client for connectable test is created.");

                    async Task Func(UdpClient pUdpClient, CancellationToken cancellationToken)
                    {
                        try
                        {
                            var serverAddress = ((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address;
                            while (true)
                            {
                                var receiveResult = await Task.Run(pUdpClient.ReceiveAsync, cancellationToken)
                                                    .ConfigureAwait(false);

                                Logger.Log(LogLevel.Debug,
                                           $"A message is received: \"{System.Text.Encoding.ASCII.GetString(receiveResult.Buffer)}\"");
                                if (cancellationToken.IsCancellationRequested)
                                {
                                    return;
                                }

                                // reply only if the message is from the server
                                if (receiveResult.RemoteEndPoint.Address.EqualsIpAddressSource(serverAddress))
                                {
                                    // Echo received message
                                    await Task.Run(async() =>
                                    {
                                        await pUdpClient.SendAsync(receiveResult.Buffer, receiveResult.Buffer.Length,
                                                                   receiveResult.RemoteEndPoint).ConfigureAwait(false);
                                    }, cancellationToken).ConfigureAwait(false);

                                    Logger.Log(LogLevel.Debug, "The received message is sent to the server.");
                                }
                                else
                                {
                                    Logger.Log(LogLevel.Debug,
                                               $"The sender ({receiveResult.RemoteEndPoint.Address}) of the message is not the server ({serverAddress}), so this message is ignored.");
                                }
                            }
                        }
                        // ObjectDisposedException is thrown when we cancel accepting after we started accepting.
                        catch (ObjectDisposedException)
                        {
                            Logger.Log(LogLevel.Debug, "UDP client for connectable test is disposed.");
                        }
                    }

                    task = Func(udpClient, cancelTokenSource.Token);
                }

                var requestBody = new ConnectionTestRequestMessage {
                    Protocol = protocol, PortNumber = portNumber
                };
                Logger.Log(LogLevel.Info,
                           $"Send ConnectionTestRequest. ({nameof(requestBody.Protocol)}: {requestBody.Protocol}, {nameof(requestBody.PortNumber)}: {requestBody.PortNumber})");
                await SendRequestAsync(requestBody).ConfigureAwait(false);

                var reply = await ReceiveReplyAsync <ConnectionTestReplyMessage>().ConfigureAwait(false);

                Logger.Log(LogLevel.Info, $"Receive ConnectionTestReply. ({nameof(reply.Succeed)}: {reply.Succeed})");
                return(reply.Succeed);
            }
            finally
            {
                cancelTokenSource.Cancel();
                tcpListener?.Stop();
                udpClient?.Close();
                if (task != null)
                {
                    await Task.WhenAll(task).ConfigureAwait(false);
                }

                task?.Dispose();
                udpClient?.Dispose();
                cancelTokenSource.Dispose();
            }
        }
        /// <summary>
        /// Check if other machine can connect to this machine with the port for game without semaphore.
        /// </summary>
        /// <param name="protocol">The protocol which is used for accept TCP connection of game</param>
        /// <param name="portNumber">The port number which is used for accept TCP connection of game</param>
        /// <exception cref="ClientErrorException"></exception>
        /// <exception cref="InvalidOperationException">The port is already used by other connection or listener</exception>
        /// <returns></returns>
        private async Task <bool> ConnectionTestCoreAsync(TransportProtocol protocol, ushort portNumber)
        {
            TcpListener tcpListener       = null;
            UdpClient   udpClient         = null;
            Task        task              = null;
            var         cancelTokenSource = new CancellationTokenSource();

            try
            {
                if (!Connected)
                {
                    throw new ClientErrorException(ClientErrorCode.NotConnected);
                }

                if (!NetworkHelper.CheckPortAvailability(protocol, portNumber))
                {
                    throw new InvalidOperationException(
                              $"The port \"{portNumber}\" is already used by other {portNumber} connection or listener.");
                }

                // Accept both IPv4 and IPv6
                if (protocol == TransportProtocol.Tcp)
                {
                    tcpListener = new TcpListener(IPAddress.IPv6Any, portNumber);
                    tcpListener.Server.SetSocketOption(
                        SocketOptionLevel.IPv6,
                        SocketOptionName.IPv6Only,
                        0);
                    tcpListener.Start();

                    async Task Func(CancellationToken cancellationToken)
                    {
                        try
                        {
                            while (!cancellationToken.IsCancellationRequested)
                            {
                                using (await Task.Run(tcpListener.AcceptSocketAsync, cancellationToken)
                                       .ConfigureAwait(false))
                                {
                                }
                            }
                        }
                        // ObjectDisposedException is thrown when we cancel accepting after we started accepting.
                        catch (ObjectDisposedException)
                        {
                        }
                    }

                    task = Func(cancelTokenSource.Token);
                }
                else
                {
                    udpClient = new UdpClient(portNumber);

                    async Task Func(UdpClient pUdpClient, CancellationToken cancellationToken)
                    {
                        try
                        {
                            var serverAddress = ((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address;
                            while (true)
                            {
                                var receiveResult = await Task.Run(pUdpClient.ReceiveAsync, cancellationToken)
                                                    .ConfigureAwait(false);

                                if (cancellationToken.IsCancellationRequested)
                                {
                                    return;
                                }

                                // reply only if the message is from the server
                                if (Equals(receiveResult.RemoteEndPoint.Address, serverAddress))
                                {
                                    await Task.Run(async() =>
                                    {
                                        await pUdpClient.SendAsync(receiveResult.Buffer, receiveResult.Buffer.Length,
                                                                   receiveResult.RemoteEndPoint).ConfigureAwait(false);
                                    }, cancellationToken).ConfigureAwait(false);
                                }
                            }
                        }
                        // ObjectDisposedException is thrown when we cancel accepting after we started accepting.
                        catch (ObjectDisposedException)
                        {
                        }
                    }

                    task = Func(udpClient, cancelTokenSource.Token);
                }

                var requestBody = new ConnectionTestRequestMessage {
                    Protocol = protocol, PortNumber = portNumber
                };
                Logger.Log(LogLevel.Info,
                           $"Send ConnectionTestRequest. ({nameof(requestBody.Protocol)}: {requestBody.Protocol}, {nameof(requestBody.PortNumber)}: {requestBody.PortNumber})");
                await SendRequestAsync(requestBody).ConfigureAwait(false);

                var reply = await ReceiveReplyAsync <ConnectionTestReplyMessage>().ConfigureAwait(false);

                Logger.Log(LogLevel.Info, $"Receive ConnectionTestReply. ({nameof(reply.Succeed)}: {reply.Succeed})");
                return(reply.Succeed);
            }
            finally
            {
                cancelTokenSource.Cancel();
                tcpListener?.Stop();
                udpClient?.Close();
                if (task != null)
                {
                    await Task.WhenAll(task).ConfigureAwait(false);
                }

                task?.Dispose();
                udpClient?.Dispose();
                cancelTokenSource.Dispose();
            }
        }