我正在使用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);
但是,当我尝试解释数据时,另一方面会遇到一些例外。我接近演员都错了吗?我是否会通过转换为无符号字符来丢失数据?
提前致谢。
答案 0 :(得分: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
函数)或者你需要检测字节顺序(来自接收者的POV的“原生”或“向后”)并在“向后”修复它。
答案 1 :(得分:1)
我会用这样的东西:
#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);
}
答案 2 :(得分:0)
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 ) );
答案 3 :(得分:0)
这将有效:
#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类型。
答案 4 :(得分:0)
是的,请使用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);
结构方法是最好的,最好的发送和接收方式,但布局在编译时是固定的。 如果在运行时之前未确定消息的格式,请使用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);