robtot
robtot

Reputation: 1021

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? 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

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

    }
}

Upvotes: 0

Views: 2049

Answers (1)

Breno Almeida
Breno Almeida

Reputation: 58

i think your problem is on while loop inside Listen method.

 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. 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.

I hope that makes sense for you.

Upvotes: 1

Related Questions