将 unsigned int + 字符串转换为 unsigned char 向量
Posted
技术标签:
【中文标题】将 unsigned int + 字符串转换为 unsigned char 向量【英文标题】:Casting an unsigned int + a string to an unsigned char vector 【发布时间】:2011-10-07 04:27:33 【问题描述】:我正在使用 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() 需要无符号字符,而不是指向内存的 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】:我喜欢他们如何让你创建一个向量(它必须使用堆,因此在不可预测的时间内执行)而不是仅仅退回到 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
抛出异常,它也不会泄漏缓冲区。
注意:字节顺序必须与此连接两端的平台匹配。如果没有,您需要选择一个字节顺序并坚持使用(Internet 标准通常这样做,并且您使用 htonl
和 htons
函数)或者您需要检测字节顺序(“native”或从接收者的 POV “向后”)并在“向后”时修复它。
【讨论】:
代替所有条件编译,你应该使用hton_s
将数字转换为网络顺序。
条件编译比更多的文本段落更简洁地解释你的选择。我完全希望 OP 选择一个选项并删除其余选项。
这看起来不错,但是两个 static_cast 调用都引发了编译错误:错误 C2440: 'static_cast' : cannot convert from 'uint16_t *' to 'const unsigned char *'
将 static_cast
更改为 reinterpret_cast
(已修复答案)。
太棒了,谢谢迈克!它现在可以编译,今晚晚些时候我将对其进行测试。看起来它应该可以完美运行。也感谢所有回答的人;您的回复都很棒,很有见地。【参考方案2】:
我会使用这样的东西:
#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);
【讨论】:
为什么不使用重载的add
方法呢?或者只是制作一个包装矢量的类?有了一个类,你可以做一个cout
风格的重载operator <<
。附言您不需要在addRaw
中的size_t len
前面加上const
,因为该参数是按值传递的,而不是指针或引用。【参考方案3】:
std::vector<unsigned char>* loginRequestData = new std::vector<unsigned char>();
uint16_t opcode = OPCODE_LOGINREQUEST;
loginRequestData->push_back(opcode);
如果unsigned char
的长度为 8 位 - 在大多数系统中是这样的 - 每次推送时您都会丢失来自 opcode
的高 8 位。你应该会收到警告。
rawSend
采用vector
的决定很奇怪,通用库将在不同的抽象级别上工作。我只能猜测是这样,因为rawSend
会复制传递的数据,并保证其生命周期,直到操作完成。如果不是,那只是一个糟糕的设计选择;再加上它通过指针获取参数的事实......你应该看到这个data
作为原始内存的容器,有一些怪癖要正确,但这里是你应该如何使用pod类型这种情况:
data->insert( data->end(), reinterpret_cast< char const* >( &opcode ), reinterpret_cast< char const* >( &opcode ) + sizeof( opcode ) );
【讨论】:
大多数系统不会给出向下投射的警告。 @Dietrich Epp:太糟糕了,因为我希望警告会上升,因为值可能会被截断。也许是时候考虑提高警告级别了。 与警告级别无关。由于“通常的算术转换”,误报的数量将不可能高。比如(unsigned char)1 + (unsigned char)1
的类型是什么?如果你说unsigned char
,那你就错了——正确答案是int
。
@Dietrich Epp:我不确定你从哪里得到你的信息;使用 VC++ 时,我每次尝试分配或传递一个比目标类型更精确的整数作为参数时都会收到警告。搜索警告C4244
以供参考...
有趣...那么它是否会针对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 *)&opcode;
for(int i = 0; i < sizeof(opcode); i++)
loginRequestData->push_back(opcode_data[i]);
socket->rawSend(loginRequestData);
这也适用于任何 POD 类型。
【讨论】:
【参考方案5】:是的,使用 rawSend,因为 send 可能需要一个 NULL 终止符。
通过转换为 char 而不是 void*,您不会丢失任何东西。记忆就是记忆。除了 RTTI 信息,类型永远不会存储在 C++ 的内存中。您可以通过转换为操作码指示的类型来恢复数据。
如果您可以在编译时决定所有发送的格式,我建议使用结构来表示它们。我以前专业地这样做过,这只是清楚地存储各种消息格式的最佳方式。而且在另一边打开包装非常容易;只需根据操作码将原始缓冲区转换为结构!
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);
struct 方法是最好、最简洁的发送和接收方式,但布局在编译时是固定的。 如果消息的格式直到运行时才决定,请使用 char 数组:
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);
【讨论】:
void*
为您做的唯一一件事就是当您从其他指向 void*
的指针隐式类型转换时,让编译器不会抱怨。以上是关于将 unsigned int + 字符串转换为 unsigned char 向量的主要内容,如果未能解决你的问题,请参考以下文章
将 unsigned short * 转换为 unsigned int *
如何将 unsigned long long int 转换为 unsigned char*?
在 Win32 上双重转换为 unsigned int 被截断为 2,147,483,648