3

I'm wondering what is the proper way to send structures as char array by sockets.

At this moment I have somethig like below but it isn't good solution because it causes undefined behavoiur. Is it possible to send types like string or other user-defined types?

Or maybe structures which works as protocol frames should only have bult-in types like int, char, or char array with fixed size?

Maybe you know any best practices about this? Maybe you know any working libraries to do that?

I didn't found any working example which would be different of mine.

class FrameProtocol
{
public:
    std::array<char, 4096> buffer;
};

class FrameHeader
{
public:
    typedef enum { hello, priv} types;
    types type;
    const size_t size = sizeof(FrameHeader);
};

class PrivateMessage
{
public:
    int sender;
    int receiver;
    std::string value;
    const size_t size = sizeof(PrivateMessage);
};

class HelloMessage
{
public:
    std::string nickname;
    size_t size = sizeof(HelloMessage);
};


void sendTo(FrameProtocol& protocol)
{
    FrameHeader frame_header;
    memcpy(&frame_header, protocol.buffer.begin(), frame_header.size);

    std::cout << "Type: " << frame_header.type << std::endl;

    switch (frame_header.type)
    {
        case frame_header.priv:
        {
            std::cout << "###############################################" << std::endl;
            std::cout << "PRIV RECEIVED!" << std::endl;
            PrivateMessage priv;
            memcpy(&priv, protocol.buffer.begin() + frame_header.size, priv.size);
            std::cout << "PrivateMessage!, From: " << priv.sender << " To: " << priv.receiver << " val: " << priv.value << std::endl;
            std::cout << "################################################\n" << std::endl;
            break;
        }
        case frame_header.hello:
        {
            std::cout << "###############################################" << std::endl;
            std::cout << "HELLO RECEIVED!" << std::endl;
            HelloMessage hello {};
            memcpy(&hello, protocol.buffer.begin() + frame_header.size, sizeof(HelloMessage));
            std::cout << "HelloMessage!, Nickname: " << hello.nickname << std::endl;
            std::cout << "################################################\n" << std::endl;
        break;

            break;
        }

    }
}

int main()
{
    FrameProtocol frame;
    FrameHeader frame_header;
    frame_header.type = frame_header.priv;

    PrivateMessage pm;
    pm.sender = 10;
    pm.receiver = 20;
    pm.value = "Test";

    memcpy(frame.buffer.begin(), &frame_header, frame_header.size); // write header
    memcpy(frame.buffer.begin() + frame_header.size, &pm, pm.size); // write body

    FrameProtocol frame2;
    FrameHeader frame_header2;
    frame_header2.type = frame_header2.hello;

    HelloMessage hello;
    hello.nickname = "testas";
    hello.size = sizeof(hello);
    memcpy(frame2.buffer.begin(), &frame_header2, frame_header2.size); //write header
    memcpy(frame2.buffer.begin() + frame_header2.size, &hello, hello.size); // write body

    sendTo(frame2);
    sendTo(frame);
4
  • 1
    Useful low-level C++ often has to rely on UB. But your code isn't merely relying on UB; it just doesn't work. There is no effective way to communicate non-TriviallyCopyable user-defined types by memcpying the object's representation. Jun 15, 2018 at 14:02
  • I think boost serialisation will be of use to you.
    – UKMonkey
    Jun 20, 2018 at 16:35
  • 4
    I have successfully used google protocol buffers in a few projects. I've been happy with the results. developers.google.com/protocol-buffers Also see Apache Thrift, Fast Buffers etc. Jun 26, 2018 at 12:43
  • 1
    "Is it possible to send types like string or other user-defined types?" - you can send them but need to decide on a protocol - e.g. that you'll send a uint32_t (4GB limit enough?) with the size() in network-byte-ordering first, then the textual content, so the receiver who's expecting a std::string can first read the size and know how much text to expect. You can use std::string::size() and ::data(). It's more complex if you buffer yourself, rather than write directly to the socket and let the TCP Nagle algo consolidate for you. For an existing lib: check boost asio.
    – Tony
    Jun 30, 2018 at 0:30

3 Answers 3

3

If you want to send data via sockets, you are best off converting the data first to some textual format (like JSON, or XML).

A significant problem with sending binary data over the internet is that it presumes both sides of the communication channel will run with similar computer architectures (like endianness of data, or size of integer). You can address these problems too using binary data exchange formats, like ASN.1, BSON, XDR, Thrift etc.

Libraries you can use to do stuff like (there are many) include gSOAP (https://www.cs.fsu.edu/~engelen/soap.html), or Stroika (https://github.com/SophistSolutions/Stroika). GSoap lets you define C++ structures, generate WSDL, and simple client / server proxy/stubs to transmit and receive requests with these data structures. The Stroika framework contains a similar builtin webserver and web-client layer to handle the communications, and easy to use tools to map C++ objects to/from JSON (or XML, or other text format), as well as web-service layer functionality tying all this together.

2
  • 2
    The internet solves the problem quite well without JSON ;)
    – tofro
    Sep 11, 2018 at 6:47
  • 1
    @tofro: Going with a standardized format like JSON or XML and an existing library has the added benefit of resolving wonky compatibility issues when handling binary data, as mentioned in the answer. This is precisely why these formats are used, unless you like dealing with things like "endianness"... Dec 10, 2018 at 14:50
0

Don't worry about behaviour that is undefined by the standard for processes like this; much of C++'s low level behaviour is undefined. What matters is that you can get consistent behaviour for your platform. So decide on a protocol and then write custom decoding/encoding that understands exactly what byte order to send/receive. Traditionally, this has meant sending data in "Network Byte Order" (= Big Endian) and transforming the byte order for Little Endian architectures. Floating point numbers are theoretically more of a pain since they could use all manner of representations but fortunately, in practice, current architectures have coalesced around IEEE 754 so it's not so much of an issue.

You also need to ensure that your data for transmission is trivially copyable POD; and your current classes aren't because they contain std::string. You will need to recode those strings into a transmittable format.

The alternative is to transform the data into human-readable text only format, such as JSON or XML, but this is necessarily slower than binary approaches.

0

The easiest by far is converting to/from JSON. That’s what you do if it is not performance critical, and it works very well with more complex structures, and it supports upgrading formats if used cleverly.

Next there are third party libraries for converting to some binary format, which is a bit more complicated, but faster. Usually this needs to be integrated into your build process.

Or the manual method which I wouldn’t recommend: use an array of bytes, and fill it byte by byte.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.