本文参考:封装利用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;
}