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

2023-11-08 17:28

本文主要是介绍一种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

这篇关于一种libuv实现websockets服务的解决方案的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

golang 对象池sync.Pool的实现

《golang对象池sync.Pool的实现》:本文主要介绍golang对象池sync.Pool的实现,用于缓存和复用临时对象,以减少内存分配和垃圾回收的压力,下面就来介绍一下,感兴趣的可以了解... 目录sync.Pool的用法原理sync.Pool 的使用示例sync.Pool 的使用场景注意sync.

IDEA实现回退提交的git代码(四种常见场景)

《IDEA实现回退提交的git代码(四种常见场景)》:本文主要介绍IDEA实现回退提交的git代码(四种常见场景),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1.已提交commit,还未push到远端(Undo Commit)2.已提交commit并push到

Kotlin Compose Button 实现长按监听并实现动画效果(完整代码)

《KotlinComposeButton实现长按监听并实现动画效果(完整代码)》想要实现长按按钮开始录音,松开发送的功能,因此为了实现这些功能就需要自己写一个Button来解决问题,下面小编给大... 目录Button 实现原理1. Surface 的作用(关键)2. InteractionSource3.

java对接第三方接口的三种实现方式

《java对接第三方接口的三种实现方式》:本文主要介绍java对接第三方接口的三种实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录HttpURLConnection调用方法CloseableHttpClient调用RestTemplate调用总结在日常工作

golang中slice扩容的具体实现

《golang中slice扩容的具体实现》Go语言中的切片扩容机制是Go运行时的一个关键部分,它确保切片在动态增加元素时能够高效地管理内存,本文主要介绍了golang中slice扩容的具体实现,感兴趣... 目录1. 切片扩容的触发append 函数的实现2. runtime.growslice 函数gro

golang实现动态路由的项目实践

《golang实现动态路由的项目实践》本文主要介绍了golang实现动态路由项目实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习... 目录一、动态路由1.结构体(数据库的定义)2.预加载preload3.添加关联的方法一、动态路由1

使用Python实现调用API获取图片存储到本地的方法

《使用Python实现调用API获取图片存储到本地的方法》开发一个自动化工具,用于从JSON数据源中提取图像ID,通过调用指定API获取未经压缩的原始图像文件,并确保下载结果与Postman等工具直接... 目录使用python实现调用API获取图片存储到本地1、项目概述2、核心功能3、环境准备4、代码实现

MySQL数据库实现批量表分区完整示例

《MySQL数据库实现批量表分区完整示例》通俗地讲表分区是将一大表,根据条件分割成若干个小表,:本文主要介绍MySQL数据库实现批量表分区的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考... 目录一、表分区条件二、常规表和分区表的区别三、表分区的创建四、将既有表转换分区表脚本五、批量转换表为分区

宝塔安装的MySQL无法连接的情况及解决方案

《宝塔安装的MySQL无法连接的情况及解决方案》宝塔面板是一款流行的服务器管理工具,其中集成的MySQL数据库有时会出现连接问题,本文详细介绍两种最常见的MySQL连接错误:“1130-Hostisn... 目录一、错误 1130:Host ‘xxx.xxx.xxx.xxx’ is not allowed

Spring Boot 整合 Redis 实现数据缓存案例详解

《SpringBoot整合Redis实现数据缓存案例详解》Springboot缓存,默认使用的是ConcurrentMap的方式来实现的,然而我们在项目中并不会这么使用,本文介绍SpringB... 目录1.添加 Maven 依赖2.配置Redis属性3.创建 redisCacheManager4.使用Sp