Reputation: 698
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
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:
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:
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