最近需要在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实现不太一样所致。
来源:oschina
链接:https://my.oschina.net/u/1583678/blog/2997370