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";
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 ;-)
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; }
}
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
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.
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);
}
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();
}
Related
I have having troubles sending objects back and forth (duplex communication) using named pipes. The code:
Dto:
[ProtoContract]
public class ServerResponse
{
[ProtoMember(1)]
public string FirstName { get; set; }
[ProtoMember(2)]
public string LastName { get; set; }
}
[ProtoContract]
public class ServerRequest
{
[ProtoMember(1)]
public string Name { get; set; }
[ProtoMember(2)]
public int Age { get; set; }
}
Server:
static void Main(string[] args)
{
Console.WriteLine("Starting Server");
StartServer();
Console.WriteLine("Press any key to stop server..");
Console.ReadKey();
}
static void StartServer()
{
Task.Factory.StartNew(() =>
{
var server = new NamedPipeServerStream("MyPipes");
server.WaitForConnection();
Console.WriteLine("Connected");
//StreamReader reader = new StreamReader(server);
//StreamWriter writer = new StreamWriter(server);
while (true)
{
//string line = reader.ReadLine();
//Console.WriteLine(line);
//writer.WriteLine(line.ToUpper());
//writer.Flush();
var request = Serializer.Deserialize<ServerRequest>(server);
Console.WriteLine($"Name: {request.Name}, Age: {request.Age}");
var response = new ServerResponse() { FirstName = request.Name, LastName = "Always this name!" };
Serializer.Serialize(server, response);
server.Flush();
}
});
}
Client:
static void Main(string[] args)
{
Console.WriteLine("Client");
var client = new NamedPipeClientStream("MyPipes");
client.Connect();
//StreamReader reader = new StreamReader(client);
//StreamWriter writer = new StreamWriter(client);
while (true)
{
//string input = Console.ReadLine();
//if (String.IsNullOrEmpty(input)) break;
//writer.WriteLine(input);
//writer.Flush();
//Console.WriteLine(reader.ReadLine());
string firstName = Console.ReadLine();
var request = new ServerRequest() { Name = firstName, Age = firstName.Length };
Serializer.Serialize(client, request);
client.Flush();
var response = Serializer.Deserialize<ServerResponse>(client);
Console.WriteLine($"Name: {response.FirstName}, Age: {response.LastName}");
}
Console.WriteLine("Done");
Console.ReadKey();
}
It works just fine, when sending a single line of string, but the protobuf fails on me.
For some reason, Deserialize method seems to never stop reading data from stream, therefore I am never able to decode the incoding server request. But if the client is forcefully stopped, then the request gets received.
I also tried using SteamWriter and StreamReader to no avail.. Is it possible to transmit protobuf objects in duplex communication? I would appreciate if somebody would point out what I am doing wrong..
protobuf is not a self-terminating data format; you need to use some kind of framing. Fortunately, the library includes a few basic implementations for your convenience, so : if you use SerializeWithLengthPrefix and DeserializeWithLengthPrefix (making sure to use the same configuration on both sides): it should work.
Without framing: the nature of protobuf is "read until the end of the stream", i.e. exactly what you're seeing. The reason for this is that protobuf is desiged to be concatenatable (is that a word?) even for single messages.
So I have a client which I don't have access to the source code of, the client is super basic though, it connects to my server, sends a string that looks something like this A8745783-K8757853 to my client.
Then my code receives it like this:
How do I store it as a string that looks like this A8745783-K8757853 I want my received hold the value of A8745783-K8757853and not A/06/05/02/06..
I am not sure why my server is storing it like that, that's what I am wondering and how do I properly receive a buffer as a string?
public class Connection
{
private const int Port = 12345;
public static List<string[]> CardList = new List<string[]>();
public static List<string[]> UserList = new List<string[]>();
public static List<string[]> ReceivedList = new List<string[]>();
//Make them private because we don't want to mess with these, it's "almost" like having them in a const form.. Keyword "Almost".
private static readonly IPAddress Ip = IPAddress.Parse("127.0.0.1");
private static TcpListener _listener;
private static TcpClient _client;
private static NetworkStream _nwStream;
public static int BytesAmount;
public static byte[] Buffer;
public Connection()
{
_listener = new TcpListener(Ip, Port);
}
public static string User { get; set; }
public static string Card { get; set; }
public static string UserData { get; set; }
public static string Received { get; set; }
public async Task StartListenAsync()
{
_listener.Start();
// This will sit in idle, waiting for a connection..
Console.WriteLine("Listening..");
// Assign the first connection to the client obj, we're doing this async.
_client = await _listener.AcceptTcpClientAsync();
_nwStream = _client.GetStream();
if (_client.Connected)
Console.WriteLine("Client has connected!");
// Keep listening for incoming data and split it.
while (true)
if (_nwStream.DataAvailable)
{
//long i = _nwStream.Length;
//Console.WriteLine(i);
Buffer = new byte[_client.ReceiveBufferSize];
BytesAmount = _nwStream.Read(Buffer, 0, _client.ReceiveBufferSize);
Received = Encoding.UTF8.GetString(Buffer, 0, BytesAmount);
string something = Received;
// Our IDE (Visual Studio (VS) knows to make this into a string array because we're calling split on a string
// So to not make it redundant we'll be fine by doing new[] which in this case represents a new string array)
var splitArray = Received.Split(new[] { "-" }, StringSplitOptions.None);
User = splitArray[0].Replace(" ", "");
Card = splitArray[1].Replace(" ", "");
Console.WriteLine("Testing 1: " + User);
Console.WriteLine("Testing 2: " + Card);
Console.WriteLine("Received: " + Received);
Console.WriteLine("Sending back : " + Received);
_nwStream.Write(Buffer, 0, BytesAmount);
}
}
}
So the issue was in the encoding, I ad to use
Unicode
Recieved = Encoding.Unicode.GetString(Buffer, 0, BytesAmount);
Instead of UTF8
I'm making a simple program that receive UDP Packets from another program and I want to cast this packet to a class.
I have a class :
public class Packet
{
public string MyFirstProperty {get; set;}
public string MySecondProperty {get; set;}
public string MyThirdProperty {get; set;}
public OtherObject[] ObjectArray {get; set}
}
The packet that I receive are bytes array. How can I transform the packet to a class. I've heard of marshalling but I'm not experienced enough to fully understand it.
What should I do.
Thank you.
To send an object from client to server (utilizing Json.Net) ; assuming you have already done your own research and have a working UDP client/server
Client site:
c1) Packet p = new Packet();
c2) //fill the properties
c3) string json = JsonConvert.SerializeObject(p);
c4) byte[] buf = Encoding.UTF8.GetBytes(json);
c5) Send *buf* to server
Server site:
s1) Receive data from client( call it *buf*)
s2) string json = Encoding.UTF8.GetString(buf); (must be equal to json at client site)
s3) Packet p = JsonConvert.DeserializeObject<Packet>(json);
s4) Tada........
Now you can use the same algorithm to send object from server to client
PS: As long as you can send and then receive the same byte array using UDP(c5 => s1), you can get the original object back.
You need to use Serialization and De-Serialization to convert back class objects from and to Byte Array.
Here is a sample example.
public class MyClass {
public int Id { get; set; }
public string Name { get; set; }
public byte[] Serialize() {
using (MemoryStream m = new MemoryStream()) {
using (BinaryWriter writer = new BinaryWriter(m)) {
writer.Write(Id);
writer.Write(Name);
}
return m.ToArray();
}
}
public static MyClass Desserialize(byte[] data) {
MyClass result = new MyClass();
using (MemoryStream m = new MemoryStream(data)) {
using (BinaryReader reader = new BinaryReader(m)) {
result.Id = reader.ReadInt32();
result.Name = reader.ReadString();
}
}
return result;
}
}
Link to MSDN for more on serialization
I have an object that I am sending to my server from my client on Unity. The server is able to reassemble the data into it's natural type and read from the properties.
Client MessagaData
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
[Serializable]
public class MessagaData
{
public int type { get; set; }
public string dataString { get; set; }
public string User { get; set; }
public string Password { get; set; }
}
Server MessagaData
using System;
namespace Server
{
[Serializable]
public class MessagaData
{
public int type { get; set; }
public string dataString { get; set; }
public string User { get; set; }
public string Password { get; set; }
}
}
When the user presses the login button it will first create a MessagaData object and assign it some properties.
public void sendLoginDetails()
{
if (string.IsNullOrEmpty (username.text))
return;
if (string.IsNullOrEmpty (password.text))
return;
MessagaData md = new MessagaData ();
md.type = 1;
md.User = username.text;
md.Password = password.text;
Client.Send (md);
}
Then the Static function is called: where md is passed.
static public void Send(MessagaData msgData)
{
if (singleton.client == null)
return;
string output = JsonConvert.SerializeObject(msgData);
byte[] data = new byte[1024];
NetworkStream ns = singleton.client.GetStream ();
data = Encoding.ASCII.GetBytes (output);
ns.Write (data, 0, data.Length);
ns.Flush ();
ReadThis ();
}
After sending the code the object, this function, ReadMe is invoked, where the error is obtained;
{"type":1,"dataString":null,"User":null,"Password":null}
Tracking the debugger on the code
throw CreateJsonReaderException("After parsing a value an unexpected character was encountered: {0}. Line {1}, position {2}.", currentChar, _currentLineNumber, _currentLinePosition);
it appears that what I receive from the server is something I Json canto read.
The Server:
private void myMethod(object obj)
{
int type = (int)obj;
MessagaData msgData = new MessagaData();
msgData.type = type;
string output = JsonConvert.SerializeObject(msgData);
byte[] data = new byte[1024];
NetworkStream ns = client.GetStream();
data = System.Text.Encoding.ASCII.GetBytes(output);
ns.Write(data, 0, data.Length);
ns.Flush();
}
Why does my client crash when a MessagaData is trying to be built?
EDIT -- 01/03/2016
1) What does your ReadMe() method look like?
public static void ReadThis()
{
if (singleton.client == null)
return;
NetworkStream ns = singleton.client.GetStream ();
byte[] data = new byte[1024];
ns.Read (data, 0, data.Length);
string output = System.Text.Encoding.ASCII.GetString(data);
Debug.LogWarning (output);
MessagaData md = JsonConvert.DeserializeObject<MessagaData>(output); //i am the error
switch(md.type)
{
case 1:
Debug.LogWarning ("Success");
break;
case 2:
Debug.LogWarning ("Failed");
//Thread.Sleep (500);
break;
}
}
Since it's the server where the exception is thrown, can you share that code?
The exception is thrown in the client side of the code, in the ReadMe() method
2) Can you give the full ToString() output of the exception including the message and traceback? It would be helpful to know the current character, line number, and line position, and stack trace inside Json.NET.
This is on the client;
try {
MessagaData md = JsonConvert.DeserializeObject<MessagaData> (output);
switch(md.type)
{
case 1:
Debug.LogWarning ("Success");
break;
case 2:
Debug.LogWarning ("Failed");
//Thread.Sleep (500);
break;
}
} catch (System.Exception ex) {
Debug.Log (ex.ToString ());
}
Debug shows:
> Newtonsoft.Json.JsonReaderException: After parsing a value an unexpected character was encountered:
UnityEngine.Debug:Log(Object)
Client:ReadThis() (at Assets/Scripts/Client.cs:73)
Client:Send(MessagaData) (at Assets/Scripts/Client.cs:45)
SendLoginInformation:sendLoginDetails() (at Assets/Scripts/SendLoginInformation.cs:26)
UnityEngine.EventSystems.EventSystem:Update()
3) Is the server using unity3d also, or using Microsoft .Net?
The server is C# Microsoft .NET If you like I can zip it and upload it since it is a very small program
To DBC.
So I am just trying to create a basic Stack Overflow Client using WebClient. When I run the program as is, I get an empty string result, even if I sleep and wait. However when I open up Fiddler2 the program works... All I have to do is open Fiddler... Here is the relevant code.
public partial class MainWindow : Window
{
public ObservableCollection<question> questions { get; set; }
public MainWindow()
{
questions = new ObservableCollection<question>();
this.DataContext = this;
InitializeComponent();
}
void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
MessageBox.Show(e.Result); //Right here is the difference. When
<BREAK POINT HERE OR IT BREAKS>
string data = data = e.Result.Substring(e.Result.IndexOf("class=\"question-summary narrow\"") + 31);
string content = data.Substring(0, data.IndexOf("class=\"question-summary narrow\""));
string v, a, t, b, tgs, link;
questions.Add(new question
{
//votes = v,
//answers = a,
//title = t.ToUpper(),
//body = b,
////tags = tgs
//href = link
});
}
private void button1_Click(object sender, RoutedEventArgs e)
{
WebClient wc = new WebClient();
wc.DownloadStringAsync(new Uri(#"http://api.stackoverflow.com/1.1/questions"));
wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
}
}
public class question
{
public string votes { get; set; }
public string answers { get; set; }
public string title { get; set; }
public string body { get; set; }
public string tags { get; set; }
public string href { get; set; }
}
Also worth noting is the fidler results
When I load http://api.stackoverflow.com/1.1/questions in the browser fiddler shows
GET http://api.stackoverflow.com/1.1/questions
200 OK (application/json)
and
GET http://api.stackoverflow.com/favicon.ico
503 Service Unavailable (text/html)
When I load it in my program though only this shows
GET http://api.stackoverflow.com/1.1/questions
200 OK (application/json)
Looks like the problem is with the API itself. Even though you are not telling it that you accept GZipped content, it's GZipping it anyway, and apparently Fiddler deals with that and unzips it for you. In your app, you have to deal with this by unzipping the content. Here's a simple example of how to do that:
var wc = new WebClient();
var bytes = wc.DownloadData(new Uri(#"http://api.stackoverflow.com/1.1/questions"));
string responseText;
using (var outputStream = new MemoryStream())
{
using (var memoryStream = new MemoryStream(bytes))
{
using (var gzip = new GZipStream(memoryStream, CompressionMode.Decompress))
{
byte[] buffer = new byte[1024];
int numBytes;
while ((numBytes = gzip.Read(buffer, 0, buffer.Length)) > 0)
{
outputStream.Write(buffer, 0, numBytes);
}
}
responseText = Encoding.UTF8.GetString(outputStream.ToArray());
}
}
Console.WriteLine(responseText);
Whether or not it will always be GZipped, who knows - you can check the Content-Encoding HTTP header to see if it's gzip, and if so, then run this code, and if not, then you can convert the bytes directly into text.