【问题标题】:Boost::asio:: async_read() buffer corruption issueBoost::asio:: async_read() 缓冲区损坏问题
【发布时间】:2015-05-22 07:45:27
【问题描述】:

我已从 boost 的 chat_server 示例中获取代码。

enum eTransactionType{
    eBuy=0,
    eSell=1
};
struct stOrderPacket{
    int     ID;
    int     MarketID;
    char    m_strSignalName[22];
    char    m_strTradeSymbol[22];
    int     m_iQty;
    float   m_fPrice;
    eTransactionType m_eTransactionType;
};

stOrderPacket是TCP客户端和TCPServer共享的结构。

class chat_message
{
public:

  enum { max_body_length = sizeof(stOrderPacket) };

  chat_message()
    : body_length_(0)
  {
  }

  const char* data() const
  {
    return data_;
  }

  char* data()
  {
    return data_;
  }

  size_t length() const
  {
    return body_length_;
  }

  void SetData(char* msg, int len)   
  {           
      memset(data_,0x00,len);memcpy(data_,msg,len);
  }
  void SetOrderParams(stOrderPacket a_stOrderParams);
  size_t body_length() const
  {
    return body_length_;
  }
  void ClearPacket()
 {
      memset(data_,0x00,max_body_length);    
 }
 void body_length(size_t length);
private:
    char data_[sizeof(stOrderPacket)];
    size_t body_length_;        

};

chat_message 类是一个保存要写入或读取的消息的类,数据存储在一个大小等于结构 stOrderPacket 大小的字符数组中。

class chat_session{
    void start()
    {                

        boost::asio::async_read(socket_,boost::asio::buffer(read_msg_.data(),sizeof(stOrderPacket)),
               boost::bind(&chat_session::handle_read_body,shared_from_this(),placeholders::error, placeholders::bytes_transferred()));

    }

chat_session 类中的上述函数启动与已连接客户端的会话。

     void handle_read_body(const boost::system::error_code& error,std::size_t bytes_transferred)
    {

        if (0!=error)
        {
          // handle Close connection. 
          return;
        }    

        /// stub for parsing the packet
        memcpy(&m_stOrderPacket,&m_pvBuffer,sizeof(m_stOrderPacket));

        read_msg_.ClearPacket();
        boost::asio::async_read(
        socket_, buffer(read_msg_.data(),sizeof(structs::stOrderParameters)),
            boost::bind(&chat_session::handle_read_body,shared_from_this(),placeholders::error,placeholders::bytes_transferred()));

    }
};

客户端发送的数据包如下:

 ID   | MarketID | Symbol     | SignalName        | TradeType | Qty | EntryPrice |

| 3021 |       1030320 | RELIANCEEQ | MU_30_INLE_4097_3 | Long      | 285 |     1121.1 |

| 3022 |       1030321 | RELIANCEEQ | MU_30_INLE_4097_3 | Long      | 178 |       1121 |

| 3038 |       1030505 | RELIANCEEQ | AS_15_SE_53       | Short     | 340 

|    1116.95 |

但是从 read_msgs_.data 读取的值,当 memcopy 到结构 stOrderPacket 时,是以这样的方式接收的:

  • a) 第一个数据包是正确的

  • b) 在第二个数据包中,前 4 个字节是垃圾值,然后我能够将 ID 的值设为 3022,我知道 cz 这个值已分配给 stOrderPacket.MarketID。

  • c) 第三个值可以从 0+2* sizeof(int) 的索引中正确读取

所以基本上对于每个接收到的数据包,开始 (n-1)*4 字节首先是垃圾,然后信息开始。 此外,所有 3 个数据包的 bytes_transferred 的值都是 64。

注意:我在 x86_64 架构的 CentOS 7 上运行此代码。

如果有人可以,请帮忙。

【问题讨论】:

  • 您应该继续累积数据,直到收到正文的所有字节。您可以在要传输的主体字节前面加上一个 int(4 字节)来表示主体大小。其他方法是使用某种分隔符来标记数据体的结尾。为此,您可以使用 async_read_unitil。检查这个 www.boost.org/doc/libs/1_55_0/doc/html/boost_asio/reference/async_read_until.html。
  • boost::asio::async_read() 函数有以下限制:程序必须确保流不执行其他读取操作(例如 async_read、流的 async_read_some 函数或执行的任何其他组合操作读取)直到此操作完成。
  • 您的handle_read_body 函数无法处理接收到的字节数不等于协议数据单元大小的情况!您可以处理这些情况,也可以使用 async_read 的变体来读取您需要的内容 (async_read_until),但您必须在某个地方实现协议!

标签: c++ sockets boost tcp boost-asio


【解决方案1】:

围绕发送结构的原始数据构建网络协议是不好的做法并且非常不安全。结构内存在所有机器上并不完全相同,这可能会导致错误。实现此目的的一种简单方法是使用序列化库从结构中构建数据缓冲区,另一方面获取数据缓冲区并构建结构。 Boost Serialization 是一个非常好的选择,Google Protocol Buffers 也是如此。这是一个例子:

发件人

std::vector<unsigned char> buffer;
stOrderPacket packet;
Serialize(packet, buffer);
boost::asio::write(socket, boost::asio::buffer(buffer), boost::asio::transfer_all());

接收器

std::vector<unsigned char> buffer;
buffer.resize(buffer_size);
stOrderPacket packet;
boost::asio::read(socket, boost::asio::buffer(buffer), boost::asio::transfer_exactly(buffer_size));
Deserialize(packet, buffer);

缓冲区的大小会因数据而异,因此您需要在协议中添加大小传输。这将包括序列化数据,然后告诉接收器期望的大小。然后接收器将读取该大小,然后反序列化数据。

【讨论】:

    【解决方案2】:

    基于所描述的应用程序协议使用固定大小消息(64 字节)的行为,并且当服务器读取消息时,消息n 的起点偏移4 * (n - 1) 字节,然后最可能的情况是客户端每条消息发送68字节的数据,其中第一个64字节包含stOrderPacket,客户端只期望每条消息64字节。以下是需要检查的几个方面:

    • 验证客户端和服务器是否使用相同的应用程序协议。例如,可能服务器正在使用从末尾删除 4 字节字段的较新版本,或者客户端正在使用在末尾添加 4 字节字段的较新版本。
    • 使用网络分析器工具,例如Wireshark,来验证网络上的应用协议。 TCP 是一种数据流,因此不能依赖网络分析器工具来根据应用程序协议进行适当的成帧。要进行分析,可能需要发送一些消息,将来自多个 TCP 数据包的数据连接在一起,然后推断帧和字段。经过分析,如果每条消息都是68字节,那么需要更新客户端或服务器使用相同的应用协议。另一方面,如果消息是64 字节,则错误可能存在于服务器中(错误的逻辑、未定义的行为等)。
    • 如果客户端每条消息发送64字节,则验证服务器是否正确地从套接字读取:
      • socket_ 不是线程安全的,因此请确认没有对其进行并发调用。
      • 提供给 boost::asio::async_read() 的缓冲区 (read_msg_.data()) 必须至少在调用完成处理程序之前保持有效。
      • async_read() 完成之前,不会对socket_ 执行其他读取操作。

    此外,当使用标准布局结构来定义应用程序协议的任何部分时,人们可能会发现通过在可能的情况下使用精确宽度类型和/或绘制协议图来提高可读性和安全性:

    //     0                   1                   2                   3
    //     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    // 00 +-------+-------+-------+-------+-------+-------+-------+------+
    //    |                              ID                              |
    // 04 +-------+-------+-------+-------+-------+-------+-------+------+
    //    |                           Market ID                          |
    // 08 +-------+-------+-------+-------+-------+-------+-------+------+
    //    |                          Signal Name                         |
    //    /                                                              /
    // 28 +              ...              +-------+-------+-------+------+
    //    |                               |         Trade Symbol ...     |
    // 32 +-------+-------+-------+-------+                              +
    //    /                                                              /
    // 48 +                              ...                             +
    //    |                                                              |
    // 52 +-------+-------+-------+-------+-------+-------+-------+------+
    //    |                           Quantity                           |
    // 56 +-------+-------+-------+-------+-------+-------+-------+------+
    //    |                             Price                            |
    // 60 +-------+-------+-------+-------+-------+-------+-------+------+
    //    |                         Transaction Type                     |
    // 64 +-------+-------+-------+-------+-------+-------+-------+------+
    struct stOrderPacket
    {
      int32_t  ID;
      int32_t  MarketID;
      char     m_strSignalName[22];
      char     m_strTradeSymbol[22];
      int32_t  m_iQty;
      float    m_fPrice;
      eTransactionType m_eTransactionType;
    };
    

    【讨论】:

    • 您好 Tanner Sansbury,首先感谢您如此详细地解释所有内容。
    • 我通过此链接catb.org/esr/structure-packing 并通过更改结构中字段的位置,通过在 ID 和 MarketID 之前放置 char 数组,它起作用了。我不知道怎么做!,因为这篇文章说 int 值应该在 char 值之前。
    • @HemantParasha 如果客户端和服务器构建环境不是完全相同(似乎是这种情况),那么我会依赖在编译器内存布局上是我的data transfer object。我强烈建议明确处理序列化和反序列化。有些库可以帮助进行序列化,例如 Boost.Serialization,在 Boost.Asio serialization 示例中使用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-19
    相关资源
    最近更新 更多