利用libwebsockets写ws、wss服务端和客户端
生活随笔
收集整理的這篇文章主要介紹了
利用libwebsockets写ws、wss服务端和客户端
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
服務端:
server.c
#include "libwebsockets.h" #include <signal.h> #include <string.h>static volatile int exit_sig = 0; #define MAX_PAYLOAD_SIZE 10 * 1024void sighdl( int sig ) {lwsl_notice( "%d traped", sig );exit_sig = 1; }/*** 會話上下文對象,結構根據需要自定義*/ struct session_data {int msg_count;unsigned char buf[LWS_PRE + MAX_PAYLOAD_SIZE];int len;bool bin;bool fin; };static int protocol_my_callback( struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len ) {struct session_data *data = (struct session_data *) user;switch ( reason ) {case LWS_CALLBACK_ESTABLISHED: // 當服務器和客戶端完成握手后printf("Client connect!\n");break;case LWS_CALLBACK_RECEIVE: // 當接收到客戶端發(fā)來的幀以后// 判斷是否最后一幀data->fin = lws_is_final_fragment( wsi );// 判斷是否二進制消息data->bin = lws_frame_is_binary( wsi );// 對服務器的接收端進行流量控制,如果來不及處理,可以控制之// 下面的調用禁止在此連接上接收數據lws_rx_flow_control( wsi, 0 );// 業(yè)務處理部分,為了實現Echo服務器,把客戶端數據保存起來memcpy( &data->buf[ LWS_PRE ], in, len );data->len = len;printf("recvied message:%s\n",in);// 需要給客戶端應答時,觸發(fā)一次寫回調lws_callback_on_writable( wsi );break;case LWS_CALLBACK_SERVER_WRITEABLE: // 當此連接可寫時lws_write( wsi, &data->buf[ LWS_PRE ], data->len, LWS_WRITE_TEXT );// 下面的調用允許在此連接上接收數據lws_rx_flow_control( wsi, 1 );break;}// 回調函數最終要返回0,否則無法創(chuàng)建服務器return 0; }/*** 支持的WebSocket子協議數組* 子協議即JavaScript客戶端WebSocket(url, protocols)第2參數數組的元素* 你需要為每種協議提供回調函數*/ struct lws_protocols protocols[] = {{//協議名稱,協議回調,接收緩沖區(qū)大小"ws", protocol_my_callback, sizeof( struct session_data ), MAX_PAYLOAD_SIZE,},{NULL, NULL, 0 // 最后一個元素固定為此格式} };int main(int argc,char **argv) {// 信號處理函數signal( SIGTERM, sighdl );struct lws_context_creation_info ctx_info = { 0 };ctx_info.port = 8000;ctx_info.iface = NULL; // 在所有網絡接口上監(jiān)聽ctx_info.protocols = protocols;ctx_info.gid = -1;ctx_info.uid = -1;ctx_info.options = LWS_SERVER_OPTION_VALIDATE_UTF8;ctx_info.ssl_ca_filepath = "../ca/ca-cert.pem";ctx_info.ssl_cert_filepath = "./server-cert.pem";ctx_info.ssl_private_key_filepath = "./server-key.pem";ctx_info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;//ctx_info.options |= LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT;struct lws_context *context = lws_create_context(&ctx_info);while ( !exit_sig ) {lws_service(context, 1000);}lws_context_destroy(context);return 0; }編譯腳本,compile.sh
######################################################################### # File Name: compile.sh # Author: loon # mail: 2453419889@qq.com # Created Time: 2018年09月07日 星期五 10時08分52秒 ######################################################################### #!/bin/bashlibdir=libwebsocketsg++ -g -o server server.c -I$libdir/include -L$libdir/lib -lwebsockets客戶端
client.c
#include "libwebsockets.h" #include <signal.h>static volatile int exit_sig = 0; #define MAX_PAYLOAD_SIZE 10 * 1024void sighdl( int sig ) {lwsl_notice( "%d traped", sig );exit_sig = 1; }/*** 會話上下文對象,結構根據需要自定義*/ struct session_data {int msg_count;unsigned char buf[LWS_PRE + MAX_PAYLOAD_SIZE];int len; };/*** 某個協議下的連接發(fā)生事件時,執(zhí)行的回調函數** wsi:指向WebSocket實例的指針* reason:導致回調的事件* user 庫為每個WebSocket會話分配的內存空間* in 某些事件使用此參數,作為傳入數據的指針* len 某些事件使用此參數,說明傳入數據的長度*/ int callback( struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len ) {struct session_data *data = (struct session_data *) user;switch ( reason ) {case LWS_CALLBACK_CLIENT_ESTABLISHED: // 連接到服務器后的回調lwsl_notice( "Connected to server ok!\n" );break;case LWS_CALLBACK_CLIENT_RECEIVE: // 接收到服務器數據后的回調,數據為in,其長度為lenlwsl_notice( "Rx: %s\n", (char *) in );break;case LWS_CALLBACK_CLIENT_WRITEABLE: // 當此客戶端可以發(fā)送數據時的回調if ( data->msg_count < 3 ) {// 前面LWS_PRE個字節(jié)必須留給LWSmemset( data->buf, 0, sizeof( data->buf ));char *msg = (char *) &data->buf[ LWS_PRE ];data->len = sprintf( msg, "你好 %d", ++data->msg_count );lwsl_notice( "Tx: %s\n", msg );// 通過WebSocket發(fā)送文本消息lws_write( wsi, &data->buf[ LWS_PRE ], data->len, LWS_WRITE_TEXT );}break;}return 0; }/*** 支持的WebSocket子協議數組* 子協議即JavaScript客戶端WebSocket(url, protocols)第2參數數組的元素* 你需要為每種協議提供回調函數*/ struct lws_protocols protocols[] = {{//協議名稱,協議回調,接收緩沖區(qū)大小"ws", callback, sizeof( struct session_data ), MAX_PAYLOAD_SIZE,},{NULL, NULL, 0 // 最后一個元素固定為此格式} };int main() {// 信號處理函數signal( SIGTERM, sighdl );// 用于創(chuàng)建vhost或者context的參數struct lws_context_creation_info ctx_info = { 0 };ctx_info.port = CONTEXT_PORT_NO_LISTEN;ctx_info.iface = NULL;ctx_info.protocols = protocols;ctx_info.gid = -1;ctx_info.uid = -1;//ssl支持(指定CA證書、客戶端證書及私鑰路徑,打開ssl支持)ctx_info.ssl_ca_filepath = "../ca/ca-cert.pem";ctx_info.ssl_cert_filepath = "./client-cert.pem";ctx_info.ssl_private_key_filepath = "./client-key.pem";ctx_info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;// 創(chuàng)建一個WebSocket處理器struct lws_context *context = lws_create_context( &ctx_info );char address[] = "127.0.0.1";int port = 8000;char addr_port[256] = { 0 };sprintf(addr_port, "%s:%u", address, port & 65535 );// 客戶端連接參數struct lws_client_connect_info conn_info = { 0 };conn_info.context = context;conn_info.address = address;conn_info.port = port;conn_info.ssl_connection = 1;conn_info.path = "./";conn_info.host = addr_port;conn_info.origin = addr_port;conn_info.protocol = protocols[ 0 ].name;// 下面的調用觸發(fā)LWS_CALLBACK_PROTOCOL_INIT事件// 創(chuàng)建一個客戶端連接struct lws *wsi = lws_client_connect_via_info( &conn_info );while ( !exit_sig ) {// 執(zhí)行一次事件循環(huán)(Poll),最長等待1000毫秒lws_service( context, 1000 );/*** 下面的調用的意義是:當連接可以接受新數據時,觸發(fā)一次WRITEABLE事件回調* 當連接正在后臺發(fā)送數據時,它不能接受新的數據寫入請求,所有WRITEABLE事件回調不會執(zhí)行*/lws_callback_on_writable( wsi );}// 銷毀上下文對象lws_context_destroy( context );return 0; }編譯腳本:
######################################################################### # File Name: compile.sh # Author: loon # mail: 2453419889@qq.com # Created Time: 2018年09月07日 星期五 10時22分58秒 ######################################################################### #!/bin/bashlibdir=libwebsocketsg++ -g -o client client.c -I$libdir/include -L$libdir/build/lib -lwebsockets官網例子
其實下載了官網源碼后minimal-examples下面有很多的例子,里面已經寫好了cmake的文件,看一下對應的README后直接可以在對應位置編譯,我這里復制minimal-ws-client和一個minimal-ws-server的例子,我基本只改了服務地址和端口,然后增加了打印(還有tx和rx客戶端分別代表發(fā)送和接收,都可以看一下):
客戶端:
服務端:
/** lws-minimal-ws-server** Written in 2010-2019 by Andy Green <andy@warmcat.com>** This file is made available under the Creative Commons CC0 1.0* Universal Public Domain Dedication.** This demonstrates the most minimal http server you can make with lws,* with an added websocket chat server.** To keep it simple, it serves stuff in the subdirectory "./mount-origin" of* the directory it was started in.* You can change that by changing mount.origin.*/#include <libwebsockets.h> #include <string.h> #include <signal.h>#define LWS_PLUGIN_STATIC #include "protocol_lws_minimal.c"static struct lws_protocols protocols[] = {{ "http", lws_callback_http_dummy, 0, 0 },LWS_PLUGIN_PROTOCOL_MINIMAL,{ NULL, NULL, 0, 0 } /* terminator */ };static const lws_retry_bo_t retry = {.secs_since_valid_ping = 3,.secs_since_valid_hangup = 10, };static int interrupted;static const struct lws_http_mount mount = {/* .mount_next */ NULL, /* linked-list "next" *//* .mountpoint */ "/", /* mountpoint URL *//* .origin */ "./mount-origin", /* serve from dir *//* .def */ "index.html", /* default filename *//* .protocol */ NULL,/* .cgienv */ NULL,/* .extra_mimetypes */ NULL,/* .interpret */ NULL,/* .cgi_timeout */ 0,/* .cache_max_age */ 0,/* .auth_mask */ 0,/* .cache_reusable */ 0,/* .cache_revalidate */ 0,/* .cache_intermediaries */ 0,/* .origin_protocol */ LWSMPRO_FILE, /* files in a dir *//* .mountpoint_len */ 1, /* char count *//* .basic_auth_login_file */ NULL, };void sigint_handler(int sig) {interrupted = 1; }int main(int argc, const char **argv) {struct lws_context_creation_info info;struct lws_context *context;const char *p;int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE/* for LLL_ verbosity above NOTICE to be built into lws,* lws must have been configured and built with* -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE *//* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER *//* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY *//* | LLL_DEBUG */;signal(SIGINT, sigint_handler);if ((p = lws_cmdline_option(argc, argv, "-d")))logs = atoi(p);lws_set_log_level(logs, NULL);lwsl_user("LWS minimal ws server | visit http://localhost:7681 (-s = use TLS / https)\n");memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */info.port = 7681;info.mounts = &mount;info.protocols = protocols;info.vhost_name = "localhost";info.ws_ping_pong_interval = 10;info.options =LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;if (lws_cmdline_option(argc, argv, "-s")) {lwsl_user("Server using TLS\n");info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;info.ssl_cert_filepath = "localhost-100y.cert";info.ssl_private_key_filepath = "localhost-100y.key";}if (lws_cmdline_option(argc, argv, "-h"))info.options |= LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK;if (lws_cmdline_option(argc, argv, "-v"))info.retry_and_idle_policy = &retry;context = lws_create_context(&info);if (!context) {lwsl_err("lws init failed\n");return 1;}while (n >= 0 && !interrupted)n = lws_service(context, 0);lws_context_destroy(context);return 0; }由于服務端和客戶端支持的回調協議名不一樣,所以服務器這里一直打印,不影響使用:
下面有很多人問lws_getaddrinfo46 failed的問題,我懷疑是dns解析的問題,建議先使用ip和端口的形式試下,域名的問題我再看看源碼,暫時沒分析出原因,客戶端似乎沒辦法自動根據域名解析出對應的ip和端口,目前從源碼看是“ipv6 lws_getaddrinfo46 failed”。
還有客戶端一般可以用官網給的不加密客戶端,需要加密的話則需要證書,對于自簽名的證書options配置參數需要改一下,是支持自簽名證書的。
?
?
總結
以上是生活随笔為你收集整理的利用libwebsockets写ws、wss服务端和客户端的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android:从零开始打造自己的深度链
- 下一篇: 【扯皮系列】一篇与众不同的 String