繁体   English   中英

将unsigned int +字符串转换为unsigned char向量

[英]Casting an unsigned int + a string to an unsigned char vector

我正在使用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);

但是,在我试图解释数据的另一端时,有些例外。 我接近演员都错了吗? 我是否会通过转换为未签名的字符来丢失数据?

提前致谢。

我喜欢他们如何让你创建一个向量( 必须使用堆,从而在不可预测的时间内执行),而不是只回到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标准通常这样做,你使用htonlhtons函数)或者你需要检测字节顺序(“本机”或“向后”)来自接收者的POV)并在“向后”修复它。

我会用这样的东西:

#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);
}
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 ) );

这将有效:

#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类型。

是的,请使用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);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM