使用boost::asio从socket读取JSON

5

我现在正试图使用boost-asio的socket API从客户端向服务器传输一些JSON数据。我的客户端基本上是这样做的:

int from = 1, to = 2;

boost::asio::streambuf buf;
ostream str(&buf);

str << "{"
    << "\"purpose\" : \"request\"" << "," << endl
    << "\"from\" : " << from << "," << endl
    << "\"to\" : " << to << "," << endl
    << "}" << endl;

// Start an asynchronous operation to send the message.
boost::asio::async_write(socket_, buf,
    boost::bind(&client::handle_write, this, _1));

在服务器端,我可以选择使用各种boost::asio::async_read*函数。我想使用JsonCpp来解析接收到的数据。通过学习JsonCpp API (http://jsoncpp.sourceforge.net/class_json_1_1_reader.html),我发现Reader是在std::string, char*数组或std::istream之上操作的,而这些我可以从传递给这些函数的boost::asio::streambuf中操作。
关键是据我所知,并不一定会一次性传输整个内容,因此我需要某种确认,以确保缓冲区包含足够的数据来使用JsonCpp处理整个文档。如何确保缓冲区包含足够的数据?
2个回答

9

这是一个应用层协议区域。

有以下几种方式:

  • 读取直到流结束(发送方断开连接);但对于保持超过单个消息的连接,此方法不适用。
  • 提供类似于 Content-Length: 12346\r\n 的头部信息以提前了解需要读取多少内容。
  • 提供分隔符(类似于 MIME 边界,但您可以使用任何序列作为 JSON 负载的一部分不允许/不支持的字符)(async_read_until)。
  • 将负载视为“二进制风格”(例如 BSON),并在文本传输之前提供(网络顺序的)长度字段。

ASIO Http 服务器示例包含了一种非常好的模式来解析 HTTP 请求/头,您可以使用它。这假设您的解析器可以检测完整性,并且只会在所有信息都已经出现时才“软失败”。

void connection::handle_read(const boost::system::error_code& e,
    std::size_t bytes_transferred)
{
  if (!e)
  {
    boost::tribool result;
    boost::tie(result, boost::tuples::ignore) = request_parser_.parse(
        request_, buffer_.data(), buffer_.data() + bytes_transferred);

    if (result)
    {
      request_handler_.handle_request(request_, reply_);
      boost::asio::async_write(socket_, reply_.to_buffers(),
          boost::bind(&connection::handle_write, shared_from_this(),
            boost::asio::placeholders::error));
    }
    else if (!result)
    {
      reply_ = reply::stock_reply(reply::bad_request);
      boost::asio::async_write(socket_, reply_.to_buffers(),
          boost::bind(&connection::handle_write, shared_from_this(),
            boost::asio::placeholders::error));
    }
    else
    {
      socket_.async_read_some(boost::asio::buffer(buffer_),
          boost::bind(&connection::handle_read, shared_from_this(),
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));
    }
  }
  else if (e != boost::asio::error::operation_aborted)
  {
    connection_manager_.stop(shared_from_this());
  }
}

我之前提供了一个使用Boost Spirit解析JSON的答案(链接);你可以使用它来检测完整的JSON文档的结尾(如果不完整,结尾将与开头重合)。


无论如何,我想立即写一封回复,因此关闭连接对我来说行不通。 - hfhc2
这里是否有一个普遍被接受的模式?我将把服务器提供为其他开发人员的服务。我希望尽可能地简化他们的操作。 - hfhc2
是的。有几种被接受的做法,我已经列在了项目符号中 :) 就个人而言,我更喜欢使用 content-length 头部,因为它可以作为一种自然的方式来检测传输错误/质量控制 :) 话虽如此,如果我有选择的话,我更喜欢二进制协议。 - sehe
@sehe 看起来第二个 else 永远不会被触发! - John
@John 是的!tribool不是boolean类型:https://www.boost.org/doc/libs/1_76_0/doc/html/tribool/tutorial.html#:~:text=因此,以下惯用法可用于确定tribool当前持有的三种状态中的哪一种。 - sehe

0

这里有两个问题: 1) 告诉服务器要读取多少字节; 2) 读取JSON

对于1) 您可以制定自己的简单协议

300#my message here

发送一个300字节大小的消息;#是大小和消息之间的分隔符。
int write_request(socket_t &socket, const char* buf_json)
{
  std::string buf;
  size_t size_json = strlen(buf_json);
  buf = std::to_string(static_cast<long long unsigned int>(size_json));
  buf += "#";
  buf += std::string(buf_json);
  return (socket.write_all(buf.data(), buf.size()));
}

在服务器上读取

//parse header, one character at a time and look for for separator #
  //assume size header lenght less than 20 digits
  for (size_t idx = 0; idx < 20; idx++)
  {
    char c;
    if ((recv_size = ::recv(socket.m_sockfd, &c, 1, 0)) == -1)
    {
      std::cout << "recv error: " << strerror(errno) << std::endl;
      return str;
    }
    if (c == '#')
    {
      break;
    }
    else
    {
      str_header += c;
    }
  }

要读取JSON,您可以使用

https://github.com/nlohmann/json


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接