3

I'm trying to send multiple different objects (well, they are the same object types, just with different values) from one socket to another. I can send one object just fine using the following code:

Client:

var buffer = new byte[1024];
var binFormatter = new BinaryFormatter();
using (var ms = new MemoryStream())
{
    int bytesRead;
    while ((bytesRead = _clientReceive.Receive(buffer)) > 0);
    {
        ms.Write(buffer, 0, bytesRead);
    } 

    ms.Position = 0;
    message = (Message)binFormatter.Deserialize(ms);
} 

server:

var gm = new Message { Message = "ObjectType", Object = Data };

using (var mem = new MemoryStream())
{
    var binFormatter = new BinaryFormatter();
    binFormatter.Serialize(mem, gm);
    var gmData = mem.ToArray();
    client.Send(gmData, gmData.Length, SocketFlags.None);
    client.Close();
}

but if I want to send multiple messages (put the full client code in a loop and not call client.Close()) how would I be able to determine when the full object has been received by the client? Putting the client code in a loop like this:

while (_isReceivingStarted)
{
    var buffer = new byte[1024];
    var binFormatter = new BinaryFormatter();
    using (var ms = new MemoryStream())
    {
        int bytesRead;
        while ((bytesRead = _clientReceive.Receive(buffer)) > 0)
        {
            ms.Write(buffer, 0, bytesRead);
        }
        ms.Position = 0;
        message = (Message) binFormatter.Deserialize(ms);
    }
    // Do stuff then wait for new message
}

the client will hang at _clientReceive.Receive(buffer) because it doesn't receive the Close() from the client and never get the 0 received bytes. If I close the connection on the server, it will loop through and then error out at the Deserialize MemoryStream instead of blocking at the_clientReceive.Receive(buffer) in anticipation of another object being sent.

I hope this makes sense. Any pointers?

joe_coolish
  • 7,201
  • 13
  • 64
  • 111

5 Answers5

3

I would highly recommend looking at Windows Communication Foundation. It will handle all of this plumbing for you, including serializing and deserializing your types across the wire, through multiple configurable channels, etc.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • Replacing a socket with wcf is jumping between extreme ends in terms of overheads etc – Marc Gravell Apr 11 '11 at 17:07
  • @Marc: True, to some extent - if overhead is very important in this scenario, then it could be an issue. Though given the current code (opening/closing connections and doing everything with synchronous socket code), the perceived overhead could easily be much lower... – Reed Copsey Apr 11 '11 at 17:10
  • I need quick communication with low overhead, otherwise WCF would be excellent. Most objects are 600-800 bytes, but every once in a while there will be an object that can get as big as 3 mb. I need to send the smaller ones at 20-100 per second. – joe_coolish Apr 11 '11 at 17:29
3

When using a socket like this, I would use something like protobuf-net instead of BinaryFormatter (caveat/disclosure: I wrote it) In particular, if you write each item with Serializer.SerializeWithLenthPrefix(...) using Base128 for the prefix-style (one of the options), and some known tag (the other option) such as 1, then at the other end you can just use DeserializeItems<Foo>(...) (again specifying Base128 and 1).

This will correctly pick off items without trying to over-read until the stream is closed, when it will yield break. Each object is returned (yield return) separately, so it won't try to consume the entire stream before giving you data.

It can also be used with heterogeneous data if you like, but homogeneous is easiest.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Hmmm... this sounds very much like what I want. I'll look into it and get back! thanks :) – joe_coolish Apr 11 '11 at 17:32
  • @Joe there is a sockets example in the quick-start project, IIRC – Marc Gravell Apr 11 '11 at 17:32
  • 1
    @Joe and for the record, protobuf is almost always much smaller bandwidth and CPU than BinaryFormatter – Marc Gravell Apr 11 '11 at 17:33
  • Thanks! I went through and it all worked great, but I talked with my professor (this is for a school project) and he said since I didn't write protobuf, I can't use it :( BUT! I will definitely be using this on other applications. The overhead is SO small! I was getting my 800 byte serialzed arrays down to <600, and my 3 objects were 2.5! Is that because it doesn't have to encode any of the names of the members? – joe_coolish Apr 11 '11 at 19:59
  • 2
    @Joe correct; now... I wonder if your prof realises you also didn't wrote BinaryFormatter, Stream, etc... Courses are bizarre environments, but the thing to look at here is "NIH". In professional work "NIH" is a costly attitude :) – Marc Gravell Apr 11 '11 at 21:25
  • lol, seriously. I remember a few years back in my first year of college we had to re-invent several pieces to the STL in C++ (which was good, dgmw) with the premise that we'd do it the hard way, then learn the easy way. Then when it came time to learn STL, the term was over... I still haven't learned about the STL from a professor. SOO glad this is my LAST CLASS!!! Thanks again for the great project. I'll be looking over the code in my down time. Glad to see someone still supporting .netCF! – joe_coolish Apr 12 '11 at 04:45
1

You might send a header message that informs the receiving end how many bytes to expect. This is similar to the Content-Length directive in HTTP. Other option, make a custom termination string you send at the end (obviously it can't appear bit for bit anywhere in the serialized objects' binary payload, which is why the former solution is what I'd do).

Aaron Anodide
  • 16,906
  • 15
  • 62
  • 121
0

Look into the TcpListener class:

http://msdn.microsoft.com/en-us/library/system.net.sockets.tcplistener.aspx

Or go with Reed's suggestion and look into WCF.

Merritt
  • 2,333
  • 21
  • 23
0

If you want to go this way, here's some advice:

  • Sending X number of bytes does not guarantee that you will receive them in one single Receive() on the client side. You could have a MemoryStream on the client and a buffer. Use the buffer for receiving and write to the MemoryStream until Receive returns 0.
  • When you are done sending data, use client.Shutdown (SocketShutdown.Send) (or .Both) before calling Close(). This will prevent a TCP RST on the client.
  • If you want to serialize multiple objects just serialize them one after the other in the server. Then the client will buffer all the incoming data and when Receive() returns 0, move the position in the client MemoryStream to 0 and begin deserializing objects one by one until ms.Position == ms.Length.
Gonzalo
  • 20,805
  • 3
  • 75
  • 78