Search code examples
c#socketstcp

Broadcasting message from server


I am learning sockets and currently; I am struggling with broadcasting message to all connected clients.

Server is simply starting up with this code:

public static int Main(String[] args)
{

    Thread t1 = new Thread(Test);
    t1.Start();


    #region Start listening to socket
    IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
    IPAddress ipAddress = ipHostInfo.AddressList[0];
    IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 11000);

    Socket listener = new Socket(ipAddress.AddressFamily,  SocketType.Stream, ProtocolType.Tcp);

    try
    {
        listener.Bind(localEndPoint);
        listener.Listen(100);

        Console.WriteLine("[{0}] Server started listening!", DateTime.Now);
        while (true)
        {
            // Set the event to nonsignaled state.  
            allDone.Reset();

            // Start an asynchronous socket to listen for connections.
            listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);

            // Wait until a connection is made before continuing.  
            allDone.WaitOne();
        }
    }
    catch (Exception e)
    {
        Console.WriteLine(e.ToString());
    }
    #endregion

    Console.WriteLine("\nPress ENTER to continue...");
    Console.Read();

    return 0;
}

There, I start socket to listen to if someone sends message.

As you can see, I have thread up there and I want that thread to send all connected users some message from time to time (for now all time few bytes just for testing).

Here is that method from thread

private static void Test()
{
    while (true)
    {
        for (int i = 0; i < Players.Length; i++)
        {
            if (Players[i] == null)
                continue;

            Socket cs = new Socket(Players[i].IPEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

            cs.Bind(Players[i].IPEndPoint);

            byte[] data = Encoding.ASCII.GetBytes("Random data");

            cs.Send(data);
        }
    }
}

This method is only last try but not successful. Problem is it drops me errors that are:

socket is reference of null.

In this case but in any case I couldn't manage to send message to each and every client.

Here is how I track client connection to server.

case "0": // Client tries to connect to server

    if (nClients >= MAX_PLAYERS)
    {
        Send(handler, "Too many players. Try again later.");
        return;
    }

    for (int i = 0; i < MAX_PLAYERS; i++)
    {
        if(Players[i] == null)
        {
            Players[i] = new Client();
            Players[i].Name = parts[1].ToString();
            Players[i].IPEndPoint = handler.RemoteEndPoint as IPEndPoint;

            Send(handler, String.Format("1|{0}", i));
            Console.WriteLine("[{0}] Succesfully registered client ID: {1}, NAME: {2}!", DateTime.Now, i, parts[1].ToString());
            i = MAX_PLAYERS;
            nClients++;
        }
    }
    break;

This is only part of code focused on handling connect message which is located inside my method:

private static void HandleMessages(string message, Socket handler)

and I call it from here:

public static void AcceptCallback(IAsyncResult ar)
{
    // Signal the main thread to continue.  
    allDone.Set();

    // Get the socket that handles the client request.  
    Socket listener = (Socket)ar.AsyncState;
    Socket handler = listener.EndAccept(ar);

    // Create the state object.  
    StateObject state = new StateObject();
    state.workSocket = handler;
    handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
}
public static void ReadCallback(IAsyncResult ar)
{
    String content = String.Empty;

    // Retrieve the state object and the handler socket  
    // from the asynchronous state object.  
    StateObject state = (StateObject)ar.AsyncState;
    Socket handler = state.workSocket;

    // Read data from the client socket.   
    int bytesRead = handler.EndReceive(ar);

    if (bytesRead > 0)
    {
        // There  might be more data, so store the data received so far.  
        state.sb.Append(Encoding.ASCII.GetString(
            state.buffer, 0, bytesRead));

        // Check for end-of-file tag. If it is not there, read   
        // more data.  
        content = state.sb.ToString();
        if (content.IndexOf("<EOF>") > -1)
        {
            string message = content.Remove(content.Length - 5, 5);
            HandleMessages(message, handler);
        }
        else
        {
            // Not all data received. Get more.  
            handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
            new AsyncCallback(ReadCallback), state);
        }
    }
}

Solution

  • While I cannot write all code for you I will teach you some patterns that should get you on track.

    I assume you are communicating over TCP. A socket represents a connection. If you want to send something you have to use the same Socket instance that you are using for receiving. Creating a new socket and Bind would be used to open another listening port (not applicable here).

    Probably, you want to have one object for each connection that tracks all related state. For example:

    class MyConnection {
     Socket socket;
     Player player;
    }
    

    Track instances of that class in a List<MyConnection>. You can then iterate through that list and use socket to send something.


    You can simplify the accept loop a lot. Here is a good pattern:

    while (true) {
     var connectionSocket = listeningSocket.Accept();
     Task.Run(() => ProcessConnection(connectionSocket));
    }
    

    All that async stuff in Microsofts sample code serves no purpose. There is only one accepting thread. Async is used to save threads. Saving one thread does not help. Also, blocking on events entirely negates the benefit of async anyway.

    If the number of connected clients is low (<100) and/or if you care about simple code you can simply get rid of all async IO and use synchronous IO (no Begin/End).

    Alternatively, you can use a Task-based wrapper for socket or use NetworkStream which has Task-based methods. You can then use await. This also gets rid of those callbacks which make writing simple logic very hard.

    We can work in the comments if this is not sufficient to help you.