user112016322
user112016322

Reputation: 698

Simple string messaging client/server protocol

I am looking for a way to implement a protocol in TCP for sending and receiving simple string messages. I have a client and a server app and when both start running for the first time I press the connect button on the client to connect to the server. Some text pops up on the server's listbox saying that a client has connected, and on the client's listbox some text appears saying that it is connected to the server.

That part works ok, but now I want to be able to send other strings to the server and depending on what string I send over would make the server perform a certain operation, how can I do this? First idea that comes to mind is to use if-then statements somewhere. I will post my code below:

Server:

    private static int port = 8080;
    private static TcpListener listener;
    private static Thread thread;


   private void Form1_Load(object sender, EventArgs e)
    {

        listener = new TcpListener(new IPAddress(new byte[] { 10, 1, 6, 130 }), port);
        thread = new Thread(new ThreadStart(Listen));
        thread.Start(); 


    }

   private void Listen()
    {
        listener.Start();
        listBox1.Invoke(new EventHandler(delegate { listBox1.Items.Add("Listening on: " + port.ToString()); }));


        while (true)
        {              
            listBox1.Invoke(new EventHandler(delegate { listBox1.Items.Add("Waiting for connection...."); }));
            TcpClient client = listener.AcceptTcpClient();
            Thread listenThread = new Thread(new ParameterizedThreadStart(ListenThread));
            listenThread.Start(client);

        }
    }

    //client thread 
    private void ListenThread(Object client)
    {

        NetworkStream netstream = ((TcpClient)client).GetStream();

        listBox1.Invoke(new EventHandler(delegate { listBox1.Items.Add("Request made"); }));


        byte[] resMessage = Encoding.ASCII.GetBytes("Connected to Server");



        netstream.Write(resMessage, 0, resMessage.Length);
        netstream.Flush();

    } 

Client:

 TcpClient tcpclnt;

 private void buttonConnect_Click(object sender, EventArgs e)
    {
        try
        {
            tcpclnt = new TcpClient();
            userEventBox.Items.Add("Connecting.....");

            try
            {

                tcpclnt.Connect("10.1.6.130", 8080);

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());


                return;
            }

            Stream stm = tcpclnt.GetStream();

            byte[] bb = new byte[100];
            int k = stm.Read(bb, 0, 100);

            string returndata = System.Text.Encoding.ASCII.GetString(bb);

            userEventBox.Items.Add(returndata);
            tabControl1.Enabled = true;


           //need a way for the server see this string
            string populateList = "Test";

            ASCIIEncoding asen = new ASCIIEncoding();

            byte[] ba = asen.GetBytes(populateList);
            stm.Write(ba, 0, ba.Length);              
        }

        catch (Exception ex)
        {
            Console.WriteLine("Error..... " + ex.StackTrace);
        }
    }

I am quite stuck and would appreciate any help you could provide on this subject. Thank you.

Source -> C# client-server protocol/model question

Upvotes: 1

Views: 6117

Answers (1)

Cole Campbell
Cole Campbell

Reputation: 4867

After you've established your connection, both the client and the server need to continuously monitor their NetworkStream objects for new data. When data is received, it should be appended to some kind of data buffer until you've received enough to constitute a complete message. Once you've received the appropriate amount of data, you can attempt to parse the message out of it and respond appropriately.

Essentially, you need to set up logic that looks something like this:

var data = new byte[1024];
var dataLength = 0;
var dataBuffer = new MyCustomDataBuffer();
while (true)
{
    while (stream.DataAvailable)
    {
        dataLength = stream.Read(data, 0, data.Length);
        dataBuffer.Append(data, dataLength);
        while (dataBuffer.ContainsCompleteMessage())
        {
            dataBuffer.ProcessMessage();
        }
    }
}

The control flow of this example is greatly simplified, but it should get the idea across. I also don't provide an implementation for MyCustomDataBuffer, but writing such a class isn't too complicated; all it really is is a stream of bytes.

How do we know if we've received a complete message? Well, that's the point of a protocol: to establish the rules that allow us to know these things. Let's consider a simple example of a data protocol, where each message consists of the same two parts:

  • 2 header bytes, which signify the total message size in bytes
  • An arbitrary number of bytes which constitute the message string

With this protocol, we know that all messages are at least two bytes long (the theoretical smallest valid message is 00 02, which represents an empty string). We also know that the first two bytes of a message tell us the message's total size, so we'll know when to stop reading data.

So, to implement the ContainsCompleteMessage() method from the code above, we need to:

  • Ensure that the data buffer has at least 2 bytes in it;
  • Convert those two bytes to an integer;
  • And determine if we have at least that many bytes in our data buffer.

Once we know that we have a valid message -- and we know how big it is -- all we have to do is chop the first N bytes off of our data buffer (where N is the message's size), then pull the message string out of the relevant segment of the message data:

// here, 'msg' is a byte array that contains just the message data
var msgString = Encoding.UTF8.GetString(msg, 2, msg.Length - 2);
switch (msgString)
{
    case "kill": KillServer(); break;
    // etc. 
}

The protocol I describe here will meet your stated need for sending short strings across the network. Generalizing it allows you to send more complicated objects; you just need to add additional metadata that specifies the layout of the message. I leave that as an exercise for the reader.

Upvotes: 4

Related Questions