Boost :: asio :: async_read()缓冲区损坏问题

时间:2015-05-22 07:45:28

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

我已经从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大小的char数组中。

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 |

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

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

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

  • c)可以从索引中读取第三个值,该索引为0 + 2 * sizeof(int)

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

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

请帮助,如果有人可以的话。

2 个答案:

答案 0 :(得分:2)

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

SENDER

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

RECEIVER

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

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

答案 1 :(得分:1)

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

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

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

socket_