Преобразование типа unsigned int + строка в вектор без знака - PullRequest
0 голосов
/ 07 октября 2011

Я работаю с библиотекой сокетов NetLink (https://sourceforge.net/apps/wordpress/netlinksockets/) и хочу отправить некоторые двоичные данные по сети в указанном формате.

Формат, который я запланировалдовольно прост и выглядит следующим образом:

  • Байты 0 и 1: код операции типа uint16_t (т. е. целое число без знака всегда длиной 2 байта)

  • 2 байта и более: любые другие необходимые данные, такие как строка, целое число, комбинация каждого и т. Д., Другая сторона будет интерпретировать эти данные в соответствии с кодом операции.Например, если код операции равен 0, что означает «вход в систему», эти данные будут состоять из целого числа в один байт, указывающего длину имени пользователя, сопровождаемого строкой, содержащей имя пользователя, после которой следует строка, содержащая пароль.Для кода операции 1 «отправить сообщение чата» все данные здесь могут быть просто строкой сообщения чата.

Вот то, с чем библиотека дает мне работать для отправки данных,хотя:

void send(const string& data);
void send(const char* data);
void rawSend(const vector<unsigned char>* data);

Я предполагаю, что хочу использовать rawSend () для этого ... но rawSend () принимает беззнаковые символы, а не пустой указатель * на память?Не будет ли здесь какая-то потеря данных, если я попытаюсь привести определенные типы данных к массиву беззнаковых символов?Пожалуйста, исправьте меня, если я ошибаюсь ... но если я прав, значит ли это, что я должен искать другую библиотеку, которая поддерживает реальную передачу двоичных данных?

Предполагая, что эта библиотека служит моим целям,Как именно я приведу и объединю свои различные типы данных в один std :: vector?Я попробовал что-то вроде этого:

#define OPCODE_LOGINREQUEST 0

std::vector<unsigned char>* loginRequestData = new std::vector<unsigned char>();
uint16_t opcode = OPCODE_LOGINREQUEST;
loginRequestData->push_back(opcode);
// and at this point (not shown), I would push_back() the individual characters of the strings of the username and password.. after one byte worth of integer telling you how many characters long the username is (so you know when the username stops and the password begins)
socket->rawSend(loginRequestData);

Однако, с другой стороны, я столкнулся с некоторыми исключениями, когда попытался интерпретировать данные.Я все неправильно подхожу к кастингу?Собираюсь ли я потерять данные путем приведения к неподписанным символам?

Заранее спасибо.

Ответы [ 5 ]

1 голос
/ 07 октября 2011

Я бы использовал что-то вроде этого:

#define OPCODE_LOGINREQUEST 0 
#define OPCODE_MESSAGE 1

void addRaw(std::vector<unsigned char> &v, const void *data, const size_t len)
{
    const unsigned char *ptr = static_cast<const unsigned char*>(data);
    v.insert(v.end(), ptr, ptr + len);
}

void addUint8(std::vector<unsigned char> &v, uint8_t val)
{
    v.push_back(val);
}

void addUint16(std::vector<unsigned char> &v, uint16_t val)
{
    val = htons(val);
    addRaw(v, &val, sizeof(uint16_t));
}

void addStringLen(std::vector<unsigned char> &v, const std::string &val)
{
    uint8_t len = std::min(val.length(), 255);
    addUint8(v, len);
    addRaw(v, val.c_str(), len);
}

void addStringRaw(std::vector<unsigned char> &v, const std::string &val)
{
    addRaw(v, val.c_str(), val.length());
}

void sendLogin(const std::string &user, const std::string &pass)
{
    std::vector<unsigned char> data(
        sizeof(uint16_t) +
        sizeof(uint8_t) + std::min(user.length(), 255) +
        sizeof(uint8_t) + std::min(pass.length(), 255)
    );
    addUint16(data, OPCODE_LOGINREQUEST);
    addStringLen(data, user);
    addStringLen(data, pass);
    socket->rawSend(&data);
}

void sendMsg(const std::string &msg)
{
    std::vector<unsigned char> data(
      sizeof(uint16_t) +
      msg.length()
    );
    addUint16(data, OPCODE_MESSAGE);
    addStringRaw(data, msg);
    socket->rawSend(&data);
}
1 голос
/ 07 октября 2011

Мне нравится, как они заставляют вас создавать вектор (который должен использовать кучу и, следовательно, выполняться в непредсказуемое время) вместо того, чтобы просто вернуться к стандартному кортежу C (const void* buffer, size_t len), который совместим с все и не могут быть побеждены за производительность.О, хорошо.

Вы можете попробовать это:

void send_message(uint16_t opcode, const void* rawData, size_t rawDataSize)
{
    vector<unsigned char> buffer;
    buffer.reserve(sizeof(uint16_t) + rawDataSize);
#if BIG_ENDIAN_OPCODE
    buffer.push_back(opcode >> 8);
    buffer.push_back(opcode & 0xFF);
#elseif LITTLE_ENDIAN_OPCODE
    buffer.push_back(opcode & 0xFF);
    buffer.push_back(opcode >> 8);
#else
    // Native order opcode
    buffer.insert(buffer.end(), reinterpret_cast<const unsigned char*>(&opcode), 
        reinterpret_cast<const unsigned char*>(&opcode) + sizeof(uint16_t));
#endif
    const unsigned char* base(reinterpret_cast<const unsigned char*>(rawData));
    buffer.insert(buffer.end(), base, base + rawDataSize);
    socket->rawSend(&buffer); // Why isn't this API using a reference?!
}

При этом используется insert, что должно оптимизировать лучше, чем рукописный цикл с push_back().Он также не будет пропускать буфер, если rawSend выбросит исключение.

ПРИМЕЧАНИЕ: Порядок байтов должен совпадать для платформ на обоих концах этого соединения.Если этого не произойдет, вам нужно либо выбрать один порядок байтов и придерживаться его (обычно это используют стандарты Интернета, и вы используете функции htonl и htons), либо вам необходимо определить порядок байтов («нативный»).или «назад» от POV получателя) и исправьте его, если «назад».

0 голосов
/ 07 октября 2011

Это будет работать:

#define OPCODE_LOGINREQUEST 0

std::vector<unsigned char>* loginRequestData = new std::vector<unsigned char>();
uint16_t opcode = OPCODE_LOGINREQUEST;
unsigned char *opcode_data = (unsigned char *)&opcode;
for(int i = 0; i < sizeof(opcode); i++)
    loginRequestData->push_back(opcode_data[i]);
socket->rawSend(loginRequestData);

Это также будет работать для любого типа POD.

0 голосов
/ 07 октября 2011

Да, переходите к rawSend, так как send, вероятно, ожидает терминатор NULL.

Вы ничего не потеряете, если приведете к char вместо void *.Память это память.Типы никогда не хранятся в памяти в C ++, за исключением информации RTTI.Вы можете восстановить свои данные, приведя их к типу, указанному в вашем коде операции.

Если вы можете определить формат всех ваших отправлений во время компиляции, я рекомендую использовать структуры для их представления.Я делал это раньше профессионально, и это просто лучший способ четко хранить форматы для широкого спектра сообщений.И это очень легко распаковать на другой стороне;просто приведите необработанный буфер в структуру, основанную на коде операции!

struct MessageType1 {
    uint16_t opcode;
    int myData1;
    int myData2;
};

MessageType1 msg;

std::vector<char> vec;
char* end = (char*)&msg + sizeof(msg);
vec.insert( vec.end(), &msg, end );

send(vec);

Структурный подход - лучший, самый удобный способ отправки и получения, но компоновка фиксируется во время компиляции.Если формат сообщений не определен до времени выполнения, используйте массив символов:

char buffer[2048];

*((uint16_t*)buffer) = opcode;
// now memcpy into it
// or placement-new to construct objects in the buffer memory

int usedBufferSpace = 24; //or whatever

std::vector<char> vec;
const char* end = buffer + usedBufferSpace;
vec.insert( vec.end(), buffer, end );

send(&buffer);
0 голосов
/ 07 октября 2011
std::vector<unsigned char>* loginRequestData = new std::vector<unsigned char>();
uint16_t opcode = OPCODE_LOGINREQUEST;
loginRequestData->push_back(opcode);

Если unsigned char имеет длину 8 битов, что в большинстве систем равно, вы будете терять старшие 8 битов с opcode каждый раз, когда нажимаете.Вы должны получить предупреждение за это.

Решение для rawSend взять vector довольно странное, общая библиотека будет работать на другом уровне абстракции.Я могу только догадываться, что это так, потому что rawSend делает копию переданных данных и гарантирует их время жизни до завершения операции.Если нет, то это просто плохой выбор дизайна;добавьте к этому тот факт, что он принимает аргумент указателем ... Вы должны увидеть это data как контейнер необработанной памяти, есть некоторые причуды, которые нужно исправить, но вот как вы должны работать с типами pod вэтот сценарий:

data->insert( data->end(), reinterpret_cast< char const* >( &opcode ), reinterpret_cast< char const* >( &opcode ) + sizeof( opcode ) );
...