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

news2024/11/27 3:45:28

方法是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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ubuntu 安装 zsh、ohmyzsh并配置必要插件

下述记录是完成全部操作后回忆记录得来&#xff0c;或有不准确。我只记录安装中确实用到的指令&#xff0c;参考资料中有扩展内容&#xff0c;记录如下&#xff1a; ubuntu使用zsh终端并安装nerd font字体——nerd font字体不太好安装&#xff0c;使用fonts-powerline替代。 Ub…

基于SpringBoot+Vue的点餐管理系统

基于springbootvue的点餐平台网站系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 菜品详情 个人中心 订单 管理员界面 菜品管理 摘要 点餐管理系统是一种用…

【Mac开发环境搭建】安装HomeBrew、HomeBrew安装Docker、Docker安装Mysql5.7和8

文章目录 HomeBrew安装相关命令安装包卸载包查询可用的包更新所有包更新指定包查看已经安装的包查看包的信息清理包查看brew的版本更新brew获取brew的帮助信息 Brew安装DockerDocker常用命令镜像相关查看已经拉取的所有镜像删除镜像 容器相关停止运行容器启动容器重启容器删除容…

uniapp+uview2.0+vuex实现自定义tabbar组件

效果图 1.在components文件夹中新建MyTabbar组件 2.组件代码 <template><view class"myTabbarBox" :style"{ backgroundColor: backgroundColor }"><u-tabbar :placeholder"true" zIndex"0" :value"MyTabbarS…

【Linux】:文件系统

文件系统 一.认识硬件-磁盘1.磁盘的物理构成2.磁盘的存储构成3.逻辑结构 二.文件系统 文件内容属性&#xff0c;前面我们所说的文件操作都是针对以打开的文件&#xff0c;那么未打开的文件呢&#xff1f;当然是在磁盘上储存着&#xff0c;接下来谈谈它是如何储存的。 一.认识硬…

学完能拿20k的接口自动化测试框架教程 涵盖框架源码+视频教程以及搭建流程

自动化测试 自动化测试&#xff0c;这几年行业内的热词&#xff0c;也是测试人员进阶的必备技能&#xff0c;更是软件测试未来发展的趋势。 特别是在敏捷模式下&#xff0c;产品迭代速度快&#xff0c;市场不断调整&#xff0c;客户需求不断变化&#xff0c;单纯的手工测试越…

【已验证】微信小程序php连接SQL Server数据库代码

微信实现通过PHP连接到数据库&#xff0c;分为三步&#xff1a;首先写index.php的接口代码&#xff0c;然后在微信小程序的js页面上调用php接口方法连接到数据库&#xff08;通过url地址的方式&#xff09;&#xff0c;最后在小程序的wxml页面将控制台的数据显示在页面上。 微信…

口袋参谋:如何挑选淘宝热词?这一招很重要!

​在电商行业待了快十年之久了&#xff0c;据我的了解99.99%的中小卖家&#xff0c;在开店初期根本就不知道如何选词&#xff01; 最后选出来的关键词&#xff0c;无非就一种结果&#xff0c;那就是根本搜不到宝贝&#xff0c;或者就干脆没人搜&#xff0c;根本没流量&#xf…

数据库数据迁移常见方式

数据库数据迁移常见方式 数据库数据迁移常见方式1、通过sql2、通过数据迁移工具3、云服务进行数据迁移什么是DRS服务如何使用DRS服务DRS云服务可以干什么 数据库数据迁移常见方式 1、通过sql 批量导入sql insert into tableName select * 2、通过数据迁移工具 在数据库里面…

概念解析 | 数字通信中的调制技术

注1:本文系“概念解析”系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:调制。 数字通信中的调制技术 一、背景介绍 在数字通信系统中,信息源产生的信息信号通常是低频基带信号,而用于传输的载波通常是高频正弦信号。为了将信息信号有效地加载到载波…

Spring RabbitMQ那些事(1-交换机配置消息发送订阅实操)

这里写目录标题 一、序言二、配置文件application.yml三、RabbitMQ交换机和队列配置1、定义4个队列2、定义Fanout交换机和队列绑定关系2、定义Direct交换机和队列绑定关系3、定义Topic交换机和队列绑定关系4、定义Header交换机和队列绑定关系 四、RabbitMQ消费者配置五、Rabbit…

npm ERR! code ERESOLVE,npm ERR! ERESOLVE unable to resolve dependency tree

前言 当你第一次下包&#xff0c;或者删除node_module之后&#xff0c;突然npm i报错&#xff0c;这是因为npm版本导致的 可能是某些包版本跟npm 不兼容导致的&#xff08;peerDependencies&#xff09; npm ERR! code ERESOLVE,npm ERR! ERESOLVE unable to resolve depend…

mysql之高阶语句

1、使用select语句&#xff0c;用order by对表进行排序【尽量用数字列进行排序】 select id,name,score from info order by score desc; ASC升序排列&#xff08;默认&#xff09; DESC降序排列&#xff08;需要添加&#xff09; &#xff08;1&#xff09;order by结合whe…

怎么写好宣传稿件?纯干货

企业宣传稿件撰写并不难&#xff0c;有框架、有模板&#xff0c;再有知识储备就行&#xff0c;今天伯乐网络传媒和大家分享新闻稿写作套路&#xff0c;超实用的新闻稿套路➕模板&#xff0c;教你从0到1学会企业宣传稿写作&#xff01;纯干货&#xff0c;建议收藏起来慢慢看&…

uniapp原生插件之安卓友盟消息推送原生插件

插件介绍 安卓友盟消息推送原生插件&#xff0c;支持自定义响铃&#xff0c;震动&#xff0c;免打扰时间段&#xff0c;厂商离线推送等 插件地址 安卓友盟消息推送原生插件 - DCloud 插件市场 详细使用文档 uniapp 安卓友盟消息推送原生插件使用文档 超级福利 uniapp 插…

深度学习_10_softmax_实战

由于网上代码的画图功能是基于jupyter记事本&#xff0c;而我用的是pycham,这导致画图代码不兼容pycharm,所以删去部分代码&#xff0c;以便能更好的在pycharm上运行 完整代码&#xff1a; import torch from d2l import torch as d2l"创建训练集&创建检测集合"…

STM32 TIM定时器,配置,详解(1)

计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR)。 PSC预分频器&#xff0c;顾名思义&#xff0c;先预备一下分频&#xff0c;有时候频率过高&#xff0c;后面的定时器承受不住&#xff0c;就先用PSC先分频一下。如何分频的&#xff1f;将每接受到…

PTE作文练习(一)

目录 65分备考建议 WE模版 范文 Supporting ideas: SWT 65分备考建议 RA重在多听标准的正确的示范&#xff0c;RS重在抓大放小&#xff0c;WFD重在整理错题&#xff0c;以及反反复复的车轮战&#xff0c;FIBRW重在“以对代记” 就是直接看答案&#xff0c;节约时间&#…

Python编程的四个关键点——都知道吗?快来查漏补缺!

文章目录 前言一、Python 中的类型提示二、Python 虚拟环境和包管理三、新的 Python 语法四、Python 测试关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战案例③Python小…

响应式新闻博客资讯网站模板源码带后台

模板信息&#xff1a; 模板编号&#xff1a;29779 模板编码&#xff1a;UTF8 模板分类&#xff1a;博客、文章、资讯、其他 适合行业&#xff1a;博客类企业 模板介绍&#xff1a; 本模板自带eyoucms内核&#xff0c;无需再下载eyou系统&#xff0c;原创设计、手工书写DIVCSS&a…