C# 使用多线程通过NetworkStream发送数据
我正在尝试构建一个命令行聊天室,服务器在其中处理连接,并将输入从一个客户端重复到所有其他客户端。 目前,服务器能够接收来自多个客户端的输入,但只能单独将信息发送回这些客户端。我认为我的问题是每个连接都是在一个单独的线程上处理的。我将如何允许线程彼此通信,或者能够向每个线程发送数据 服务器代码:C# 使用多线程通过NetworkStream发送数据,c#,multithreading,async-await,.net-core,C#,Multithreading,Async Await,.net Core,我正在尝试构建一个命令行聊天室,服务器在其中处理连接,并将输入从一个客户端重复到所有其他客户端。 目前,服务器能够接收来自多个客户端的输入,但只能单独将信息发送回这些客户端。我认为我的问题是每个连接都是在一个单独的线程上处理的。我将如何允许线程彼此通信,或者能够向每个线程发送数据 服务器代码: namespace ConsoleApplication { class TcpHelper { private static object _lock = new
namespace ConsoleApplication
{
class TcpHelper
{
private static object _lock = new object();
private static List<Task> _connections = new List<Task>();
private static TcpListener listener { get; set; }
private static bool accept { get; set; } = false;
private static Task StartListener()
{
return Task.Run(async () =>
{
IPAddress address = IPAddress.Parse("127.0.0.1");
int port = 5678;
listener = new TcpListener(address, port);
listener.Start();
Console.WriteLine($"Server started. Listening to TCP clients at 127.0.0.1:{port}");
while (true)
{
var tcpClient = await listener.AcceptTcpClientAsync();
Console.WriteLine("Client has connected");
var task = StartHandleConnectionAsync(tcpClient);
if (task.IsFaulted)
task.Wait();
}
});
}
// Register and handle the connection
private static async Task StartHandleConnectionAsync(TcpClient tcpClient)
{
// start the new connection task
var connectionTask = HandleConnectionAsync(tcpClient);
// add it to the list of pending task
lock (_lock)
_connections.Add(connectionTask);
// catch all errors of HandleConnectionAsync
try
{
await connectionTask;
}
catch (Exception ex)
{
// log the error
Console.WriteLine(ex.ToString());
}
finally
{
// remove pending task
lock (_lock)
_connections.Remove(connectionTask);
}
}
private static async Task HandleConnectionAsync(TcpClient client)
{
await Task.Yield();
{
using (var networkStream = client.GetStream())
{
if (client != null)
{
Console.WriteLine("Client connected. Waiting for data.");
StreamReader streamreader = new StreamReader(networkStream);
StreamWriter streamwriter = new StreamWriter(networkStream);
string clientmessage = "";
string servermessage = "";
while (clientmessage != null && clientmessage != "quit")
{
clientmessage = await streamreader.ReadLineAsync();
Console.WriteLine(clientmessage);
servermessage = clientmessage;
streamwriter.WriteLine(servermessage);
streamwriter.Flush();
}
Console.WriteLine("Closing connection.");
networkStream.Dispose();
}
}
}
}
public static void Main(string[] args)
{
// Start the server
Console.WriteLine("Hit Ctrl-C to close the chat server");
TcpHelper.StartListener().Wait();
}
}
}
代码中的主要错误是没有尝试将从一个客户端接收到的数据发送到其他连接的客户端。您的服务器中有
\u connections
列表,但列表中存储的唯一内容是连接的任务
对象,您甚至不需要对这些对象执行任何操作
相反,您应该维护连接本身的列表,这样当您从一个客户端收到消息时,就可以将该消息重新传输到其他客户端
至少,这应该是一个列表
,但由于您使用的是StreamReader
和StreamWriter
,因此您还需要在列表中初始化和存储这些对象。此外,还应包括客户端标识符。一个明显的选择是客户端的名称(即用户输入的名称),但是您的示例没有在聊天协议中提供任何机制来传输该标识作为连接初始化的一部分,因此在我的示例(下面)中,我只使用一个简单的整数值
您发布的代码中还有其他一些不规则之处,例如:
- 在一个全新的线程中启动一个任务,只需执行一些语句就可以启动一个异步操作。在我的示例中,我只是省略了
部分代码,因为它不是必需的Task.Run()
- 为
返回特定于连接的任务时,检查该任务是有故障的。由于在返回此
对象时不太可能实际发生任何I/O,因此此逻辑几乎没有用处。对任务
的调用将抛出一个异常,该异常将传播到主线程的Wait()
调用,从而终止服务器。但是,如果发生任何其他错误,您不会终止服务器,因此不清楚您为什么要在这里这样做Wait()
- 有一个对
的虚假调用。我不知道你想在那里实现什么,但不管是什么,那句话都没有用。我只是把它拿走了Task.Yield()
- 在客户端代码中,只有在发送数据时才尝试从服务器接收数据。这是非常错误的;您希望客户机能够响应,并在数据发送给他们时立即接收数据。在我的版本中,我包含了一个简单的匿名方法,该方法被立即调用以启动一个单独的消息接收循环,该循环将与主用户输入循环异步并发执行
- 同样在客户端代码中,您在“退出”消息之后发送“…已离开…”消息,这将导致服务器关闭连接。这意味着服务器永远不会收到“…已离开…”消息。我颠倒了消息的顺序,因此“退出”始终是客户端发送的最后一个消息
class TcpHelper
{
class ClientData : IDisposable
{
private static int _nextId;
public int ID { get; private set; }
public TcpClient Client { get; private set; }
public TextReader Reader { get; private set; }
public TextWriter Writer { get; private set; }
public ClientData(TcpClient client)
{
ID = _nextId++;
Client = client;
NetworkStream stream = client.GetStream();
Reader = new StreamReader(stream);
Writer = new StreamWriter(stream);
}
public void Dispose()
{
Writer.Close();
Reader.Close();
Client.Close();
}
}
private static readonly object _lock = new object();
private static readonly List<ClientData> _connections = new List<ClientData>();
private static TcpListener listener { get; set; }
private static bool accept { get; set; }
public static async Task StartListener()
{
IPAddress address = IPAddress.Any;
int port = 5678;
listener = new TcpListener(address, port);
listener.Start();
Console.WriteLine("Server started. Listening to TCP clients on port {0}", port);
while (true)
{
var tcpClient = await listener.AcceptTcpClientAsync();
Console.WriteLine("Client has connected");
var task = StartHandleConnectionAsync(tcpClient);
if (task.IsFaulted)
task.Wait();
}
}
// Register and handle the connection
private static async Task StartHandleConnectionAsync(TcpClient tcpClient)
{
ClientData clientData = new ClientData(tcpClient);
lock (_lock) _connections.Add(clientData);
// catch all errors of HandleConnectionAsync
try
{
await HandleConnectionAsync(clientData);
}
catch (Exception ex)
{
// log the error
Console.WriteLine(ex.ToString());
}
finally
{
lock (_lock) _connections.Remove(clientData);
clientData.Dispose();
}
}
private static async Task HandleConnectionAsync(ClientData clientData)
{
Console.WriteLine("Client connected. Waiting for data.");
string clientmessage;
while ((clientmessage = await clientData.Reader.ReadLineAsync()) != null && clientmessage != "quit")
{
string message = "From " + clientData.ID + ": " + clientmessage;
Console.WriteLine(message);
lock (_lock)
{
// Locking the entire operation ensures that a) none of the client objects
// are disposed before we can write to them, and b) all of the chat messages
// are received in the same order by all clients.
foreach (ClientData recipient in _connections.Where(r => r.ID != clientData.ID))
{
recipient.Writer.WriteLine(message);
recipient.Writer.Flush();
}
}
}
Console.WriteLine("Closing connection.");
}
}
TcpHelper类
{
类ClientData:IDisposable
{
私有静态int_nextId;
public int ID{get;private set;}
公共TcpClient客户端{get;private set;}
公共文本阅读器{get;private set;}
公共文本编写器{get;private set;}
公共客户端数据(TcpClient客户端)
{
ID=_nextId++;
客户=客户;
NetworkStream=client.GetStream();
读卡器=新的流读卡器(流);
Writer=新的流Writer(流);
}
公共空间处置()
{
Writer.Close();
Reader.Close();
Client.Close();
}
}
私有静态只读对象_lock=new object();
私有静态只读列表_connections=new List();
专用静态TcpListener侦听器{get;set;}
私有静态bool接受{get;set;}
公共静态异步任务StartListener()
{
IPAddress地址=IPAddress.Any;
int端口=5678;
侦听器=新的TcpListener(地址、端口);
listener.Start();
WriteLine(“服务器已启动。正在端口{0}上侦听TCP客户端”,端口);
while(true)
{
var tcpClient=await listener.AcceptTcpClientAsync();
Console.WriteLine(“客户端已连接”);
var任务=StartHandleConnectionAsync(tcpClient);
if(task.IsFaulted)
task.Wait();
}
}
//注册并处理连接
专用静态异步任务StartHandleConnectionAsync(TcpClient TcpClient)
{
ClientData ClientData=新的ClientData(tcpClient);
锁定连接。添加(clientData);
//捕获HandleConnectionAsync的所有错误
尝试
{
等待HandleConnectionAsync(clientData);
}
捕获(例外情况除外)
{
//记录错误
Console.WriteLine(例如ToString());
}
最后
{
锁定(_lock)_连接。删除(clientData);
Dispose();
}
}
专用静态异步任务HandleConnectionAsync(ClientData ClientData)
{
Console.WriteLine(“客户端已连接。正在等待数据”);
字符串clientmessage;
while((clientmessage=wait clientData.Reader.readlinesync())!=null&&clientmessage!=“退出”)
{
string message=“From”+clientData.ID+:“+clientmessage;
class TcpHelper
{
class ClientData : IDisposable
{
private static int _nextId;
public int ID { get; private set; }
public TcpClient Client { get; private set; }
public TextReader Reader { get; private set; }
public TextWriter Writer { get; private set; }
public ClientData(TcpClient client)
{
ID = _nextId++;
Client = client;
NetworkStream stream = client.GetStream();
Reader = new StreamReader(stream);
Writer = new StreamWriter(stream);
}
public void Dispose()
{
Writer.Close();
Reader.Close();
Client.Close();
}
}
private static readonly object _lock = new object();
private static readonly List<ClientData> _connections = new List<ClientData>();
private static TcpListener listener { get; set; }
private static bool accept { get; set; }
public static async Task StartListener()
{
IPAddress address = IPAddress.Any;
int port = 5678;
listener = new TcpListener(address, port);
listener.Start();
Console.WriteLine("Server started. Listening to TCP clients on port {0}", port);
while (true)
{
var tcpClient = await listener.AcceptTcpClientAsync();
Console.WriteLine("Client has connected");
var task = StartHandleConnectionAsync(tcpClient);
if (task.IsFaulted)
task.Wait();
}
}
// Register and handle the connection
private static async Task StartHandleConnectionAsync(TcpClient tcpClient)
{
ClientData clientData = new ClientData(tcpClient);
lock (_lock) _connections.Add(clientData);
// catch all errors of HandleConnectionAsync
try
{
await HandleConnectionAsync(clientData);
}
catch (Exception ex)
{
// log the error
Console.WriteLine(ex.ToString());
}
finally
{
lock (_lock) _connections.Remove(clientData);
clientData.Dispose();
}
}
private static async Task HandleConnectionAsync(ClientData clientData)
{
Console.WriteLine("Client connected. Waiting for data.");
string clientmessage;
while ((clientmessage = await clientData.Reader.ReadLineAsync()) != null && clientmessage != "quit")
{
string message = "From " + clientData.ID + ": " + clientmessage;
Console.WriteLine(message);
lock (_lock)
{
// Locking the entire operation ensures that a) none of the client objects
// are disposed before we can write to them, and b) all of the chat messages
// are received in the same order by all clients.
foreach (ClientData recipient in _connections.Where(r => r.ID != clientData.ID))
{
recipient.Writer.WriteLine(message);
recipient.Writer.Flush();
}
}
}
Console.WriteLine("Closing connection.");
}
}
class Program
{
private const int _kport = 5678;
private static async Task clientConnect()
{
IPAddress address = IPAddress.Loopback;
TcpClient socketForServer = new TcpClient();
string userName;
Console.Write("Input Username: ");
userName = Console.ReadLine();
try
{
await socketForServer.ConnectAsync(address, _kport);
Console.WriteLine("Connected to Server");
}
catch (Exception e)
{
Console.WriteLine("Failed to Connect to server {0}:{1}", address, _kport);
return;
}
using (NetworkStream networkStream = socketForServer.GetStream())
{
var readTask = ((Func<Task>)(async () =>
{
using (StreamReader reader = new StreamReader(networkStream))
{
string receivedText;
while ((receivedText = await reader.ReadLineAsync()) != null)
{
Console.WriteLine("Server:" + receivedText);
}
}
}))();
using (StreamWriter streamwriter = new StreamWriter(networkStream))
{
try
{
while (true)
{
Console.Write(userName + ": ");
string clientmessage = Console.ReadLine();
if ((clientmessage == "quit") || (clientmessage == "QUIT"))
{
streamwriter.WriteLine(userName + " has left the conversation");
streamwriter.WriteLine("quit");
streamwriter.Flush();
break;
}
else
{
streamwriter.WriteLine(userName + ": " + clientmessage);
streamwriter.Flush();
}
}
await readTask;
}
catch (Exception e)
{
Console.WriteLine("Exception writing to server: " + e);
throw;
}
}
}
}
public static void Main(string[] args)
{
clientConnect().Wait();
}
}