0

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

但是,当我试图解释数据时,在另一端遇到了一些例外情况。我在接近演员阵容时都错了吗?我会通过转换为无符号字符来丢失数据吗?

提前致谢。

4

5 回答 5

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 标准通常这样做,并且您使用htonlandhtons函数)或者您需要检测字节顺序(“本机”或“向后”)接收器的 POV)并在“向后”时修复它。

于 2011-10-07T04:56:33.530 回答
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);
}
于 2011-10-07T06:28:41.407 回答
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采用 a的决定vector很奇怪,通用库将在不同的抽象级别上工作。我只能猜测它是这样的,因为它rawSend会复制传递的数据,并保证它的生命周期,直到操作完成。如果不是,那只是一个糟糕的设计选择;除此之外,它通过指针获取参数......您应该将其data视为原始内存的容器,有一些怪癖需要解决,但在这种情况下,您应该如何使用 pod 类型:

data->insert( data->end(), reinterpret_cast< char const* >( &opcode ), reinterpret_cast< char const* >( &opcode ) + sizeof( opcode ) );
于 2011-10-07T04:31:36.120 回答
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 类型。

于 2011-10-07T04:34:49.043 回答
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);

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);
于 2011-10-07T04:39:50.147 回答