C#TCP连接保存客户端并向其广播

时间:2016-03-26 14:00:02

标签: c# sockets tcp

为了练习,我想创建客户端和服务器应用程序来模拟大厅。

因此,在服务器应用程序中,我接受传入的连接,创建一个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是我处理接收数据所需的委托方法。接收由每个客户的单独线程处理。

1 个答案:

答案 0 :(得分:1)

作为猜测,您似乎误解了static在C#中的含义。

static表示方法或字段是类型的一部分,而不是类型的实例。因此,如果所有字段都是静态的,则实际上并没有任何实例数据,并且所有状态都在类的所有实例中共享 - 因此第二个客户端也会覆盖与第一个客户端关联的所有数据。解决方案很简单 - 只需删除static,你就可以了。

除此之外,您的代码存在一些线程安全问题。 .NET中的大多数类型默认情况下不是线程安全的,您需要添加适当的锁定以确保保持一致性。这可能是CodeReview的一个主题,所以我只想到我想到的第一件事:

  • Send始终启动一个新线程来发送消息。但是,这也意味着如果在恰当的条件下连续两次调用它,它可能会完全破坏您的TCP流 - 例如,第一个线程可能会写入长度数据,然后第二个线程在第一个写入之前写入其长度数据实际数据,你有麻烦。您也可能只发送第二条消息两次,因为您要传递文本以通过字段发送。
  • List<T>不是线程安全的。这意味着你只能从一个线程中安全地使用它 - 你的代码并不完全清楚,但似乎你可能遇到麻烦。使用像ConcurrentDictionary<IPEndPoint, ClientInfo>这样的东西可能是一个更好的主意,但这实际上取决于你正在做什么。

您还可以探索一些替代选项,例如使用异步I / O而不是垃圾邮件线程,但这是一个更高级的选项(请注意,多线程更糟糕:))。无论如何,线程安全的良好开端是http://www.albahari.com/threading/它有点长,但多线程 是一个非常复杂和危险的话题,它往往会产生很难的错误查找和重现,尤其是在调试器中运行时。