|
| 1 | +# Protobuffers over network for Unity3D |
| 2 | +This project lets you quickly and easily send protobuffer objects over the |
| 3 | +network between 2 apps. The protobuffers will get automatically compressed if |
| 4 | +they are larger than 1mb in order to save space when sending them through the |
| 5 | +network. |
| 6 | + |
| 7 | +# How it works |
| 8 | +The way the project works is with a messaging system. Messages get sent |
| 9 | +through sockets. On one side one application has a listening socket and on |
| 10 | +the other side the application sends messages to it. |
| 11 | + |
| 12 | +*Messages* are the basic way how protobuffers are sent, a message always |
| 13 | +contains a Protobuffer and a *Type* that identifies the protobuffer object you |
| 14 | +are sending. |
| 15 | + |
| 16 | +# Project Structure |
| 17 | +In the unity project, There are 2 scenes. **SimpleClient** and |
| 18 | +**SimpleServer**. |
| 19 | + |
| 20 | +All the information you need to run the project is contained in these 2 |
| 21 | +scenes, specifically in 2 files called *ClientTest.cs* (for sending messages) |
| 22 | +and *ServerTest.cs* for receiving messages. |
| 23 | + |
| 24 | +## SimpleClient scene. |
| 25 | +*SimpleClient* is an empty scene that contains a *ClientTest.cs* Behaviour |
| 26 | +added to the main camera. |
| 27 | + |
| 28 | +This scene acts as a client and sends messages to the other application |
| 29 | +called *SimpleServer* which acts as the listening socket. |
| 30 | + |
| 31 | +### ClientTest.cs |
| 32 | +In order to send a Protobuf object to another application, you need to open a |
| 33 | +socket and send a Protobuf object encapsulated in a Message object. convert it |
| 34 | +to an array of bytes and send it. |
| 35 | + |
| 36 | +The *ClientTest.cs* class contains precissely that function already for you to |
| 37 | +send messages and its called: |
| 38 | + |
| 39 | +*void SendMessage(Message m);* |
| 40 | + |
| 41 | +In ClientTest.cs you will find the UI for setting up values for the |
| 42 | +SampleObject protobuf object and the address and port of the app you want to |
| 43 | +send the values to. |
| 44 | + |
| 45 | +## SimpleServer Scene. |
| 46 | +*SimpleServer* is an empty scene that contains a *ServerTest.cs* Behaviour |
| 47 | +attached to the main camera. |
| 48 | + |
| 49 | +This scene acts as a server that is continuously listening on a port for |
| 50 | +incoming messages from any client. It processes the messages, rebuilds them |
| 51 | +and print them. |
| 52 | + |
| 53 | +### ServerTest.cs |
| 54 | +In order to receive a protobuf object, a *SocketProtobufListener* object |
| 55 | +needs to be created. This object receives an IP, a port where the socket |
| 56 | +is going to be listening for incoming messages and a *ConcurrentQueue* for |
| 57 | +*ProtobufMessages* that are stored in the Queue as they come. |
| 58 | + |
| 59 | +The *ServerTest.cs* creates a SocketProtobufListener object, binds a socket |
| 60 | +automatically to the current IP used and on a separate thread a socket starts |
| 61 | +to listen for incoming messages, these messages are stored in the |
| 62 | +*messaQueue* declared in the class. |
| 63 | + |
| 64 | +When a message gets added to the queue, in the Update function as soon as a |
| 65 | +message arrives, the protobuf message gets read and depending on the type it |
| 66 | +has, it gets casted and then printed in the UI with the |
| 67 | +*PrintSampleObjectFields(SampleObject s)* function. |
| 68 | + |
| 69 | + |
| 70 | +# How to add a Protobuf Object and transmit it over the network |
| 71 | +Here I will walk you through on how to send an object and receive it on the |
| 72 | +other end. This app doesnt check for errors in connections nor anything |
| 73 | +else. This is left for you, the reader. |
| 74 | + |
| 75 | +First things first. We need to create a Protobuf object. I will not cover the |
| 76 | +specific syntax on how protobuffer objects work, for more reference just |
| 77 | +check it out [here](https://developers.google.com/protocol-buffers/docs/overview). |
| 78 | + |
| 79 | +In order to compile our SampleObject you need to have **protoc** installed |
| 80 | +and compile the protobuffer for C#. After that you are pretty much done. |
| 81 | + |
| 82 | +Our *SampleObject.proto* looks like this: |
| 83 | + |
| 84 | +>package Protobuf; |
| 85 | +> |
| 86 | +>message SampleObject { |
| 87 | +> int32 type = 1; |
| 88 | +> string objectName = 2; |
| 89 | +> string sampleString = 3; |
| 90 | +> int32 sampleInt = 4; |
| 91 | +> float sampleFloat = 5; |
| 92 | +>} |
| 93 | +
|
| 94 | +It contains a *type* which is **necessary** for all your objects you want to |
| 95 | +transmit. 2 sample strings, a float and an integer. |
| 96 | + |
| 97 | +After compiling the protocol buffer to C# we need to tell the system with a |
| 98 | +unique identifier the type of the object. This is added in *ProtobufMessagesTypes.cs* |
| 99 | + |
| 100 | +>//ProtobufMessageTypes.cs |
| 101 | +>public static class ProtobufMessageTypes { |
| 102 | +> public const int SAMPLE_OBJECT = 1;//can have any value, just make sure is unique |
| 103 | +>} |
| 104 | +
|
| 105 | +Then, we create a function for creating this object message in |
| 106 | +*MessageCreator.cs*. Here we define a function that creates an object with |
| 107 | +the parameters and returns a message (See MessageCreator.cs for the |
| 108 | +implementation details). |
| 109 | + |
| 110 | +>//MessageCreator.cs |
| 111 | +>public static Message CreateSampleObjectMessage(string objName, string sampleStr, int sampleInt, float sampleFloat) |
| 112 | +
|
| 113 | +In order to receive and undertand the message, we have to convert the message |
| 114 | +to a ProtobufMessage, this is done in *DeserializeByType(int type, MemoryStream memStream)* |
| 115 | + |
| 116 | +>//MessageDeserializer.cs |
| 117 | +>private static ProtobufMessage DeserializeByType(int type, MemoryStream memStream) { |
| 118 | +> object protobuf = null; |
| 119 | +> switch (type) { |
| 120 | +> case ProtobufMessageTypes.SAMPLE_OBJECT: |
| 121 | +> protobuf = Protobuf.SampleObject.Parser.ParseFrom(memStream); |
| 122 | +> break; |
| 123 | +> default: |
| 124 | +> return null; |
| 125 | +> } |
| 126 | +> return new ProtobufMessage(protobuf, type); |
| 127 | +>} |
| 128 | +
|
| 129 | +Finally, in the *Update()* function in *ServerTest.cs* we can cast our |
| 130 | +ProtobufMessage.cs with the specific type to our object. |
| 131 | + |
| 132 | +>// ServerTest.cs |
| 133 | +>void Update() { |
| 134 | +> if(messageQueue.Count > 0) {//when there's a protobuf message in the queue, we print it |
| 135 | +> ProtobufMessage pm; |
| 136 | +> messageQueue.TryDequeue(out pm); |
| 137 | +> |
| 138 | +> switch(pm.MessageType) { |
| 139 | +> case ProtobufMessageTypes.SAMPLE_OBJECT: |
| 140 | +> SampleObject sampleObject = (SampleObject) pm.Protobuf; |
| 141 | +> //Do something with our object |
| 142 | +> break; |
| 143 | +> } |
| 144 | +> } |
| 145 | +> } |
| 146 | +>} |
0 commit comments