Serializing object ready to send over TCPClient Stream

23,269

Solution 1

Assuming you have a class House (available on both sides of your connection) looking like this:

[Serializable]
public class House
{
    public string Street { get; set; }
    public string ZipCode { get; set; }
    public int Number { get; set; }
    public int Id { get; set; }
    public string Town { get; set; }
}

You can serialize the class into a MemoryStream. You can then use in your TcpClient connection like this:

// Create a new house to send house and set values.
var newHouse = new House
    {
        Street = "Mill Lane", 
        ZipCode = "LO1 BT5", 
        Number = 11, 
        Id = 1, 
        Town = "London"
    };  

var xmlSerializer = new XmlSerializer(typeof(House));
var networkStream = tcpClient.GetStream();
if (networkStream.CanWrite)
{
    xmlSerializer.Serialize(networkStream, newHouse);
}

Of course you have to do a little more investigation to make the program running without exception. (e.g. Check memoryStream.Length not to be greater than an int, a.s.o.), but I hope I gave you the right suggestions to help you on your way ;-)

Solution 2

First create a empty ServerApplication and ClientApplication as Console Application to simplify the example.

Then, put the definition for the serializable object into a separate assembly and then add a reference to the shared assembly to each project (server and client). Is necesary share the same object, not just an identical class copy.

To Generate DLL > Right clic in Solution 'ServerApplication' in the Solution Explorer > Add New Project... -> select Class Library (e.g. name this project MySharedHouse) Rename the default Class1 to House and complete it

[Serializable]
public class House
{
    public string Street { get; set; }
    public string ZipCode { get; set; }
    public int Number { get; set; }
    public int Id { get; set; }
    public string Town { get; set; }
}

enter image description here

Right clic in MySharedHouse and Build.

Now the dll is build and we need to add it in Server Project and Client Project. Right clic in ServerApplication > Add Reference > Browse and find the dll, for this example

Projects\ServerApplication\MySharedHouse\bin\Debug\MySharedHouse.dll

Repeat the process in ClientApplication using the same dll (same path).

Now you can use instances of House class in ServerApplication and ClientApplication as a single object, simply adding the sentence "using MySharedHouse" at the top.

SERVER CODE

using System;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using MySharedHouse;

namespace ServerApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            MessageServer s = new MessageServer(515);
            s.Start();
        }
    }

    public class MessageServer
    {
        private int _port;
        private TcpListener _tcpListener;
        private bool _running;
        private TcpClient connectedTcpClient;
        private BinaryFormatter _bFormatter;
        private Thread _connectionThread;

        public MessageServer(int port)
        {
            this._port = port;
            this._tcpListener = new TcpListener(IPAddress.Loopback, port);
            this._bFormatter = new BinaryFormatter();
        }

        public void Start()
        {
            if (!_running)
            {
                this._tcpListener.Start();
                Console.WriteLine("Waiting for a connection... ");
                this._running = true;
                this._connectionThread = new Thread
                    (new ThreadStart(ListenForClientConnections));
                this._connectionThread.Start();
            }
        }

        public void Stop()
        {
            if (this._running)
            {
                this._tcpListener.Stop();
                this._running = false;
            }
        }

        private void ListenForClientConnections()
        {
            while (this._running)
            {
                this.connectedTcpClient = this._tcpListener.AcceptTcpClient();
                Console.WriteLine("Connected!");
                House house = new House();
                house.Street = "Evergreen Terrace";
                house.ZipCode = "71474";
                house.Number = 742;
                house.Id = 34527;
                house.Town = "Springfield";
                _bFormatter.Serialize(this.connectedTcpClient.GetStream(), house);
                Console.WriteLine("send House!");
            }
        }
    }
}

CLIENT CODE

using System;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using MySharedHouse;

namespace ClientApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            MessageClient client = new MessageClient(515);
            client.StartListening();
        }
    }

    public class MessageClient
    {
        private int _port;
        private TcpClient _tcpClient;
        private BinaryFormatter _bFormatter;
        private Thread _listenThread;
        private bool _running;
        private House house;

        public MessageClient(int port)
        {
            this._port = port;
            this._tcpClient = new TcpClient("127.0.0.1", port);
            this._bFormatter = new BinaryFormatter();
            this._running = false;
        }

        public void StartListening()
        {
            lock (this)
            {
                if (!_running)
                {
                    this._running = true;
                    this._listenThread = new Thread
                        (new ThreadStart(ListenForMessage));
                    this._listenThread.Start();
                }
                else
                {
                    this._running = true;
                    this._listenThread = new Thread
                        (new ThreadStart(ListenForMessage));
                    this._listenThread.Start();
                }
            }
        }

        private void ListenForMessage()
        {
            Console.WriteLine("Reading...");
            try
            {
                while (this._running)
                {
                    this.house = (House)this._bFormatter.Deserialize(this._tcpClient.GetStream());
                    Console.WriteLine(this.house.Street);
                    Console.WriteLine(this.house.ZipCode);
                    Console.WriteLine(this.house.Number);
                    Console.WriteLine(this.house.Id);
                    Console.WriteLine(this.house.Town);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                Console.ReadLine();
            }
        }
    }
}

Wooala! the first house to be sent over TCP/IP

Solution 3

Your answer implies the following object (it is common practice to name classes using PascalCase):

[Serializable]
class House:ISerializable
{
    public string Street {get; set;}
    public string PostalCode {get; set;}
    public int HouseNumber {get; set;}
    public int HouseID {get; set;}
    public string City {get; set;}

    public House() { }
    protected House(SerializationInfo info, StreamingContext context)
    {
        if (info == null)
            throw new System.ArgumentNullException("info");
        Street = (string)info.GetValue("Street ", typeof(string));
        PostalCode = (string)info.GetValue("PostalCode", typeof(string));
        HouseNumber = (int)info.GetValue("HouseNumber", typeof(int));
        HouseID = (int)info.GetValue("HouseID", typeof(int));
        City = (string)info.GetValue("City", typeof(string));
    }

    [SecurityPermissionAttribute(SecurityAction.LinkDemand, 
        Flags=SecurityPermissionFlag.SerializationFormatter)]
    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) 
    {
        if (info == null)
            throw new System.ArgumentNullException("info");
        info.AddValue("Street ", Street);
        info.AddValue("PostalCode", PostalCode);
        info.AddValue("HouseNumber", HouseNumber);
        info.AddValue("HouseID", HouseID );
        info.AddValue("City", City);
    }
}

Now you can serialize your objects:

void Send(Stream stream)
{
    BinaryFormatter binaryFmt = new BinaryFormatter();
    House h = new House()
    {
        Street = "Mill Lane",
        PostalCode = "LO1 BT5",
        HouseNumber = 11,
        HouseID = 1,
        City = "London"
    };

    binaryFmt.Serialize(stream, h);
}

Solution 4

You can simply decorate your House class with the [Serializable] attribute. (You do not need to define all the other stuff as posted in the other answer)

You can then send this object on the wire by serializing it using the BinaryFormatter class.

Have you considered setting up a WCF service instead of using TcpListener and TcpClient ? Makes life a lot easier.

For instance you could define a service that returned a house

[ServiceContract]
public interface IService
{
    [OperationContract]
    House GetHouse(int houseId);
}

See this real world example.

Solution 5

How would you deserialize the xml House stream back to a House object on the receiving end? I'm refering to the solution given in Fischermaen's answer.

On my recieving end I can see a string representation in my Output window by using the following:

ASCIIEncoding encoder = new ASCIIEncoding();
            System.Diagnostics.Debug.WriteLine(encoder.GetString(message, 0, bytesRead));

Thank you in advance.

EDIT *

Ok well this solution has worked for me. Might need some tidying up.

Here's a method to deserialize a string:

public static T DeserializeFromXml<T>(string xml)
    {
        T result;
        XmlSerializer ser = new XmlSerializer(typeof(T));
        using (TextReader tr = new StringReader(xml))
        {
            result = (T)ser.Deserialize(tr);
        }
        return result;
    }

Then from my TPC/IP Recieving end I call the method like so:

TcpClient tcpClient = (TcpClient)client;
        NetworkStream clientStream = tcpClient.GetStream();


        byte[] message = new byte[4096];
        int bytesRead;

        while (true)
        {
            bytesRead = 0;

            try
            {
                //blocks until a client sends a message
                bytesRead = clientStream.Read(message, 0, 4096);
            }
            catch
            {
                //a socket error has occured
                break;
            }

            if (bytesRead == 0)
            {
                //the client has disconnected from the server
                break;
            }


            //message has successfully been received
            ASCIIEncoding encoder = new ASCIIEncoding();
            System.Diagnostics.Debug.WriteLine(encoder.GetString(message, 0, bytesRead));

            House house = DeserializeFromXml<House>(encoder.GetString(message, 0, bytesRead));

            //Send Message Back
            byte[] buffer = encoder.GetBytes("Hello Client - " + DateTime.Now.ToLongTimeString());

            clientStream.Write(buffer, 0, buffer.Length);
            clientStream.Flush();
        }

        tcpClient.Close();
    }
Share:
23,269
Luke
Author by

Luke

I'm a keen website and software developer. I am particularly knowledgeable in C#, .NET Core, WebAPI, HTML, CSS/SCSS, JavaScript, Powershell, Webpack and AWS.

Updated on September 19, 2020

Comments

  • Luke
    Luke over 3 years

    I've got a server and client set up using TcpListener and TcpClient.

    I want to send an object to my server application for processing.

    I've discovered the using System.Runtime.Serialization and the following documentation, but I didn't want to faff around to find that I'm doing it in long winded way.

    The question: What is the best way to process and send an object over the TCP stream?

    Sending and receiving.

    Here's an example of my object:

    // Create a new house to send
    house newHouse = new house();
    
    // Set variables
    newHouse.street = "Mill Lane";
    newHouse.postcode = "LO1 BT5";
    newHouse.house_number = 11;
    newHouse.house_id = 1;
    newHouse.house_town = "London";
    
  • L.B
    L.B over 12 years
    You can directly serialize to tcpClient's stream.
  • Fischermaen
    Fischermaen over 12 years
    @L.B: Thanks for your tip, I changed the sample in that way!