xmlserializer and sockets

U

Ultrakorne

hi, i have some problems with my client talk to my server...
i am using xmlserializer to serialize object and send them to the other
side of the connection. I need to send / recive by both client and
server. client after login waits all the time listening for objects on a
thread, and sends objects on users events on the main thread. server
waits connections, start a new thread for each connection and after
validating login waits for object, and answer with an object to every
incoming object.

If i send only one packet, all works fine, i tryed many solution but i
cannot send 2 packets, or send and recive...

here is my code:

LoginPacket is a class with informations

SERVER SIDE:
the method run of this class is launched as a thread to serve a client,
with a socket initializated (and passed as parameter)
now for testing it will only waits for 2 login packets

public class ServeClient {
private Socket sock;
public ServeClient(Socket s) {
this.sock = s;
}
public void Run(){
NetworkStream networkStream = new NetworkStream(sock);

//waiting for login request
LoginPacket lp;
this.DeserializeMessage( networkStream, out lp);

LoginPacket lp2;
this.DeserializeMessage( networkStream, out lp2);


Console.WriteLine("Incoming login request: {0}", lp.Nick);

networkStream.Close();
sock.Close();
}
// this will deserialize the message, and put it in message
public bool DeserializeMessage(NetworkStream networkStream,out
LoginPacket message) {
Byte []buffer=new Byte[500];
XmlSerializer deserializer=new XmlSerializer(typeof(LoginPacket));
int count=networkStream.Read(buffer,0,buffer.Length);
if(count <= 0) {
message=null;
return false;
}
MemoryStream memoryStream=new MemoryStream(buffer,0,count);
message= ((LoginPacket)deserializer.Deserialize(memoryStream));
return true;
}
}

CLIENT SIDE:
// run is launched as a thread, this thread will wait listening after
// login succed. now for testing it will only send 2 login requests
class ListenerThread {
private TcpClient client; //FIXME : it is no syncronized
public void Run() {
client = new TcpClient( "localhost", 6666);;


LoginPacket login = new LoginPacket( PacketType.LOGIN_REQUEST,
"nick", "pass");

LoginPacket login2 = new LoginPacket( PacketType.LOGIN_REQUEST,
"nick2", "pass2");


this.Send(login);
this.Send(login2);

client.Close();

}
//send a loginpacket
public void Send(LoginPacket message) {
NetworkStream netWorkStream=null;
XmlSerializer serializer=new XmlSerializer(message.GetType());
//Fixme: metterlo fuori dal metodo
netWorkStream = client.GetStream();
Stream stream=(Stream)netWorkStream;
serializer.Serialize(stream,message);
}
}

thx if someone can solve my problems.
 
S

Sami Vaaraniemi

What is the error message you get?

Anyway, there are some invalid assumptions in your code. See below:

Ultrakorne said:
hi, i have some problems with my client talk to my server...
i am using xmlserializer to serialize object and send them to the other
side of the connection. I need to send / recive by both client and server.
client after login waits all the time listening for objects on a thread,
and sends objects on users events on the main thread. server waits
connections, start a new thread for each connection and after validating
login waits for object, and answer with an object to every incoming
object.

If i send only one packet, all works fine, i tryed many solution but i
cannot send 2 packets, or send and recive...

here is my code:

LoginPacket is a class with informations

SERVER SIDE:
the method run of this class is launched as a thread to serve a client,
with a socket initializated (and passed as parameter)
now for testing it will only waits for 2 login packets

public class ServeClient {
private Socket sock;
public ServeClient(Socket s) {
this.sock = s;
}
public void Run(){
NetworkStream networkStream = new NetworkStream(sock);

//waiting for login request
LoginPacket lp;
this.DeserializeMessage( networkStream, out lp);

LoginPacket lp2;
this.DeserializeMessage( networkStream, out lp2);


Console.WriteLine("Incoming login request: {0}", lp.Nick);

networkStream.Close();
sock.Close();
}
// this will deserialize the message, and put it in message
public bool DeserializeMessage(NetworkStream networkStream,out
LoginPacket message) {
Byte []buffer=new Byte[500];
XmlSerializer deserializer=new XmlSerializer(typeof(LoginPacket));
int count=networkStream.Read(buffer,0,buffer.Length);
if(count <= 0) {
message=null;
return false;
}

You assume that 'buffer' now contains the serialized data for one
LoginPacket, but this is not necessarily the case. It could contain data for
half of a LoginPacket, one and a half, two LoginPackets, or anything in
between. The basic issue here is that TCP is a stream-oriented protocol and
the stuff you send arrives to its destination as an unstructured stream of
bytes.

One way to solve the problem would be to first send the size of the
serialized LoginPacket (number of bytes) and then the LoginPacket itself.
The receiver would first read the size and then the expected number of bytes
into the buffer. The receiver should deserialize the LoginPacket only after
the expected number of bytes have arrived and are in the buffer.

Another way that might work (don't have the time to try it now) would be to
have the XmlSerializer deserialize straight from the NetworkStream.

Regards,
Sami
 
W

William Stacey [MVP]

Another way that might work (don't have the time to try it now) would be
to
have the XmlSerializer deserialize straight from the NetworkStream.

I was thinking that too. However it probably exposes server to DoS attacks.
If you know the len before hand, you can decide if want to read that many
bytes or not. So the safe way is to send the len (i.e. uint) before hand
and read that many bytes, then deserialize that byte[]. Maybe something
like:

// Object
XmlRequest xr = new XmlRequest();
xr.Data = "Hello";

// Serialize it.
MemoryStream ms = new MemoryStream();
ms.Position = 4; // leave 4 bytes for our len.
XmlSerializer ser = new XmlSerializer(typeof(XmlRequest));
ser.Serialize(ms, xr);
int size = (int)ms.Length - 4;
byte[] lenBytes = BitConverter.GetBytes(size);
ms.Position = 0;
ms.Write(lenBytes, 0, 4);

// Write ms bytes to socket.

// Deserialize it.
ms.Position = 0; // pretent we blocking on first byte.
ms.Read(lenBytes, 0, 4); // pretent we read four bytes.
int len = BitConverter.ToInt32(lenBytes, 0);
byte[] bytes = new byte[len];
ms.Read(bytes, 0, len); // read len count data bytes only.
string xmlString = Encoding.UTF8.GetString(bytes, 0, bytes.Length); //
Used by Stream overload on xmlserializer.
Console.WriteLine("Raw:"+xmlString);
XmlSerializer ser2 = new XmlSerializer(typeof(XmlRequest));
using (StringReader sr = new StringReader(xmlString))
{
xr = (XmlRequest)ser.Deserialize(sr);
Console.WriteLine("Data:"+xr.Data);
}
 
U

ultrakorne

Sami said:
What is the error message you get?

Anyway, there are some invalid assumptions in your code. See below:

i have changed the recive method and make it simpler:

public LoginPacket DeserializeMessage() {
NetworkStream networkStream = new NetworkStream(sock);
LoginPacket message = null;
try {
message = ((LoginPacket)deserializer.Deserialize(networkStream));
} catch(Exception e) { Console.WriteLine("EXC 3"); return null; }
finally {
networkStream.Close();
}
return message;
}

deserializer is XmlSerializer = new XmlSerializer(typeof(LoginPacket));

the problem is the same: exception is raised by .Deserialize:
System.Xml.XmlException: XML declaration cannot appear in this state.
Line 5, position 20.

William Stancey wrote:
I was thinking that too. However it probably exposes server to DoS
attacks.
If you know the len before hand, you can decide if want to read that
many
bytes or not. So the safe way is to send the len (i.e. uint) before
hand
and read that many bytes, then deserialize that byte[]. Maybe
something like:

thanks for your hit, but first i have to solve and understand whats
wrong with my code
 
S

Sami Vaaraniemi

I did a bit of digging and here's what I found out.

It seems that XmlSerializer.Deserialize blocks until it gets end-of-file or
its internal buffer of 4k is full. If the sender closes the socket after the
LoginPacket is sent, then it works and the deserialized object is returned
properly. But this is hardly a solution as you want to send more than just
the one LoginPacket. On the other hand, if there is more data coming from
the socket after the last closing tag, then XML parsing fails and there is
an exception.

Bottom line, it appears you cannot call XmlSerializer.Deserialize with a
NetworkStream, or at least I could not figure out how to make it work. Even
if it were possible, you probably don't want to do it this way as the server
would be vulnerable to a DoS attack just like William pointed out.

So the solution is to first send the length of the serialized object, and
then the serialized object itself. The receiver should copy the bytes from
the socket into a temporary buffer, then deserialize out of the buffer.

Alternatively, you could use the BinaryFormatter instead of XmlSerializer as
it does not suffer from the blocking problem.

Regards,
Sami

ultrakorne said:
Sami said:
What is the error message you get?

Anyway, there are some invalid assumptions in your code. See below:

i have changed the recive method and make it simpler:

public LoginPacket DeserializeMessage() {
NetworkStream networkStream = new NetworkStream(sock);
LoginPacket message = null;
try {
message = ((LoginPacket)deserializer.Deserialize(networkStream));
} catch(Exception e) { Console.WriteLine("EXC 3"); return null; }
finally {
networkStream.Close();
}
return message;
}

deserializer is XmlSerializer = new XmlSerializer(typeof(LoginPacket));

the problem is the same: exception is raised by .Deserialize:
System.Xml.XmlException: XML declaration cannot appear in this state. Line
5, position 20.

William Stancey wrote:
I was thinking that too. However it probably exposes server to DoS
attacks.
If you know the len before hand, you can decide if want to read that
many
bytes or not. So the safe way is to send the len (i.e. uint) before
hand
and read that many bytes, then deserialize that byte[]. Maybe
something like:

thanks for your hit, but first i have to solve and understand whats wrong
with my code
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top