John J. Hughes II wrote:
Using the below code I am send multiple sterilized object across an IP port.
This works fine if only one object is received at a time but with packing
sometimes there is more then one object or half an object in the received
data. If I place the data in a memory stream on the received side is there
a way to determine where one ends and the next one start?
Through _painstaking_ tracing and hours of debugging thinking that a
network card driver was buggy, I have found that:
- The SoapSerializer.Deserialize (sometimes) read more data than
SoapSerializer.Serialize generates (due to buffered reads).
- The BinarySerializer.Deserialize doesn't always read all the data
generated by BinarySerializer.Serialize, and will Close() the stream
when done deserializing!
So basically, you cannot send multiple serialized objects down the same
stream without being really lucky, especially after .NET-sp1.
SoapSerializer used to work most of the time before .NET-sp1.
My solution for sending multiple serialized objects, in a streaming way,
inside the same trasport-Stream is to implement a Stream that does
chunk-length en/de-coding of the amount of data written.
So I have a BlockSubStreamWriter, that writes a 16-bit length and then
the data to be written to the stream, and writes a 16-bit 0-length and
flushes to mark Close(), and a BlockSubStreamReader that reads 16-bit
length and then corresponding data, making EOF if the length is 0, and
"eating" the rest of stream untill a 0-length is received if the
BlockSubStreamReader is Close() or Dispose()'ed without having read a
0-length.
So now i can say
new BinarySerializer.Serialize(new BlockSubStreamWriter(stream), graph);
new BinarySerializer.Deserialize(new BlockSubStreamReader(stream)).
On top of this i have a 4-byte magic ID, so I can do
serialize/deserialize using multiple protocols from a simple interface:
public interface IObjectChannel: IDisposable
{
/// <summary>
/// Send object down the channel
/// </summary>
void SendObject(Object o);
/// <summary>
/// Read object from channel
/// </summary>
/// <exception cref="ObjectChannels.Closed">ObjectChannels.Closed
is thrown if the transport is closed
/// before any data is received</exception>
Object ReceiveObject();
}
I have a (rather pretty, if I should say it myself) implementation (and
some rather nifty handshaking and stuff) making up a transparent way of
shipping objects from one .NET instance to another, including extensive
unit and integration-tests.
Clients can now do (/actual code from test/)
using ( IObjectChannel c = ksio.ChannelPool.Global.Connect(host,port))
{
c.SendObject(command);
Object reply = c.ReceiveObject();
}
Which will reuse an existing connection if one is available, and GC
connections that are idle for more than a specific time (default: 1minute).
Servers simply do (/actual code from test - implements an "echo-service"/)
Socket s = ...;
try
{
using ( NetworkStream stream = new NetworkStream(s, true) )
while ( true )
using ( IObjectChannel c =
ObjectChannels.Auto.Global.Create(stream) )
c.SendObject(c.ReceiveObject());
}
catch ( ObjectChannels.MagicIdObjectChannel.Closed)
{
// That's the expected way to close
}
catch ( Exception e )
{
Console.WriteLine("UNEXPECTED END\n{0}", e);
}
The entire stuff (including a version that does gzip compression on the
data, thanks to ICSharpCode.SharpZipLib) is about 200kb worth of code,
..proj- and .sln-files, which I would be happy to publish if someone
wants to check it out.
--
Helge