繁体   English   中英

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

[英]C# TCP Connection saving clients and broadcasting to them

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

因此,在服务器应用程序中,我接受传入连接,创建一个包含TcpClient对象,用户名,ID等以及发送和接收数据的方法的ClientInfo对象,并将该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是我处理接收到的数据所需的委托方法。 接收由每个客户端的单独线程处理。

猜测似乎是您误解了C#中static含义。

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM