【问题标题】:C++ sending a packet over socketC ++通过套接字发送数据包
【发布时间】:2018-05-23 00:30:29
【问题描述】:

我正在尝试与我安装的服务器程序通信。服务器以构造数据包的形式发送和接收所有数据,这些数据包遵循以下设置: int int int string nullbyte 像这样:

little endian signed int -> ID 的大小(4 字节)+ Type 的大小(4 字节)+ Body 的大小(空终止符最小为 1)+ 最小为 10 的空字节作为值;

little endian signed int -> id

little endian signed int -> 数据包类型

以空结尾的 ascii 字符串 -> 正文

空字节

我已经设法很好地读取了数据包,但是当我尝试使用密码发送数据包时,服务器完全忽略它,这意味着数据包在某种程度上是错误的。我这样构造数据包:

void Packet::build(){

/*
 * Create unsigned char vector to store
 * the data while we build the byte array
 * and create a pointer so the byte array can
 * be modified by the required functions.
 */
std::vector<unsigned char> packet(m_size);
unsigned char *ptr = packet.data();

/*
 * Convert each of the three integers as well
 * as the string into bytes which will be stored
 * back into the memory that ptr points to.
 *
 * Packet data follows format:
 * int32 -> Size of ID + Server Data + body (minimum of 10).
 * int32 -> ID of request. Used to match to response.
 * int32 -> Server Data type. Identifies type of request.
 * String -> Minimum of 1 byte for null terminator.
 * String -> Null terminator.
 */
storeInt32Le(ptr, m_sizeInPacket);
storeInt32Le(ptr, m_requestID);
storeInt32Le(ptr, m_serverData);
storeStringNt(ptr, m_body);

/*
 * Store the vector in member variable m_cmdBytes;
 *
 */
m_cmdBytes = packet;
}

storeInt32Le:

void Packet::storeInt32Le(unsigned char* &buffer, int32_t value) {
/*
 * Copy the integer to a byte array using
 * bitwise AND with mask to ensure the right
 * bits are copied to each segment then
 * increment the pointer by 4 for the next
 * iteration.
 */
buffer[0] = value & 0xFF;
buffer[1] = (value >> 8) & 0xFF;
buffer[2] = (value >> 16) & 0xFF;
buffer[3] = (value >> 24) & 0xFF;
buffer += 4;
}

storeStringNt:

void Packet::storeStringNt(unsigned char* &buffer, const string &s) {
/*
 * Get the size of the string to be copied
 * then do a memcpy of string char array to
 * the buffer.
 */
size_t size = s.size() + 1;
memcpy(buffer, s.c_str(), size);
buffer += size;

}

最后,我发送它:

bool Connection::sendCmd(Packet packet) {
unsigned char *pBytes = packet.bytes().data();
size_t size = packet.size();

while (size > 0){
    int val = send(m_socket, pBytes, size, 0);

    if (val <= 0) {
        return false;
    }

    pBytes += val;
    size -= val;
}

return true;
}

Packet::bytes() 只返回 m_cmdBytes

【问题讨论】:

  • 你需要检查val是否>0但packet.size();它可能不会一次性发送所有数据。您可能希望使用调试器(或日志记录)在发送之前查看您的数据包是否正确,这样您就可以查看问题是构造还是传输。 (或者甚至使用 wireshark 来查看正在发送的确切内容。)
  • 你的 memcpy 调用错误:cplusplus.com/reference/cstring/memcpy 对了,你为什么不直接把字节复制到packet
  • 我为 val == packet.size() 添加了一个小检查,它输出到控制台,说明整个数据包已发送并且正在响应。
  • @AlexisShepard David 的观点是您声明整数在协议中定义为小端,但您没有尝试确保整数以小端形式发送。
  • 如果您的平台的下一个版本使用超过四个字节来存储整数怎么办?如果它以小端以外的某种形式存储整数怎么办?您正在对您的平台进行代码假设。除非你有充分的理由这样做,否则不要这样做。不这样做很容易。

标签: c++ sockets


【解决方案1】:

如 cmets 所述,您是:

  • 对编译器的int 数据类型的字节大小和字节序进行假设。由于您的协议需要非常特定的字节大小和整数的字节序,因此您需要在准备数据包时强制您的代码遵循这些要求。

  • 错误地使用memcpy()。您已颠倒了源缓冲区和目标缓冲区。

  • 无法确保 send() 实际上正确发送完整的数据包。

试试这样的:

void store_uint32_le(unsigned char* &buffer, uint32_t value)
{
    buffer[0] = value & 0xFF;
    buffer[1] = (value >> 8)  & 0xFF;
    buffer[2] = (value >> 16) & 0xFF;
    buffer[3] = (value >> 24) & 0xFF;
    buffer += 4;
}

void store_string_nt(unsigned char* &buffer, const std::string &s)
{
    size_t size = s.size() + 1;
    memcpy(buffer, s.c_str(), size);
    buffer += size;
}

...

std::vector<unsigned char> packet(13 + m_body.size());

unsigned char *ptr = packet.data(); // or = &packet[0] prior to C++11
store_uint32_le(ptr, packet.size() - 4);
store_uint32_le(ptr, m_requestID);
store_uint32_le(ptr, m_serverData);
store_string_nt(ptr, m_body);

...

unsigned char *pBytes = packet.data(); // or = &packet[0] prior to C++11
size_t size = packet.size();
while (size > 0)
{
    int val = send(m_socket, pBytes, size, 0);
    if (val < 0)
    {
        // if your socket is non-blocking, enable this...
        /*
        #ifdef _WINDOWS // check your compiler for specific defines...
        if (WSAGetLastError() == WSAEWOULDBLOCK)
        #else
        if ((errno == EAGAIN) || (errno == EWOULDBLOCK) || (errno == EINTR))
        #endif
            continue;
        */

        return false;
    }

    if (val == 0)
        return false;

    pBytes += val;
    size -= val;
}

return true;

【讨论】:

  • 显然,我不想在不理解的情况下使用此代码,所以我有几个问题。首先,您使用的是 uint32_t 但我需要有符号整数,我想改用 int32_t 吗?其次,为什么缓冲区在store_uint32_le中有+= 4?编辑:等等,我记得这部分,它将指针移动到下一个位置!抱歉,我真的是 C++ 新手,现在感觉自己像个白痴。
  • 可以使用int32_t,但uint32_t 更好,尤其是对于从不为负的值,例如字节大小。如果您不超过int32_t 的最大值,接收者将不知道其中的差异。至于函数,buffer 是通过引用传递的,因此函数可以在将字节写入指向的内存后向前推进指针。这样调用者不必在函数调用之间推进指针,例如:store_uint32_le(ptr, m_size - 4); ptr += 4; store_uint32_le(ptr, m_requestID); ptr += 4; store_uint32_le(ptr, m_serverData); ptr += 4; ...
  • 一个值 m_requestID 可以返回负数。如果您使用错误的密码发送 SERVERDATA_AUTH 类型,它将返回 -1。
  • 仍在处理这个问题。我想我已经正确地实现了代码。我将编辑我的原始帖子,以便您可以看到新代码。出于某种原因,服务器仍然没有响应我,但它发送了这些“保持活动”数据包,我一直在完美地阅读这些数据包,所以我知道这不是阅读问题。
猜你喜欢
  • 2013-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-15
  • 2013-05-05
  • 2016-04-06
  • 1970-01-01
  • 2011-06-09
相关资源
最近更新 更多