boost实现websocket(client)

泄露秘密 提交于 2020-04-27 18:08:52

最近需要在c++程序中用到websocket技术,上网查了一下,找到四个库:libwebsocket, websocketpp, mongoose, boost.beast. 因为项目已经依赖于boost了,而且版本也比较新1.68,可以直接用boost.beast(从boost 1.66起可用),因此就不费心搞别的了,直接用boost.beast来实现。

 

先直接从样例代码开始,做了少许修改,就实现了同步版本的客户端。

client.cpp

#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <cstdlib>
#include <iostream>
#include <string>

using tcp = boost::asio::ip::tcp;  // from <boost/asio/ip.tcp.hpp>
namespace websocket = boost::beast::websocket;  // from <boost/beast/websocket.hpp>

// Sends a WebSocket message and prints the response
int main(int argc, char** argv)
{
  try {
    // Check command line arguments.
    if (argc != 4) {
      std::cerr <<
	"Usage: client <host> <port> <text>\n" <<
	"Example:\n" <<
	"    client echo.websocket.org 80 \"Hello, world!\"\n";
      return EXIT_FAILURE;
    }
    auto const host = argv[1];
    auto const port = argv[2];
    auto const text = argv[3];

    // The io_context is required for all I/O
    boost::asio::io_context ioc;

    // These objects perform our I/O
    tcp::resolver resolver{ioc};
    websocket::stream<tcp::socket> ws{ioc};

    // Look up the domain name
    auto const results = resolver.resolve(host, port);

    // Make the connection on the IP address we get from a lookup
    boost::asio::connect(ws.next_layer(), results.begin(), results.end());

    // Perform the websocket handshake
    ws.handshake(host, "/");

    // This buffer will hold the incoming message
    boost::beast::multi_buffer buffer;

    // Send the message
    ws.write(boost::asio::buffer(std::string(text)));

    // Read a message into our buffer
    do {
      // Append up to 512 bytes of the message into the buffer
      ws.read_some(buffer, 512);
    } while(! ws.is_message_done());

    // The buffers() function helps print a ConstBufferSequence
    std::cout << boost::beast::buffers(buffer.data()) << std::endl;

    // Discard all of the bytes stored in the dynamic buffer,
    // otherwise the next call to read will append to the existing
    // data instead of building a fresh message.
    buffer.consume(buffer.size());

    // Close the WebSocket connection
    ws.close(websocket::close_code::normal);

    // If we get here then the connection is closed gracefully
    std::cout << "connection closed!" << std::endl;
  } catch (std::exception const& e) {
    std::cerr << "Error: " << e.what() << std::endl;
    return EXIT_FAILURE;
  }
  return EXIT_SUCCESS;
}

测试的时候,echo.websocket.org倒是正常,但是http://www.blue-zero.com/WebSocket/就不行了,因为后者自动推送了两条信息,于是在buffer声明后面加了2条read,如下所示

    // This buffer will hold the incoming message
    boost::beast::multi_buffer buffer;
    ws.read(buffer);
    ws.read(buffer);

这下倒是可以了,问题是close的时候会报错,直接跳到异常处理,输出如下

不知道是哪里出了问题,有空的时候再调试一下吧。

 

接下来试了一下异步的客户端,在样例代码基础上稍微做了修改,绕过了close的问题。

async_client.cpp

#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <chrono>

using tcp = boost::asio::ip::tcp;  // from <boost/asio/ip.tcp.hpp>
namespace websocket = boost::beast::websocket;  // from <boost/beast/websocket.hpp>

// Report a failure
void fail(boost::system::error_code ec, char const* what)
{
  std::cerr << what << ": " << ec.message() << "\n";
}

// Sends a WebSocket message and prints the response
class session : public std::enable_shared_from_this<session>
{
  tcp::resolver resolver_;
  websocket::stream<tcp::socket> ws_;
  boost::beast::multi_buffer buffer_;
  std::string host_;
  std::string text_;

public:
  // Resolver and socket require an io_context
  explicit session(boost::asio::io_context& ioc)
    : resolver_(ioc), ws_(ioc)
  {}

  // Start the asynchronous operation
  void run(char const* host, char const* port, char const* text)
  {
    // Save these for later
    host_ = host;
    text_ = text;

    // Look up the domain name
    resolver_.async_resolve(host, port, std::bind(
      &session::on_resolve, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
  }

  void on_resolve(boost::system::error_code ec, tcp::resolver::results_type results)
  {
    if (ec) return fail(ec, "resolve");

    // Make the connection on the IP address we get from a lookup
    boost::asio::async_connect(ws_.next_layer(), results.begin(), results.end(),
      std::bind(&session::on_connect, shared_from_this(), std::placeholders::_1));
  }

  void on_connect(boost::system::error_code ec)
  {
    if (ec) return fail(ec, "connect");

    // Perform the websocket handshake
    ws_.async_handshake(host_, "/",
      std::bind(&session::on_handshake, shared_from_this(), std::placeholders::_1));
  }

  void on_handshake(boost::system::error_code ec)
  {
    if (ec) return fail(ec, "handshake");

    // Send the message
    ws_.async_write(boost::asio::buffer(text_), std::bind(
      &session::on_write, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
  }

  void on_write(boost::system::error_code ec, std::size_t bytes_transferred)
  {
    boost::ignore_unused(bytes_transferred);

    if (ec) return fail(ec, "write");

    // Read a message into our buffer
    ws_.async_read(buffer_, std::bind(
      &session::on_read, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
  }

  void on_read(boost::system::error_code ec, std::size_t bytes_transferred)
  {
    boost::ignore_unused(bytes_transferred);

    if (ec) return fail(ec, "read");
    std::cout << boost::beast::buffers(buffer_.data()) << std::endl;
    buffer_.consume(buffer_.size());
    on_write(ec, bytes_transferred);

    // Close the WebSocket connection
    ws_.async_close(websocket::close_code::normal, std::bind(
      &session::on_close, shared_from_this(), std::placeholders::_1));
  }

  void on_close(boost::system::error_code ec)
  {
    if (ec) return fail(ec, "close");

    // If we get here then the connection is closed gracefully

    // The buffers() function helps print a ConstBufferSequence
    std::cout << boost::beast::buffers(buffer_.data()) << std::endl;
  }
};

int main(int argc, char** argv)
{
    // Check command line arguments.
    if (argc != 4) {
      std::cerr <<
	"Usage: client <host> <port> <text>\n" <<
	"Example:\n" <<
	"    async_client echo.websocket.org 80 \"Hello, world!\"\n";
      return EXIT_FAILURE;
    }
    auto const host = argv[1];
    auto const port = argv[2];
    auto const text = argv[3];

    // The io_context is required for all I/O
    boost::asio::io_context ioc;

    // Launch the asynchronous operation
    std::make_shared<session>(ioc)->run(host, port, text);

    // Run the I/O service. The call will return when the socket is closed.
    //ioc.run();
    ioc.run_for(std::chrono::milliseconds(1000));

    return EXIT_SUCCESS;
}

关键在于on_read中增加了如下三句

    std::cout << boost::beast::buffers(buffer_.data()) << std::endl;
    buffer_.consume(buffer_.size());
    on_write(ec, bytes_transferred);

并且最后把ioc.run()改成了ioc.run_for(std::chrono::milliseconds(1000)); 这样一来实际上没有执行async_close,直接超时就退出了。

 

时间关系,暂时就这样吧,等以后有空看看为何close会导致End of File异常,估计是 http://www.blue-zero.com/WebSocket/ 网站的websocket实现不太一样所致。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!