3
\$\begingroup\$

I'm currently creating a game which should be multiplayer (client-server-client), but I'm stuck.

I have been doing crazy things like building a text chain like

string networkMessage = userHash+" "+userX+" "+userY+" "+userRotation;

but it looks crazy and parsing is also bad

string[] pieces = networkMessage.Split(' ');
string userHash = pieces[0];
int userX = pieces[1];
int userY = pieces[2];
float userRotation = pieces[3];

So, what is the best way to pack my network commands & parse them on the second side?

Yeah, ehm.. I'm using TCP & StreamReader/StreamWriter

PS: yes, I know that giving client a possibility to set their coordinates is cheating risk and I'll change it later, so server will be more authorative

\$\endgroup\$

3 Answers 3

5
\$\begingroup\$

Given your comment on Blua's answer you should look into two projects: Lidgren and Protobuf-Net. Lidgren is straight-forward and there are multiple tutorials available on how to use it - ProtoBuf-like packing can be used to provide Lidgren with the network data.

Protobuf is pretty specialized (there is nothing stopping you from using BinaryReader or BinaryWriter - but bit-packed is a tiny bit faster and quite a bit smaller). I wouldn't grab the entire library as-is: just the bit packing methods (VarInt etc.). Jon Skeet's Implementation (ProtoSharp) is really small and these methods are easy to find within it. Once you have the methods just make your own BinaryReader/BinaryWriter (I called mine PackReader/PackWriter) that read and write from the underlying stream. Avoid StreamReader and StreamWriter entirely, BinaryReader/BinaryWriter are marginally less efficient than ProtoBuf but StreamReader/StreamWriter are obtusely wasteful and slow for this purpose.

Once you have a writer implementation it's as simple as:

// Possibly defined in an interface like IPackSerializable.
public void WriteTo(PackWriter writer)
{
   writer.Write(this.Position);
   // Make some XNA-specific methods. Equivalent to:
   // writer.Write(this.Position.X);
   // writer.Write(this.Position.Y);
   writer.Write(this.Speed);
   writer.Write(this.Name);
}

public void ReadFrom(PackReader reader)
{
   this.Position = reader.ReadVector2();
   this.Speed = reader.ReadVector2();
   this.Name = reader.ReadString();
}
\$\endgroup\$
3
  • \$\begingroup\$ I can't use XNA library in my server project as I am planning to port it to Linux later. This is amazing, elegant solution, and I'm currently using it! Only one question: If I set Config.ConnectionTimeout to 5 seconds, how do I make sure that x() will be executed after that time? \$\endgroup\$
    – Martin.
    Jan 25, 2012 at 14:51
  • \$\begingroup\$ @Martin you want to queue up something for the future? You shouldn't do that on the network-level. You might want to refer to my other (typo-ridden) post for information and sources on network programming. If that doesn't get you going feel free to ask there - the Age of Empires article should be especially interesting to you. \$\endgroup\$ Jan 25, 2012 at 15:14
  • \$\begingroup\$ Please, tell me, is there a way to get the whole message sent by server? I mean, without using .ReadString() etc but just output the WHOLE message? \$\endgroup\$
    – Martin.
    Jan 26, 2012 at 18:19
4
\$\begingroup\$

I hate C#, what the hell are StreamWriter and StreamReader supposed to be good for, except luring attention away from the classes that does the useful stuff?

You should use the classes BinaryWriter and BinaryReader, just by knowing that you will already have grown way better at this.

With the binary classes you can simply write a bunch of floats, integers and strings, and if you run the corresponding read functions in the right order you get all the data back, so for your example you'd just:

handle.Write(userHash)
handle.Write(userX)
handle.Write(userY)
handle.Write(userRotation)

And then to read the data:

userHash=handle.ReadString()
userX=handle.ReadInt32()
userY=handle.ReadInt32()
userRotation=handle.ReadSingle()

I also don't like the choice of the polymorphic write method, you have to be careful that the data types you pass to it correspond exactly to the data types you read. Failure to do so does not even result in a runtime error, you simply get garbled data.

Binary data structures in general
The string reading and writing use a length prefix, this means that the writer starts out by writing the length of the string as an integer, followed by the plain string. The reader start out by reading the integer, and then read that number of bytes from the string. Thus there is no need for escape sequences or any such bulky workaround know from plain text file types and protocols.

The very same method can be used to build larger data structures. If for instance you'd want to send a variable number of positions followed by some other data you'd start out with an integer telling how many such positions follow, then the data structures for those positions, and then the other data. The reader then know how many such positions to read before continuing with the other data.

If need be you can build such structure in several layers with lists of objects that themselves contain lists of objects etc.

Considerations
It may be a good idea to add a little room for expansion of the data format, for instance adding in a byte that must be zero at the end of an object leaves you room for adding one or more optional fields to that object thus making Dataformat v2 a superset of v1. This is mostly relevant for file formats, but I ain't going to say never for streams.

Adding a checksum, or even auto-correcting redundancy may also be a good idea, especially for files. Though TCP and UDP both employ checksums these are rather weak, so very occasionally erroneous data will slip through the internet unless a stronger error check is used.

\$\endgroup\$
3
  • \$\begingroup\$ Can I merge my answer with yours? You go into more detail than me but both answers probably need to be considered (yep, that means free rep for you :)). \$\endgroup\$ Jan 25, 2012 at 14:15
  • \$\begingroup\$ Oh UDP does have a checksum but I don't know how resilient it is. \$\endgroup\$ Jan 25, 2012 at 14:17
  • \$\begingroup\$ I don't see any need for merging answers. If you are afraid that people will miss my genius bit about data structures you can put a pointer in your own question. \$\endgroup\$ Jan 25, 2012 at 15:52
0
\$\begingroup\$

I think you should use a binary format to avoid a wastw of space.

Shanw Hargreaves proposes the use a PackedVectors: Link

In the Networking Section you have other post:

http://www.shawnhargreaves.com/blogindex.html

\$\endgroup\$
2
  • \$\begingroup\$ It should be noted that the networking side of XNA, specifically matchmaking / connecting via LIVE is only for the xbox, a PC during development and PCs with a Creaters Club membership (don't know about windows phone). See blogs.msdn.com/b/xna/archive/2007/11/16/… \$\endgroup\$ Jan 25, 2012 at 12:19
  • \$\begingroup\$ @GeorgeDuckett: I don't want to use Window Live \$\endgroup\$
    – Martin.
    Jan 25, 2012 at 12:27

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .