Sign in to follow this  
teremy

Network Library that supporst RPCs

Recommended Posts

Hello.

 

I am making games in unity, however I dislike unity's network library ( UNET ).

What I need is a network library that is build on top of UDP and lets you send packets reliably if required and also support RPCs.

 

For my master server I simply used tcp sockets ( it's writen in c# ) and wrote my own RPC logic using reflection ( is there even another way to make RPCs? I basically send the method name and its parameters and on the receiving side I invoke that method with the parameters. This works good, still need some work on security and performance though ).

 

But for the game server/client I currently use UNET and UNET doesn't exactly allow RPCs. You can't call a method on a specific client ( I think they will include this option in one of the future version but there are also other things I don't like about UNET, mainly the architecture you are compelled to use. Another way was to use network messages and basically create a class for each different message type but that is way to much work.

I found RPCs are the easiest way to communicate. I read something about lidgren networking, but this doesn't support RPCs right? Any idea what I could use? Or should I program my own RPC logic on top of an existing networking library?

 

Even though I created my own RPC logic before, is there some sort of tutorial or a good description of an implementation? I know the implementations can be different, so if I have to make my own, I want to at least make a good one.

 

 

Thanks for your time!

Share this post


Link to post
Share on other sites
The only "magic" part of UNET is that they replace the body of the sender's RPC method with a replacement that performs parameter serialization and sends the call, and generate the receiver-side deserialization code to call the function for you.

To do RPC manually, you just need to:

- Serialize parameters and function context (if applicable, like the gameObject's unique ID or whatever)
- Send the serialized information over the network like any other network data
- On the receiving end, deserialize the parameters and then call the appropriate function in the appropriate context (if applicable) using lookup tables (or reflection - it's debatable whether this is OK or not in the context of RPC - you maintain less code with reflection anyway).

If you want to handle returning a value back to the caller, you need an additional ID for the call itself, and a lookup table of code to call when you get a response. Since Unity doesn't have C# 5.0 await support yet, I would use a lambda-style continuation. Edited by Nypyren

Share this post


Link to post
Share on other sites

Thank you for your answer.

In my RPC implementation for my tcp sockets, the sender makes a call like sendRPC("Methodname", methodParameter1,methodParameter2, ... );

 

Internally this gets send as a string in the form of Methodname:methodParamter1:methodParameter2...

The receiving side will dismantle that string and cast the parameters to the respective type of the method's paramater.

It works only with basic types, since they can easily put and gotten from a string.

 

I think the reflection part can be made efficient by using a dictionary string -> method.

So reflection is used only at program startup to populate the dictionary.

 

The difficult part about RPCs is the serialization. For example if you want to send a List as a parameter. There are great and efficient ways to serialize an object, but the internal message, that is send when making an rpc, contains multiple parameters and the method name, the receiving side has to know which is which, and that I find a bit difficult. Took me some time to figure out how tcp works and that you don't even send messages, but its streambased, so I had to mark my messages, so the receiving side knows when a message ends etc... In the end it worked, but very basic.

Share this post


Link to post
Share on other sites
For a more compact serialization, you should consider using a series of bytes instead of strings. For more fancy ways to make your network messages smaller, look at the encoding tricks that protobuf and SQLite4 use:

https://developers.google.com/protocol-buffers/docs/encoding
https://sqlite.org/src4/doc/trunk/www/varint.wiki

To handle more complex types, you write functions to read/write those complex types. For example with a list, you write the number of items in the list as a varint (-1 for null if you care about distinguishing between null and empty lists), followed by the serialized form of each element in the list. For a Dictionary<K,V> you send the number of pairs as a varint, then alternate between key,value,key,value.

For polymorphism you make a table of type IDs and send the type ID as a varint immediately before any polymorphic object.

For arbitrary graphs of objects, you traverse the graph, adding each object to a list the first time you see it, until you've seen all reachable objects. Then you write: # of objects, the type IDs for each object in the list, followed by the fields for each object in the list. Any time you encounter a field which is a reference type, you find that object in the list and serialize its index (you can use a Dictionary<object,int> to speed this up). Deserialization creates the object list, news each object by its type id, then fills in their fields and cross-references, then returns the first entry in the list as the "root" of the graph.


If you pass a function name over the network and use reflection to invoke it, it's a security risk because someone could send a network message like [mscorlib.dll:System.IO.Directory.Delete, "C:/Users", true] over the network, and you would *never* want to let your receiving side execute that.

To handle the issue of looking up the functions from method names, I suggest making part of your program startup register all of the possible functions you might want to use. Then, when you want to RPC that function, you look up its index in that table and send just the index over the network. Assuming both ends built the function table the same way, the receiver will know which function to use as well. Edited by Nypyren

Share this post


Link to post
Share on other sites

Thank you again, I will try to grasp everything you said.

 

About the security risk by invoking a function by name, I don't want to manually add every function to a list of methods, that are allowed to be invoked. I thought of maybe using attributes to declare a function as an RPC method, or to have a naming convention, so only methods with a name, that starts with "RPC" will be invoked. Maybe I will even use both methods. The example you posted of a malicious rpc message is really scary.

What I like about UNET is that when you use network messages ( create a class for each different type of network message ) it will automatically implement a serialize and deserialize method for it, but I guess I have to write those methods manually for a more complex type ( basically everything that is not a basic type like int, string, char, boolean etc. ).

 

The good thing if I write my own RPC implementation ( which includes the serialization/deserialization stuff ) I can easily switch the underlying network library.

 

Again thank you very much, this was very helpful.

Share this post


Link to post
Share on other sites

I found RPCs are the easiest way to communicate.


RPC is convenient, but it does have known failure modes. If you synchronously block for an answer, for example, that's really bad from a performance point of view. If you assume that all function calls will succeed, that's another problem, as networks are unreliable. What does the RPC do when the client goes away?
Structuring game networking as a bidirectional stream of "fire and forget" updates is a LOT more robust than RPC.

I don't want to manually add every function to a list of methods


I don't want to manually have to lock my door when I leave the house, either, but I do it, because the cost is worth the benefit.

Note that, with default RPC function-and-arguments, a client can invoke any of the functions on your server with any argument at any time. For example, they can invoke "player X takes damage" at will, if there is such a function.
Sending a stream of messages and structuring your simulation to pay attention to (and validate) those messages, is, again, more robust and has less accidental-invocation surface area.

Share this post


Link to post
Share on other sites

The RPCs I used on my master server are all not returning anything ( void ).

It's an authoritative server system, so the client only sends requests.

However my master server is also using tcp sockets and it's ok if things take some time.

A game server is of course something else. But I'd also use methods, that don't return anything ( void ), and therefore also don't block.

And it's ok if a function call fails ( packet is lost ... ). I will probably implement the possibility of reliably calling a method, which is simply done with the help of the underlying network protocol, that allows sending reliably sending udp packets ( and even lets you choose other options like in-order packets ).

 

What I meant by not wanting to manually add every function to a list of methods is that I want my RPC methods to be automatically put into the list, that contains the allowed methods to call. I could for example add an attribute to every RPC method or as I said, I could have a naming convention, so every method with methodname "RPC..." is allowed to be called over the network ( which means that its allowed to be invoked when the rpc message contains its methodname ).

 

This way they can't invoke a "player X takes damage" function, because it won't have an attribute to tag it as an RPC function or the methodname won't start with "RPC".

 

The syntax I used on my master server was something like SendRpc("Methodname", parameter1, parameter2...);

It wasn't Methodname(parameter1, parameter2...);, but on the receiving side, the respective method gets called with the parameters.

The server could also make RPCs in the form of SendRpc(clientId, "Methodname", parameter1, parameter2...);

 

When the server received an rpc message with "Methodname" and parameter1 etc., then the method "Methodname" gets invoked, but with the clientId as its first parameter, so the server can easily use the information from which client the message came. This is of course automatically done by my RPC engine ( or whatever you want to call it ), the clientId is not a parameter the sender has control of.

 

This was my basic implementation of RPC.

 

Reliabilty etc. I think is not even part of the RPC engine, it's part of the underlying protocol that is used and since I am going to use a udp based protocol with the ability for in order and reliable packet transfer, these properties will then of course be part of my RPC calls as well. It's pretty much just the syntax and structure of RPCs that I like, so I don't have to make a huge switch case for all the different network messages but rather have this done automatically by the rpc engine and invoke the corresponding method with its parameters.

Share this post


Link to post
Share on other sites

I thought of maybe using attributes to declare a function as an RPC method, or to have a naming convention, so only methods with a name, that starts with "RPC" will be invoked.


Using attributes is a good way to do it. You just need to make sure that the way you scan for attributes always keeps the results in the same order. You could search for everything that has the attribute, and then alphabetically sort the search results so that they're always guaranteed to be in the same order.

Share this post


Link to post
Share on other sites

 

I thought of maybe using attributes to declare a function as an RPC method, or to have a naming convention, so only methods with a name, that starts with "RPC" will be invoked.


Using attributes is a good way to do it. You just need to make sure that the way you scan for attributes always keeps the results in the same order. You could search for everything that has the attribute, and then alphabetically sort the search results so that they're always guaranteed to be in the same order.

 

 

Why do they have to be in the same order? Isn't it somehow possible to use a dictionary that contains the allowed rpc methods and gives me the method via the methodname as a string? Don't know if this is possible with delegates, but I've read reflection isn't really a performant way of doing this, I could use reflection only once at the program start to get all the allowed rpc functions and save them in a dictionary with methodname (string ) -> method ( delegate or something ). This way I can get the method in O(1) time and invoke it with the parameters...

 

Another thing I thought about was that the rpcmessage won't contain the methodname, but rather a number ( or a hash value or something ) that corresponds to a method ( it's also less data sent ), so when someone is looking into the packets, that are sent, he doesn't exactly know the methodname. The client however has to know those numbers then and since my code should be readable I have to create an enumeration or something so I still know which method is represented and I have to manually add to this enumeration if I add an rpc function. I guess I don't like to manually do stuff, when I think they can be automatically done, so the server has to provide some sort of interface for the rpc methods... but that's another topic....

Share this post


Link to post
Share on other sites

Why do they have to be in the same order? Isn't it somehow possible to use a dictionary that contains the allowed rpc methods and gives me the method via the methodname as a string? Don't know if this is possible with delegates, but I've read reflection isn't really a performant way of doing this, I could use reflection only once at the program start to get all the allowed rpc functions and save them in a dictionary with methodname (string ) -> method ( delegate or something ). This way I can get the method in O(1) time and invoke it with the parameters...
 
Another thing I thought about was that the rpcmessage won't contain the methodname, but rather a number ( or a hash value or something ) that corresponds to a method ( it's also less data sent ), so when someone is looking into the packets, that are sent, he doesn't exactly know the methodname. The client however has to know those numbers then and since my code should be readable I have to create an enumeration or something so I still know which method is represented and I have to manually add to this enumeration if I add an rpc function. I guess I don't like to manually do stuff, when I think they can be automatically done, so the server has to provide some sort of interface for the rpc methods... but that's another topic....


If you use strings, it's O(1)... but you still have to transmit the whole string over the network (which will be larger than an integer), and the Dictionary<string,MethodInfo> will use GetHashCode on the string when looking up the dictionary entry. GetHashCode depends on the length of the string - it's pretty fast, but if you use integers you don't need it at all.

If you use integers, you can make a List<MethodInfo> instead and have an O(1) lookup with no hashing, and slightly less memory use than the Dictionary (the size of your function table will be insignificant compared to texture memory though, so don't worry about that).

You would still need a way on the sending side to find the function's ID before you know what integer to send over the network. In the first case you could reuse the same Dictionary and just send functions by name as well. In the second case you can look up the ID in any way you see fit (A Dictionary<string,int> for example).

In both cases, you would search for your functions once at startup and put them into the collection(s). After that you would use your function ID (either string or integer) to pick the method from the collection. If you use integers, you just need to make sure every client/server has the same IDs for the same functions. That's what I meant by the "in the same order" thing above.

Generally I only use Dictionary when the keys are sparse. If I have a Dictionary where the keys are all integers from 0 to N, I just use a List or possibly even an array instead.

MethodInfo aren't very fast to invoke, either. If you had a hardcoded switch statement, it would be much faster. Although since the RPC already has the delay of being transferred over the network, a MethodInfo.Invoke probably won't factor into performance noticably. Edited by Nypyren

Share this post


Link to post
Share on other sites

 

MethodInfo aren't very fast to invoke, either. If you had a hardcoded switch statement, it would be much faster. Although since the RPC already has the delay of being transferred over the network, a MethodInfo.Invoke probably won't factor into performance noticably.

 

 

Well if I want to communicate from client to server or vice versa, then there always has to be transferred something over the network. There is always a delay, whatever syntax I use. There is not really an alternative to using MethodInfo.Invoke, right? If I use hardcoded switch statements than I guess we are not talking about RPCs anymore. I like the RPC syntax, because there is a different method for every different message. So it's easily maintainable. There is no way to use delegates instead of invoking a MethodInfo, right? Or I could use reflection to generate a switch statement out of all the rpc methods at program start ( definitely won't do this :D ). I once used Unity's UNET for a fast paced bomberman clone, that of course demanded a very fast communication process. Had no problems, even though using their RPC stuff ( so I guess invoking a method might not be the most performant way compared to the hardcoded switch statement, but it's still more than fast enough for fast online games that are very time-sensitive ).

Share this post


Link to post
Share on other sites

there always has to be transferred something over the network


True, but your job is to minimize what that is.
For example, giving each method a small integer instead of a string name, would reduce the size needed in the network packet.
Similarly, building a wrapper that introspects the server code, and generates a class that exposes exactly the methods that the server exports, and then marshals the arguments using known-types (only the data) will save a lot of space over marshaling arguments with type information like default .NET Serialization does.
You should aim for the overhead of a method invocation to be a few bytes (say, five at most,) and for each argument in an invocation to only need the size of the data (4 bytes for an int, num-chars bytes for a string, etc.)

Share this post


Link to post
Share on other sites

Maybe one last question.

As I said, the rpc message, that was sent for my master server stuff was something like "methodname:parameter1:parameter2:parameter3...", it also contained the string "<EOM>" at the end, because since tcp ( that was the protocol I used for my master server stuff ) is streambased, I had the option to either tell the message size at the beginning of the message, or to use a delimiter ( "<EOM>" in my case ) to indicate the end of the message.

A problem with this is of course, that the message itself must not contain this delimiter, or else the message gets cut at this point and we have two messages of garbage.

There's also the delimiter I used between the methodname and the different parameters ( ":" ).

I could of course have a static packet size, so I know which bytes belong to which parameter, but it would be more efficient to have a variable packet size, so only the information is sent, that is needed ( for example a packet of a chat message, that simply contains "hi" should be much less in size than the packet of a chat message containing "blablablablablablablablablablablablablablabla....", in reality I will of course have a maximum chat message length or send a very long chat message with multiple packets, but that's another topic ).

I took a look into protobuf and my current serialization method was to simply convert everything into a string and use the getBytes() method ( which is still much less data than using JSON ). But there are tons of great and efficient serialization librarys out there, like protobuf or msgpack. I'm thinking of using one of those, but I still have to implement something, so the receiver knows which bytes belong to which parameter.

As I did before, I could put a ":" ( in byte format of course ) after a parameter. Is this the right way todo this? It would work, however to make this robust ( same with the message delimiter ) I would need to escape any occurences of this in the bytes of the parameters ( and deescaping might be a bit tricky ).

Any better way todo it? This way a parameter can have a variable size (depending on the actual value), which is the case if I serialize an integer like 1 or 20000 with the above mentioned serialization librarys.

Share this post


Link to post
Share on other sites

A problem with this is of course, that the message itself must not contain this delimiter


Which is why most game protocols use the size-first approach. Also, for efficiency, they typically marshal data as binary, rather than as text.

For formats that have to be textual by convention or definition (email, JSON, etc) the two options are to either encode your payload as text using something like base64 encoding, or introducing a quoting mechanism.
For example, URLs use a quoting mechanism where "%xx" will replace a URL-reserved character (slash, ampersand, equals, percent, space, etc) with its hexadecimal equivalent.
You could do this as well -- whenever you see a '<' or a ':' or a '%' in the payload, insert the appropriate %xx code. Then, in the protocol decoder, when you see a %, read the next two characters and decode. When you see a '<' or a ':' you know that it's part of the protocol.

All that being said -- I think the way you're doing this is not optimal, and the netcode that you're working on will never be "great" except possibly for slow, turn-based games like chess or whatever.
If that's OK by you, then you should keep going. If you want "great" netcode for games with significant data volume or significant latency requirements, you need to take the advice in this thread about using binary marshaling and reducing the size of data that you need to send.
But, not everything needs to be "great," because "good enough and shipping" beats "great and not shipping" :-)

Share this post


Link to post
Share on other sites
Definitely put the size first. It's so much easier than worrying about delimiters, EOM indicators and dealing with escaping other data somehow.

Really the only nuisance part of doing size first is you have to build up your whole message in a temporary buffer first, then measure its length immediately before sending. But that's not nearly as bad as dealing with escaping data (which inevitably leads to bugs).

If you use a binary format you don't have to worry about any kind of string escaping or delimiters. Your data is arranged in consistent blocks with the leading information letting you know exactly how much data comes next, either using lengths, or type IDs, or any other deterministic approach. Edited by Nypyren

Share this post


Link to post
Share on other sites

Of course my current code is not optimal, it's what I wrote for my master server stuff and my main goal was to just get it to work. The good part is, that I can make changes to the rpc engine at anytime, but don't have to make changes to the client or server itself.

 

The reason I created this thread was to gather some information of what's the best or at least a better approach than what I am currently doing on my master server. I want to have a viable solution for fast paced games.

 

Let's say I go for the approach, that puts the packet size first. Now the receiver can tell which parts belong to a packet and btw. even with the EOM indicator, I still have to work with a buffer, since tcp is streambased and although the transfer is reliable, maybe just parts of it have arrived at a given time.

 

Still I don't know how do I know which bytes belong to which parameters? The first byte(s) of the message will tell the size of the whole packet, but how does the receiver figure out which bytes belong to which parameter? If I send 2 integers for example, how does the receiver know which bytes belong to the first and which belong to the second one? Should I add a byte before every parameter that tells the size of the parameter? Since I want things to be most efficient I'm not working with a fixed parameter size.

So sending an rpc message would be something like: byte for packet size + byte for the method to call + byte for size of parameter 1 + byte(s) of parameter 1 + byte for size of parameter 2 + byte(s) of parameter 2+...

I don't really see a more efficient way? Is this the right approach with prepending each parameter ( that can have many bytes, depending on what the parameter contains, like a small number or a long string ) with a byte of its size?

 

 

EDIT: This approach would also be very easy to implement and I can just serialize stuff with msgpack ( or another great serialization method ), check the bytesize and add the bytesize + the resulting bytes of the serializer to the packet. The max. size of a parameter with this approach would be 256, if all 8 bits of the byte, that indicates the parameter's size, are set.

Edited by teremy

Share this post


Link to post
Share on other sites
For most types, the sender and receivers both know how big they are based on agreed-upon information. You typically do NOT need to send the size of every field.

For example, you could write code that agrees that all 32-bit integers are 4 bytes. Or you could agree to write them using SQLite4 varint encoding, or VLQ encoding, or whatever.

Let's look at a bare-bones BinaryReader/BinaryWriter pair in C#:
 
public class Foo
{
    public int A;
    public int B;

    public void Read(BinaryReader reader)
    {
        A = reader.ReadInt32();
        B = reader.ReadInt32();
    }

    public void Write(BinaryWriter writer)
    {
        writer.Write(A);
        writer.Write(B);
    }
}
The Read and Write functions don't need to write bytes indicating that A is 4 bytes and B is 4 bytes, because it's implicit.


You only really need lengths for two main things:

1. Knowing how much data to wait for in your network receiver before you attempt to deserialize the whole message.
2. Skipping fields of unknown types if you have a protocol that allows for different client versions to talk to each other. You probably won't need this, though.


For RPC, you could do something like this (very barebones example):
 
public void HandleRPC(BinaryReader reader)
{
    var function = (RPCFunction)reader.ReadInt32(); // RPCFunction being an enum

    switch (function)
    {
        case RPCFunction.ConsoleWriteLine:
        {
            // This is a bit of a security risk if you were to do this in C++, but in C# the worst you'll get is an exception.
            var formatString = reader.ReadString(); // Internally it has the string length first
            var numArgs = reader.ReadInt32();
            object[] args = new object[numArgs];
            for (int i=0; i<numArgs; ++i)
                args[i] = ReadPolymorphicType(reader);
            
            Console.WriteLine(formatString, args);
        }

        // Other functions read their expected parameters, then call the appropriate function in the same way.
    }
}

public object ReadPolymorphicType(BinaryReader reader)
{
    int typeId = reader.ReadInt32();
    switch (typeId)
    {
        case 0: return null;
        case 1: return reader.ReadByte();
        case 2: return reader.ReadInt32();
        case 3: return reader.ReadSingle();
        case 4: return reader.ReadDouble();
        case 5: return reader.ReadString();
        case 6: { var foo = new Foo(); foo.Read(reader); return foo; }
        // etc.
    }
}
This is a manually-written, tedious way to go about it, but it serves as an example for how you can serialize/deserialize data. You would ideally want to use whatever is most convenient for your project.

I would personally use varints for all integers, have string backreference support, etc. Edited by Nypyren

Share this post


Link to post
Share on other sites

you have to build up your whole message in a temporary buffer first, then measure its length immediately before sending


You want to do that anyway, because you don't want to call send() multiple times for as single packet, because system calls have a cost.
Just leave a few bytes empty at the beginning of your buffer, and after you know the size, pre-fill that at the head of the buffer, then call send() on the entire thing.

I still have to work with a buffer


Yes; any TCP receiver has to call recv() into the end of whatever buffer it keeps around, and then try to decode as many whole packets as possible out of that buffer, and then move whatever is left to the beginning of the buffer.
(Cyclic buffers are sometimes convenient for this.)

how do I know which bytes belong to which parameters?


Decode each argument in order. You presumably know the type of each argument.
The simplest method: If the type is an int, it's 4 bytes. If the type is a double, it's 8 bytes. If the type's a string, then make the rule that strings are less than 256 bytes long, and send a length byte, followed by that many bytes.
When you have decoded the correct number of arguments, you know that the next part of your message follows.
If you support variable number of arguments, first encode the number of arguments to decode, using another byte.

Less simple methods will decode integers as some kind of var-int, floats using some kind of quantization (based on what the particular value is,) strings using a dictionary that gets built up over the connection, etc.

Share this post


Link to post
Share on other sites

Thank you guys, again, very helpful information!

 

If every an argument has a fixed size, then it sure is easy. A string for example doesn't really have a fixed size, although since I know it is a string, I could look for the null character ( '\0' ), or just send a length byte first.

I was thinking of using MsgPack, which for example will have an output less in size with a small integer as with a large integer. So the result depends on the actual value, not just the type.

The mentioned SQLite4 varint encoding and VLQ encoding would allow the receiver to guess the size of the parameter by the encoding, right? So I don't have to prepend a length byte, but the encoding will tell which bytes belong to the parameter.

This sounds very good to me, so it contains the length information in the information itself. Although I guess having 1 byte extra ( for length ) for each argument, is still efficient and it lets me send an integer like 10 with only 2 bytes( length byte + byte for the value ), instead of 4.

 

This knowledge should really lead to an efficient RPC implementation, as I will probably use an efficient serialization method.

Can't thank you enough for your time and effort to help me, you are the best!

 

EDIT: https://gist.github.com/frsyuki/2908191

This site even mentions RPC as a use case for MsgPack :). Good thing I can easily switch the serialization method, but with MsgPack I guess I'll have an efficient way of storing data.

Edited by teremy

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this