【C++】libwebsockets库的简易教程
說在前面
最近很長一段時間,都有在嵌入式上進行websocket通信的需求。
 查了很多資料,現在C++可用的ws第三方庫不多,尤其是在較老的嵌入式開發環境中,既要支持C99和SSL,又需要輕量級不依賴第三方庫,基本上就只剩下libwebsockets這個庫了。
 但是libwebsockets庫是純C開發,沒有C++的特性,所以很多邏輯非常抽象,設計思路也很詭異,與之前接觸的很多三方模塊差異太大。我扒了源碼的demo,又從官方git、wiki上找了一點資料,才勉強搞清楚了一個簡單的ws客戶端的大致生命周期流程,寫了個簡單的client端
 
編譯libwebsockets
- [github地址]https://github.com/warmcat/libwebsockets
- 使用版本 v4.0-stable
安裝cmake
 libwebsockets的編譯部署是基于cmake,所以需要事先安裝cmake,可以從源或者cmake官網下載到二進制文件或者源碼 [cmake官網]https://cmake.org
- x84 Linux ,使用相關命令從源里直接獲取cmake二進制包。例如Ubuntu:sudo apt-get install cmake
- 如果是嵌入式arm的Linux,一般推薦下載cmake源碼自行編譯(本文不再贅述編譯部署方式)
- win系統,直接下載msi/exe安裝包,一鍵安裝即可
編譯
 以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的周期流程
- 核心思想:
回調函數: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來反饋。
消息循環
 當開發者將所有的參數配置結束后,需要循環調用lws_service,來反復進行lws內部邏輯并觸發回調函數。這個循環就是消息循環。
- 基本流程:
- 處理協議(定義回調函數)
- 配置lws_context_creation_info參數
- 創建lws_context
- 配置連接信息lws_client_connect_info
- 進入消息循環
- 通過回調函數實時獲取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库的简易教程的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 针对好玩游戏物品清单
- 下一篇: 读John Stuart Mill的《功
