libwebsockets 简介

本文详细介绍了如何在ARM架构上使用libwebsockets库进行WebSocket服务器的编译和基本操作,包括配置交叉编译环境、构建运行上下文、事件处理循环以及websocket协议的基本原理。
摘要由CSDN通过智能技术生成

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.html

8 directories, 23 files

2.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 涉及的协议包括 RFC6455RFC7936 。知乎博文 WebSocket 协议完整解析 是一个不错的参考。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值