一种libuv实现websockets服务的解决方案

方法是libuv用多事件循环来驱动。说起来容易,做起来还是比下面的方法更容易:

在这里插入图片描述

上图是某位网友的方法代表子大部分网络资料。此方法对部署不友好,因为软件仓库提供的libwebsockets是不能用了。如何简化部署,利用好现有的软件仓库呢?

libwebsockets版本历史

libwebsockets曾经是无缝支持libuv的。不过,随着版本号的增大,它对libuv的支持越来越差了。首先快速回顾下libwebsockets的版本历史:

  • v0.1.0 - 2011年9月:这是libwebsockets的第一个版本,它提供了基本的WebSocket协议支持,包括创建WebSocket连接、发送和接收消息等基本功能。
  • v0.2.0 - 2012年1月:这个版本添加了对HTTP协议的支持,使得libwebsockets可以处理HTTP请求和响应。此外,还添加了对多线程的支持,使得你可以在多个线程中安全地使用libwebsockets。
  • v0.3.0 - 2012年7月:这个版本添加了对SSL/TLS加密的支持,使得你可以使用安全连接来传输WebSocket消息。此外,还添加了一些新的API和功能,比如定时器和回调函数等。
  • v0.4.0 - 2013年1月:这个版本添加了对HyBi-17协议的支持,这是WebSocket协议的一个扩展版本,提供了更多的功能和更好的性能。此外,还修复了一些已知的漏洞和错误。
  • v0.5.0 - 2014年2月:这个版本添加了对多路复用和支持,这意味着你可以同时处理多个WebSocket连接。此外,还添加了一些新的API和功能,比如获取连接信息、处理文件上传等。
  • v1.0.0 - 2015年9月:这是libwebsockets的一个重要版本,它标志着libwebsockets已经成熟并稳定。这个版本添加了对HTTP/2协议的支持,同时优化了性能和内存使用。此外,还修复了一些已知的漏洞和错误。
  • v1.1.0 - 2016年3月:这个版本主要修复了一些已知的漏洞和错误,同时添加了一些新的功能和优化,比如更好的日志记录和内存管理。
  • v2.0.0 - 2018年8月:这是一个具有里程碑意义的版本,它引入了许多新的功能和改变。其中包括更好的多线程支持、对更多协议的支持(如Raw WebSocket、HyBi-17、HTTP等),以及对更多操作系统的支持。此外,它还改进了API设计,并修复了许多已知问题。
  • v2.1.0 - 2019年4月:这个版本主要修复了一些已知的漏洞和错误,同时添加了一些新的功能和优化,比如更好的SSL/TLS支持和对更多操作系统的支持。
  • v3.0.0 - 2020年5月:这个版本标志着libwebsockets进入了一个新的阶段。它引入了更多新的功能和改变,包括更好的多线程支持、对更多协议的支持(如Raw WebSocket、HyBi-17、HTTP等),以及对更多操作系统的支持。此外,它还改进了API设计,并修复了许多已知问题。从这个版本开始,事件循环对libuv的支持逐渐边缘化。

关键问题

  1. 从v3.0.0开始,lws_uv_initloop函数不存在。
  2. lws_context_create_info.options增加LWS_SERVER_OPTION_LIBUV导致lws_service函数崩溃。

复现问题的常规操作:

  1. 编译时开启LIBUV支持:在编译libwebsockets库时,需要开启LIBUV支持并指定LIBUV的目录位置。这可以通过修改CMakeLists.txt文件来实现。具体来说,需要在CMakeLists.txt文件中添加以下行:

    option(WITH_LIBUV "Enable libuv support" ON)  
    find_package(libuv REQUIRED)  
    include_directories(${LIBUV_INCLUDE_DIRS})  
    link_directories(${LIBUV_LIBRARY_DIRS})  
    add_definitions(-DLIBUV_SUPPORT=1)
    

    这将启用LIBUV支持,并自动搜索和链接LIBUV库。

  2. 修改线程模型:libwebsockets默认使用自己的线程模型,但可以使用libuv的线程模型替代。这需要在初始化libwebsockets时指定使用libuv线程模型。具体来说,需要在调用lws_create_context函数时,将thread_mode参数设置为LWS_THREAD_MODE_UV。例如:

    struct lws_context_creation_info info;  
    memset(&info, 0, sizeof(info));  
    info.port = 8000;  
    info.protocols = protocols;  
    info.thread_mode = LWS_THREAD_MODE_UV; // 使用libuv线程模型  
    struct lws_context *context = lws_create_context(&info);
    

    这将告诉libwebsockets使用libuv的线程模型进行初始化。

  3. 使用libuv的API:在使用libwebsockets时,需要使用libuv提供的API来进行读写操作和事件处理。例如,可以使用uv_read_startuv_write_t等函数来进行读写操作,使用uv_run函数来进行事件循环。这些函数将在libuv库中提供。例如:

    uv_stream_t *stream;  
    uv_buf_t buf;  
    uv_read_start(stream, on_read, &buf); // 开始读取操作  
    uv_write(&req, stream, buf.base, nread, on_write); // 开始写入操作  
    uv_run(uv_default_loop(), UV_RUN_DEFAULT); // 运行事件循环
    

解决思路

每个线程一个循环。可以在同一线程中使用多个事件循环。但这通常没有意义,因为一个循环的 uv_run() 调用将阻止并停止另一个循环的运行。通过仔细组合 uv_run(loop, UV_RUN_ONCE) 你可以做一些非常有趣的事情。您可以使用多个循环在程序中创建“模态”步骤,其中第二个事件循环“暂停”第一个事件循环,直到发生某些操作(用户按 Return 键或您收到新事件或其他事件)。

有一个非常具体的用例,可以使用两个事件循环作为同步机制来代替条件变量。当时 libuv 没有条件变量支持,现在我保持这种方式,以允许它与早期的节点版本一起使用。具体用例是:

  1. 主线程使用 uv_queue_work() 在工作线程中调用阻塞函数。
  2. 工作线程必须调用自定义函数。问题是自定义函数必须在主线程上运行。
  3. 工作线程必须等待该函数返回。

条件变量方法是:

  1. 工作线程不直接调用自定义函数。相反,它创建一个 uv_async_t 处理程序。此处理程序的回调调用自定义函数。
  2. 初始化条件变量。
  3. 它使用 uv_async_send() 来让主线程(事件循环运行的地方)代表它调用该函数。
  4. 等待条件变量。
  5. 回调调用自定义函数,然后向条件变量发出信号,让工作线程继续运行。

事件循环实现改为:

  1. 在工作线程中创建一个新的事件循环。
  2. uv_async_t 与这个新循环关联起来。
  3. 通过原始 uv_async_t 处理程序的数据字段将此处理程序传递到主线程。
  4. uv_run() 新的事件循环,现在会阻塞,因为异步处理程序已经增加了它的 refcount
  5. 主线程中的回调调用自定义函数,然后使用 uv_async_send() 向新循环上的异步处理程序发出信号。
  6. 该异步处理程序的回调只是关闭处理程序本身,新循环的引用计数降至零,uv_run() 返回并且工作线程可以继续。

解决办法

下面的示例由TCP服务和websockets服务组成。TCP服务只是一个echo服务端,websockets则是静态文件服务,仅两个网页:index.html和404.html。

TCP线程

void tcp_thread_cb(void* args)
{
    uv_loop_t loop;
    uv_loop_init(&loop);
    struct sockaddr_in addr;
    uv_tcp_t server;
    int ierr = uv_tcp_init(&loop, &server);
    VOID_RETURN(ierr);
    ierr = uv_ip4_addr("0.0.0.0", IPORT, &addr);
    VOID_RETURN(ierr);
    ierr = uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0);
    VOID_RETURN(ierr);
    ierr = uv_listen((uv_stream_t*)&server, 128, on_new_connection);
    VOID_RETURN(ierr);
    printf("server started.\n");
    ierr = uv_run(&loop, UV_RUN_DEFAULT);
    VOID_RETURN(ierr);
    uv_stop(&loop);
    uv_barrier_wait((uv_barrier_t*)args);
}

websockets线程

void websockets_thread_cb(void* args)
{
    static struct lws_context* context;
    static const struct lws_http_mount mounts[] = { {
            /* .mount_next */		NULL,		/* linked-list "next" */
            /* .mountpoint */		"/",		/* mountpoint URL */
            /* .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,
        },{0,} };
    struct lws_context_creation_info info = { 0, };
    lws_set_log_level(LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE, NULL);
    info.port = 20001;
    info.mounts = mounts;
    info.error_document_404 = "/404.html";
    info.options = LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
    context = lws_create_context(&info);
    assert(context);
    lwsl_user("websockets started\n");
    while (1)
        lws_service(context, 0);
    lws_context_destroy(context);
    uv_barrier_wait((uv_barrier_t*)args);
}

主函数

int main(int argc, char** argv)
{
    uv_loop_t* loop = uv_default_loop();
    uv_thread_t t[2];
    uv_barrier_t b;
    uv_barrier_init(&b, 3);
    int ierr = uv_thread_create(&t[0], tcp_thread_cb, &b);
    RAISE_RETURN(ierr);
    ierr = uv_thread_create(&t[1], websockets_thread_cb, &b);
    RAISE_RETURN(ierr);
    ierr = uv_run(loop, UV_RUN_DEFAULT);
    RAISE_RETURN(ierr);
    uv_barrier_wait(&b);
    return 0;
}

通用部分

#include <uv.h>
#include <libwebsockets.h>
#include <stdio.h>
#include <malloc.h>
#include <assert.h>

#define RAISE_RETURN(x) \
    if((x)) \
    { \
        fprintf(stderr, "error {%s} code %ld %s\n", __func__, (long int)(x), uv_strerror(x)); \
        printf("error {%s} code %ld %s\n", __func__, (long int)(x), uv_strerror(x)); \
        raise(x); \
        return (x); \
    }
#define VOID_RETURN(x) \
    if((x)) \
    { \
        fprintf(stderr, "error {%s} code %ld %s\n", __func__, (long int)(x), uv_strerror(x)); \
        printf("error {%s} code %ld %s\n", __func__, (long int)(x), uv_strerror(x)); \
        return; \
    }

验证

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这里插入图片描述

在这里插入图片描述

作者:岬淢箫声
日期:2023年11月7日
版本:1.0
链接:http://caowei.blog.csdn.net

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/123493.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue开发环境搭建部署(mac版)

前言 目前后端工作越来越少了&#xff0c;年底了&#xff0c;为了先过验收。项目负责人、产品、需求制定的方案就是先做假页面&#xff0c;所以前端的活多点。 其实现在不喜欢搞前端&#xff0c;原因很多&#xff0c;但是感觉现在似乎流行的码林绝学又是九九归一的瓶颈期…

【Kurbernetes资源管理】声明式资源管理+配置清单文件详解(附实例)

声明式 一、声明式资源管理方式1.1 简介1.2 基本语法1.3 子命令详解1.3.1 获取资源配置清单1.3.2 创建/更新资源补充&#xff1a;creat和apply的区别 1.3.3 删除资源----- delete1.3.4 编辑资源配置 -----edit1.3.5 获取资源的解释-----explain 二、资源清单格式详解2.1 yaml语…

Flutter 第三方 flutter_screenutil(屏幕适配)

一直觉得自己写的不是技术&#xff0c;而是情怀&#xff0c;一个个的教程是自己这一路走来的痕迹。靠专业技能的成功是最具可复制性的&#xff0c;希望我的这条路能让你们少走弯路&#xff0c;希望我能帮你们抹去知识的蒙尘&#xff0c;希望我能帮你们理清知识的脉络&#xff0…

Git的基本使用

目录 一.Git的简介 1.1 Git与SVN的区别&#xff08;优势与劣势&#xff09; 1.2 Git的工作流程 二.Git的安装及常用命令 2.1 使用前准备 ​编辑 ​编辑 2.2 在Windows中安装Git 官网链接 Git - Downloadshttps://git-scm.com/downloads 2.3 Git的常用命令 三、Git命令…

人工智能-卷积神经网络之多输入多输出通道

多输入多输出通道 每个图像的多个通道和多层卷积层。例如彩色图像具有标准的RGB通道来代表红、绿和蓝。 但是到目前为止&#xff0c;我们仅展示了单个输入和单个输出通道的简化例子。 这使得我们可以将输入、卷积核和输出看作二维张量。 当我们添加通道时&#xff0c;我们的输…

基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(二)

新增员工功能开发 1. 新增员工1.1 需求分析和设计1.1.1 产品原型1.1.2 接口设计1.1.3 表设计 1.2 代码开发1.2.1 设计DTO类1.2.2 Controller层1.2.3 Service层接口1.2.4 Service层实现类1.2.5 Mapper层 1.3 功能测试1.3.1 接口文档测试 1.4 代码完善1.4.1 问题一1.4.2 问题二1.…

H5网页漫画小说苹果cms模板源码/支持对接公众号/支持三级分销

H5网页漫画小说苹果cms模板源码&#xff0c;支持对接公众号、支持三级分销&#xff0c;评论、收藏、历史记录、三级分销。 独有的模板搜索功能&#xff0c;微信、qq防红&#xff0c;站外采集接口、记录阅读章节&#xff0c;SEO优化&#xff08;后台配置&#xff09;&#xff0…

16.字符连接

#include<stdio.h> #include <cstring> int main(){char s1[44];char s2[33];scanf("%s",s1);scanf("%s",s2);strcat(s1,s2) ;printf("连接两个字符为&#xff1a;%s ",s1); return 0;}

Java数据的基本(原始)类型和引用类型的特点差别

本文作为“Java数据类型”一文的补充https://blog.csdn.net/cnds123/article/details/110517272 Java的数据类型可以分为基本类型&#xff08;primitive types&#xff09;和引用类型&#xff08;reference types&#xff09;两大类。在实际编程中&#xff0c;要根据需求选择合…

MySQL json相关函数详解

MySQL提供了一系列的JSON函数&#xff0c;用于解析、提取、修改和操作JSON数据。以下是一些常用的JSON函数及其功能。 以下所有操作都使用该表&#xff08;zone_test&#xff09;用来演示&#xff1a; 一&#xff1a;JSON_OBJECT(key1,value1,key2,value2) 1、作用&#xff1a;…

​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​

软考-高级-系统架构设计师教程&#xff08;清华第2版&#xff09;【第1章-绪论-思维导图】 课本里章节里所有蓝色字体的思维导图

Read Completion Boundary (RCB)切分规则

Read Completion Boundary(RCB) 切分规则 Read Completion Boundary(RCB) 简介 当Read Completion 包含multi-completions时&#xff0c;RCB 规定了多个Completions地址的align规则。Spec中规定RCB可以是64 Byte或者128 Byte&#xff0c;该值可以在link_control register中得…

Unity随笔:C#运行时

Unity是如何编译运行C#的 &#xff08;1&#xff09;Unity会通过编译器将C#脚本编译成IL指令。 Unity会通过Roslyn来对C#代码进行编译&#xff0c;生成中间IL指令集。 当我们每次修改或者添加新的C#代码文件&#xff0c;Unity界面的右下角会出现短暂的“转圈”现象。这就意味…

PTE-RA总结

目录 FIBW刷题记录 1.The Plains Indians were people who did not like 2.An economic depression is a period of sustained 3.Pidgins are languages that are born after contact between 4.It is tempting to try to prove that good looks 5.The stock of Austral…

chrome安装vue devtools

不能访问应用商店 如果可以访问应用商店可以往下看 插件源代码 选择shell-chrome&#xff0c;这是官方的插件源码 下载源代码打包 参考教程 点击扩展按钮->管理扩展程序->打开开发者模式->把crx文件拖拽进去即可 可以访问chrome应用商店 插件地址 官方文档地址 选…

15.数组逆置【不是为啥我第四行不太对呢?】

#include<stdio.h>int fun(int a[4][4]){int i,j;int b[4][3];} int main(){int i,j;int a[3][4], b[4][3];for (i0;i<3;i)for(j0;j<4;j)scanf("%d",&a[i][j]);for (i0;i<3;i)for(j0;j<4;j)b[j][i]a[i][j];printf("逆置后&#xff1a;\n&…

Voice Control for ChatGPT简单高效的与ChatGPT进行交流学习。

快捷又不失灵活性 日常生活中&#xff0c;我们与亲人朋友沟通交流一般都是喜欢语音的形式来完成的&#xff0c;毕竟相对于文字来说语音就不会显的那么的苍白无力&#xff0c;同时最大的好处就是能解放我们的双手吧&#xff0c;能更快实现两者间的对话&#xff0c;沟通便更高效…

STM32 堆栈空间分布

参考 运行时访问__initial_sp和__heap_base 无RTOS时的情况 在以上配置的情况下&#xff0c;生成工程。在工程的startup.s文件中&#xff0c;由如下代码&#xff1a; Stack_Size EQU 0x400AREA STACK, NOINIT, READWRITE, ALIGN3 __Stack_top ; 自己添加 Stack_Mem…

MySQL代码子查询续集

dept表&#xff1a; emp表&#xff1a; -- 查询每个部门的信息(包括&#xff1a;部门名&#xff0c;编号&#xff0c;地址)和人员数量 -- 1.将两张表结合起来&#xff0c;筛选出部门名&#xff0c;编号&#xff0c;地址 tips: 表.* 表示将该表所有列都显示出来&#xff…

获取请求IP以及IP解析成省份

某些业务需要获取请求IP以及将IP解析成省份之类的&#xff0c;于是我写了一个工具类&#xff0c;可以直接COPY /*** IP工具类* author xxl* since 2023/11/9*/ Slf4j public class IPUtils {/*** 过滤本地地址*/public static final String LOCAL_ADDRESS "127.0.0.1&quo…