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

news2024/11/18 20:32:30

目录

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};
    //回调函数的参数为bufferevent
    event_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 );
        //设置读超时时间  10s
        struct 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.coloradmin.cn/o/1614126.html

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

相关文章

LLM学习笔记-2

在未标记数据上进行预训练 本章概要 在上节的笔记中,因为训练出的效果,并不是特别理想,在本节中,会用数据进行训练,使得模型更加的好; 计算文本生成损失 inputs torch.tensor([[16833, 3626, 6100],…

知攻善防应急靶场-Windows(Web1-2-3)

知攻善防应急靶场-Web1 1.要求 2.过程 直接扫网站根目录 发现后门 <?php error_reporting(0); session_start();$key"e45e329feb5d925b"; //该密钥为连接密码32位md5值的前16位&#xff0c;默认连接密码rebeyond$_SESSION[k]$key;session_write_close();$postf…

集成学习-Bagging与随机森林回归

reg_fRFR() reg_tDTR()#实例化决策树 cvKFold(n_splits5,shuffleTrue,random_state1412)#实例化验证方式 result_tcross_validate(reg_t#要进行交叉验证的评估器,X,y,cvcv,scoringneg_mean_squared_error#评估指标,return_train_scoreTrue#是否返回训练分数&#xff0c;后面这几…

vue 脚手架创建

脚手架创建 介绍 脚手架是什么呢&#xff0c;就是vue自动创建脚手架的项目模板&#xff0c;用于搭建项目的整体骨架&#xff0c;就比如后端开发时&#xff0c;咱们可以创建一个空项目&#xff0c;一步步创建为mvc项目&#xff0c;但是vs封装了mvc的框架&#xff0c;我们可以直…

房地产行业与软件行业的可持续发展模式对比

一、什么是可持续发展&#xff1f; 可持续发展是一种注重长远发展的经济增长模式&#xff0c;旨在实现经济、社会和环境的协调发展&#xff0c;而不损害未来世代的需求和权益。其核心思想是在满足当前人类需求的同时&#xff0c;不危及地球生态环境的承载能力&#xff0c;保持资…

03-JAVA设计模式-命令模式

命令模式 什么是命令模式 命令模式&#xff08;Command Pattern&#xff09;是一种行为设计模式&#xff0c;它将请求封装为对象&#xff0c;从而使你可用不同的请求把客户端与请求的处理者解耦,也称动作模式或事物模式。 在命令模式中&#xff0c;命令对象封装了接收者对象…

【每日刷题】Day20

【每日刷题】Day20 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 面试题 17.04. 消失的数字 - 力扣&#xff08;LeetCode&#xff09; 2. 189. 轮转数组 - 力扣&#…

CCF PTA 2023年5月C++富有的大壮

【问题描述】 给在一个神秘的国度&#xff0c;有一种多拿多得的疯狂游戏&#xff0c;某日大壮去参赛&#xff0c;在规定区域内里面有 N(N≤100) 堆金币&#xff0c;第i堆金币的总重量和总价值分别是mi,vi(1≤ mi,vi≤100)。大壮有一个承重量为T(T≤1000) 的背包&#xff0c;但…

kali没有数字签名

一开始以为是国外源访问缓慢问题&#xff0c;更新国内源后依旧报错 解决方案&#xff1a; 你需要下载apt源对应的签名文件&#xff0c;并使用apt-key命令将其添加到系统中。例如&#xff0c;对于Kali的官方源&#xff0c;你可以使用以下命令下载并安装签名文件&#xff1a; …

从迷宫问题理解dfs

文章目录 迷宫问题打印路径1思路定义一个结构体要保存所走的路径&#xff0c;就需要使用到栈遍历所有的可能性核心代码 部分函数递归图源代码 迷宫问题返回最短路径这里的思想同上面类似。源代码 迷宫问题打印路径1 定义一个二维数组 N*M &#xff0c;如 5 5 数组下所示&…

我与C++的爱恋:隐式类型转换

​ ​ &#x1f525;个人主页&#xff1a;guoguoqiang. &#x1f525;专栏&#xff1a;我与C的爱恋 朋友们大家好&#xff0c;本篇内容我们来介绍初始化列表&#xff0c;隐式类型转换以及explicit的内容 一、初始化列表 1.1 构造函数体赋值 在创建对象时&#xff0c;编译器…

WP-AutoPostPro 汉化版: WordPress自动采集发布插件

WP-AutoPostPro 是目前最好用的WordPress自动采集发布插件&#xff0c;最大的特点是可以采集来自于任何网站的内容并自动发布到你的WordPress站点。真正做到可以采集任何网站的内容并自动发布&#xff0c;采集过程完全自动进行无需人工干预&#xff0c;并提供内容过滤、HTML标签…

C++从入门到精通——模板

模板 前言一、泛型编程二、函数模板函数模板的概念函数模板格式示例 函数模板的原理函数模板的实例化隐式实例化显式实例化示例 auto做模板函数的返回值模板参数的匹配原则总结 三、类模板类模板的定义格式类模板的实例化 前言 C模板是C语言中的一种泛型编程技术&#xff0c;可…

用ESP32的ADC引脚,结合分压电路测量电压

该代码基于ESP32&#xff08;Arduino库&#xff09;实现ADC&#xff08;模拟数字转换器&#xff09;数据采集。它配置ADC参数、获取校准特性&#xff0c;循环采样并计算平均值&#xff0c;将ADC读数转换为电压&#xff0c;考虑分压电阻影响&#xff0c;计算实际电压值&#xff…

SpringBoot 根据不同环境切换不同文件路径

最简单的办法就是使用多个 application.yml 配置文件 。一个叫 application-test.yml 测试用&#xff1b;另一个是正式使用的 application-prod.yml 。win环境下大部分是开发测试时候使用的&#xff0c;服务正式上线需要部署在Linux服务器上又换成了Linux。但开发初期或者项目…

SEGGER Embedded Studio IDE移植FreeRTOS

SEGGER Embedded Studio IDE移植FreeRTOS 一、简介二、技术路线2.1 获取FreeRTOS源码2.2 将必要的文件复制到工程中2.2.1 移植C文件2.2.2 移植portable文件2.2.3 移植头文件 2.3 创建FreeRTOSConfig.h并进行配置2.3.1 处理中断优先级2.3.2 configASSERT( x )的处理2.3.3 关于系…

Unity3D 爆火的休闲益智游戏工程源码/3D资源 大合集

Unity3D休闲益智游戏工程源码大合集 一、关卡类游戏工程源码二、跑酷类游戏工程源码三、消除合成类游戏工程源码四、棋牌类游戏工程源码五、RPG(角色扮演)类游戏工程源码六、FPS&#xff08;射击&#xff09;类游戏工程源码十、Unity3D工艺仿真六、Unity游戏资源1、Unity3D 吃鸡…

金融时报:波场亮相哈佛大学并举办TRON Builder Tour活动

近日,波场TRON作为顶级白金赞助商出席哈佛区块链会议并成功举办TRON Builder Tour哈佛站活动,引发海外媒体热议。美联社、金融时报、Cointelegraph等国际主流媒体及加密知名媒体均对此给予了高度评价,认为本次大会对TRON Builder Tour活动具有里程碑意义,彰显了波场TRON致力于促…

护眼台灯什么品牌好?台灯的十大品牌推荐

长时间的使用眼睛&#xff0c;出现疲劳感就会对眼睛造成伤害&#xff0c;最常见的场景就是青少年儿童学习看书&#xff0c;成年人晚上工作时。相信不少人就是这样度过的&#xff0c;因此数据表明目前中国近视患者超过6亿人。所以想要拥有一个良好的视力健康一款光源合适的台灯是…

【数据结构】栈和队列(链表模拟队列)

学习本章节必须具备 单链表的前置知识&#xff0c; 建议提前学习&#xff1a;点击链接学习&#xff1a;单链表各种功能函数 细节 详解 本章节是学习用 单链表模拟队列 1. 单链表实现队列 思路如下 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数…