说在前面

最近很长一段时间,都有在嵌入式上进行websocket通信的需求。
查了很多资料,现在C++可用的ws第三方库不多,尤其是在较老的嵌入式开发环境中,既要支持C99和SSL,又需要轻量级不依赖第三方库,基本上就只剩下libwebsockets这个库了。
但是libwebsockets库是纯C开发,没有C++的特性,所以很多逻辑非常抽象,设计思路也很诡异,与之前接触的很多三方模块差异太大。我扒了源码的demo,又从官方git、wiki上找了一点资料,才勉强搞清楚了一个简单的ws客户端的大致生命周期流程,写了个简单的client端

编译libwebsockets

  • [github地址]https://github.com/warmcat/libwebsockets
  • 使用版本 v4.0-stable
  1. 安装cmake
    libwebsockets的编译部署是基于cmake,所以需要事先安装cmake,可以从源或者cmake官网下载到二进制文件或者源码 [cmake官网]https://cmake.org

    • x84 Linux ,使用相关命令从源里直接获取cmake二进制包。例如Ubuntu:

      sudo apt-get install cmake
      
    • 如果是嵌入式arm的Linux,一般推荐下载cmake源码自行编译(本文不再赘述编译部署方式)
    • win系统,直接下载msi/exe安装包,一键安装即可
  2. 编译
    以Linux为例

    • 进入libwebsockets源码目录
    • 创建build目录
      mkdir build
      
    • cmake编译
      cd build
      cmake ..
      make
      
    • 在cmake … 命令执行过程中,会检测系统的openssl模块
      如果需要使用wss,则需要提前安装openssl,Ubuntu可以直接__sudo apt-get install libssl-dev__;
      或者下载openssl源码安装,并设置OPENSSL_ROOT_DIR环境变量,来指定openssl的根目录位置__export OPENSSL_ROOT_DIR=[openssl的目录]__
      如果不需要wss,直接忽略cmake过程中的OPENSSL NOT FOUND警告即可
    • make之后,会在build目录下生成include目录和lib目录,这个就是libwebsockets的头文件和库文件。

libwebsockets的周期流程

  • 核心思想:
  1. 回调函数:libwebsockets的回调函数(lws_callbacks.h

    typedef int lws_callback_function(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
    

    lws在初始化配置时,需要定一个回调函数,lws会通过该回调函数返回给开发者当前的所有状态:初始化、连接建立、连接失败、数据读写等等,而状态类型通过枚举reason来反馈。

  2. 消息循环
    当开发者将所有的参数配置结束后,需要循环调用lws_service,来反复进行lws内部逻辑并触发回调函数。这个循环就是消息循环。

  • 基本流程:

    1. 处理协议(定义回调函数)
    2. 配置lws_context_creation_info参数
    3. 创建lws_context
    4. 配置连接信息lws_client_connect_info
    5. 进入消息循环
    6. 通过回调函数实时获取ws状态并进行下一步操作(发送、接受、断开、异常控制)

简易客户端代码

范例代码


#include <pthread.h>
#include <iostream>
#include <string.h>
#include <string>
#include <libwebsockets.h>static lws *wsi = NULL;
static bool established = false;
static bool isBreak = false;
static bool stop = false;//分析ws地址
//parameters :
//@ _url      [in]完整的url
//@ _protocol [out]协议字符串 ws/wss
//@ _host     [out]主机地址
//@ _port     [out]端口,如果没有端口号,返回-1
//@ _path     [out]url的path部分
int UnmarshalURL(const char *_url, std::string &_protocol, std::string &_host, int &_port, std::string &path)
{std::string url(_url);int pslash = url.find("//", 0);if (pslash >= 0){_protocol = url.substr(0, pslash - 1);url = url.substr(pslash + 2);}pslash = url.find(':');if (pslash < 0){//没有端口号_port = -1;pslash = url.find('/', 0);if (pslash < 0){//没有path_host = url;}else{//有path_host = url.substr(0, pslash);path = url.substr(pslash);}}else{//有端口号_host = url.substr(0, pslash);url = url.substr(pslash + 1);pslash = url.find('/', 0);if (pslash < 0){//没有path_port = atoi(url.c_str());}else{//有path_port = atoi(url.substr(0, pslash).c_str());path = url.substr(pslash);}}return 0;
}//记录接收10次服务器返回
static int recvSum = 0;
// lws消息回调函数
int ws_callback(lws *_wsi, enum lws_callback_reasons _reasons, void *_user, void *_in, size_t _len)
{printf("CALLBACK REASON: %d\n", _reasons);//发送或者接受buffer,建议使用栈区的局部变量,lws会自己释放相关内存//如果使用堆区自定义内存空间,可能会导致内存泄漏或者指针越界char buffer[2560];memset(buffer, 0, 2560);switch (_reasons){case LWS_CALLBACK_CLIENT_ESTABLISHED://连接成功时,会触发此reasonprintf("established\n");//调用一次lws_callback_on_writeable,会触发一次callback的LWS_CALLBACK_CLIENT_WRITEABLE,之后可进行一次发送数据操作lws_callback_on_writable(_wsi);break;case LWS_CALLBACK_CLIENT_CLOSED:// 客户端主动断开、服务端断开都会触发此reasonisBreak = true; // ws关闭,发出消息,退出消息循环printf("ws closed\n");break;case LWS_CALLBACK_CLIENT_CONNECTION_ERROR://连接失败、异常printf("connect error\n");break;case LWS_CALLBACK_CLIENT_RECEIVE://获取到服务端的数据memcpy(buffer, _in, _len);printf("recv: %s\n", buffer);usleep(1 * 1000 * 1000);lws_callback_on_writable(_wsi);break;case LWS_CALLBACK_CLIENT_WRITEABLE://调用lws_callback_on_writeable,会触发一次此reasonif (stop)break;recvSum++;if (recvSum >= 10){stop = true;printf("will close\n");//使用lws_close_reason来准备断开连接的断开信息lws_close_reason(_wsi, LWS_CLOSE_STATUS_GOINGAWAY, NULL, 0);//当callback中return非0 时,则会主动断开websocketreturn -1;}sprintf(buffer, "send data %02d\0", recvSum);printf("%s\n", buffer);int len = lws_write(_wsi, buffer, strlen(buffer), LWS_WRITE_TEXT);printf("write len=%d\n", len);break;default:break;}return 0;
}//程序输入参数 范例
//exe ws://172.31.234.19:4455/ws/demo
//exe wss://www.unruly.online:3344/ws/demo
int main(int argc, char **argv)
{if (argc != 2){printf("cmd parameters error!\n");return -1;}char *url = argv[1];printf("des URL:%s\n", url);std::string protocol; //ws/wss协议std::string host;     //主机IPint port;             //端口std::string path;     //path//解析URL的参数UnmarshalURL(url, protocol, host, port, path);bool ssl = protocol == "wss" ? true : false; //确认是否进行SSL加密//lws初始化阶段struct lws_context_creation_info info; //websocket 配置参数struct lws_context *context;           //websocket 连接上下文struct lws_client_connect_info ci;     //websocket 连接信息//建议初始化全部置为0memset(&info, 0, sizeof(info));memset(&ci, 0, sizeof(ci));struct lws_protocols lwsprotocol[2];//ws处理协议初始化时,建议将所有内存空间置0memset(&lwsprotocol[0], 0, sizeof(lws_protocols));memset(&lwsprotocol[1], 0, sizeof(lws_protocols));lwsprotocol[0].name = "ws-client";lwsprotocol[0].callback = ws_callback; //设置回调函数lwsprotocol[0].user = NULL;lwsprotocol[0].tx_packet_size = 5120;lwsprotocol[0].rx_buffer_size = 5120;lwsprotocol[1].name = NULL;lwsprotocol[1].callback = NULL;info.protocols = lwsprotocol;       //设置处理协议info.port = CONTEXT_PORT_NO_LISTEN; //作为ws客户端,无需绑定端口//ws和wss的初始化配置不同info.options = ssl ? LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT : 0; //如果是wss,需要做全局SSL初始化context = lws_create_context(&info); //创建连接上下文if (context == NULL){printf("create context error\n");return -1;}//初始化连接信息ci.context = context;      //设置上下文ci.address = host.c_str(); //设置目标主机IPci.port = port;            //设置目标主机服务端口ci.path = path.c_str();    //设置目标主机服务PATHci.host = ci.address;      //设置目标主机IPci.origin = ci.address;    //设置目标主机IPci.pwsi = &wsi;            //设置wsi句柄ci.userdata = NULL;        //userdata 指针会传递给callback的user参数,一般用作自定义变量传入ci.protocol = lwsprotocol[0].name;//ws/wss需要不同的配置ci.ssl_connection = ssl ? (LCCSCF_USE_SSL | LCCSCF_ALLOW_SELFSIGNED | LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK | LCCSCF_ALLOW_INSECURE) : 0;lws_client_connect_via_info(&ci); //使连接信息生效//进入消息循环while (!isBreak){lws_service(context, 500);}printf("ws disconnect\n");return 0;
}

libwebsockets也还在学习阶段,代码只实现了简单的客户端通信,如果有其他问题,可以评论,我会在空闲时间回复解答

PS:
libwebsockets的接口非常底层,很多逻辑需要自己封装实现
libwebsockets不是线程安全的库,如果想要将其进行封装为类,那么线程、信号量、锁的控制一定要非常严谨

【C++】libwebsockets库的简易教程相关推荐

  1. ColorUI组件库简易教程之交互组件

    官方示例在此!!!官方示例在此!!!官方示例在此!!! 这主要是基于uni-app开发的,所以,请多看uni-app文档,了解一些标签.属性,可以更好的使用该组件 目录 交互组件 Bar操作条 Nav ...

  2. ColorUI组件库简易教程之扩展插件

    按照惯例,在此奉上官方示例!!! 目录 扩展插件 索引列表 微动画 全屏抽屉 垂直导航 扩展插件 索引列表 索引列表主要由scroll-view标签实现,类名为indexes,具体js逻辑请看源码,有 ...

  3. eslint不报错 vue_【简易教程】基于Vue-cli使用eslint指南

    插件安装 首先在vscode插件中搜索eslint和prettier. 啥也不管,这俩必须得装. 插件简介 vscode插件库里的eslint是用来在你写代码的时候就直接给你报错.(vue-cli中的 ...

  4. 安装python程序后要进行什么设置-安装好Pycharm后如何配置Python解释器简易教程...

    这两天有许多Python小白加入学习群,并且问了许多关于Pycharm基本使用的问题,今天小编就以配置Python解释器的问题给大家简单絮叨一下. 1.一般来说,当我们启动Pycharm,如果Pych ...

  5. Android开发简易教程

    Android开发简易教程 Android 开发因为涉及到代码编辑.UI 布局.打包等工序,有一款好用的IDE非常重要.Google 最早提供了基于 Eclipse 的 ADT 作为开发工具,后来在2 ...

  6. 文件上传利器SWFUpload入门简易教程

    凡做过网站开发的都应该知道表单file的确鸡肋. Ajax解决了不刷新页面提交表单,但是却没有解决文件上传不刷新页面,当然也有其它技术让不刷新页面而提交文件,该技术主要是利用隐藏的iFrame, 较A ...

  7. 【简易教程】基于Vue-cli使用eslint指南

    [简易教程]基于Vue-cli使用eslint指南 插件安装 首先在vscode插件中搜索eslint和prettier. 啥也不管,这俩必须得装. 插件简介 vscode插件库里的eslint是用来 ...

  8. Makefile简易教程

    Reference: http://www.cnblogs.com/owlman/p/5514724.html Makefile简易教程 本文部分内容引用: 中文维基百科. 一个简单的Makefile ...

  9. 文科妹学 GitHub 简易教程(转)

    文科妹学 GitHub 简易教程 #什么是 Github ?必须要放这张图了!!! Git 是由 Linux 之父 Linus Tovalds 为了更好地管理linux内核开发而创立的分布式版本控制/ ...

最新文章

  1. 用小神经网络和光谱仪优化关键词识别
  2. centos6.5 mysql 远程访问_centos6.5 mysql 设置支持远程ip访问
  3. php超链接_一个纯PHP库,用于读写文字处理文档
  4. c 语言 结构体 编程,C语言:结构体的编程问题(很简单)
  5. GET请求如何传递数组参数
  6. 阿里云服务器企业该如何选择
  7. 循环中需要调用异步怎么确保执行完再执行其他的_什么是事件循环和异步编程?5种使用async/await更好地编码方式!...
  8. 聊天实录:刘静平谈网管员职业规划与技术
  9. 由于您的系统没有安装html help,win10遇到“您未安装FLASH控件”的提示怎么办
  10. 科目二经验之谈 10小时必过秘笈
  11. 多线程与单线程的区别
  12. 如何做云班课上的计算机作业,云班课如何提交课后作业 作业提交教程
  13. 免费u盘数据恢复软件有哪些?找个最适合你的!
  14. 好的设计要多分享,5款优秀在线原型设计案例
  15. 计算机管理系统绪论,计算机控制系统绪论.ppt
  16. 前端websocket连接mqtt服务器(Paho-mqtt,mqttws31.js)以及断开重连
  17. ApiPost使用时的一些坑
  18. 西门子SMART200和三菱D700变频器485通讯例程
  19. android中添加arial字体(非android默认字体)
  20. Android Glide图片加载-缓存机制(内存缓存和磁盘缓存)

热门文章

  1. 双路cpu比单路强多少_双路cpu比单路强多少
  2. 自动控制原理系统的“误差”
  3. DT时代下数据安全运营面临的主要挑战
  4. 2022护眼产品展,北京眼健康展,眼科医学展,近视矫正设备展
  5. 机器学习技法11: Gradient Boosted Decision Tree(GBDT)
  6. PPT基础(四十一)设置默认字体和形状
  7. 杭漂两年,深漂两年,宇宙的尽头到底在哪儿
  8. excel怎么设置自动计算_61份电气自动计算表,excel函数输入数据秒出精准结果,限时分享...
  9. 国内10个最美高校图书馆!一定要考上!
  10. 手机版Excel Office跳过登录