基于官方异步websocket客户端示例代码而进行的改进
功能点说明
1.封装了连接回调、收到数据回调,此2个回调在构造类对象时传入(其实应该还有个close的回调,自己封装吧)
2.对外提供的接口:Request(发送数据)、Run(客户端运行)、Close(主动关闭连接)
3.默认用的wss,需要设置ssl证书(具体在Certificate.h中)
4.优化了发送数据的相关代码,官方的示例中调用dowrite发送一个数据时,必须等待dowrite操作完才能继续调用dowrite发送下一个,如果不等待则程序会抛异常而崩溃,这里我优化了下,内部用了一个队列缓存待发送数据,然后每次发送完一个数据再从队列取下一个继续发送,直到没有数据
5.on_ssl_handshake函数中设置了tcp不启用nagle,这样底层收到待发送数据会立即发出而不是等待数据缓存到一定大小再发送
客户端代码如下:
/**
webscoket客户端实现 支持ssl
Certificate.h为ssl证书,格式为beast官方提供的格式,
请自行前往beast官方文档中的exmaple中下载(地址:https://www.boost.org/doc/libs/1_86_0/libs/beast/example/common/server_certificate.hpp)
*/
#ifndef __WSS_Client_H__
#define __WSS_Client_H__
#include "Log.h"
#include "Certificate.h"
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/beast/websocket/ssl.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/strand.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.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::cout << what << ": " << ec.message() << "\n";
}
// Sends a WebSocket message and prints the response
class ClientSession : public std::enable_shared_from_this<ClientSession>
{
tcp::resolver m_resolver;
websocket::stream<ssl::stream<tcp::socket>> m_ws;
boost::beast::multi_buffer m_buffer;
boost::asio::strand<boost::asio::io_context::executor_type> m_strand;
std::queue<std::shared_ptr<std::string const>> m_queue;
std::string m_host;
std::string m_port;
std::function<int(const std::string&)> m_callfunc;
std::function<int(const std::string&)> m_connectfunc;
public:
bool m_isclose = false;
// Resolver and socket require an io_context
explicit
ClientSession(boost::asio::io_context& ioc, ssl::context& ctx , std::function<int(const std::string&)> connectfunc, std::function<int(const std::string&)> receivefunc)
: m_resolver(ioc),
m_ws(ioc, ctx),
m_strand(m_ws.get_executor())
{
m_callfunc = receivefunc;
m_connectfunc = connectfunc;
}
~ClientSession()
{
if (m_ws.is_open()) {
Close();
}
}
//发送数据到服务端
void Request(const std::string& data)
{
boost::beast::multi_buffer buffer;
size_t n = 0;
auto bufs = buffer.prepare(data.length());
for (auto it: bufs) {
memcpy(it.data(), data.c_str() + n, it.size());
n += it.size();
}
buffer.commit(n);
//不直接调用do_write 而用post是为了保证线程安全
boost::asio::post(m_ws.get_executor(), boost::asio::bind_executor(m_strand, std::bind(&ClientSession::do_write,
shared_from_this(), std::make_shared<std::string const>(std::move(boost::beast::buffers_to_string(buffer.data()))))));
}
//主动关闭websocket
void Close(){
// Close the WebSocket connection
m_ws.async_close(websocket::close_code::normal,
std::bind(
&ClientSession::on_close,
shared_from_this(),
std::placeholders::_1));
}
// Start the asynchronous operation
void
run(
char const* host,
char const* port)
{
// Save these for later
m_host = host;
m_port = port;
// Look up the domain name
m_resolver.async_resolve(
host,
port,
std::bind(
&ClientSession::on_resolve,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
private:
size_t
read_buf(std::string &data)
{
data = boost::beast::buffers_to_string(m_buffer.data());
// Clear the buffer
m_buffer.consume(m_buffer.size());
return data.length();
}
size_t
write_buf(std::string &data)
{
size_t n = 0;
auto bufs = m_buffer.prepare(data.length());
for (auto it: bufs) {
memcpy(it.data(), data.c_str() + n, it.size());
n += it.size();
}
m_buffer.commit(n);
return n;
}
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(
m_ws.next_layer().next_layer(),
results.begin(),
results.end(),
std::bind(
&ClientSession::on_connect,
shared_from_this(),
std::placeholders::_1));
}
void
on_connect(boost::system::error_code ec)
{
if(ec)
return fail(ec, "connect");
// Perform the SSL handshake
m_ws.next_layer().async_handshake(
ssl::stream_base::client,
std::bind(
&ClientSession::on_ssl_handshake,
shared_from_this(),
std::placeholders::_1));
}
void
on_ssl_handshake(boost::system::error_code ec)
{
if(ec)
return fail(ec, "ssl_handshake");
tcp::socket& m_socket = m_ws.next_layer().next_layer();
m_socket.set_option(boost::asio::ip::tcp::no_delay(true));//设置tcp发送数据立即发送,而不是等待包缓存到一定大小再发送
// Perform the websocket handshake
m_ws.async_handshake(m_host, "/",
std::bind(
&ClientSession::on_handshake,
shared_from_this(),
std::placeholders::_1));
}
void
on_handshake(boost::system::error_code ec)
{
if(ec)
return fail(ec, "handshake");
if (m_connectfunc)
{
m_connectfunc("");
}
// Read a message into our buffer
do_read();
}
void do_read()
{
// Read a message into our buffer
m_ws.async_read(
m_buffer,
std::bind(
&ClientSession::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 == websocket::error::closed) {
printf("websocket is close.");
return;
}
if(ec)
return fail(ec, "read");
std::string receive;
read_buf(receive); //默认服务端传输过来的数据也是text文本格式
if (m_callfunc)
{
m_callfunc(receive);
}
do_read();
}
void do_write(std::shared_ptr<std::string const> const& s)
{
m_queue.push(s);
if (m_queue.size() > 1) {
return;
}
m_ws.text(true);//使用文本格式 传输数据
m_ws.async_write(boost::asio::buffer(*m_queue.front()), boost::asio::bind_executor(m_strand, std::bind(&ClientSession::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");
m_queue.pop();
if (!m_queue.empty()) {
m_ws.text(m_ws.got_text());
m_ws.async_write(boost::asio::buffer(*m_queue.front()), boost::asio::bind_executor(m_strand, std::bind(&ClientSession::on_write,
shared_from_this(), std::placeholders::_1, std::placeholders::_2)));
}
}
void
on_close(boost::system::error_code ec)
{
if(ec)
return fail(ec, "close");
// If we get here then the connection is closed gracefully
m_isclose = true;
// The buffers() function helps print a ConstBufferSequence
std::cout << "session close:" <<ec.message()<< std::endl;
}
};
#endif //__WSS_Client_H__
使用示例
#include "WssClient.h"
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <vector>
#include <string.h>
int ConnectFunc(const std::string& data){
std::cout<<"Conneced:"<<data<<std::endl;
return 0;
}
int ReceiveFunc(const std::string& data){
std::cout<<"ReceiveFunc:"<<data<<std::endl;
return 0;
}
int main(int argc, char* argv[])
{
// Check command line arguments.
if(argc < 3)
{
std::cerr << "Usage: websocket-client-async-ssl <ip> <port>\n";
return EXIT_FAILURE;
}
auto const ip= argv[1];
auto const port= argv[2];
// The io_context is required for all I/O
boost::asio::io_context ioc;
// The SSL context is required, and holds certificates
ssl::context ctx{ssl::context::sslv23};
// This holds the root certificate used for verification
load_server_certificate(ctx);
// Launch the asynchronous operation
g_session = std::make_shared<ClientSession>(ioc, ctx, ConnectFunc, ReceiveFunc);
g_session->run(ip.c_str(), port.c_str());
// Run the I/O service. The call will return when
// the socket is closed.
int threads = 3;
std::vector<std::thread> v;
for(auto i = threads; i > 0; --i) {
v.emplace_back(
[&ioc]{
ioc.run();
});
}
g_session->Close();
for(auto i = 0; i < v.size(); ++i) {
v[i].join();
}
while (!g_session->m_isclose)
{
sleep(1);//等待socket关闭
}
return EXIT_SUCCESS;
}