Jump to content
  • Advertisement
Sign in to follow this  
ChristianPena

[.net] Serializing to Network Stream Asynchronously

This topic is 4557 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi all, I am playing around with networking in .NET and making alot of headway. I am having the following problem, however. When I use a BinaryFormatter to serialize an object to the NetworkStream from a TcpClient, I am able to deserialize the object on the other side of the wire. Unfortunately, this is done through a synchronous blocking call as far as I can tell. In order to do an asynchronous call, I want to serialize an object to a byte array and send that across in an asynchronous BeginWrite on the TcpClient's NetworkStream. When I serialize the object to a MemoryStream, it serializes it to a 256 byte array. I send that byte array through the BeginWrite function on the NetworkStream. The first object is deserialized appropriately. Subsequent objects sent across are not deserialized properly because there appear to be extra bytes with a value of 0 at the end of the array for the prior serialized object. These bytes are not consumed off the stream by the deserialization. As a result, the deserialization fails because the Header is all 0's. Has anyone else come across the need to send serialized objects through asynchronous calls on a NetworkStream? If so, how did you get around this? Thanks in advance.

Share this post


Link to post
Share on other sites
Advertisement
I am hoping that by posting the code I will get some help or hints.

Place the Server and Client classes in seperate console applications. Place the Message class in an object library and reference it from each of the console applications.


// Server.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;
using Objects;
using System.Runtime.Serialization.Formatters.Binary;

namespace Server
{
class Server
{
// List of connect clients.
private List<TcpClient> mClients;

public Server()
{
mClients = new List<TcpClient>();
}

public List<TcpClient> Clients
{
get
{
return mClients;
}
}

public void OnClientConnect(IAsyncResult result)
{
// AsynCallback that handles a client connection

// Get the listener
TcpListener listener = result.AsyncState as TcpListener;

// Get the client
TcpClient client = (listener).EndAcceptTcpClient(result);
lock (mClients)
{
// Add the client to list of clients
mClients.Add(client);
}

Console.WriteLine("Connected.");
// Send a message to welcome the client.
client.GetStream().Write(Encoding.ASCII.GetBytes("Cat"), 0, 3);
Console.WriteLine("Message delivered to client.");

// Go back into wait mode waiting for a new connection
Console.WriteLine("Waiting for a connection...");
listener.BeginAcceptTcpClient(this.OnClientConnect, listener);
}

static void Main(string[] args)
{
// Create a listener on port 27800 and start it
IPHostEntry myiHe = Dns.GetHostEntry(Dns.GetHostName());
IPAddress myIp = myiHe.AddressList[0];
TcpListener listener = new TcpListener(myIp, 27800);
listener.Start();
Console.WriteLine("Server online...");

// Create the server
Server s = new Server();

// Start the non-blocking wait for connections
listener.BeginAcceptTcpClient(s.OnClientConnect, listener);

// Used to deserialize the messages
BinaryFormatter formatter = new BinaryFormatter();

// The main processing loop will receive messages from each client
while (true)
{
// Lock the current clients and copy to an array
TcpClient[] clients = null;
lock (s.Clients)
{
clients = s.Clients.ToArray();
}

// Get a message from each client
foreach (TcpClient c in clients)
{
if (c.Connected)
{
try
{
// Get the message
Message m = (Message)formatter.Deserialize(c.GetStream());
if (m == null)
{
// Close the connection if the message is null
c.Close();
}
else
{
Console.WriteLine(m.MessageType.ToString() + " " + m.Parameters[0] + " " + m.Parameters[1]);
}
}
catch (Exception e)
{
// Drop that fool
Console.WriteLine(e.GetType());
Console.WriteLine("Error: " + e.Message);
c.Close();
}
}
else
{
// Remove the client
Console.WriteLine("Client disconnected..");
lock (s.Clients)
{
s.Clients.Remove(c);
}
}
}
clients = null;
}
}
}
}





// Client.cs

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
using Objects;

namespace Client
{
public class Client
{
public Client()
{

}

public void OnEndSend(IAsyncResult result)
{
// AsyncCallback to handle the end of an asynchronous send

// Get the stream and end the write
Console.WriteLine("Finished sending");
NetworkStream stream = result.AsyncState as NetworkStream;
stream.EndWrite(result);
stream.Flush();
}

static void Main(string[] args)
{
// The client that handles connections
Client client = new Client();

// The tcp connection to the server
TcpClient c = new TcpClient();
IPHostEntry myiHe = Dns.GetHostEntry(Dns.GetHostName());
IPAddress myIp = myiHe.AddressList[0];
//c.NoDelay = true;
string error = "";

try
{
//
c.Connect(myIp, 27800);
}
catch (Exception e)
{
error = e.Message;
}

if (c.Connected)
{
Message m = new Message();
Message m2 = new Message();
m.MessageType = 0;
m.Parameters = new object[] { "Cat", "Dog", 4 };

Console.WriteLine("Connected...");
byte[] bytes = new byte[3];
c.GetStream().Read(bytes, 0, 3);
Console.WriteLine(Encoding.ASCII.GetChars(bytes));

BinaryFormatter formatter = new BinaryFormatter();
while (true)
{
m.MessageType++;

try
{
MemoryStream memStream = new MemoryStream();
formatter.Serialize(memStream, m);
formatter.Serialize(memStream, m);
byte[] buffer = memStream.GetBuffer();
Console.WriteLine("Serialized to " + buffer.Length.ToString()
+ " bytes with capacity " + memStream.Capacity + ".");
c.GetStream().BeginWrite(buffer, 0, buffer.Length,
client.OnEndSend, c.GetStream());

Thread.Sleep(1000);
}
catch (Exception e)
{
c.Close();
Console.WriteLine("Error: " + e.Message);
break;
}
}
c.Close();
}
else
{
Console.WriteLine("Unable to connect [" + error + "]");
}
c = null;
}
}
}





// Message.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace Objects
{
[Serializable]
public class Message
{
public int MessageType;
public object[] Parameters;
}
}


Share this post


Link to post
Share on other sites
I gave the code a try and sure enough it does cause the error you mention.
However on looking into it, the error is actually pretty simple. Just a little logical mistake you made...

The memory stream class will keep an interneal byte[], and it will expand it as it needs to. So the byte[] will increase in size by 2x each time it is expanded. So 64 bytes, 128bytes, 256, 512.. and so on.
The two serialized messages were 384 bytes, so the buffer of the memory stream was 512 bytes. When you do this call here:


c.GetStream().BeginWrite(buffer, 0, buffer.Length,
client.OnEndSend, c.GetStream());


you are passing in the 512 byte array, and also the number '512' as the length. This is the problem. you need to specify (int)memStream.Length as the length arguement. ie,


c.GetStream().BeginWrite(buffer, 0, (int)memStream.Length,
client.OnEndSend, c.GetStream());


I tried it like this and it worked fine.

Share this post


Link to post
Share on other sites
Where'd you get that juicy little tidbit of information? :)

You just saved me an indescribable amount of frustration. I was considering how I could iterate through the byte array and determine where the end of the serialized data is, but that would be dangerous and most likely unreliable.

I really appreciate your help.

So... Is this an efficient method for sending data across? I am guessing that I should be preallocating a fair amount of space. In any case, it seems that I should probably be doing custom serialization since the data seems to grow in size when serialized. What do you think?



Share this post


Link to post
Share on other sites
no it's not perticuarly efficient way. Serialization does have a fairly big overhead for each object. But, then again, it works, so it's not too bad.

You can also look into XML serialization. It's no where near as useful, because it only works on public properties, but it will produce a smaller output. Then you can whack it through the GZIP compressor in System.IO.Compression, which should make it pretty small. There are, of course, even better ways, but they get complicated fast.
If the objects you are sending are really simple (just states, for example), then you can probably just make use bytes, shorts, ints etc to represent them. But, this is much harder to expand on.


The reason the memory stream doubles the size of it's internal data buffer is pretty simple; it's a performance thing. Having to possibly reallocate the buffer for every byte added would be pretty diasterous [smile]

Just keep an eye on the amount of data being sent, 384 bytes isn't too much if you are only sending that amount ever second or so, but once you are getting into more than a kilobyte per second, then you really need to start thinking about what you are sending. You can try using compression with serialization, it will help but of course be slower.

There may be a way to do data only serialization (ie, no things like public key tokens, structure, etc) but I've never looked into this.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!