为了练习,我想创建客户端和服务器应用程序来模拟大厅。
因此,在服务器应用程序中,我接受传入的连接,创建一个ClientInfo对象,包含TcpClient对象,用户名,id等,以及发送和接收数据的方法,并将ClientInfo对象存储在我的大厅的List中类。当用户执行聊天等操作时,邮件将被发送到服务器并广播给所有可用的客户端。
我遇到的问题是: 第一个客户连接。广播转到DefaultUser1。 第二个客户端连接。广播转到DefaultUser2 + DefaultUser2。
如您所见,第一个客户端不再接收数据,服务器也不能从他那里接收数据。不知何故,列表中的数据必须已损坏。以下是相关的代码:
接受传入的连接并创建ClientInfo对象并将其存储到大厅:
while (mWorking)
{
TcpClient client = mListener.AcceptTcpClient();
mNumberOfClients++;
Console.WriteLine("New Tcp-Connection with client: " + client.Client.LocalEndPoint.ToString());
ClientInfo newInfo = new ClientInfo(client, mNumberOfClients);
mLobby.AddClient(newInfo);
}
ClientInfo构造函数:
public ClientInfo(TcpClient client, int clientNumber)
{
mClient = client;
mClientNumber = clientNumber;
mUsername = "DefaultUser" + mClientNumber.ToString();
mStream = client.GetStream();
mEncoding = new ASCIIEncoding();
}
ClientInfo中的发送方法:
public void Send(String message)
{
mCurrentMessage = message;
Thread sendThread = new Thread(this.WriteTask);
sendThread.Start();
}
private void WriteTask()
{
byte[] data = mEncoding.GetBytes(mCurrentMessage);
byte[] sizeinfo = new byte[4];
sizeinfo[0] = (byte)data.Length;
sizeinfo[1] = (byte)(data.Length >> 8);
sizeinfo[2] = (byte)(data.Length >> 16);
sizeinfo[3] = (byte)(data.Length >> 24);
mStream.Write(sizeinfo, 0, sizeinfo.Length);
mStream.Write(data, 0, data.Length);
}
大厅课程中的相关代码:
private static List<ClientInfo> mClients;
private static processDel mProcessDel;
public Lobby(processDel del)
{
mProcessDel = del;
mClients = new List<ClientInfo>();
}
public void AddClient(ClientInfo client)
{
mClients.Add(client);
client.Listen(mProcessDel);
Broadcast("UJOIN§" + client.username + "$");
}
public void Broadcast(String message)
{
for (int i = 0; i < mClients.Count; i++)
{
Console.WriteLine("Broadcasting to " + mClients[i].username);
mClients[i].Send(message);
}
}
我也用foreach尝试了广播,结果相同。 processDel是我处理接收数据所需的委托方法。接收由每个客户的单独线程处理。
答案 0 :(得分:1)
作为猜测,您似乎误解了static
在C#中的含义。
static
表示方法或字段是类型的一部分,而不是类型的实例。因此,如果所有字段都是静态的,则实际上并没有任何实例数据,并且所有状态都在类的所有实例中共享 - 因此第二个客户端也会覆盖与第一个客户端关联的所有数据。解决方案很简单 - 只需删除static
,你就可以了。
除此之外,您的代码存在一些线程安全问题。 .NET中的大多数类型默认情况下不是线程安全的,您需要添加适当的锁定以确保保持一致性。这可能是CodeReview的一个主题,所以我只想到我想到的第一件事:
Send
始终启动一个新线程来发送消息。但是,这也意味着如果在恰当的条件下连续两次调用它,它可能会完全破坏您的TCP流 - 例如,第一个线程可能会写入长度数据,然后第二个线程在第一个写入之前写入其长度数据实际数据,你有麻烦。您也可能只发送第二条消息两次,因为您要传递文本以通过字段发送。List<T>
不是线程安全的。这意味着你只能从一个线程中安全地使用它 - 你的代码并不完全清楚,但似乎你可能遇到麻烦。使用像ConcurrentDictionary<IPEndPoint, ClientInfo>
这样的东西可能是一个更好的主意,但这实际上取决于你正在做什么。您还可以探索一些替代选项,例如使用异步I / O而不是垃圾邮件线程,但这是一个更高级的选项(请注意,多线程更糟糕:))。无论如何,线程安全的良好开端是http://www.albahari.com/threading/它有点长,但多线程 是一个非常复杂和危险的话题,它往往会产生很难的错误查找和重现,尤其是在调试器中运行时。