4

I have implemented my Server application regarding this post here: http://www.codeguru.com/csharp/csharp/cs_network/sockets/article.php/c8781#Client1

Sum up: I am using async Sockets ala BeginAccept(..), BeginReceive(..). My Server is capable of handling mutliple clients and everything works fine until a client performas two or more synchronous send operation without waiting some time. The client does not get any error and so is not notified, that the server does not get the second message! If the client waits approx. 100ms after the first send operation, everything works fine. I thought that when i use TCP i can ensure that the server receives the message. (Except there is an exception thrown)! Could you provide me a solution to fix this.

Here are the WaitForData(..) & OnDataReceive(..) Methods that i implemented in the server

public void WaitForData(MyClient client)
{
    try
    {
        if (pfnCallBack == null)
        {
            pfnCallBack = new AsyncCallback(OnDataReceived);
        }

        iarResult = client.Socket.BeginReceive(client.DataBuffer,
                                                0, client.DataBuffer.Length,
                                                SocketFlags.None,
                                                pfnCallBack,
                                                client);
    }
    catch (SocketException se)
    {
        MessageBox.Show("SocketException@WaitForData" + se.Message);
    }
}
public void OnDataReceived(IAsyncResult asyn)
{
    try
    {
        MyClient user= (MyClient)asyn.AsyncState;
        int iRx = user.Socket.EndReceive(asyn);

        byte[] receivedData = user.DataBuffer;

        MemoryStream memStream = new MemoryStream();
        BinaryFormatter binForm = new BinaryFormatter();
        memStream.Write(receivedData, 0, receivedData.Length);
        memStream.Seek(0, SeekOrigin.Begin);
        MyMessage msg = (MyMessage)binForm.Deserialize(memStream);

        switch (msg.Command)
        {
            case (MyMessage.MyCommand.ConnId):
                this.connId = (int) msg.MyObject;
                tsslConnStatus.Text += " | ID: " + connId.ToString();
            break;

            case (MyMessage.MyCommand.Text):
                MessageBox.Show(msg.MyObject.ToString());
                break;
        }
        WaitForData(server);
    }
    catch (ObjectDisposedException ode)
    {
        MessageBox.Show("ObjectDisposedException@OnReceiveData" + ode.Message);
    }
    catch (SocketException se)
    {
        MessageBox.Show("SocketException@OnReceiveData" + se.Message);
    }
}

The CLIENT calls a synchronous SEND METHOD TWICE or MORE! server INSTANCEOF MyClient

if (server.Socket.Connected)
{
    BinaryFormatter bf = new BinaryFormatter();
    MemoryStream ms = new MemoryStream();
    bf.Serialize(ms, message);
    MyMessage = new MyMessage(something);
    server.Socket.Send(ms.ToArray());
}

so, i think this code snippets must be enough for you to get the idea i was trying to use! If you need further details or code snippets, just tell me i will post it!

Thanx!

Martin F.
  • 55
  • 2
  • 8
  • With the SerialPort.OnDataReceived handle I learned, that the handler is called, when the transtition "idle"->"received" occurd. I hat to read until no data is available. Probably something similar here. – harper May 09 '11 at 08:49
  • I would not expect to receive the complete message in the handler. Keep care for incomplete messages. – harper May 09 '11 at 08:53
  • to be honest, i don't know exactly how those "transitions" can be accessed and read? – Martin F. May 09 '11 at 08:54
  • Just a guess: I would try the Socket.Available property. – harper May 09 '11 at 08:59

1 Answers1

14

TCP is stream based and not message based. One Read can contain any of the following alternatives:

  • A teeny weeny part of message
  • A half message
  • Excactly one message
  • One and a half message
  • Two messages

Thus you need to use some kind of method to see if a complete message have arrived. The most common methods are:

  • Add a footer (for instance an empty line) which indicates end of message
  • Add a fixed length header containing the length of the message

Update

Simple example having just length as header.

Server side:

var buffer = binaryFormmater.Serialize(myobj);
var length = buffer.Length;
networkStream.Send(length);
networkStream.Send(buffer, 0, buffer.Length);

Client side:

var header = new buffer[4];
// TODO: You need to make sure that 4 bytes have been read.
networkStream.Read(header, 0, 4);
var length = BitConverter.ToInt32(buffer);

var readbuffer= new byte[65535];
var bytesLeft = length;
var messageStream = new MemoryStream();
while (bytesLeft > 0)
{
     var read = networkStream.Read(readbuffer, 0, bytesLeft);
     messageStream.Write(readbuffer, 0, read);
     bytesLeft -= read,
}

messageStream.Seek(0, SeekOrigin.Begin);
MyMessage msg = (MyMessage)binForm.Deserialize(messageStream);
jgauffin
  • 99,844
  • 45
  • 235
  • 372
  • +1 this answer is so correct I'd up-vote it twice if I could. Message-based application layer protocols are outside the scope of TCP. You can implement one yourself or use one of the many frameworks available. – Jake T. May 09 '11 at 09:26
  • well, thanks for your answer. but i don't know where to start right now. i can't do anything as long the OnReceiveData(..) method isn't called, and the problem is, that this method isn't called twice. furthermore, as you can see in the code, i do deserialize the whole receive buffer into a single MyMessage object without failures. If i am not wrong, there must be an exception if this stream contains furhter information of the second message? Mabe you can get me closer to what i actually have to do now! Thanx! – Martin F. May 09 '11 at 09:51
  • I would not do anything else before the mentioned problem is fixed. You might get two complete messages when you do `Read`, and since you only deserialize one the other one might be discarded. – jgauffin May 09 '11 at 09:57
  • okay, i see. didn't know that two messages could be within this one read event. however, can you post me some example of how such a fixed length or footer could be implemented the best way? The current MyMessage should be supported too, as far as i unsderstood it is to create the Ojbect MyMessage and then store the size of it. i would be very glad if you could post me some code on how to achieve this – Martin F. May 09 '11 at 11:03
  • thnx for the code, but what is the networkStream. I am working with the raw Socket class!? Is your code suitable for async Sockets? – Martin F. May 09 '11 at 11:45
  • it's a helper class. use socket.Send/socket.Receive instead. – jgauffin May 09 '11 at 11:50
  • but concernig socket.Receive, does this make sense after i do "int iRx = user.Socket.EndReceive(asyn); "?? – Martin F. May 09 '11 at 11:55
  • I used synchronous (Receive) methods in my example. You need to use the asynchronous methods (BeginReceive/EndReceive). – jgauffin May 09 '11 at 12:00
  • and how is it that you can assume the header to be within the first 4 array items? buffer[4]. what if the length is only a small value? – Martin F. May 09 '11 at 12:16
  • @Martin F: You are correct. I really can't assume that it's 4 bytes. You need to make sure that 4 bytes have been read. And no, I can't create a async version for you. I've told you what the problem is and how it can be solved. It's up to you to create a proper solution or switch to something easier like WCF. – jgauffin May 09 '11 at 12:27
  • Once again, thanks! I did now implement the solution for async sockets that works! I choosed the option with the header. now i take care of all possible receives that jgauffin mentiones above (a teeny message, half a message, ...) thanks to all of you for support! – Martin F. May 12 '11 at 11:55