简体   繁体   English

C# TcpClient 断开重连

[英]C# TcpClient Disconnect and Reconnect

I am attempting to create a simple chat/server application.我正在尝试创建一个简单的聊天/服务器应用程序。 It works to connect multiple users, and they can communicate with each other.它可以连接多个用户,并且他们可以相互通信。 However, when I want to disconnect a client I am receiving the following error:但是,当我想断开客户端连接时,我收到以下错误:

System.IO.IOException: Could not read data from the transport connection: A blocking action was interrupted by a call to WSACancelBlockingCall. ---> System.Net.Sockets.SocketException: A blocking action was interrupted by a call to WSACancelBlockingCall
   at System.Net.Sockets.Socket.Receive(Byte[] buffer, Int32 offset, Int32 size, SocketFlags socketFlags)
   at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 size)
   --- End of stack tracking for internal exceptions ---
   at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 size)
   at BeerChatClient.MainWindow.Listen() i C:\Users\Damien\source\repos\BeerChatClient\BeerChatClient\MainWindow.xaml.cs:rad 104

Therefore my question is: How can I properly disconnect and reconnect a TcpClient in my scenario?因此我的问题是:如何在我的场景中正确断开和重新连接 TcpClient? Or maybe even close and re-create a new connection if that is more performant?或者如果性能更高,甚至可以关闭并重新创建一个新连接?

Below is all code to make it is easier for you to re-create the issue.以下是使您更容易重新创建问题的所有代码。

(BeerChatClient) MainWindow.xaml.cs (BeerChatClient) MainWindow.xaml.cs

using System;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows;

namespace BeerChatClient
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        TcpClient clientSocket = null;
        NetworkStream serverStream = null;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Connect(object sender, RoutedEventArgs e)
        {
            try
            {
                // Initiate TcpClient and NetworkStream
                clientSocket = new TcpClient();
                serverStream = default(NetworkStream);

                // Connect to server
                clientSocket.Connect("127.0.0.1", 8888);
                serverStream = clientSocket.GetStream();

                // Send user's username to the server
                byte[] outStream = Encoding.UTF8.GetBytes(Username.Text + "$");
                serverStream.Write(outStream, 0, outStream.Length);
                serverStream.Flush();

                // Start listening to incoming traffic from the server
                Thread clientThread = new Thread(Listen);
                clientThread.Start();

                // Set visibility of UI elements
                btnConnect.Visibility = Visibility.Collapsed;
                btnDisconnect.Visibility = Visibility.Visible;
                Username.IsEnabled = false;
            }
            catch (Exception)
            {
                throw;
            }
        }

        private void Button_Disconnect(object sender, RoutedEventArgs e)
        {
            byte[] outStream = Encoding.UTF8.GetBytes(Username.Text + " disconnected from server$");
            serverStream.Write(outStream, 0, outStream.Length);
            serverStream.Flush();

            // Close the TcpCLient stream
            clientSocket.GetStream().Close();
            clientSocket.Close();

            // Reset TcpClient
            clientSocket = null;
            serverStream = null;

            // Reset visibility of UI elements
            btnConnect.Visibility = Visibility.Visible;
            btnDisconnect.Visibility = Visibility.Collapsed;
            Username.IsEnabled = true;
        }

        private void Button_Send(object sender, RoutedEventArgs e)
        {
            byte[] outStream = Encoding.UTF8.GetBytes(ChatText.Text + "$");

            serverStream.Write(outStream, 0, outStream.Length);
            serverStream.Flush();
        }

        private void SendMessage(string message)
        {
            if (!CheckAccess())
            {
                Dispatcher.Invoke(() => SendMessage(message));
            }
            else
            {
                ChatTextBlock.Text = ChatTextBlock.Text + Environment.NewLine + message;
            }
        }

        private void Listen()
        {
            try
            {
                while (clientSocket.Connected)
                {
                    serverStream = clientSocket.GetStream();
                    byte[] incomingStream = new byte[clientSocket.ReceiveBufferSize];

                    serverStream.Read(incomingStream, 0, clientSocket.ReceiveBufferSize);
                    incomingStream = TrimTailingZeros(incomingStream);

                    string message = Encoding.UTF8.GetString(incomingStream);

                    SendMessage(message);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }

        private byte[] TrimTailingZeros(byte[] arr)
        {
            if (arr == null || arr.Length == 0)
                return arr;

            return arr.Reverse().SkipWhile(x => x == 0).Reverse().ToArray();
        }


    }
}

(BeerChatClient) MainWindow.xaml (BeerChatClient) MainWindow.xaml

<Window x:Class="BeerChatClient.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BeerChatClient"
        mc:Ignorable="d"
        Title="MainWindow" Height="380" Width="600" ResizeMode="NoResize">
    <Grid>
 
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <ScrollViewer 
            Grid.Row="0"
            Background="GhostWhite"
            MinHeight="250"
            MaxHeight="250"  
            Width="Auto"
            Margin="20 10"
            HorizontalScrollBarVisibility="Disabled"
            VerticalScrollBarVisibility="Auto">
            
            <Border BorderBrush="Silver" BorderThickness="1">
                <TextBlock Name="ChatTextBlock"  TextWrapping="Wrap" />
            </Border>
            
        </ScrollViewer>

        <Grid Grid.Row="1" Margin="20 0">

            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition />
            </Grid.RowDefinitions>
            
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>

            <TextBox Grid.Row="0" Grid.Column="0" Name="Username" Text="Enter name..." />
            <Button Grid.Row="0" Grid.Column="1" Name="btnConnect" Content="Connect to Server"  Margin="10 0 0 0" Click="Button_Connect" />
            <Button Grid.Row="0" Grid.Column="1" Name="btnDisconnect" Content="Disconnect" Margin="10 0 0 0" Visibility="Collapsed" Click="Button_Disconnect" />

            <TextBox Grid.Row="1" Grid.Column="0" Text="Enter chat message..." Name="ChatText"  Margin="0 10 0 0" />
            <Button Grid.Row="1" Grid.Column="1" Name="btnSend" Content="Send" Margin="10 10 0 0" Click="Button_Send" />
            
        </Grid>

    </Grid>
</Window>

(BeerChatServer) Program.cs (BeerChatServer) Program.cs

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace BeerChatServer
{
    /// <summary>
    /// Main program that initiates the server
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            Server server = new Server();
            server.StartServer();
        }
    }

    /// <summary>
    /// Server class
    /// </summary>
    class Server
    {
        TcpListener serverSocket = new TcpListener(IPAddress.Any, 8888);
        TcpClient clientSocket = default(TcpClient);

        // Create client list
        ConcurrentDictionary<string, TcpClient> clientList = new ConcurrentDictionary<string, TcpClient>();
        readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

        public Server()
        {

        }

        /// <summary>
        /// Initializes and starts the TCP Server
        /// </summary>
        public void StartServer()
        {
            serverSocket.Start();

            Console.WriteLine(">> Server started on port {0}. Waiting for clients...", serverSocket.LocalEndpoint);

            StartListener();
        }

        /// <summary>
        /// Initializes and starts listening to incoming clients
        /// </summary>
        private void StartListener()
        {
            // Start listen to incoming connections
            try
            {
                int counter = 0;
                while (true)
                {
                    // Accept incoming client request
                    // clientSocket = await serverSocket.AcceptTcpClientAsync();
                    clientSocket = serverSocket.AcceptTcpClient();

                    // Get username of incoming connection
                    byte[] username = new byte[50];
                    NetworkStream networkStream = clientSocket.GetStream();
                    networkStream.Read(username, 0, username.Length);
                    string usernameStr = Encoding.UTF8.GetString(username);
                    usernameStr = usernameStr.Substring(0, usernameStr.IndexOf("$"));

                    // Add new user to clientList
                    if (!clientList.TryAdd(usernameStr, clientSocket))
                    {
                        continue;
                    }

                    counter++;
                    Console.WriteLine(">> Clients connected: {0} <<", counter);

                    // Broadcast new connection
                    Broadcast(usernameStr + " joined the chatroom.", "Server");

                    ProcessClient client = new ProcessClient();
                    client.InitClient(clientList, clientSocket, usernameStr);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                serverSocket.Stop();
            }
        }

        /// <summary>
        /// Broadcast message to all connected clients
        /// </summary>
        /// <param name="message"></param>
        /// <param name="username"></param>
        private void Broadcast(string message, string username)
        {
            byte[] clientBytes = Encoding.UTF8.GetBytes(username + ": " + message);

            foreach (KeyValuePair<string, TcpClient> client in clientList)
            {
                TcpClient clientSocket = client.Value;
                NetworkStream clientStream = clientSocket.GetStream();

                clientStream.Write(clientBytes, 0, clientBytes.Length);
                clientStream.Flush();
            }
        }
    }

    class ProcessClient
    {
        ConcurrentDictionary<string, TcpClient> clientList = null;
        TcpClient clientSocket = null;
        string username;

        public ProcessClient() { }

        public void InitClient(ConcurrentDictionary<string, TcpClient> clientList, TcpClient clientSocket, string username)
        {
            this.clientList = clientList;
            this.clientSocket = clientSocket;
            this.username = username;

            Thread clientThread = new Thread(InitChat);
            clientThread.Start();
        }

        private void InitChat()
        {
            string incomingData = null; // Message from client
            byte[] incomingBytes = new byte[clientSocket.ReceiveBufferSize];

            bool listen = true;

            while (listen)
            {
                try
                {
                    // Read incoming data from client
                    NetworkStream networkStream = clientSocket.GetStream();
                    networkStream.Read(incomingBytes, 0, clientSocket.ReceiveBufferSize);

                    // Translate bytes into a string
                    incomingData = Encoding.UTF8.GetString(incomingBytes);
                    incomingData = incomingData.Substring(0, incomingData.IndexOf("$"));

                    // Broadcast message to all clients
                    Broadcast(incomingData, username);
                }
                catch (Exception ex)
                {
                    Console.WriteLine($">> Server Exception {ex.ToString()} <<");
                }
                finally
                {
                    clientSocket.Close();
                    listen = false;
                }
            }
        }

        /// <summary>
        /// Broadcast message to all connected clients
        /// </summary>
        /// <param name="message"></param>
        /// <param name="username"></param>
        private void Broadcast(string message, string username)
        {
            byte[] clientBytes = Encoding.UTF8.GetBytes(username + ": " + message);

            foreach (KeyValuePair<string, TcpClient> client in clientList)
            {
                TcpClient clientSocket = client.Value;
                NetworkStream clientStream = clientSocket.GetStream();

                clientStream.Write(clientBytes, 0, clientBytes.Length);
                clientStream.Flush();
            }
        }

    }
}

i think your problem is on while loop inside Listen method.我认为您的问题出在 Listen 方法中的 while 循环上。

 private void Listen()
        {
            try
            {
                while (clientSocket.Connected)
                {
                    serverStream = clientSocket.GetStream();
                    byte[] incomingStream = new byte[clientSocket.ReceiveBufferSize];

                    serverStream.Read(incomingStream, 0, clientSocket.ReceiveBufferSize);
                    incomingStream = TrimTailingZeros(incomingStream);

                    string message = Encoding.UTF8.GetString(incomingStream);

                    SendMessage(message);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }

It happens because when you use clientSocket.GetStream(), the client will await for the stream.这是因为当您使用 clientSocket.GetStream() 时,客户端将等待 stream。 When you hit disconnect, the client is awaiting for the response and the method is interrupted, generating this exception.当您点击断开连接时,客户端正在等待响应并且该方法被中断,从而产生此异常。

If my suposition is right, you can capture the exception and treat as expected system exception, a simple message for disconnection would do.如果我的假设是正确的,您可以捕获异常并将其视为预期的系统异常,一个简单的断开连接消息就可以了。

If you want to get rid of the exception, try to do it from the otherside with the response.如果您想摆脱异常,请尝试从响应的另一端执行此操作。

You have to send a message from the server/client to client/server, executing the method that will disconnect the client from the server from within, i think that will avoid the error and will reproduce a clean disconnect.您必须从服务器/客户端向客户端/服务器发送一条消息,执行从内部断开客户端与服务器的连接的方法,我认为这将避免错误并重现干净的断开连接。

Ex: The client hits the disconnect button, it will send a message to the server that will start a disconnection method of this client inside the server, and before disconnecting, it will send a message to the client that starts a disconnection method on his side too.例如:客户端点击断开连接按钮,它会向服务器发送消息,服务器内部会启动该客户端的断开连接方法,在断开连接之前,它会向客户端发送一条消息,该客户端启动断开连接方法也。

that way you wont have problem with the getstream() awaiting for a response, since you wont loop to it again, the listen() loop would break.这样你就不会在 getstream() 等待响应时遇到问题,因为你不会再次循环到它,listen() 循环会中断。

I hope that makes sense for you.我希望这对你有意义。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM