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() 循环会中断。
我希望这对你有意义。
我正在尝试创建一个简单的 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() 循环会中断。
我希望这对你有意义。