C++ 使用libwebsockets开源库封装client类

本文参考:封装利用libwebsockets写出的客户端、服务端程序为客户端服务端类_逍遥游的博客-CSDN博客_libwebsockets封装

最近项目需要使用C++连接websocket服务器,选择了纯C实现的libwebsockets库;

libwebsockets下载地址:GitHub - warmcat/libwebsockets: canonical libwebsockets.org networking library

添加的openssl是openssl-1.1.1f

编译:

cmake ..  -DLWS_WITH_HTTP2=1 -DLWS_OPENSSL_INCLUDE_DIRS=../../openssl-1.1.1f/include -DLWS_OPENSSL_LIBRARIES="../../openssl-1.1.1f/libssl.a;../../openssl-1.1.1f/libcrypto.a" -DLWS_WITH_LIBEV=1 -DLIBEV_INCLUDE_DIRS=../../libev -DLIBEV_LIBRARIES=../../libev/.libs/libev.a

常用的cmake参数:

  • -DLWS_WITH_STATIC=1:编译静态库
  • -DLWS_WITH_SHARED=1:编译动态库
  • -DLWS_WITH_SSL=1:添加openssl
  • -DLWS_WITH_HTTP2=1:添加http
  • -DLWS_OPENSSL_INCLUDE_DIRS=:添加openssl的头文件目录
  • -DLWS_OPENSSL_LIBRARIES=:添加openssl的库(动态库静态库均可)
  • -DLWS_WITH_LIBEV=1:添加libev
  • -DLIBEV_INCLUDE_DIRS=:添加libev的头文件目录
  • -DLIBEV_LIBRARIES=:添加libev的库(动态库静态库均可)

注:

  • 以上参数若不想再cmake时携带,也可以直接修改 CMakeLists.txt对用参数
  • 建议可以自己实现IO复用,使用lws_get_socket_fd()获取fd,自己实现epoll进行监听,也可以直接使用libwebsockets自带的poll

目前只实现了client封装类:

client.h

#ifndef _CLIENT
#define _CLIENT

#include <iostream>
#include <fstream>
#include <stack>
#include <queue>
#include <signal.h>
#include <pthread.h>
#include "json/json.h"
#include "libwebsockets.h"

using namespace std;

#define MAX_PAYLOAD_SIZE  8 * 1024   

typedef enum 
{
    CLIENT_IDLE,
    CLIENT_CONNECTING,
    CLIENT_CONNECTED,
    CLIENT_AWAITING_SEND,
    ClIENT_COMPLETE_RECV,
    CLIENT_CLOSED
}TSTAT;

typedef struct session_data {
    int msg_count;
    unsigned char buf[LWS_PRE + MAX_PAYLOAD_SIZE];
    int len;
}session_data, *SESSIONDATA;

class CWSClient:public CLog
{
    public:

        CWSClient(string url);
        ~CWSClient();

        void       init();
        int        set_ssl(const char* ca_filepath,
                            const char* server_cert_filepath,
                            const char*server_private_key_filepath,
                            int ssl_conn);
        bool       create();
        bool       connect(int ssl_conn);
        bool       run(int wait_time);
        int        send(string data);
        int        recv(string data);
        int        getRecvSize();
        string     getRecvMsg();
        void       popRecvMsg();
        void       setLogLevel(int level);
        TSTAT      getConnStat();
        void       destroy();
        void       setEnd(bool flag);

        friend     void* WSpthreadFunc(void* arg);
        friend     int callback( struct lws *wsi, 
                                    enum lws_callback_reasons reason, 
                                    void *user, 
                                    void *in, 
                                    size_t len );

    private:

        string                            m_cacheFile;
        bool                              m_end;
        int                               m_waittime;
        pthread_t                         m_threadID;
        queue<string>                     m_sendMsgQueue;
        queue<string>                     m_recvMsgQueue;
        SESSIONDATA                       m_session_data;
        string                            m_url;
        const char*                       m_prot;
        const char*                       m_ads;
        const char*                       m_path;
        int                               m_port;
        TSTAT                             m_stat;
        int                               m_logLevel;
        struct lws_protocols *            m_protocols;
        struct lws_context_creation_info  m_ctx_info;  /* 用于创建vhost或者context的参数 */
        struct lws_context *              m_context;
        struct lws_client_connect_info    m_conn_info;
        struct lws *                      m_wsi;
        pthread_mutex_t                   m_rmutex; 
        pthread_mutex_t                   m_smutex; 
};

#endif

client.cpp

#include "websocketclient.h" 

static void emit_log(int level, const char *line)
{
    /*自己实现日子打印回调,如将对应日志写入文件,
      如果此回调置为NULL的话,则日志默认打印到stderr*/
}
void* WSpthreadFunc(void* arg);

int callback( struct lws *wsi, 
        enum lws_callback_reasons reason, 
        void *user, 
        void *in, 
        size_t len )
{
    CWSClient *wsclient = NULL;
    const struct lws_protocols* protocols = lws_get_protocol(wsi);
    if(protocols && protocols[0].user)
        wsclient = (CWSClient *) protocols[0].user;

    switch ( reason )
    {
        case LWS_CALLBACK_PROTOCOL_INIT:
            {
                lwsl_notice("Protocol init\n");
            }
            break;
        case LWS_CALLBACK_CLIENT_ESTABLISHED:   // 连接到服务器后的回调
            {
                lwsl_notice( "Connected to server ok!\n" );
                wsclient->m_stat = CLIENT_CONNECTED;
            }
            break;
        case LWS_CALLBACK_CLIENT_RECEIVE:       // 接收到服务器数据后的回调,数据为in,其长度为len
            {
                lwsl_notice( "recv: %s\n", (char *) in );
                char *in_str = (char *) in; 
            }
            break;
        case LWS_CALLBACK_CLIENT_WRITEABLE:     // 当此客户端可以发送数据时的回调
            if( !wsclient->m_sendMsgQueue.empty() )
            {
                lwsl_notice( "entry callback client write \n");
                pthread_mutex_lock(&wsclient->m_smutex);
                string tts_params = wsclient->m_sendMsgQueue.front(); 

                memset( wsclient->m_session_data->buf, 0, sizeof( wsclient->m_session_data->buf ));
                char *msg = (char*)&wsclient->m_session_data->buf[ LWS_PRE ];
                wsclient->m_session_data->len = sprintf( msg, "%s", tts_params.c_str() );
                lwsl_notice( "send: %s\n", msg );
                /*LWS will buffer the remainder automatically, and send it out autonomously.*/
                lws_write( wsi, &wsclient->m_session_data->buf[ LWS_PRE ], 
                        wsclient->m_session_data->len, LWS_WRITE_TEXT );

                wsclient->m_session_data->msg_count++;
                wsclient->m_sendMsgQueue.pop();
                pthread_mutex_unlock(&wsclient->m_smutex);
            }
            break;
        case LWS_CALLBACK_CLIENT_CLOSED:
            {
                lwsl_notice( "ws_client is closed!\n" );
                wsclient->m_stat = CLIENT_CLOSED;
            }
            break;
        default:
            break;
    }

    return 0;
}

CWSClient::CWSClient(string url)
{
    m_url          = url;
    m_context      = NULL;
    m_wsi          = NULL;
    m_stat         = CLIENT_IDLE;
    m_end          = false;       //结束标志
    memset(&m_ctx_info,0x00,sizeof(m_ctx_info));
    memset(&m_conn_info,0x00,sizeof(m_conn_info));

    pthread_mutex_init(&m_smutex, NULL);
    pthread_mutex_init(&m_rmutex, NULL);
}

CWSClient::~CWSClient()
{
    pthread_mutex_destroy( &m_smutex );
    pthread_mutex_destroy( &m_rmutex );
    lws_context_destroy( m_context );
    if (m_session_data)
        delete(m_session_data);
    if(m_protocols)
        delete(m_protocols);
    m_end = true;
}

void CWSClient::init()
{
    m_protocols = new (struct lws_protocols[2]);   //该列表以具有空回调指针的条目结束
    m_ctx_info.port = CONTEXT_PORT_NO_LISTEN;
    m_ctx_info.iface = NULL;
    m_ctx_info.protocols = m_protocols;
    m_ctx_info.gid = -1;
    m_ctx_info.uid = -1;

    m_protocols[0].name  = "wss";
    m_protocols[0].callback = &callback;
    m_protocols[0].per_session_data_size = 0;
    m_protocols[0].rx_buffer_size = 0;
    m_protocols[0].id = 0;
    m_protocols[0].user = this;

    m_protocols[1].name  = NULL;
    m_protocols[1].callback = NULL;
    m_protocols[1].per_session_data_size = 0;

    m_session_data = new session_data;

}

/**client一般不使用证书,直接置为NULL就可以**/
int CWSClient::set_ssl(const char* ca_filepath,
                        const char* server_cert_filepath,
                        const char*server_private_key_filepath,
                        int ssl_conn)
{
    m_ctx_info.ssl_ca_filepath = ca_filepath;
    m_ctx_info.ssl_cert_filepath  = server_cert_filepath;
    m_ctx_info.ssl_private_key_filepath = server_private_key_filepath;

    if(ssl_conn == LCCSCF_USE_SSL || ssl_conn == (LCCSCF_USE_SSL|LCCSCF_ALLOW_INSECURE))
        m_ctx_info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;

    return ssl_conn;
}

bool CWSClient::create()
{
    /**创建一个WebSocket处理器**/
    m_context = lws_create_context( &m_ctx_info );
    if(!m_context)
    {
        lwsl_err("%s: lws_create_context error \n", __func__);
        return false;
    }
    return true;
}

bool CWSClient::connect(int ssl_conn)
{
    char tmp_url[1024] = {0};
    strncpy(tmp_url,m_url.c_str(),sizeof(tmp_url)-1);  //lws_parse_uri解析之后会改变原url,不能使用string = ,浅拷贝
    if (lws_parse_uri(tmp_url, &m_prot, &m_ads, &m_port, &m_path))
        lwsl_err("%s: uri error %s\n", __func__, m_url.c_str());

    m_conn_info.context = m_context;
    m_conn_info.address = m_ads;
    m_conn_info.port = m_port;
    m_conn_info.ssl_connection = ssl_conn;
    m_conn_info.path = (char*)m_url.c_str();
    m_conn_info.host = m_ads;
    m_conn_info.sys_tls_client_cert = 0;
    m_conn_info.protocol = m_protocols[0].name;

    // 下面的调用触发LWS_CALLBACK_PROTOCOL_INIT事件
    // 创建一个客户端连接
    m_stat = CLIENT_CONNECTING;
    m_wsi = lws_client_connect_via_info( &m_conn_info );
    if(!m_wsi)
    {
        lwsl_err("%s: lws_client_connect_via_info error \n", __func__);
        return false;
    }

    return true;
}

bool CWSClient::run(int wait_time)
{
    m_waittime = wait_time;

    pthread_attr_t attr;
    pthread_attr_init(&attr);

    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
    pthread_attr_setstacksize(&attr, 512*1024);

    if(pthread_create(&m_threadID, &attr, WSpthreadFunc, this) != 0)
    {   
        lwsl_err("[CWSClient] Failed with pthread_create: %s\n", strerror(errno));
        return false;
    }   
    else
        lwsl_notice("[CWSClient] Successed with pthread_create\n");

    return true;
}

int CWSClient::send(string data)
{
    pthread_mutex_lock(&m_smutex);
    m_sendMsgQueue.push(data);
    pthread_mutex_unlock(&m_smutex);

    return (int)data.length();
}

int CWSClient::recv(string data)
{
    pthread_mutex_lock(&m_rmutex);
    m_recvMsgQueue.push(data);
    pthread_mutex_unlock(&m_rmutex);

    return (int)data.length();
}

int CWSClient::getRecvSize()
{
    return m_recvMsgQueue.size();
}

string CWSClient::getRecvMsg()
{
    string data;
    pthread_mutex_lock(&m_rmutex);
    if( !m_recvMsgQueue.empty() )
        data = m_recvMsgQueue.front();
    pthread_mutex_unlock(&m_rmutex);

    return data;
}

void CWSClient::popRecvMsg()
{
    pthread_mutex_lock(&m_rmutex);
    if( !m_recvMsgQueue.empty() )
        m_recvMsgQueue.pop();
    pthread_mutex_unlock(&m_rmutex);
}

void CWSClient::setLogLevel(int level)
{
     /*第二个参数置为NULL ,打印到stderr*/
    lws_set_log_level(level, emit_log);
}

TSTAT CWSClient::getConnStat()
{
    return m_stat;
}

void CWSClient::destroy()
{
    pthread_mutex_destroy( &m_smutex );
    lws_context_destroy( m_context );
    if(m_session_data)
    {
        delete(m_session_data);
        m_session_data = NULL;
    }
    if(m_protocols)
    {
        delete[] m_protocols;
        m_protocols = NULL;
    }
    m_end = true;
}

void CWSClient::setEnd(bool flag)
{
    m_end = flag;
    lwsl_notice("WSpthreadFunc end = %d\n",flag);
}


void* WSpthreadFunc(void* arg)
{
    CWSClient* wsClient = (CWSClient*)arg;
    if(!wsClient)
    {
        lwsl_err("WSpthreadFunc is NULL\n");
        return NULL;
    }

    while(1)
    {
        if(wsClient == NULL || wsClient->m_end)
        {
            lwsl_notice("WSpthreadFunc exit\n");
            break;
        }

        lws_service( wsClient->m_context, wsClient->m_waittime );
        /**
         *下面的调用的意义是:当连接可以接受新数据时,触发一次WRITEABLE事件回调
         *当连接正在后台发送数据时,它不能接受新的数据写入请求,所有WRITEABLE事件回调不会执行
         **/
        if(!wsClient->m_sendMsgQueue.empty())
        {
            lws_callback_on_writable( wsClient->m_wsi );
            wsClient->m_stat = CLIENT_AWAITING_SEND;
        }
    }
    wsClient->m_threadID = 0;

    return NULL;
}

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 19
    评论
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值