0

I'm facing a weird trouble with sockets and threads. It's a simple multithread server-clients, a just wan't to send and receive messages.

Here's the server code:

public class Server
{
    Thread t;
    Thread listen;
    byte[] data1;
    byte[] data2;
    NetworkStream ns;
    TcpClient client;
    TcpListener newsock;

    public bool Running;

    List<ClStr> ListOfStreams = new List<ClStr>();
    List<ClientList> ListOfClients = new List<ClientList>();

    public Server()
    {
        Listen = new Thread(new ThreadStart(ExecuteThread));
        Listen.Start();
    }

    private void ExecuteThread()
    {
        newsock = new TcpListener(Globals.ServerPort);
        newsock.Start();
        Running = true;
        LoopClients();
    }

    public void LoopClients()
    {
        try
        {
            while (Running)
            {
                client = newsock.AcceptTcpClient();

                t = new Thread(new ParameterizedThreadStart(NewClient));
                t.Start(client);

                ClientList LCli = new ClientList(client);
                ListOfClientes.Add(LCli);
            }
        }
        catch (SocketException e)
        {
            MessageBox.Show(e.ToString());
        }
        finally
        {
            newsock.Stop();
        }
    }

    private void NewClient(object obj)
    {
        TcpClient client = (TcpClient)obj;
        ns = client.GetStream();

        ClStr Cli = new ClStr(ns);

        ListOfStreams.Add(Cli);

        string TicketDelivery = "TKT:" + Cli.Ticket.ToString();

        byte[] TKTbuffer = Encoding.ASCII.GetBytes(TicketDelivery);

        ns.Write(TKTbuffer, 0, TKTbuffer.Length);

        while (true)
        {
         int recv;
         data2 = new byte[200 * 1024];

         recv = ns.Read(data2, 0, data2.Length);

         if (recv == 0)
             break;
         MessageBox.Show(Encoding.ASCII.GetString(data2, 0, recv));
       }
    }

Note that every new client is a new thread. No problem until here.

See the client code.

public class Client
{
  Thread Starter;
  Socket server;
  byte[] data;
  string stringData;
  int recv;

  public int Ticket;

  public void Connect()
  {
      data = new byte[200 * 1024]; 

      IPEndPoint ipep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), Port); 
      server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 

      try
      {
          server.Connect(ipep); 
          Starter = new Thread(new ThreadStart(execute));
          Starter.Start();
      }
      catch (SocketException e)
      {
         MessageBox.Show("Unable to connect to server.");
         Starter.Abort();
         return;
      }
    }

    public void SendMessageToServer(string msg)
    {
        server.Send(Encoding.ASCII.GetBytes(msg)); 
    }

    private void execute()
    {
        while (true)
        {
                data = new byte[200 * 1024]; 
                recv = server.Receive(data); 

                stringData = Encoding.ASCII.GetString(data, 0, recv); 
                byte[] byteData = data;

                MessageBox.Show(stringData);
        }
    }

Now here's the problem:

I can only send an message to server (using the method SendMessageToServer) from the last client connected to server. If I add 3 clients, and try to send an message with first added, nothing happens! It seems that the client is not connected to the socket, but is not true, because I can send messages from the server with no problem.

So, considering that the server creates one thread per client, how can I send messages to server from any connected client?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
João Telles
  • 1
  • 1
  • 1
  • Can you provide a sample of how you're calling SendMessageToServer()? – Shar1er80 Apr 06 '15 at 21:16
  • I answered with the async solution, but if you don't want to use the async methods you might find some info here. For the record, i don't want to say that it's not possible to do it without async methods, but I never managed to get it working back in the day and so I was forced to learn the async way (which is actually pretty simple if you find yourself a simple solution) http://stackoverflow.com/a/19218867/1274820 – user1274820 Apr 06 '15 at 22:10
  • public void SendoMessageToServer (string msg) { server.Send(Encoding.ASCII.GetBytes(msg)); } – João Telles Apr 07 '15 at 00:59

1 Answers1

1

I really think you should use Async methods if you intend to make an async server.

This is a very basic async server that sends whatever it receives to all connected clients.

You can run it like this:

Server s = new Server(1338);
s.startListening();

Try connecting your client to it:

class Server
{
    private int port;
    private Socket serverSocket;
    private List<StateObject> clientList;
    private const int DEFAULT_PORT = 1338;

    public Server()
    {
        this.port = 1338; //default port
        serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        clientList = new List<StateObject>();
    }

    public Server(int port)
    {
        this.port = port;
        serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        clientList = new List<StateObject>();
    }

    public void startListening(int port = DEFAULT_PORT)
    {
        this.port = port;
        //Bind to our port
        serverSocket.Bind(new IPEndPoint(IPAddress.Any, this.port));
        //Listen on the port
        serverSocket.Listen(1);
        //Async Method to wait for a client to connect
        serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);
    }

    private void AcceptCallback(IAsyncResult AR)
    {
        try
        {
            //A client has connected so create a stateobject for them
            //This will hold their buffer and socket
            StateObject state = new StateObject();
            //This is where the socket is passed to our state
            state.workSocket = serverSocket.EndAccept(AR);
            //Add this client to our client list
            clientList.Add(state);
            //Async method to receive data from them (Non Blocking)
            state.workSocket.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), state);
            //Immediately go back to waiting for a new client (Non Blocking)
            serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);
        }
        catch { }
    }

    private void ReceiveCallback(IAsyncResult AR)
    {
        //A client has sent us data
        //We pass ourselves the state in our previous method
        //Now we can retrieve that StateObject from the callback
        StateObject state = (StateObject)AR.AsyncState;
        //Use this to get the socket so we can retrieve the data from it
        Socket s = state.workSocket;
        //Variable to hang on to the amount of data we received
        int received = 0;
        //Call the socket EndReceive Method
        received = s.EndReceive(AR);

        //If we received no data from the client, they have disconnected
        if (received == 0)
        {
            //Remove them from our clientlist
            clientList.Remove(state);
            //Tell the other clients that they have left
            foreach (StateObject others in clientList)
                others.workSocket.Send(Encoding.ASCII.GetBytes("Client disconnected " + s.LocalEndPoint));
            //We return here - this callback thread is now dead.
            return;
        }

        //If we received data
        if (received > 0) {
            //Append the data to our StringBuilder
            state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, received));
        }

        //Build the string and let's see if it contains a new line character
        string content = state.sb.ToString();

        //If the data has a new line character
        if(content.Contains("\n") && content.Length > 1)
        {
            //Send this data to every other client
            foreach (StateObject others in clientList)
                if (others != state) //Make sure we don't send it back to the sender
                    others.workSocket.Send(Encoding.ASCII.GetBytes(content.ToCharArray()));
            state.sb.Clear(); //Clear their StringBuilder so we can retrieve the next message
        }

        //Clear the temporary buffer
        Array.Clear(state.buffer, 0, StateObject.BufferSize);
        //And prepare to receive more data
        s.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
    }
}
class StateObject
{
    public Socket workSocket = null;
    public const int BufferSize = 1024;
    public byte[] buffer = new byte[BufferSize];
    public StringBuilder sb = new StringBuilder();
}

Asynchronous

Some explanation:

No problem at all. I didn't really understand them either so I steered clear of them for too long. Basically the callback methods are registered as events. When something triggers them (like receiving data), the server executes the function. Calling BeginAccept can be thought of as creating a new thread that waits for a connection before executing the linked method.

When it receives one, it executes the AcceptCallback method that we entered as a parameter. From there, AcceptCallback sets up BeginAccept again which waits for a new connection as well as BeginReceive which waits for data to be sent from the client.

Neither of these methods block execution (think of it as creating a new thread and starting it) and as soon as they are triggered their respective functions are called.

Each client is assigned a StateObject that contains their socket and a buffer, and these are put in a list that we can loop through and access.

The StateObject is passed as a parameter with the async calls so that we can tell which client sent the data and store it in a separate buffer.

Second edit:

Your client receive code looks okay. The server will not respond with data to the client sending data. If I sent the server "Hello!" it will forward that message to all other connected clients. If you want to try receiving something, connect 2 clients. Or, you can simply remove the line of code if (others != state) //Make sure we don't send it back to the sender

I wrote a client/server for chat purposes and the code can be found here:

Async chat server buffer issue

Community
  • 1
  • 1
user1274820
  • 7,786
  • 3
  • 37
  • 74
  • 1
    I really appreciate your answer, certainly I'll google it! But, could you explain to me (in very basics words) how async works? More specifically, I'm not well familliarized with Callback term.. to be honest, its the first time that I'll try to use it.. but thanks again – João Telles Apr 07 '15 at 01:05
  • I edited with some explanation. I'm on my phone, but I will write more if you want more of an explanation. – user1274820 Apr 07 '15 at 02:15
  • Its perfect.. I understood a bit more, thank you! I put your server code on my application, and my client sent an message that was successfully received by server. However, my client can't receive any messages from the server. Could you post some code example of a simple client? – João Telles Apr 07 '15 at 15:59
  • Your client receive code looks okay. The server will not respond with data to the client sending data. If I sent the server "Hello!" it will forward that message to all other connected clients. If you want to try receiving something, connect 2 clients. Or, you can simply remove the line of code `if (others != state) //Make sure we don't send it back to the sender` – user1274820 Apr 07 '15 at 16:25
  • 1
    Ok! Finally works! Thanks for your time! Helps me a lot buddy! – João Telles Apr 07 '15 at 18:42