C# TcpClient 断开和重新连接

C# TcpClient Disconnect and Reconnect

我正在尝试创建一个简单的 chat/server 应用程序。它可以连接多个用户,并且他们可以相互通信。但是,当我想断开客户端连接时,我收到以下错误:

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

因此我的问题是:如何在我的场景中正确断开和重新连接 TcpClient?如果性能更高,甚至可以关闭并重新创建新连接?

以下是使您更容易重现问题的所有代码。

(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

<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

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();
            }
        }

    }
}

我认为你的问题出在 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());
            }
        }

发生这种情况是因为当您使用 clientSocket.GetStream() 时,客户端将等待流。当您点击断开连接时,客户端正在等待响应并且该方法被中断,从而生成此异常。

如果我的猜测是正确的,您可以捕获异常并将其视为预期的系统异常,一个简单的断开连接消息就可以了。

如果您想摆脱异常,请尝试从响应的另一端进行。

您必须从 server/client 发送消息到 client/server,执行从内部断开客户端与服务器的方法,我认为这将避免错误并重现彻底断开连接。

例:客户端点击断开按钮,它会向服务器发送消息,在服务器内部启动该客户端的断开连接方法,在断开连接之前,它会向客户端发送消息,启动一个他这边也有断线方法。

这样你就不会遇到等待响应的 getstream() 问题,因为你不会再次循环到它,listen() 循环会中断。

我希望这对你有意义。