Pregunta

Estoy creando un cliente de línea de comandos para Minecraft. Hay una especificación completa en el protocolo que se puede encontrar aquí: http://mc.kev009.com/protocol. Para responder a su pregunta de antemano, sí, soy un poco de C ++ Noob.

Tengo varios problemas en la implementación de este protocolo, de los cuales cada crítico.

  1. El protocolo dice que todos los tipos son grandes. No tengo idea de cómo debo verificar si mis datos son poco endian y, en caso afirmativo, cómo convertir a Big-Endian.
  2. El tipo de datos de cadena es un poco extraño. Es una cadena UTF-8 modificada que está precedida por un corto que contiene la longitud de la cadena. No tengo idea de cómo debo empacar esto en una matriz de char [] simple ni cómo convertir mis cadenas simples en UTF-8 modificadas.
  3. Incluso si supiera cómo convertir mis datos en Big-Endian y crear cadenas UTF-8 modificadas, todavía no sé cómo empacar esto en una matriz de char [] y enviarlo como un paquete. Todo lo que he hecho antes es una simple red HTTP que es ASCII simple.

Explicaciones, enlaces, nombres de funciones relacionadas y fragmentos cortos muy apreciados.

EDITAR

1 y 3 se responden ahora. 1 se responde a continuación por el usuario470379. 3 es respondido por este impresionante hilo que explica lo que quiero hacer muy bien: http://cboard.cprogramming.com/networking-device-communication/68196-sending-non-char*-data.html Sin embargo, aún no estoy seguro sobre el UTF-8 modificado.

¿Fue útil?

Solución

Un enfoque tradicional es definir una estructura de mensajes C ++ para cada mensaje de protocolo e implementar funciones de serialización y deserialización para ello. Por ejemplo Solicitud de inicio de sesión puede representarse así:

#include <string>
#include <stdint.h>

struct LoginRequest
{
    int32_t protocol_version;
    std::string username;
    std::string password;
    int64_t map_seed;
    int8_t dimension;
};

Ahora se requieren funciones de serialización. Primero necesita funciones de serialización para enteros y cadenas, ya que estos son los tipos de miembros en LoginRequest.

Las funciones de serialización de enteros deben hacer conversiones hacia y desde la representación de Big Endian. Dado que los miembros del mensaje se copian hacia y desde el búfer, la reversión del orden de bytes se puede hacer mientras se copia:

#include <boost/detail/endian.hpp>
#include <algorithm>

#ifdef BOOST_LITTLE_ENDIAN

    inline void xcopy(void* dst, void const* src, size_t n)
    {
        char const* csrc = static_cast<char const*>(src);
        std::reverse_copy(csrc, csrc + n, static_cast<char*>(dst));
    }

#elif defined(BOOST_BIG_ENDIAN)

    inline void xcopy(void* dst, void const* src, size_t n)
    {
        char const* csrc = static_cast<char const*>(src);
        std::copy(csrc, csrc + n, static_cast<char*>(dst));
    }

#endif

// serialize an integer in big-endian format
// returns one past the last written byte, or >buf_end if would overflow
template<class T>
typename boost::enable_if<boost::is_integral<T>, char*>::type serialize(T val, char* buf_beg, char* buf_end)
{
    char* p = buf_beg + sizeof(T);
    if(p <= buf_end)
        xcopy(buf_beg, &val, sizeof(T));
    return p;
}

// deserialize an integer from big-endian format
// returns one past the last written byte, or >buf_end if would underflow (incomplete message)
template<class T>
typename boost::enable_if<boost::is_integral<T>, char const*>::type deserialize(T& val, char const* buf_beg, char const* buf_end)
{
    char const* p = buf_beg + sizeof(T);
    if(p <= buf_end)
        xcopy(&val, buf_beg, sizeof(T));
    return p;
}

Y para cuerdas (manejo Modificado UTF-8 de la misma manera que las cadenas Asciiz):

// serialize a UTF-8 string
// returns one past the last written byte, or >buf_end if would overflow
char* serialize(std::string const& val, char* buf_beg, char* buf_end)
{
    int16_t len = val.size();
    buf_beg = serialize(len, buf_beg, buf_end);
    char* p = buf_beg + len;
    if(p <= buf_end)
        memcpy(buf_beg, val.data(), len);
    return p;
}

// deserialize a UTF-8 string
// returns one past the last written byte, or >buf_end if would underflow (incomplete message)
char const* deserialize(std::string& val, char const* buf_beg, char const* buf_end)
{
    int16_t len;
    buf_beg = deserialize(len, buf_beg, buf_end);
    if(buf_beg > buf_end)
        return buf_beg; // incomplete message
    char const* p = buf_beg + len;
    if(p <= buf_end)
        val.assign(buf_beg, p);
    return p;
}

Y un par de functores de ayuda:

struct Serializer
{
    template<class T>
    char* operator()(T const& val, char* buf_beg, char* buf_end)
    {
        return serialize(val, buf_beg, buf_end);
    }
};

struct Deserializer
{
    template<class T>
    char const* operator()(T& val, char const* buf_beg, char const* buf_end)
    {
        return deserialize(val, buf_beg, buf_end);
    }
};

Ahora, utilizando estas funciones primitivas, podemos serializar y deserializar fácilmente LoginRequest mensaje:

template<class Iterator, class Functor>
Iterator do_io(LoginRequest& msg, Iterator buf_beg, Iterator buf_end, Functor f)
{
    buf_beg = f(msg.protocol_version, buf_beg, buf_end);
    buf_beg = f(msg.username, buf_beg, buf_end);
    buf_beg = f(msg.password, buf_beg, buf_end);
    buf_beg = f(msg.map_seed, buf_beg, buf_end);
    buf_beg = f(msg.dimension, buf_beg, buf_end);
    return buf_beg;
}

char* serialize(LoginRequest const& msg, char* buf_beg, char* buf_end)
{
    return do_io(const_cast<LoginRequest&>(msg), buf_beg, buf_end, Serializer());
}

char const* deserialize(LoginRequest& msg, char const* buf_beg, char const* buf_end)
{
    return do_io(msg, buf_beg, buf_end, Deserializer());
}

Usar los functores auxiliares anteriores y representar buffers de entrada/salida como char rangos iteradores Solo se requiere una plantilla de función para hacer la serialización y la deserialización del mensaje.

Y armar todo, uso:

int main()
{
    char buf[0x100];
    char* buf_beg = buf;
    char* buf_end = buf + sizeof buf;

    LoginRequest msg;

    char* msg_end_1 = serialize(msg, buf, buf_end);
    if(msg_end_1 > buf_end)
        ; // more buffer space required to serialize the message

    char const* msg_end_2 = deserialize(msg, buf_beg, buf_end);
    if(msg_end_2 > buf_end)
        ; // incomplete message, more data required
}

Otros consejos

Para el #1, necesitarás usar ntohs y amigos. Utilizar el *s (breve) versiones para enteros de 16 bits, y el *l (Long) versiones para enteros de 32 bits. los hton* (Host a Network) convertirá los datos salientes en Big-Endian independientemente de la endiatura de la plataforma en la que se encuentra, y ntoh* (red a host) volverá a convertir los datos entrantes (nuevamente, independientemente de la plataforma endianness)

la parte superior de mi cabeza...

const char* s;  // the string you want to send
short len = strlen(s);

// allocate a buffer with enough room for the length info and the string
char* xfer = new char[ len + sizeof(short) ];

// copy the length info into the start of the buffer
// note:  you need to hanle endian-ness of the short here.
memcpy(xfer, &len, sizeof(short));

// copy the string into the buffer
strncpy(xfer + sizeof(short), s, len);

// now xfer is the string you want to send across the wire.
// it starts with a short to identify its length.
// it is NOT null-terminated.
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top