How to handle incoming protobuf message - c#

Using TCPClient's NetworkStream and protobuf-net I send and receive protobuf messages via TCP.
Saw a similar question: How to properly handle incoming protobuf message with a NetworkStream?
But in my case there can only be one message type so i dont think i need a resolver.
So i serialized my object and send it using tcp/ip, on my server i try to deserialize it and get io exception: Unable to read data from the transport connection.
Client:
...
using (var ms = new MemoryStream())
{
Serializer.Serialize(ms, person);
data = ms.ToArray();
}
NetworkStream stream = client.GetStream();
stream.Write(data, 0, data.Length);
Server:
...
Byte[] bytes = new Byte[256];
String data = null;
while(true)
{
Console.Write("Waiting for a connection... ");
TcpClient client = server.AcceptTcpClient();
Console.WriteLine("Connected!");
data = null;
NetworkStream stream = client.GetStream();
Person newPerson = Serializer.Deserialize<Person>(stream);<--- exeption
}

I think the short version here is: use SerializeWithLengthPrefix and DeserializeWithLengthPrefix. The default protobuf behaviour is "read to the end of the stream". You shouldn't need the MemoryStream when serializing, btw; you should be fine to Serialize directly to the NetworkStream. If you need the MemoryStream for other reasons, you can save yourself a copy of the data by using:
stream.Write(ms.GetBuffer(), 0, (int)ms.Length);

Related

How to know the size of the information sent through a socket (serialized object)

I am implementing a client-server communication. I need to send a response to the client whose size may vary, because it contains a serialized array:
[Serializable]
public struct ServerResponse
{
public ApplicationAction ApplicationAction { get; set; }
public Product[] AssociatedProducts { get; set; }
}
What I am currently doing is serializing my instance of ServerResponse to XML using XmlSerializer (it wouldn't bother me at all to use another method my data doesn't need to be human-readable) and writing it on the socket stream like so:
XmlSerializer xmlSerializer = new XmlSerializer(typeof(ServerResponse));
NetworkStream networkStream = client.GetStream();
if (networkStream.CanWrite)
{
xmlSerializer.Serialize(networkStream, response);
}
My problem is that the client does not know the size of the data it needs to read.
My guess is to first send the size of the data and then all the data, but to do that I need to know
how can I get the actual size of a serialized object ?
Thanks
Befor you will send any data you should serialize your Stream to the memory using MemoryStream. Then you can take its lenght and multiply by sizeof(byte)
Your code will look something like this:
XmlSerializer xmlSerializer = new XmlSerializer(typeof(JsonQuestion));
NetworkStream networkStream = client.GetStream();
MemoryStream memoryStream = new MemoryStream();
xmlSerializer.Serialize(memoryStream, response);
var length = memoryStream.Length * sizeof(byte);
//Send size
...
//Send data
if (networkStream.CanWrite)
{
memoryStream.CopyTo(networkStream);
}

Read bytes from NetworkStream (Hangs)

I'm trying to learn the basics of networking and I've built an echo server from this tutorial. I checked the server with telnet and it works perfect.
Now when I'm using some of the many client samples on the Internet:
// Create a TcpClient.
// Note, for this client to work you need to have a TcpServer
// connected to the same address as specified by the server, port
// combination.
TcpClient client = new TcpClient(server, port);
// Translate the passed message into ASCII and store it as a Byte array.
Byte[] data = System.Text.Encoding.ASCII.GetBytes(message);
// Get a client stream for reading and writing.
NetworkStream stream = client.GetStream();
// Send the message to the connected TcpServer.
stream.Write(data, 0, data.Length);
Console.WriteLine("Sent: {0}", message);
// Receive the TcpServer.response.
// Buffer to store the response bytes.
data = new Byte[256];
// String to store the response ASCII representation.
String responseData = String.Empty;
// Read the first batch of the TcpServer response bytes.
Int32 bytes = stream.Read(data, 0, data.Length);
responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes);
Console.WriteLine("Received: {0}", responseData);
// Close everything.
stream.Close();
client.Close();
It doesn't work very well. If I will comment the stream.Read line, everything works perfect (expect I can't read). I was also trying to accomplish that in a similar way using asynchronous callback method for the read. and then it only works after I terminate the program (the server handles the request)
I suspect that the way I'm reading from the stream cause this block, but I'm too clueless to understand what I'm doing wrong.
The implementation will block until at least one byte of data can be
read, in the event that no data is available.
From MSDN
Your server propably isn't sending you any data.
Edit:
I tested your client and it works perfectly fine. Try it yourself and set the following parameters:
string server = "google.com";
int port = 80;
string message = "GET /\n";
It's definitely your server which has the problem.

Characters Missing

I'm trying to send a JSON request to a remote device that then returns a JSON response.
The code I've used is this:
TcpClient client = new TcpClient();
client.Connect(IPAddress.Parse("someip"), someport);
NetworkStream stream = client.GetStream();
byte[] myWriteBuffer = Encoding.ASCII.GetBytes("some JSON");
stream.Write(myWriteBuffer, 0, myWriteBuffer.Length);
BinaryReader r = new BinaryReader(stream);
Console.WriteLine(r.ReadString())
This code successfully sends the JSON string, receives the response, but that response only shows 123 characters, meaning that it cuts some chars...
What am I doing wrong
BinaryReader / BinaryWriter are not necessarily the right tools for writing to an arbitrary stream; in particular, they choose a specific way of encoding strings, with a length-prefix. If this is not what your remote device is expecting, it will fail.
I would just use the Stream directly, with Read and Write.
In particular, { is 123 in ASCII, so it looks BinaryReader is incorrectly taking the "length" from the opening JSON brace.
Probably an encoding/decoding issue, I would change your code like so
TcpClient client = new TcpClient();
client.Connect(IPAddress.Parse("someip"), someport);
NetworkStream stream = client.GetStream();
byte[] myWriteBuffer = Encoding.ASCII.GetBytes("some JSON");
stream.Write(myWriteBuffer, 0, myWriteBuffer.Length);
byte[] readBuffer = stream.GetBuffer();
Console.WriteLine(Encoding.ASCII.GetString(bytes));

How to send a "hello" to server and reply a "hi"?

With my code I can read a message on the server and write from the client. But I am not being able to write a response from the server and read in the client.
The code on the client
var cli = new TcpClient();
cli.Connect("127.0.0.1", 6800);
string data = String.Empty;
using (var ns = cli.GetStream())
{
using (var sw = new StreamWriter(ns))
{
sw.Write("Hello");
sw.Flush();
//using (var sr = new StreamReader(ns))
//{
// data = sr.ReadToEnd();
//}
}
}
cli.Close();
The code on the server
tcpListener = new TcpListener(IPAddress.Any, port);
tcpListener.Start();
while (run)
{
var client = tcpListener.AcceptTcpClient();
string data = String.Empty;
using (var ns = client.GetStream())
{
using (var sr = new StreamReader(ns))
{
data = sr.ReadToEnd();
//using (var sw = new StreamWriter(ns))
//{
// sw.WriteLine("Hi");
// sw.Flush();
//}
}
}
client.Close();
}
How can I make the server reply after reading the data and make the client read this data?
Since you are using
TcpClient client = tcpListener.AcceptTcpClient();
, you can write back to the client directly without needing it to self-identify. The code you have will actually work if you use Stream.Read() or .ReadLine() instead of .ReadToEnd(). ReadToEnd() will block forever on a network stream, until the stream is closed. See this answer to a similar question, or from MSDN,
ReadToEnd assumes that the stream
knows when it has reached an end. For
interactive protocols in which the
server sends data only when you ask
for it and does not close the
connection, ReadToEnd might block
indefinitely because it does not reach
an end, and should be avoided.
If you use ReadLine() at one side, you will need to use WriteLine() - not Write() - at the other side. The alternative is to use a loop that calls Stream.Read() until there is nothing left to read. You can see a full example of this for the server side in the AcceptTcpClient() documentation on MSDN. The corresponding client example is in the TcpClient documentation.
Cheesy, inneficient, but does the trick on a one-time throwaway program:
Client: In the stream, include the port and IP address it wishes to receive the response from.
Client: Create a listener for that
port and IP.
Server: Read in the port/IP info and
in turn connect, then send the reply
stream.
However, this is a great place to start, look into Sockets class for proper bi-directional communication.

Problem with Socket and Serialization C#

I implemented a Client and Server model that uses Socket with thread
When I want to pass only a string from Client to the Server, it works. But I want to pass an object and it throws this error:
"Attempting to deserialize an empty stream"
Here is the code:
Client:
ASCIIEncoding asen = new ASCIIEncoding();
MemoryStream memoryStream = new MemoryStream();
BinaryFormatter binaryFormatter = new BinaryFormatter();
Command command = new Command("meuLogin", "minhaSenha");
binaryFormatter.Serialize(memoryStream, command);
stream.Write(memoryStream.ToArray(), 0, memoryStream.ToArray().Length);
Server:
byte[] message = new byte[4096];
int bytesRead = 0;
bytesRead = clientStream.Read(message, 0, 4096);
MemoryStream memoryStream = new MemoryStream(bytesRead);
BinaryFormatter bf1 = new BinaryFormatter();
memoryStream.Position = 0;
Command command = (Command)bf1.Deserialize(memoryStream);
Another question: I copied the class Command from Client and pasted at Server to deserialize. Is this correct?
Thank you
I also recommend WCF. But if you continue using sockets, the key element that you're missing in your protocol is message framing.
You never use the message that you read from the stream. The memory stream you are reading from is thus empty.
On a side note, why do you use these intermediate MemoryStreams?
To answer your second question: for maximum maintainability, the class Command should be in a separate assembly that both Client and Server reference.
To answer your first question: you are attempting to deserialize from an empty stream on your server, just as the exception tells you. You need to copy the bytes you read from the clientStream into the memoryStream before you deserialize from the memoryStream. Alternatively, use the clientStream directly rather than using the memoryStream; this may require reconsidering your protocol.
Finally, I wholeheartedly agree with #Andrey: consider using WCF. It's way way way better than raw sockets.
If you change your server code to use a different MemoryStream constructor, the problem will go away.
MemoryStream memoryStream = new MemoryStream(message, 0, bytesRead);
However, I agree with Stephen and others. Either use WCF, or use Message framing.
Another question: I copied the class Command from Client and pasted at Server to deserialize. Is this correct?
No. The namespace for your Client and Server are likely different, so the Server will be trying to deserialize a stream that matches its namespace.
You should create your Command Class using its own namespace and reference that from both your Client and the Server.
After that...
Client:
static void StreamToServer(TcpClient client, Command obj) {
using (NetworkStream ns = client.GetStream()) {
using (MemoryStream ms = new MemoryStream()) {
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
byte[] buf = ms.ToArray();
ns.Write(buf, 0, buf.Length);
}
}
}
Server:
static Command ReadStream(TcpListener listener) {
Command obj = null;
using (TcpClient client = listener.AcceptTcpClient()) { // waits for data
using (NetworkStream ns = client.GetStream()) {
byte[] buf = new byte[client.ReceiveBufferSize];
int len = ns.Read(buf, 0, buf.Length);
using (MemoryStream ms = new MemoryStream(buf, 0, len)) {
BinaryFormatter formatter = new BinaryFormatter();
obj = formatter.Deserialize(ms) as Command;
}
}
}
return obj;
}
WCF or Message framing may be easier, but I don't often have the opportunity at work to sit around and read a book.
Instead of passing bytesRead to the MemoryStream which is actually the length of the byte stream, you should pass 'message', as it is the actual stream of bytes. Like,
MemoryStream memoryStream = new MemoryStream(message);
As you are passing an integer variable, the compiler is throwing exception that the stream is empty.
As for WCF, it is remarkable framework, but for applications that require low latency and high performance, WCF is a horrible answer because of its overheads, it is built upon sockets. So if you use sockets, that will be the lowest level implementation and thus, the fastest. That depends on your application which paradigm you should choose...

Categories