文章目录
- 1. 前言
- 2. libwebsockets 的 编译 和 使用
- 2.1 编译
- 2.2 使用
- 2.2.1 构建运行上下文
- 2.2.2 事件处理循环
 
 
- 3. websocket 协议
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. libwebsockets 的 编译 和 使用
2.1 编译
先到网站 libwebsockets 下载源码包,解压后,编译源码目录下的文件 cross-arm-linux-gnueabihf.cmake ,配置交叉编译环境(假定为 ARM 架构交叉编译 libwebsockets):
#
# CMake Toolchain file for crosscompiling on ARM.
#
# This can be used when running cmake in the following way:
#  cd build/
#  cmake .. -DCMAKE_TOOLCHAIN_FILE=../cross-arm-linux-gnueabihf.cmake
#set(CROSS_PATH /path/to/cross-compiler)# Target operating system name.
set(CMAKE_SYSTEM_NAME Linux)# Name of C compiler.
set(CMAKE_C_COMPILER "${CROSS_PATH}/bin/arm-linux-gnueabihf-gcc")
set(CMAKE_CXX_COMPILER "${CROSS_PATH}/bin/arm-linux-gnueabihf-g++")# Where to look for the target environment. (More paths can be added here)
set(CMAKE_FIND_ROOT_PATH "${CROSS_PATH}")# Adjust the default behavior of the FIND_XXX() commands:
# search programs in the host environment only.
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)# Search headers and libraries in the target environment only.
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)其中,CROSS_PATH 设置为实际的交叉编译器目录。接下来编辑 CMakeLists.txt ,按需配置 libwebsockets 支持的功能特性(主要是配置 options)。如果要编译全部的调试代码,打开 _DEBUG 宏:
#if (LWS_MBED3)set(CMAKE_C_FLAGS "-D_DEBUG ${CMAKE_C_FLAGS}")
#endif()笔者这里的源码版本,只有在 LWS_MBED3 开启时,才编译所有的调试信息代码,为了调试方便,这里注释掉 CMakeLists.txt 两行。注意,开启 _DEBUG 宏并不是意味着启用了所有调试信息,它只是将所有的调试代码编译进去了,要启用所有的调试信息,还需要通过接口 lws_set_log_level() 开启相应的调试信息。
 接下来建立一个编译目录 build,然后进行编译:
$ mkdir build
$ cd build
$ cmake .. -DCMAKE_INSTALL_PREFIX=`pwd`/_install \
-DCMAKE_TOOLCHAIN_FILE=../cross-arm-linux-gnueabihf.cmake \
-DWITHOUT_EXTENSIONS=1 -DWITH_SSL=0
$ make -j8 # 按实际的处理器个数设定 -j 参数
$ make install编译生成的所有文件都位于 build 目录下,供程序使用的文件安装在 build/_install 目录下:
$ cd _install
$ tree
.
├── bin
│   ├── libwebsockets-test-client
│   ├── libwebsockets-test-echo
│   ├── libwebsockets-test-fraggle
│   ├── libwebsockets-test-fuzxy
│   ├── libwebsockets-test-ping
│   ├── libwebsockets-test-server
│   ├── libwebsockets-test-server-extpoll
│   └── libwebsockets-test-server-pthreads
├── include
│   ├── libwebsockets.h
│   └── lws_config.h
├── lib
│   ├── cmake
│   │   └── libwebsockets
│   │       ├── LibwebsocketsConfig.cmake
│   │       ├── LibwebsocketsConfigVersion.cmake
│   │       ├── LibwebsocketsTargets.cmake
│   │       └── LibwebsocketsTargets-release.cmake
│   ├── libwebsockets.a
│   ├── libwebsockets.so -> libwebsockets.so.9
│   ├── libwebsockets.so.9
│   └── pkgconfig
│       └── libwebsockets.pc
└── share└── libwebsockets-test-server├── favicon.ico├── leaf.jpg├── libwebsockets.org-logo.png├── lws-common.js└── test.html8 directories, 23 files2.2 使用
程序代码通过头文件 include/libwebsockets.h 导入 libwebsockets 的接口。下面通过 libwebsockets 来构建一个 webserver 。
2.2.1 构建运行上下文
主要构建 server 监听套接字:
#include <libwebsockets.h>// 按需自定义的 web server 数据
struct web_server_data {...
};struct lws_protocols lws_protos[] = {{ "ws", web_server_callback, sizeof(struct web_server_data), 3 * 1024 * 1024 },{ "http", lws_callback_http_dummy, 0, 0 },{ NULL, NULL,   0 }
};struct lws_http_mount http_mount = {/* .mount_next */               NULL,           /* linked-list "next" *//* .mountpoint */               "/",            /* mountpoint URL *//* .origin */                   "web",            /* serve from dir *//* .def */                      "test.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 */
};struct lws_context *context;
struct lws_context_creation_info ctx_info = { 0 };ctx_info.port = 8000;
ctx_info.iface = NULL; 
ctx_info.protocols = lws_protos;
ctx_info.gid = -1; 
ctx_info.uid = -1; 
ctx_info.options = LWS_SERVER_OPTION_DISABLE_IPV6;
ctx_info.mounts = &http_mount;
context = lws_create_context(&ctx_info);lws_create_context()struct lws_context *context = NULL;...context = lws_zalloc(sizeof(struct lws_context));...if (lws_plat_init(context, info))goto bail;...if (!lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS))// 创建 libwebsocket server [套接字 + lws]if (!lws_create_vhost(context, info)) {lwsl_err("Failed to create default vhost\n");return NULL;}...return context;2.2.2 事件处理循环
// 事件处理循环:
// . 客户端连接建立、断开
// . 和客户端通信
while (1) {lws_service(context, 1000);msleep(1);
}libwebsockets 预定义的了一些通知信息,在事件处理循环里传递给 web_server_callback() 处理:
// 建立客户端连接上下文
lws_service()lws_plat_service()lws_plat_service_tsi()...// 读取 server 监听套接字状态, 看 是否有连接进来// 读取 client 套接字状态, 看 是否有数据可读n = poll(pt->fds, pt->fds_count, timeout_ms);.../* any socket with events to service? */for (n = 0; n < pt->fds_count && c; n++) { // 处理所有 poll fd 事件...m = lws_service_fd_tsi(context, &pt->fds[n], tsi);...// 超时处理if (context->last_timeout_check_s != now) { // 秒级精度context->last_timeout_check_s = now; // poll fd 时间时候, 更新 context 时间...// 超时监测: // lws_set_timeout(wsi, reason, secs) 添加的超时监测列表wsi = context->pt[tsi].timeout_list;while (wsi) {/* we have to take copies, because he may be deleted */wsi1 = wsi->timeout_list;tmp_fd = wsi->sock;if (lws_service_timeout_check(wsi, (unsigned int)now)) { // 如果监测到超时对象, 关闭它并释放资源/* he did time out... */if (tmp_fd == our_fd)/* it was the guy we came to service! */timed_out = 1; // 标记监测到超时/* he's gone, no need to mark as handled */}wsi = wsi1;}}...switch (wsi->mode) {...case LWSCM_SERVER_LISTENER: // server 监听套接字......n = lws_server_socket_service(context, wsi, pollfd); // 客户端连接上下文的建立...switch (wsi->mode) {...case LWSCM_SERVER_LISTENER:#if LWS_POSIX/* pollin means a client has connected to us then */do {.../* listen socket got an unencrypted connection... */clilen = sizeof(cli_addr);...accept_fd  = accept(pollfd->fd, (struct sockaddr *)&cli_addr, &clilen); // 获取客户端连接套接字句柄...} ;#endif...}...goto handled;// 其它事件处理,这里不细表}...}// 完成客户端握手
lws_service()...lws_plat_service_tsi()...n = poll(pt->fds, pt->fds_count, timeout_ms);...for (n = 0; n < pt->fds_count && c; n++) { // 处理所有 poll fd 事件...m = lws_service_fd_tsi(context, &pt->fds[n], tsi);...switch (wsi->mode) {...case LWSCM_WS_SERVING:......n = lws_read(wsi, (unsigned char *)eff_buf.token, eff_buf.token_len);...switch (wsi->state) {...case LWSS_HTTP_HEADERS:...last_char = buf;if (lws_handshake_server(wsi, &buf, len))/* Handshake indicates this session is done. */goto bail;}...}...}lws_handshake_server(wsi, &buf, len)...while (len--) { // 解析 http 信息,完成和客户端的握手...if (wsi->protocol->callback)// 给回调 web_server_callback() 发 LWS_CALLBACK_ESTABLISHED 通知if (wsi->protocol->callback(wsi, LWS_CALLBACK_ESTABLISHED,wsi->user_space,...}int web_server_callback(struct lws *wsi, enum lws_callback_reasons reason,void* user, void* in, size_t len)
{...switch (reason) { // 不需要处理所有类型的通知,只处理必要的、需要的通知类型case LWS_CALLBACK_ESTABLISHED:...break;case LWS_CALLBACK_RECEIVE:...break;case LWS_CALLBACK_SERVER_WRITEABLE:...break;case LWS_CALLBACK_CLOSED:...break;default:break;}return 0;
}3. websocket 协议
websocket 协议工作在应用层,基于 TCP 套接字实现。websocket 涉及的协议包括 RFC6455 和 RFC7936 。知乎博文 WebSocket 协议完整解析 是一个不错的参考。