简体   繁体   中英

C# TCP Connection saving clients and broadcasting to them

For practicing I wanted to create client and server applications to simulate a lobby.

Therefore, in the server-application I accept incoming connections, create a ClientInfo object containing the TcpClient object, usernames, id, etc. and the methods for sending and receiving data, and store that ClientInfo object in a List in my lobby class. When the user does something like chatting, the message is being sent to the server and broadcasted to all available clients.

The problem I have is: The first client connects. Broadcasts go to DefaultUser1. The second client connects. Broadcasts go to DefaultUser2 + DefaultUser2.

As you can see, the first Client is not receiving data anymore, nor can the Server receive data from him. Somehow the data in the list must be corrupted. Here is the relevant bit of code:

Accepting incoming conenctions and creating the ClientInfo object and storing it to the lobby:

 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);
            }

The ClientInfo constructor:

public ClientInfo(TcpClient client, int clientNumber)
    {
        mClient = client;
        mClientNumber = clientNumber;
        mUsername = "DefaultUser" + mClientNumber.ToString();
        mStream = client.GetStream(); 

        mEncoding = new ASCIIEncoding();
    }

The sending method in 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);
    }

Relevant code in the lobby class:

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);
        }
    }

I also tried the broadcasting with foreach, same result. The processDel is a delegate method i need for processing the received data. Receiving is handled by a seperate thread for each client.

As a guess, it seems that you misunderstood what static means in C#.

static means that the method or field is part of the type, rather than the instance of a type. So if all of your fields are static, you don't actually have any instance data, and all the state is shared across all instances of your class - so the second client overwrites all the data associated with the first client as well. The solution is simple - just remove the static s, and you should be fine.

Other than that, your code has some thread-safety issues. Most types in .NET are not thread-safe by default, and you need to add appropriate locking to make sure that consistency is maintained. This is more of a topic for CodeReview, perhaps, so I'll just note the first things that come to mind:

  • Send always starts a new thread to send the message. However, this also means that if it's called twice in succession under just the right conditions, it can completely corrupt your TCP stream - for example, the first thread might write the length data, then the second writes its length data before the first writes the actual data and you're in trouble. It's also possible that you'd just send the second message twice, since you're passing the text to send through a field.
  • List<T> isn't thread-safe. That means that you can only safely use it from a single thread - it's not entirely clear from your code, but it seems like you might have trouble with that. Using something like ConcurrentDictionary<IPEndPoint, ClientInfo> might be a better idea, but that really depends on what you're doing.

You could also explore some alternative options, like using asynchronous I/O instead of spamming threads, but that's a bit more advanced option (mind you, multi-threading is even worse :)). Regardless, a good start for thread-safety would be http://www.albahari.com/threading/ It's somewhat long, but multi-threading is a very complex and dangerous topic, and it will tend to produce errors that are hard to find and reproduce, especially while running in a debugger.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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