Broadcasting message from server

Viewed 2461

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

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.

Using:

  • using System.Net.Sockets;
  • using System.Threading;

Client Node class:

Create a class for storing clients info

public TcpClient tclient;
public byte[] Tx, Rx;
public string strId;
public string strName;

public ClientNode(TcpClient _tclient, byte[] _tx, byte[] _rx, string _str, string _name)
{
    tclient = _tclient;
    Tx = _tx;
    Rx = _rx;
    strId = _str;
    strName = _name;
}

public string ToStrng()
{
    return strName;
}

Complete Server: (Just create a class and paste below code in that server class)

bool mIsListening = false;
TcpListener mTCPListener;
private List<ClientNode> mlClientSocks;
TcpClient tcpc;
IPAddress IP;
int Port;
Thread t;

public void StartServer(string _IP, int _Port)
{
    IP = IPAddress.Parse(_IP);
    Port = _Port;

    t = new Thread(new ThreadStart(this.StartProcessing));
    t.Start();
}

public void StartProcessing()
{
    //Server is started
    mlClientSocks = new List<ClientNode>();

    try
    {
        mTCPListener = new TcpListener(IP, Port);
        //Server is running now
        mTCPListener.Start();
        mIsListening = true;
        mTCPListener.BeginAcceptTcpClient(onCompleteAcceptTcpClient, mTCPListener);
    }
    catch (Exception exx)
    {
        // Handle exception message hare
    }
}

void onCompleteAcceptTcpClient(IAsyncResult iar)
{
    TcpListener tcpl = (TcpListener)iar.AsyncState;
    TcpClient tclient = null;
    ClientNode cNode = null;
    if (!mIsListening)
    {
        //Stopped listening for incoming connections
        return;
    }

    try
    {
        tclient = tcpl.EndAcceptTcpClient(iar);
        //Client Connected...
        StreamReader sR = new StreamReader(tclient.GetStream());

        // Read the username (waiting for the client to use WriteLine())
        String username = (String)sR.ReadLine();

        tcpl.BeginAcceptTcpClient(onCompleteAcceptTcpClient, tcpl);

        lock (mlClientSocks)
        {
            // add newly connected client node in List
            mlClientSocks.Add((cNode = new ClientNode(
                tclient,
                new byte[512],
                new byte[512],
                tclient.Client.RemoteEndPoint.ToString(),
                username,
            )));
        }

        // broadcasting newly connected client to all other clients
        BroadcastClients("New client connected: " + username);

        tclient.GetStream().BeginRead(cNode.Rx, 0, cNode.Rx.Length, onCompleteReadFromTCPClientStream, tclient);
    }
    catch (Exception exc)
    {
        // handle exception here
    }
}

void onCompleteReadFromTCPClientStream(IAsyncResult iar)
{
    int nCountReadBytes = 0;
    string strRecv;
    ClientNode cn = null;

    try
    {
        lock (mlClientSocks)
        {
            tcpc = (TcpClient)iar.AsyncState;

            // find client from list
            cn = mlClientSocks.Find(x => x.strId == tcpc.Client.RemoteEndPoint.ToString());
            // check if client is connected
            if (IsConnected)
                nCountReadBytes = tcpc.GetStream().EndRead(iar);
            else
                nCountReadBytes = 0;

            //Disconnect Client if there is no byte
            if (nCountReadBytes == 0)
            {
                mlClientSocks.Remove(cn);
                return;
            }

            // read message recieved from client (node)
            strRecv = Encoding.ASCII.GetString(cn.Rx, 0, nCountReadBytes).Trim();

            /*

              Handle messages from clients

            */

            cn.Rx = new byte[512];

            tcpc.GetStream().BeginRead(cn.Rx, 0, cn.Rx.Length, onCompleteReadFromTCPClientStream, tcpc);


        }
    }
    catch (Exception)
    {
        lock (mlClientSocks)
        {
            //Client is Disconnected and removed from list
            mlClientSocks.Remove(cn);
        }
    }
}

private void onCompleteWriteToClientStream(IAsyncResult iar)
{
    try
    {
        TcpClient tcpc = (TcpClient)iar.AsyncState;
        tcpc.GetStream().EndWrite(iar);
    }
    catch (Exception exc)
    {
        // handle exception
    }
}

public void StopServer()
{
    StopListing();
}

public void StopListing()
{
    // stop server thread
    t.Interrupt();
    try
    {
        mIsListening = false;
        mTCPListener.Stop();
    }
    catch (Exception eee)
    {
        // handle exception
    }
}

public bool IsConnected
{
    get
    {
        try
        {
            if (tcpc != null && tcpc.Client != null && tcpc.Client.Connected)
            {
                // Detect if client disconnected
                if (tcpc.Client.Poll(0, SelectMode.SelectRead))
                {
                    byte[] buff = new byte[1];
                    if (tcpc.Client.Receive(buff, SocketFlags.Peek) == 0)
                    {
                        // Client disconnected
                        return false;
                    }
                    else
                    {
                        return true;
                    }
                }

                return true;
            }
            else
            {
                return false;
            }
        }
        catch
        {
            return false;
        }
    }
}

After creating sever class with above code now create a function (BroadcastClients) in that server class for broadcasting clients like:

void BroadcastClients(string BroadcastingMsg)
{
    if (mlClientSocks.Count() <= 0)
        return;
    else
    {
        ClientNode cn = null;

        mlClientSocks.ForEach(delegate (ClientNode clntN)
        {
            cn = clntN;

            try
            {
                // broadcasting online clients list
                cn.Tx = Encoding.ASCII.GetBytes(BroadcastingMsg);
                cn.tclient.GetStream().BeginWrite(cn.Tx, 0, cn.Tx.Length, onCompleteWriteToClientStream, cn.tclient);
            }
            catch (Exception e)
            {
                // handle exception 
            }
        });
    }
}

After creating above method now you can use it anywhere you want to broadcast message to clients.

Use above server class like this:

MyServer server = new MyServer();
server.StartServer("127.0.0.1",8000);

// For stopping server use this
// server.StopServer();
Related