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;} elselwsl_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;
}
C++ 使用libwebsockets开源库封装client类相关推荐
- Fresco图片加载框架的介绍,相关开源库以及工具类的封装
Fresco图片加载框架的介绍,相关开源库以及工具类的封装 本文已授权微信公众号:鸿洋(hongyangAndroid)在微信公众号平台原创首发. 简介 Fresco 是Facebook开源的安卓上的 ...
- libcurl开源库封装ftp工具,支持多线程并发、断点续传、超时连接、传输速率控制
各位朋友好,第一次在CSDN写博客,后续时间充足的话会陆续更新一些资源,大家一起探讨交流,感谢!!! 如有任何疑问,可以留言. 目的:再次封装CURL接口,使用FTP相关接口更方便,更易懂 功能: 可 ...
- 基于 Multitype 开源库封装更好用的RecyclerView.Adapter
前言 MultiType 这个项目,至今 v3.x 稳定多时,考虑得非常多,但也做得非常克制.原则一直是 直观.灵活.可靠.简单纯粹(其中直观和灵活是非常看重的). 这是 MultiType 框架作者 ...
- PHP使用GD库封装验证码类
调试小技巧:当图片无法显示时,将header函数注释掉就可以看到报错信息了 字体文件放在当前文件目录的font文件夹中,windows的字体可以到C:\Windows\Fonts目录下复制过来,处理好 ...
- 【转】DICOM医学图像处理:开源库mDCM与DCMTK的比較分析(一),JPEG无损压缩DCM图像
转自:https://www.cnblogs.com/mfrbuaa/p/4004114.html 有修订 背景介绍: 近期项目需求,需要使用C#进行最新的UI和相关DICOM3.0医学图像模块的开发 ...
- 粉笔网iPhone端使用的第三方开源库
粉笔网iPhone端使用了哪些第三方的开源库.我在这儿整理了一下,分享给大家. ASIHttpRequest ASIHttpRequest 是一个被广泛使用的第三方网络访问开源库.用于提供更加友好的网 ...
- 开源库MusicPlayManager - 封装StarrySky音乐库
开源库MusicPlayManager - 封装StarrySky音乐库 关于 效果图 使用 Java版初始化 Kotlin版初始化 java版权限检查工具使用方法: kotlin权限检查工具使用方法 ...
- 使用linux c开源库libwebsockets编写的websocket客户端
<一>: 背景 19年中旬做的一个嵌入式项目, 应用层需要有一个心跳的功能, 当时决定用websocket协议, 所以当时就研究了下libwebsockets的使用. 网上的资料并不多, ...
- Python之tkinter:动态演示调用python库的tkinter带你进入GUI世界(Button的command/Label/PhotoImage/封装为类)
Python之tkinter:动态演示调用python库的tkinter带你进入GUI世界(Button的command/Label/PhotoImage/封装为类) 目录 tkinter应用案例五 ...
最新文章
- 一文了解 2018年最火爆的30个机器学习项目
- SAP RETAIL 参考PO创建分配表之二
- 一个Portal处理流程
- 终身学习这件事,比你想的还要重要
- php curl ssr,php curl模拟登陆
- OpenCASCADE绘制测试线束:图形命令之AIS 查看器——网格可视化服务
- surefire 拉起testng单元测试类的源码流程阅读(二)
- 51 NOD 1363 最小公倍数之和 (欧拉函数思维应用)
- Vczh Library++ 语法分析器开发指南
- bootstrapV4.6.0 图片宫格布局(案例篇)
- ORB_SLAM2中的疑难杂症
- WebForms简介
- 控制台应用程序《石头剪刀布》——新手,
- mybatis配置sql超时时间
- php投影,投影效果怎么做?PS制作逼真的投影效果
- 4G内存适合装哪个版本matlab,4G内存装win7 32位还是64位|单条4G内存选32位还是64位系统性能实测...
- 计算机专业的黑板报内容,新学期黑板报文字资料参考
- 网易2017春招[编程题]赶去公司@Java
- [转]ISE中如何将自己的verilog源代码.v或VHDL源代码.vhd封装打包成IP核?
- 《游戏改变世界》读后感 一 (懊悔?and我眼中的好游戏)