#c #sockets #casting #char #opcode
#c #сокеты #Кастинг #символ #код операции
Вопрос:
Я работаю с библиотекой сокетов NetLink ( https://sourceforge.net/apps/wordpress/netlinksockets / ), и я хочу отправить некоторые двоичные данные по сети в указанном мной формате.
Формат, который я запланировал, довольно прост и выглядит следующим образом:
-
Байты 0 и 1: код операции типа uint16_t (т.е. Целое число без знака всегда длиной 2 байта)
-
Байты 2 и далее: любые другие необходимые данные, такие как строка, целое число, комбинация каждого и т. Д. Другая сторона будет интерпретировать эти данные в соответствии с кодом операции. Например, если код операции равен 0, что означает «войти», эти данные будут состоять из целого числа в один байт, сообщающего вам, какой длины имя пользователя, за которым следует строка, содержащая имя пользователя, за которой следует строка, содержащая пароль. Для кода операции 1, «отправить сообщение чата», все данные здесь могут быть просто строкой для сообщения чата.
Однако вот что библиотека дает мне для работы с отправкой данных:
void send(const stringamp; data);
void send(const char* data);
void rawSend(const vector<unsigned char>* data);
Я предполагаю, что хочу использовать rawSend() для этого .. но rawSend() принимает символы без знака, а не указатель void * на память? Не будет ли здесь некоторой потери данных, если я попытаюсь преобразовать определенные типы данных в массив символов без знака? Пожалуйста, поправьте меня, если я ошибаюсь.. но если я прав, означает ли это, что я должен искать другую библиотеку, которая поддерживает реальную передачу двоичных данных?
Предполагая, что эта библиотека действительно служит моим целям, как именно я мог бы привести и объединить мои различные типы данных в один 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);
Однако на другом конце я столкнулся с некоторыми исключениями, когда попытался интерпретировать данные. Я неправильно подхожу к приведению? Собираюсь ли я потерять данные при приведении к неподписанным символам?
Заранее спасибо.
Ответ №1:
Мне нравится, как они заставляют вас создавать вектор (который должен использовать кучу и, следовательно, выполняться в непредсказуемое время) вместо того, чтобы просто возвращаться к стандартному (const void* buffer, size_t len)
кортежу C, который совместим со всем и не может быть превзойден по производительности. Ну и ладно.
Вы могли бы попробовать это:
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 amp; 0xFF);
#elseif LITTLE_ENDIAN_OPCODE
buffer.push_back(opcode amp; 0xFF);
buffer.push_back(opcode >> 8);
#else
// Native order opcode
buffer.insert(buffer.end(), reinterpret_cast<const unsigned char*>(amp;opcode),
reinterpret_cast<const unsigned char*>(amp;opcode) sizeof(uint16_t));
#endif
const unsigned char* base(reinterpret_cast<const unsigned char*>(rawData));
buffer.insert(buffer.end(), base, base rawDataSize);
socket->rawSend(amp;buffer); // Why isn't this API using a reference?!
}
Это использует insert
which должно оптимизировать лучше, чем рукописный цикл with push_back()
. Он также не будет пропускать буфер, если rawSend
генерирует исключение.
ПРИМЕЧАНИЕ: порядок байтов должен совпадать для платформ на обоих концах этого соединения. Если это не так, вам нужно либо выбрать один порядок байтов и придерживаться его (обычно это делают интернет-стандарты, и вы используете функции htonl
and htons
), либо вам нужно определить порядок байтов («родной» или «назад» из POV получателя) и исправить его, если «назад».
Комментарии:
1. Вместо all all вашей условной компиляции вы должны использовать
hton_s
для преобразования числа в сетевой порядок.2. Условная компиляция предназначена для того, чтобы объяснить ваши варианты в более сжатой форме, чем большее количество абзацев текста. Я полностью ожидаю, что OP выберет один вариант и удалит остальные.
3. Это выглядит великолепно, но оба вызова static_cast выдают ошибку компиляции: ошибка C2440: ‘static_cast’: не удается преобразовать из ‘uint16_t *’ в ‘const unsigned char *’
4. Изменить
static_cast
наreinterpret_cast
(ответ исправлен).5. Потрясающе, спасибо, Майк! Сейчас он компилируется, и я буду тестировать его позже сегодня вечером. Похоже, это должно работать идеально. Также спасибо всем, кто ответил; все ваши ответы были замечательными и очень содержательными.
Ответ №2:
Я бы использовал что-то вроде этого:
#define OPCODE_LOGINREQUEST 0
#define OPCODE_MESSAGE 1
void addRaw(std::vector<unsigned char> amp;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> amp;v, uint8_t val)
{
v.push_back(val);
}
void addUint16(std::vector<unsigned char> amp;v, uint16_t val)
{
val = htons(val);
addRaw(v, amp;val, sizeof(uint16_t));
}
void addStringLen(std::vector<unsigned char> amp;v, const std::string amp;val)
{
uint8_t len = std::min(val.length(), 255);
addUint8(v, len);
addRaw(v, val.c_str(), len);
}
void addStringRaw(std::vector<unsigned char> amp;v, const std::string amp;val)
{
addRaw(v, val.c_str(), val.length());
}
void sendLogin(const std::string amp;user, const std::string amp;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(amp;data);
}
void sendMsg(const std::string amp;msg)
{
std::vector<unsigned char> data(
sizeof(uint16_t)
msg.length()
);
addUint16(data, OPCODE_MESSAGE);
addStringRaw(data, msg);
socket->rawSend(amp;data);
}
Комментарии:
1. Почему бы просто не использовать перегруженный
add
метод? Или просто создать класс, который обертывает вектор? С классом вы могли бы сделатьcout
перегрузку в стилеoperator <<
a . PS вам не нужноconst
передsize_t len
inaddRaw
, поскольку этот параметр передается по значению, а не по указателю или ссылке.
Ответ №3:
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
принять a vector
довольно странное, общая библиотека будет работать на другом уровне абстракции. Я могу только догадываться, что это так, потому rawSend
что создает копию переданных данных и гарантирует их срок службы до завершения операции. Если нет, то это просто плохой выбор дизайна; добавьте к этому тот факт, что он принимает аргумент по указателю… Вы должны рассматривать это data
как контейнер необработанной памяти, есть некоторые нюансы, которые нужно исправить, но вот как вы должны работать с типами pod в этом сценарии:
data->insert( data->end(), reinterpret_cast< char const* >( amp;opcode ), reinterpret_cast< char const* >( amp;opcode ) sizeof( opcode ) );
Комментарии:
1. Большинство систем не выдают предупреждения о приведении вниз.
2. @Dietrich Epp: Очень плохо, потому что приведение вниз — это ситуация, в которой я ожидаю появления предупреждений, поскольку значение может быть усечено. Возможно, пришло время рассмотреть вопрос об увеличении уровня предупреждения.
3. Это не имеет ничего общего с уровнем предупреждения. Количество ложных срабатываний было бы невероятно высоким из-за «обычных арифметических преобразований». Например, какой тип
(unsigned char)1 (unsigned char)1
? Если вы сказалиunsigned char
, что ошибаетесь — правильный ответint
.4. @Dietrich Epp: Я не уверен, откуда вы получаете свою информацию; с VC я получаю предупреждение каждый раз, когда пытаюсь присвоить или передать в качестве аргумента целое число с большей точностью, чем целевой тип. Поиск предупреждения
C4244
для ссылки…5. Интересно… тогда выдает ли это предупреждение для таких простых вещей, как
short x = ..., y = ...; x = y;
? Потому что, если это произойдет, он также может выдать то же предупреждение дляint
.
Ответ №4:
Это будет работать:
#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 *)amp;opcode;
for(int i = 0; i < sizeof(opcode); i )
loginRequestData->push_back(opcode_data[i]);
socket->rawSend(loginRequestData);
Это также будет работать для любого типа POD.
Ответ №5:
Да, используйте rawSend, поскольку send, вероятно, ожидает нулевой терминатор.
Вы ничего не потеряете, если приведете к char вместо void* . Память — это память. Типы никогда не сохраняются в памяти в C , за исключением информации RTTI. Вы можете восстановить свои данные, приведя их к типу, указанному вашим кодом операции.
Если вы можете определить формат всех ваших отправлений во время компиляции, я рекомендую использовать структуры для их представления. Я делал это раньше профессионально, и это просто лучший способ четко сохранить форматы для самых разных сообщений. И это очень легко распаковать с другой стороны; просто добавьте необработанный буфер в структуру на основе кода операции!
struct MessageType1 {
uint16_t opcode;
int myData1;
int myData2;
};
MessageType1 msg;
std::vector<char> vec;
char* end = (char*)amp;msg sizeof(msg);
vec.insert( vec.end(), amp;msg, end );
send(vec);
Подход struct — лучший и самый аккуратный способ отправки и получения, но макет фиксируется во время компиляции.
Если формат сообщений не определен до выполнения, используйте массив символов:
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(amp;buffer);
Комментарии:
1. Единственное, что
void*
вам нужно, это не дать компилятору жаловаться, когда вы неявно вводите тип из какого-либо другого указателя наvoid*
.