It is necessary to send data of the type: float / int / char through a socket, how to organize "packaging" on the sender's side to send everything in one packet, and "unpack" on the receiving side.

In the network I found a screen application for the game, how to pack the same?

  • can be packaged in a binary format and unpacked on the client side, but to send data structures, rather you need to pack the structure - jashka

3 answers 3

A primitive way for simple cases is to describe the data structure:

struct Data { int a; float b; char c[32]; } data; 

Write it to the socket on the transmitting side ( send(socket, &data, sizeof(data), 0) and read on the host in exactly the same structure ( recv(socket, &data, sizeof(data), 0) ). It is very important that the structure on both sides (transmitting and receiving) was identical in memory location (the same type sizes, the same byte order in the system, the same alignment of the structure fields, the same representation of floating-point numbers). Otherwise we get the wrong data that was sent. In practice, if the receiving party is also written in another language, we will get extra fuss and p ostorov for error.


The next option is to fill the data buffer manually:

 int foo = 42; long bar = 0; std::string str; str.append((char*)&foo, sizeof(int)); str.append((char*)&bar, sizeof(long)); 

Here, there is no problem with the alignment of the structure fields as in the first version, we glue the data ourselves, without gaps. But the rest of the problems are still with us (as before, the byte order, the size of the types, the representation of floating-point numbers must be identical on the transmitter and the receiver).


Manual, byte packing flow.

 uint32_t foo = 42; std::vector<uint8_t> buffer; buffer.push_back(static_cast<uint8_t>(foo >> 0)); buffer.push_back(static_cast<uint8_t>(foo >> 8)); buffer.push_back(static_cast<uint8_t>(foo >> 16)); buffer.push_back(static_cast<uint8_t>(foo >> 24)); 

Here, we simply take each piece of data and manually transfer it to the output stream in a system independent order. Disassemble also have manually. The most universal way, because all aspects of the generated stream are controlled by ourselves. For convenience, you can write a serializer / deserializer class for the required types (including custom ones).


Over time (and maybe immediately), the difficulties associated with changes in the transmitted data are added (for example, additional data needed to be transferred or some old ones have already become irrelevant). Especially if the receiver must receive data in the old format and in the new one. You have to add some version identifiers. Additionally, you need to handle cases when you need to transfer optional data (which may be missing) or data of dynamic size (arrays).


In order not to solve all these problems on your own, you can take a ready-made solution, for example protobuf from google. It supports different languages, has a system of versions, support for complex data. Or a slightly simpler solution (but also faster), also from google flatbuffers .


If the amount of information transmitted is not critical, it may be convenient to generate data in the form of json (for example, using https://github.com/nlohmann/json ). If there is a javascript programmer on the receiving side, he will be very grateful to you (and not only the JavaScript programmer). Also, as a programmer from a typed language, I recommend using schemas for checking json.

As an alternative to json, you can take a messagepack , which is "like json", but more compact. If you need even more compact, you can shake the transmitted string using zlib for example.


For all variants, it is also necessary to take into account that transmitting pointers is meaningless, since on the receiving side, they will indicate unknown where. Also understand the subtleties of data transmission over the network. For example, data sent via UDP may not reach the recipient, data sent via TCP may be fragmented or glued to its neighbors upon receipt, etc. Perhaps you should think about ready-made online libraries, for example RakNet , which includes almost everything for building a multiplayer game.

  • Where is the problem with the size in the second version? There's still a str on the buffer to be replaced. - αλεχολυτ
  • Thanks for your keen glance! ) About dimensions - in the sense that on the other side int for example 8-byte. Corrected to make it clearer. - Vladimir Gamalyan
  • Thank! Very interested in "bit packing"! - yngson

In functions of sending data to another socket (for example, send) and functions for receiving data (for example, recv) one of the parameters is always a pointer to a buffer with this data (bytes). You must first create this buffer. This can be done in very different ways. For example, if the structure of the transmitted data is dynamic and / or there are a lot of such structures, then it is possible to form a buffer, so to speak, on the fly. Those. we gradually need the necessary data, as we receive them, push it into the buffer.

  std::string buffer; uint32_t i32 = 0x32fe56ad; float f = 1.0; std::string str = "1234"; uint8_t sz = str.size(); buffer.append((char*)&i32, sizeof(i32)); buffer.append((char*)&f, sizeof(f)); buffer.append((char*)&sz, sizeof(sz)); buffer.append(str); std::cout << "lenght message: " <<buffer.size() << " bytes" << std::endl; std::cout << " message: "; for(size_t i=0; i<buffer.size(); i++) printf("%x ",(uint8_t)buffer[i]); 

For simplicity, in the example, I used a string as a buffer. But it would be better to write some kind of your own class, with your own methods using templates for different types of packed data. Note that before passing a string (inside the buffer), its size is packed. When you receive a line from the other side of the reception, you will know the length of it and how many bytes from the buffer to read to get the string. In this case, I pack a size of one byte. But ideally it is better to pack VLQ . Of course, when parsing data on the other side, you need to retrieve them into variables with the same types / sizes and in the same order as they were packed.

If the data is transferred constant and of the same type, then the structure itself can be used as a pointer to the buffer with the data. But the best is probably to use it for packaging trivial types.

  struct pack { uint32_t i32 = 0x32fe56ad; float f = 1.0; unsigned char str[5] = "1234"; }; pack pax; uint8_t *p = (uint8_t*)&pax; std::cout << "lenght message: " <<sizeof(pax) << " bytes" << std::endl; std::cout << " message: "; for(size_t i=0; i<sizeof(pax); i++) { printf("%x ",*p); p++; } 

Structured data is easier to take and unpack on the other side. But there are generally a few nuances that need to be taken into account. So, for example, if there are pointers to other data types in the structure, then transferring this data will not be so easy. Also the size of the structure can be aligned and in fact will be larger than the amount of data.

And I would also like to note ... Do not confuse the data sent directly to the package leaving the network. Those data (buffer, message) that you pack and send will in fact be split into several packages. These packets (parts of your data) will come to the other end in a random order when using UDP and in order - with TCP (but still in chunks). Therefore, to the sent data, it is also desirable to pack in front something like a header (header), which will contain the length of your entire message (and for UDP also the sequence number of the message).

PS: the code in the answer is given solely for understanding the process, but not for assessing its functionality and correctness.

  • A note on sending via a UDP socket - if you send a piece of data with one command (send / write), then, despite possible fragmentation at the IP level, the receiving side receives it entirely (at the socket level) or does not receive it at all. Another thing is that two send via send can be received in any order. - Vladimir Gamalyan
  • Probably it is worth adding that the packing example does not take into account the order in which bytes are in memory. - Vladimir Gamalyan
  • @sergw and then why did you write this to me .. I have my own serialization implementation. Read the help and write it to the topicaster. - Max ZS

The correct approach would be to use data serialization . For example: Protocol Buffers , JSON , XML , ASN.1 , etc. Comparative table .