开源事件通知库libevent及网络连接管理模块bufferevent详解

目录

1、libevent介绍

1.1、什么是libevent?

1.2、libevent特点

1.3、网络连接管理模块bufferevent

2、bufferevent有什么用?

3、bufferevent的整体设计与实现细节

3.1、整体概况

3.2、evbuffer与bufferevent

3.3、defer callback

4、bufferevent的使用方法

4.1、创建和销毁bufferevent

4.2、设置bufferevent事件回调函数

4.3、启用或禁用bufferevent

4.4、读写数据

4.5、设置bufferevent选项

5、使用bufferevent时的细节问题

5.1、tcp连接断开处理

5.2、心跳处理

5.2.1、增加定时器事件

5.2.2、利用bufferevent的超时机制

5.3、高低水位的使用


C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_2276111.html

1、libevent介绍

1.1、什么是libevent?

       libevent是一个用C语言实现的、基于事件驱动(event-driven)的轻量级高性能开源网络库,适用于Windows、Linux、bsd等多种平台,内部使用select、epoll、kqueue等系统调用管理事件机制。著名的用于apache的php缓存库memcached也是基于libevent实现的。

       如果你将要开发的应用程序需要支持以上所列出的平台中的两个以上,那么建议你采用这个库,即使你的应用程序只需要支持一个平台,选择libevent也是有好处的,因为它可以根据编译/运行环境切换底层的事件驱动机制,这既能充分发挥系统的性能,又增加了软件的可移植性。

       它封装并且隔离了事件驱动的底层机制,除了一般的文件描述符读写操作外,它还提供有读写超时、定时器和信号回调,另外,它还允许为事件设定不同的优先级,当前版本的libevent还提供dns和http协议的异步封装,这一切都让这个库尤其适合于事件驱动应用程序的开发。

1.2、libevent特点

       libevent有几个显著的特点:

  • 事件驱动(event-driven),高性能;
  • 轻量级,专注于网络,不如 NGINX 那么臃肿庞大;
  • 源代码相当精炼、易读;
  • 跨平台,支持 Windows、Linux、*BSD和Mac OS,虽说Windows支持不怎么好;
  • 支持多种I/O多路复用技术,select、epoll、poll、dev/poll、select、kqueue、evports等;
  • 支持I/O,定时器和信号等事件;
  • 采用Reactor设计模式;
  • 支持HTTP(S),DNS解析。

       libevent是用于编写高速可移植非阻塞IO应用的库,其设计目标是:

  • 可移植性:使用libevent编写的程序应该可以在libevent支持的所有平台上工作。即使没有好的方式进行非阻塞IO,libevent也支持一般的方式,让程序可以在受限的环境中运行。
  • 高性能:libevent尝试使用每个平台上最高速的非阻塞IO实现,并且不引入太多的额外开销。
  • 便捷:无论何时,最自然的使用libevent编写程序的方式应该是稳定的、可移植的。
  • 可扩展性:libevent被设计为程序即使需要上万个活动套接字的时候也可以良好工作。

1.3、网络连接管理模块bufferevent

       一般通过libevent进行网络编程,都是将一个socket的fd与一个event进行绑定,并自行维护一个buffer用于存储从socket上接收的数据,同时可能也用于待发送数据的缓存。然后通过可读可写事件从socket上收取数据写入缓存并进行相应处理,或者将缓存中的数据通过socket发送。

       libevent为这种带缓存的IO模式提供了一种通用的机制,那就是bufferevent。bufferevent主要用于管理基于流的网络连接,提供了缓冲、超时、流控等功能。一个bufferevent包含了一个底层传输的fd(通常为socket),一个输入buffer和一个输出buffer,并且bufferevent已经帮我们完成了从socket上接收数据写入输入buffer,同时从输出buffer中取出数据通过socket发送,当输入输出缓存中的数据达到一定量时调用我们设置的回调函数。这样使得我们可以更加关注数据的处理。

2、bufferevent有什么用?

       bufferevent的主要作用有:

  • 读写缓冲区管理:bufferevent为我们提供了缓冲区管理的功能,可以帮助我们处理读写缓冲区的分配、管理以及数据传输的同步与异步操作。
  • 数据处理:bufferevent可以将底层的数据读写操作转化为事件的回调函数,从而使得数据的读写操作可以被更加灵活的处理。
  • 操作简单:bufferevent的使用非常简单,只需要注册事件回调函数,就可以开始读写操作,无需进行额外的操作。

总的来说 ,有了bufferevent用户就可以不用处理底层的I/O操作,直接在bufferevent中读或者写数据就行。


         在这里,给大家重点推荐一下我的几个热门畅销专栏:

专栏1:(该专栏订阅量已达到420多个,有很强的实战参考价值,广受好评!专栏文章持续更新中,预计更新到200篇以上!)

C++软件调试与异常排查从入门到精通系列文章汇总icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931

本专栏根据近几年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的实战问题分析实例,带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!

专栏中的文章均是通过项目实战总结出来的(通过项目实战积累了大量的异常排查素材和案例),有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!

专栏2: 

C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html

以多年的开发实战为基础,总结并讲解一些的C/C++基础与进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域的多个方面的内容,同时给出C/C++及网络方面的常见笔试面试题,并详细讲述Visual Studio常用调试手段与技巧!

专栏3: 

开源组件及数据库技术icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_12458859.html

以多年的开发实战为基础,分享一些开源组件及数据库技术!


3、bufferevent的整体设计与实现细节

3.1、整体概况

       bufferevent的结构体定义如下:

从bufferevent结构体的构成可以看出,bufferevent中包含了读,写两个事件,这两个事件的回调函数分别为bufferevent_readcb和bufferevent_writecb。

       bufferevent同时还包含了输入输出两个缓存区,以及读、写、事件回调函数的指针,高低水位的设置,事件驱动的句柄等。当触发可读可写事件后,回调bufferevent_readcb和bufferevent_writecb,在这里完成从fd上的数据收发,然后根据收发结果及高低水位的设置等来进行不同回调处理。

3.2、evbuffer与bufferevent

       bufferevent采用evbuffer作为输入输出缓存,evbuffer的实现如下所示:

evbuffer像是一个字节队列,在队列的末尾写入数据,在队列的头取数据。evbuffer具体实现则是一个链表,链表中的每个节点都是一块连续的内存块。往evbuffer写数据时(evbuffer_add等函数),evbuffer内部动态创建链表节点,并紧凑的写入数据(一个节点写满,再写另外一个节点),从evbuffer中删除数据时(evbuffer_remove/evbuffer_drain),从链表头部节点开始读取,当一个节点的数据被全部读取后删除该节点,如果未读取完,则用标示记录数据已读取(删除)的部分。对于这种头部有数据被标识为读取(删除)的节点,再次写入数据时,可能会进行调整,即将数据部分整体往前拷贝移动,然后再继续写入数据。

3.3、defer callback

       在创建bufferevent时,可以设置不同的选项,其中一个是BEV_OPT_DEFER_CALLBACKS,这意味着延迟进行回调。

       所谓延迟回调,是将该事件延迟等到本次事件循环中,所有active事件都处理完成后再进行该事件的处理。在event_base中,有一个active事件队列,一个defer事件队列。事件循环时,遍历active事件队列并进行相应的处理,当发现某个事件时需要延迟处理时,将该事件放到defer事件队列中,继续后续active事件的处理,等active事件队列中的事件都处理完成后,再处理defer队列中的事件。

       对于bufferevent来说,当fd上有数据可读时,其实是先进行了一次回调(bufferevent_readcb),这个回调函数中判断是否需要延迟处理,如果不需要延迟则直接调用我们设置的回调函数,如果需要延迟,则等libevent处理完其他的active事件后再次调用bufferevent的回调函数,再由该回调函数调用我们设置的回调函数。

4、bufferevent的使用方法

       使用bufferevent主要有以下几个步骤。

4.1、创建和销毁bufferevent

       使用libevent创建bufferevent非常简单。首先,创建一个event_base对象和一个套接字描述符,然后使用bev_socket_new或bev_bufferevent_new函数创建一个新的bufferevent。在不再需要时,可以使用bev_free函数释放bufferevent。 

struct event_base *base = event_base_new();
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct bufferevent *bev = bufferevent_socket_new(base, sockfd, BEV_OPT_CLOSE_ON_FREE);//释放
bufferevent_free(bev);
event_base_free(base);

4.2、设置bufferevent事件回调函数

       bufferevent需要处理不同类型的事件,例如读取数据、写入数据、错误、超时等。使用bufferevent_setcb函数设置回调函数来处理这些事件。在回调函数中,可以读取或写入数据,或者关闭bufferevent。

void my_read_cb(struct bufferevent *bev, void *ctx)
{struct evbuffer *input = bufferevent_get_input(bev);// 从输入读取数据
}void my_write_cb(struct bufferevent *bev, void *ctx)
{struct evbuffer *output = bufferevent_get_output(bev);// 将数据写入到输出
}void my_event_cb(struct bufferevent *bev, short events, void *ctx)
{if (events & BEV_EVENT_ERROR){// 处理错误事件bufferevent_free(bev);}
}bufferevent_setcb(bev, my_read_cb, my_write_cb, my_event_cb, NULL);

4.3、启用或禁用bufferevent

       可以使用bufferevent_enable和bufferevent_disable函数启用或禁用bufferevent的读、写或事件操作。如果需要暂停读或写数据,则可以使用BEV_SUSPEND读或写操作,然后使用BEV_RESUME恢复它们。

// 禁用读取操作
bufferevent_disable(bev, EV_READ);// 启用写入操作
bufferevent_enable(bev, EV_WRITE);

4.4、读写数据

       使用bufferevent_read和bufferevent_write函数读取和写入数据。bufferevent可以在内部缓冲区中缓存数据,也可以直接读取或写入套接字。

// 读数据
bufferevent_read(bev, buffer, buffer_size);// 写数据
bufferevent_write(bev, buffer, buffer_size);

4.5、设置bufferevent选项

       可以使用bufferevent_setwatermark和bufferevent_settimeout函数设置bufferevent的选项。其中,bufferevent_setwatermark设置读取和写入缓冲区的低水位和高水位。bufferevent_settimeout设置超时时间。

// 设置watermark
bufferevent_setwatermark(bev, EV_READ, lowmark, highmark);
bufferevent_setwatermark(bev, EV_WRITE, lowmark, highmark);// 设置 timeout
struct timeval tv = {5, 0}; // 5秒
bufferevent_set_timeouts(bev, &tv, NULL);

这里详细说一下bufferevent_setwatermark函数。在该函数中,第二个参数是 events,它表示在何时触发回调函数。具体来说,EV_READ 和 EV_WRITE 是事件标志,它们分别代表读事件和写事件。

EV_READ 用于在读操作时触发回调函数。
EV_WRITE 用于在写操作时触发回调函数。

       在这里,我们可以理解为:lowmark 和 highmark 是在读取数据时缓冲区的低水位和高水位。
EV_READ 表示在缓冲区的读取操作中触发回调函数。因此,bufferevent_setwatermark(bev, EV_READ, lowmark, highmark) 的作用是:当读取缓冲区的数据量达到 lowmark 或 highmark 时,触发读操作的回调函数。

关于低水位和高水位:当写入缓冲区的数据量超过了高水位时,bufferevent 将停止发送数据,等待缓冲区中的数据被消费。当写入缓冲区中的数据量低于低水位时,bufferevent 又会恢复写入数据,向下游发送数据,以保证高效的数据传输。

5、使用bufferevent时的细节问题

5.1、tcp连接断开处理

       对于客户端来说,如果仅有bufferevent这么一个事件,那么当tcp连接断开时,调用回调函数后会退出事件循环(event_base_loop)。因为bufferevent感知tcp连接断开后会删除相关的事件,这个时候事件驱动中没有任何事件,于是退出循环。

       在官网的教程中看到可以对event_base设置选项EVLOOP_NO_EXIT_ON_EMPTY保证没有等待事件时也不会退出事件循环,但是在最新稳定版本中(libevent-2.0.21-stable)没有该选项设置,在2.1.x-alpha中才有该选项。我们可以采用增加定时器事件的方式来处理断链后不退出事件循环,甚至进一步实现断链重连的功能, 这个定时器事件可以是断链后在回调函数中动态增加,也可以一开始就增加一个持久的定时器事件,检测连接状态并触发向服务器重连。例如:

int g_nState;//定时器事件回调函数
void handle_timeout(int nSock, short sWhat, void * pArg)
{if( 0 == g_nState ){struct bufferevent * pBufferEvent = (struct bufferevent *)pArg;struct sockaddr_in tSockAddr;memset(&tSockAddr, 0, sizeof(tSockAddr));tSockAddr.sin_family = AF_INET;tSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");tSockAddr.sin_port = htons(50000);bufferevent_socket_connect(pBufferEvent, (struct sockaddr*)&tSockAddr, sizeof(tSockAddr));}
}void event_callback(struct bufferevent * pBufEv, short sEvent, void * pArg)
{//成功连接 状态变更if( BEV_EVENT_CONNECTED == sEvent ){bufferevent_enable( pBufEv, EV_READ );g_nState = 1;}//出现错误if( 0 != (sEvent & (BEV_EVENT_ERROR)) ){//关闭fd并更改状态int fd = bufferevent_getfd(pBufEv);if( fd > 0 ){evutil_closesocket(fd);}bufferevent_setfd(pBufEv, -1);g_nState = 0;}
}int main( void )
{...//增加PERSIST的定时器事件struct event eTimeout;struct timeval tTimeout = {10, 0};//回调函数的参数为buffereventevent_assign(&eTimeout, pEventBase, -1, EV_PERSIST, handle_timeout, pBufferEvent);evtimer_add(&eTimeout, &tTimeout);...
}

       这里需要注意的是,重连之前最好先关闭bufferevent中fd,或者直接对bufferevent进行释放并重新创建一个新的bufferevent。如果是直接释放bufferevent再次新建,那么在创建bufferevent时记得设置BEV_OPT_CLOSE_ON_FREE参数,这样在释放bufferevent时会对fd进行关闭,从而不会出现fd泄漏。

不设置该参数,通过bufferevent_setfd传入fd,释放bufferevent后自动关闭fd也是一种处理方式。

5.2、心跳处理

       通常,客户端与服务端之间都有心跳检测,以检测tcp链路是否正常,那么通过bufferevent开发的客户端或者服务端完成心跳检测功能可以有这么几种实现方式。

5.2.1、增加定时器事件

       前面提到了可以增加持久的定时器事件来检测状态并触发断链重连,当然我们也可以利用这个定时器事件来完成定时发送心跳包的功能。个人觉得这种方式不太好的一点是:需要有一种机制让定时器事件的回调处理函数获取bufferevent的句柄,例如作为定时器事件回调函数的参数,这样才能将心跳包的数据写入该bufferevent并通过fd发送,但两种事件搅合在一起感觉会有些混乱。

5.2.2、利用bufferevent的超时机制

       bufferevent可以为读写设置超时时间,我们可以设置读超时来完成定时发送心跳包的功能。在事件回调处理函数中处理BEV_EVENT_TIMEOUT|BEV_EVENT_READING事件,然后将心跳包写入输出缓存。这种方式有一点需要注意:bufferevent触发超时事件后会将对应的可读/可写事件删除,我们在处理完超时事件后需要重新注册一下对应的事件(bufferevent_enable)。例如:

{if( BEV_EVENT_CONNECTED == sEvent ){bufferevent_enable( pBufEv, EV_READ );//设置读超时时间  10sstruct timeval tTimeout = {10, 0};bufferevent_set_timeouts( pBufEv, &tTimeout, NULL);}if(0 != (sEvent & (BEV_EVENT_TIMEOUT|BEV_EVENT_READING)) ){//发送心跳包...//重新注册可读事件bufferevent_enable(pBufEv, EV_READ);}...return;
}

5.3、高低水位的使用

       默认情况下,bufferevent从fd上接收到任何数据并写入输入缓存区时,就会回调交给我们进行处理。而我们的客户端和服务端通信时都会遵循一定的格式(数据包协议),比如固定长度的包头,然后从包头中获取包体的数据长度,然后等包体的数据都接收完成后再进行实际处理。在这种情况下,我们可以设置读的低水位减少回调的次数,bufferevent会等输入缓存区中的数据长度超过最低水位后,才回调我们的函数进行业务处理。

       同样,设置写的低水位,表示只有输出缓存区的数据低于最低水位后,调用写回调函数进行相应处理。高水位的设置可用于进行传输速率的控制,例如设置读的高水位时,当输入缓存区中的数据长度超过高水位时,会处于“挂起”状态,即不再从fd上读取数据,直到输入缓存区中的数据低于高水位。

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

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

相关文章

python爬虫之爬取微博评论(4)

一、获取单页评论 随机选取一个微博,例如下面这个 【#出操死亡女生家属... - 冷暖视频的微博 - 微博 (weibo.com) 1、fnf12,然后点击网络,搜索评论内容,然后预览,就可以查看到网页内容里面还有评论内容 2、编写代码…

【C#】rdlc报表答应报错:未能加载文件或程序集“Microsoft.SqlServer.Types

文章目录 一、报错信息二、解决方式 一、报错信息 Microsoft.Reporting.WinForms.LocalProcessingException: An error occurred during local report processing. —> Microsoft.Reporting.DefinitionInvalidException: The definition of the report ‘’ is invalid. —&…

Vmware 虚拟机自定义IP地址 - UbuntuServer2204

Vmware 虚拟机自定义IP地址 - UbuntuServer2204 设置网段 选择喜欢的网段, 例如: 166 自定义 IP地址 打开虚拟机, 输入命令查看网卡名 ip addr查看网卡配置文件 ls -al /etc/netplan/编辑网卡配置文件 sudo vim /etc/netplan/00-installe…

C++ //练习 12.20 编写程序,逐行读入一个输入文件,将内容存入一个StrBlob中,用一个StrBlobPtr打印出StrBlob中的每个元素。

C Primer(第5版) 练习 12.20 练习 12.20 编写程序,逐行读入一个输入文件,将内容存入一个StrBlob中,用一个StrBlobPtr打印出StrBlob中的每个元素。 环境:Linux Ubuntu(云服务器) 工…

《从零开始的Java世界》07常用类与基础API

《从零开始的Java世界》系列主要讲解Javase部分,从最简单的程序设计到面向对象编程,再到异常处理、常用API的使用,最后到注解、反射,涵盖Java基础所需的所有知识点。学习者应该从学会如何使用,到知道其实现原理全方位式…

电脑显示有音量但是没有声音?5个方法足够解决问题!

“为什么我的电脑明明打开了声音,但是我看视频时却没有任何声音呢?应该怎么解决呀?” 在日常使用电脑的过程中,电脑声音的正常播放是很重要的,我们无论是听音乐还是看电影,都是离不开电脑声音的。遇到电脑显…

iOS 在OC旧项目中使用Swift进行混编

iOS 在OC旧项目中使用Swift进行混编 1、创建桥接文件 ​ 第一次在Swift创建OC文件,或者第一次OC创建Swift时,xcode会提示桥接,Creat Bridging Header即可,这个文件用于Swift调用OC文件,与OC调用Swift无关。 2、在TARGETS中设置D…

Linux文件系统 软硬链接

文章目录 文件背景知识磁盘文件磁盘物理结构磁盘存储结构对磁盘的存储进行逻辑抽象Boot BlockSuper blockData blocksInode TableBlcokBitmapinode BitmapGroup Descriptor Table 文件名和inode编号创建文件删除文件查找文件 软硬链接软链接硬链接查看文件信息stat命令取消软硬…

玩转智能:深度强化学习在游戏AI中的应用

玩转智能:深度强化学习在游戏AI中的应用 1 引言 1.1 简述深度强化学习(DRL)在游戏AI中的革命性影响。 当我们回顾人工智能的发展历程,可以明显地看到深度强化学习(Deep Reinforcement Learning, DRL)的出现,在游戏AI…

【埋点探针】微信小程序SDK安装

一、下载微信小程序SDK埋点代码 选择Wechat,复制sdk代码 在项目根目录下,创建sdk文件,webfunny.event.js 二、在app.js文件中,引入埋点SDK代码 首先引入sdk代码 require("./webfunny.event.js")引入兼容代码&#x…

DHT11实验

文章目录 11.11.2 234 DS18B20 只能检测温度 右边这几个 都能 1 1.1 数字信号输出 指 0/1使用单总线通信 1个IO口就能获取温湿度 T/H要有 模数转化(内部还有个8位单片机)电容感湿元件 白色的 还有个ic NTC测温 可能在ic内部 使用单片机内部测温 精确度不…

STM32存储左右互搏 SDIO总线FATS文件读写SD/MicroSD/TF卡

STM32存储左右互搏 SDIO总线FATS文件读写SD/MicroSD/TF卡 SD/MicroSD/TF卡是基于FLASH的一种常见非易失存储单元,由接口协议电路和FLASH构成。市面上由不同尺寸和不同容量的卡,手机领域用的TF卡实际就是MicroSD卡,尺寸比SD卡小,而…

基于STM32温控风扇冷却系统设计

摘要: 在当前社会,随着大家对生活质量的追求和环保意识的加强,温控风扇作为一种节能产品备受社会的关注。温控风扇广泛应用于工业生产、家用电器、医疗美容设备和公共场所,提高了人们的生活效率和生活上的便利。这篇文章是根据单…

【网络协议】 TCP与UDP协议区别及应用场景深度分析

1. TCP与UDP简介 1.1 TCP 1.1 定义 TCP(TransmissionControl Protocol)传输控制协议。 是一种可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应…

电子印章盖骑缝章

电子印章盖骑缝章是指在电子文档(如PDF文件)中,使用电子印章技术,为文档添加一个跨越多页、连续显示的电子印章图像,以模拟传统纸质文档上的骑缝章效果。以下是实现电子印章盖骑缝章的步骤: 一. 准备电子印…

docker的安装以及docker中nginx配置

机器 test3 192.168.23.103 1机器初始化配置 1.1关闭防火墙,清空防火墙规则 systemctl stop firewalld iptables -F setenforce 01.2部署时间同步 yum install ntp ntpdate -y1.3安装基础软件包 yum install -y wget net-tools nfs-utils lrzsz gcc gcc-c make…

【webrtc】m98 RoundRobinPacketQueue的优先级处理

m98 代码 PacedSender::EnqueuePackets 的调用者可能是多个地方,所以这个要加锁保护。RoundRobinPacketQueue 本身是没有锁的发现m98和新版本不同,参考:【webrtc】m114自己实现的PrioritizedPacketQueuepush和pop都是RtpPacketToSend 但是实际上,内部是封装为QueuedPacket 处…

基于Springboot的人职匹配推荐系统

基于SpringbootVue的人职匹配推荐系统的设计与实现 开发语言:Java数据库:MySQL技术:SpringbootMybatis工具:IDEA、Maven、Navicat 系统展示 用户登录 首页 企业信息 岗位信息 新闻资讯 后台管理 用户管理 企业信息管理 岗位信…

Docker之注册中心的使用与操作

一、Docker注册中心与仓库 Reastry 可译为注册中心或注册服务器,是存放仓库的地方,一个注册中心往往有很多仓库。Docker默认的注册中心是Docker Hub,其可以通过浏览器访问,也可以使用docker search命令访问。 仓库是集中存放镜像…

html2Canvas截图包含滚动条解决思路

概况描述 在项目中使用html2Canvas进行截图时发现无法截取滚动条部分,前端是使用vue2的版本,网上找了很多方式都没效果,冷静思考后,给出解决办法。 解决思路 当我们截取的div容器的宽和高与内部的子容器div的宽和高不一样时&am…