[英]C# TCP/IP simple chat with multiple-clients
I'm learning c# socket programming.我正在学习 c# 套接字编程。 So, I decided to make a TCP chat, the basic idea is that A client send data to the server, then the server broadcast it for all the clients online (in this case all the clients are in a dictionary).
所以,我决定做一个TCP聊天,基本思想是A客户端向服务器发送数据,然后服务器将它广播给所有在线的客户端(在这种情况下所有客户端都在一个字典中)。
When there is 1 client connected, it works as expected, the problem is occurred when there is more than 1 client connected.当有1个客户端连接时,它按预期工作,当连接超过1个客户端时出现问题。
Server:服务器:
class Program
{
static void Main(string[] args)
{
Dictionary<int,TcpClient> list_clients = new Dictionary<int,TcpClient> ();
int count = 1;
TcpListener ServerSocket = new TcpListener(IPAddress.Any, 5000);
ServerSocket.Start();
while (true)
{
TcpClient client = ServerSocket.AcceptTcpClient();
list_clients.Add(count, client);
Console.WriteLine("Someone connected!!");
count++;
Box box = new Box(client, list_clients);
Thread t = new Thread(handle_clients);
t.Start(box);
}
}
public static void handle_clients(object o)
{
Box box = (Box)o;
Dictionary<int, TcpClient> list_connections = box.list;
while (true)
{
NetworkStream stream = box.c.GetStream();
byte[] buffer = new byte[1024];
int byte_count = stream.Read(buffer, 0, buffer.Length);
byte[] formated = new Byte[byte_count];
//handle the null characteres in the byte array
Array.Copy(buffer, formated, byte_count);
string data = Encoding.ASCII.GetString(formated);
broadcast(list_connections, data);
Console.WriteLine(data);
}
}
public static void broadcast(Dictionary<int,TcpClient> conexoes, string data)
{
foreach(TcpClient c in conexoes.Values)
{
NetworkStream stream = c.GetStream();
byte[] buffer = Encoding.ASCII.GetBytes(data);
stream.Write(buffer,0, buffer.Length);
}
}
}
class Box
{
public TcpClient c;
public Dictionary<int, TcpClient> list;
public Box(TcpClient c, Dictionary<int, TcpClient> list)
{
this.c = c;
this.list = list;
}
}
I created this box, so I can pass 2 args for the Thread.start()
.我创建了这个框,所以我可以为
Thread.start()
传递 2 个参数。
Client:客户:
class Program
{
static void Main(string[] args)
{
IPAddress ip = IPAddress.Parse("127.0.0.1");
int port = 5000;
TcpClient client = new TcpClient();
client.Connect(ip, port);
Console.WriteLine("client connected!!");
NetworkStream ns = client.GetStream();
string s;
while (true)
{
s = Console.ReadLine();
byte[] buffer = Encoding.ASCII.GetBytes(s);
ns.Write(buffer, 0, buffer.Length);
byte[] receivedBytes = new byte[1024];
int byte_count = ns.Read(receivedBytes, 0, receivedBytes.Length);
byte[] formated = new byte[byte_count];
//handle the null characteres in the byte array
Array.Copy(receivedBytes, formated, byte_count);
string data = Encoding.ASCII.GetString(formated);
Console.WriteLine(data);
}
ns.Close();
client.Close();
Console.WriteLine("disconnect from server!!");
Console.ReadKey();
}
}
It is not clear from your question what problems specifically it is you are having.从您的问题中不清楚您遇到的具体问题是什么。 However, inspection of the code reveals two significant problems:
但是,检查代码发现了两个重大问题:
Here is a version of your code that addresses these two issues:这是解决这两个问题的代码版本:
Server code:服务器代码:
class Program
{
static readonly object _lock = new object();
static readonly Dictionary<int, TcpClient> list_clients = new Dictionary<int, TcpClient>();
static void Main(string[] args)
{
int count = 1;
TcpListener ServerSocket = new TcpListener(IPAddress.Any, 5000);
ServerSocket.Start();
while (true)
{
TcpClient client = ServerSocket.AcceptTcpClient();
lock (_lock) list_clients.Add(count, client);
Console.WriteLine("Someone connected!!");
Thread t = new Thread(handle_clients);
t.Start(count);
count++;
}
}
public static void handle_clients(object o)
{
int id = (int)o;
TcpClient client;
lock (_lock) client = list_clients[id];
while (true)
{
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[1024];
int byte_count = stream.Read(buffer, 0, buffer.Length);
if (byte_count == 0)
{
break;
}
string data = Encoding.ASCII.GetString(buffer, 0, byte_count);
broadcast(data);
Console.WriteLine(data);
}
lock (_lock) list_clients.Remove(id);
client.Client.Shutdown(SocketShutdown.Both);
client.Close();
}
public static void broadcast(string data)
{
byte[] buffer = Encoding.ASCII.GetBytes(data + Environment.NewLine);
lock (_lock)
{
foreach (TcpClient c in list_clients.Values)
{
NetworkStream stream = c.GetStream();
stream.Write(buffer, 0, buffer.Length);
}
}
}
}
Client code:客户端代码:
class Program
{
static void Main(string[] args)
{
IPAddress ip = IPAddress.Parse("127.0.0.1");
int port = 5000;
TcpClient client = new TcpClient();
client.Connect(ip, port);
Console.WriteLine("client connected!!");
NetworkStream ns = client.GetStream();
Thread thread = new Thread(o => ReceiveData((TcpClient)o));
thread.Start(client);
string s;
while (!string.IsNullOrEmpty((s = Console.ReadLine())))
{
byte[] buffer = Encoding.ASCII.GetBytes(s);
ns.Write(buffer, 0, buffer.Length);
}
client.Client.Shutdown(SocketShutdown.Send);
thread.Join();
ns.Close();
client.Close();
Console.WriteLine("disconnect from server!!");
Console.ReadKey();
}
static void ReceiveData(TcpClient client)
{
NetworkStream ns = client.GetStream();
byte[] receivedBytes = new byte[1024];
int byte_count;
while ((byte_count = ns.Read(receivedBytes, 0, receivedBytes.Length)) > 0)
{
Console.Write(Encoding.ASCII.GetString(receivedBytes, 0, byte_count));
}
}
}
Notes:笔记:
lock
statement to ensure exclusive access by a thread of the list_clients
object.lock
语句来确保list_clients
对象的线程进行独占访问。Box
object.Box
对象。 The collection itself is referenced by a static field accessible by all the methods executing, and the int
value assigned to each client is passed as the thread parameter, so the thread can look up the appropriate client object.int
值作为线程参数传递,因此线程可以查找适当的客户端对象。0
.0
完成的读取操作。 This is the standard socket signal used to indicate that the remote endpoint is done sending.Shutdown()
method.Shutdown()
方法指示它已完成发送。 To initiate the graceful closure, Shutdown()
is called with the "send" reason, indicating that the endpoint has stopped sending, but will still receive.Shutdown()
被调用并带有“send”原因,表明端点已停止发送,但仍将接收。 The other endpoint, once done sending to the first endpoint, can then call Shutdown()
with the reason of "both" to indicate that it is done both sending and receiving.Shutdown()
,原因是“both”,以表明它已完成发送和接收。 There are still a variety of issues in the code.代码中仍然存在各种问题。 The above addresses only the most glaring, and brings the code to some reasonable facsimile of a working demonstration of a very basic server/client architecture.
以上只解决了最明显的问题,并将代码带到了一个非常基本的服务器/客户端架构的工作演示的合理复制品中。
Addendum:附录:
Some additional notes to address follow-up questions from the comments:一些附加说明以解决评论中的后续问题:
Thread.Join()
on the receiving thread (ie waits for that thread to exit), to ensure that after it's starting the graceful closure process, it does not actually close the socket until the remote endpoint responds by shutting down its end.Thread.Join()
(即等待该线程退出),以确保在它开始正常关闭过程后,它实际上不会关闭套接字,直到远程端点通过关闭其结束来响应.o => ReceiveData((TcpClient)o)
as the ParameterizedThreadStart
delegate is an idiom I prefer over the casting of the thread argument.o => ReceiveData((TcpClient)o)
作为ParameterizedThreadStart
委托是我更喜欢转换线程参数的习惯用法。 It allows the thread entry point to remain strongly-typed.ThreadStart
delegate and just let the lambda expression capture the necessary method arguments: Thread thread = new Thread(() => ReceiveData(client)); thread.Start();
ThreadStart
委托使用构造函数重载,并让 lambda 表达式捕获必要的方法参数: Thread thread = new Thread(() => ReceiveData(client)); thread.Start();
Thread thread = new Thread(() => ReceiveData(client)); thread.Start();
Then, no casting at all has to happen (and if any arguments are value types, they are handled without any boxing/unboxing overhead…not usually a critical concern in this context, but still makes me feel better :) ).Control.Invoke()
(or Dispatcher.Invoke()
, in a WPF program);Control.Invoke()
(或Dispatcher.Invoke()
,在 WPF 程序中); a more sophisticated (and IMHO, superior) approach is to use async
/ await
for the I/O.async
/ await
。 If you are using StreamReader
to receive data, that object already has an awaitable ReadLineAsync()
and similar methods.StreamReader
接收数据,则该对象已经具有可等待的ReadLineAsync()
和类似方法。 If using the Socket
directly, you can use the Task.FromAsync()
method to wrap the BeginReceive()
and EndReceive()
methods in an awaitable.Socket
,则可以使用Task.FromAsync()
方法将BeginReceive()
和EndReceive()
方法包装在可等待对象中。 Either way, the result is that while the I/O occurs asynchronously, completions still get handled in the UI thread where you can access your UI objects directly.Thread.Join()
, to ensure you don't close the socket prematurely.) Thread.Join()
,以确保您不会过早关闭套接字。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.