Crear un mensaje para escribir en un socket TCP

I don't really know how to put this question, but here we go.

So let's assume I'm using ASIO from Boost libs to connect to a server using a TCP socket and write a certain message to it.

The message contains some user data and looks like this: 1 byte (packet ID), 4 bytes (integer), user null-terminated string and 6 zero bytes (reserved by the server but unused).

What would be the most convenient way to assemble such a message for use with ASIO's boost::asio::buffer function?

I'm really confused at this point. Help greatly appreciated.

preguntado el 31 de julio de 12 a las 12:07

Your message is a string or structure? -

That's what I actually can't decide with. I have tried using a string, but I'm not sure what would be the most appropriate way to fill it with necessary byte data, and especially if it's at all the best option to go with, assuming ASIO reference states that it may work just as well with std::vector or std::string. I thought of using a struct for this, but I'm not totally sure about how to do it properly. -

3 Respuestas

Instead of making a single buffer using asio::buffer() function, you can adapt your structure to be a buffer sequence - asio functions accept such a concept. It would be convenient to send this way a pattern like fixed--null-terminated--null-terminated-fixed-fixed-etc...

Respondido 31 Jul 12, 12:07

It would be great if you could demonstrate how to do that with a simple example. I'm curious to see an example and wondering if it would be advantageous to use it for myself. - halcón vite

A. Define packet structures that can be serialized.

class ISerializable
{
public:
    virtual ~ISerializable(){}

    virtual void serialize(std::ostream& stream) = 0;
};

class LoginPacket : public ISerializable
{
public:
    // Constructor and access functions

    virtual void serialize(std::ostream& stream)
    {
        stream.write((const char*)&packetId, 1);
        stream.write((const char*)&accountId, 4);
        stream.write(username.c_str(), username.size() + 1);
        // Write the unused 6-bytes of zero
        int zero(0);
        stream.write((const char*)&zero, 4);
        stream.write((const char*)&zero, 2);
    }

private:
    unsigned char packetId;
    unsigned int32_t accountId;
    std::string username;
};

B. Now, to use this packet class do something like:

LoginPacket packet;
// Fill details for the packet
std::stringstream data;
packet.serialize(data);
// Send the data to the network
yourSocket.send(data.str().c_str(), data.str().size());

Respondido 31 Jul 12, 13:07

Is &int(0) a C++11 thing, or have I never known this existed in C++? - Shahbaz

I wouldn't use a stream for a binary protocol. A stream buffer seams more appropriate here. And there seams to be no use for virtual functions, at least not, unless there are some kind of polymorphic data structures to be serialized. - Torsten Robitzki

>stream.write(&accountId, 4); makes this implementation highly implementation dependent. - Torsten Robitzki

@ViteFalcon Simply writing an integer by writing the first 4 bytes of it's integer representation has two problems: First, an int don't have to be 4 bytes. This can even change from one version to the next of the same compiler. If you are lucky, and the size became 8 bytes, you wrote the "right" half of the int. I would recommend to use at least "sizeof int". The other problem is the representation of the int in memory (little endian, big endian etc.). This will likely be the same on the same platform, but as we are speaking about networking, this might be different on the reading side. - Torsten Robitzki

@TorstenRobitzki Or use int32_t Etc.- james mclaughlin

the boost::asio::buffer() functions adapt or convert other kind of buffers to the one, used by asio.

You should use some kind of application buffer, fill that buffer and then pass the buffer to asio for writing onto the wire. For example:

std::vector< char > data;

data.push_back( id );
data.push_back( i & 0xff );
data.push_back( ( i >> 8 ) & 0xff );
data.push_back( ( i >> 16 ) & 0xff );
data.push_back( ( i >> 24 ) & 0xff );

const char* const str = s.c_str();
data.insert( data.end(), str, str + std::strlen( str ) );

for ( int pad = 0; pad != 4; ++pad )
    data.push_back( 0 );

boost::asio::write( socket, boost::asio::buffer( data ) );

Respondido 31 Jul 12, 13:07

Wouldn't there be a way to write non-character types in a more compact way? - Samuel Moriarty

@InsomniaArray it depends on your requirements. But defining the binary protocol down to the lowest bit seems to be a not to much work. How much different primitive data type do you habe? A bool, an int and a string? That are 3 functions, taking some kind of buffer and the data to be written. That will cost you 10 minutes now and might save you hours latter, when someone ask you to support old Mac and Alpha computers ;-) - Torsten Robitzki

True, although I don't intend to have a public release or anything. But then again, who knows what path I will go. Thanks. - Samuel Moriarty

Of all solutions (I haven't seen the an example from Igor), this one looks the most complicated. - halcón vite

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.