boost beast库封装的websocket客户端且支持wss(有ssl证书)

基于官方异步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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值