【Linux】【网络】Libevent 内核实现简略版
【Linux】【网络】Libevent 内核实现简略版
1 event_base结构–>相当于Reactor
在使用libevent之前,就必须先创建这个结构。

以epoll为例:
1.1evbase
void* evbase-->epollop结构体(以epoll为例)
libevent通过一个void* 型实例 evbase 来存储这些一切类型结构的指针。
evbase所指向的结构,是一个叫做epollop的结构,它包含了:
- epoll检测到有事件触发时,填写能够描述事件信息的epoll_event* 列表
- 表示epoll_event列表中,有多少个有效事件的nevents变量
- 表示epoll实例本身的epfd
- 一个用来处理时间的可以选择使用的timerfd
1.2evsel
eventop* evsel-->eventop结构体(支持io复用的统一接口)
evsel则代表了,所有IO复用器在使用过程中的几种相似的操作。
- name:eventop结构的名称
- init函数指针:用于创建evbase实例的函数,在event_base实例初始化阶段,就要调用这个函数进行创建,在epoll的使用情景中,这个函数会创建一个上文提到的epollop结构实例。
- add函数指针:我们创建的所有的event,最后都要塞到event_io_map类型变量–io之中(event_base结构中的一个成员),而将event塞入的同时,要将与之相关联的fd,添加到事件多路分发器中,进行事件监听,而这个操作,就是通过evsel->add函数来执行(比如将fd添加到epoll的监听列表中)。这样fd就能够借助IO多路分发器感知事件,并且在触发时根据它找回对应的,与之关联的事件,并且激活,执行它。
- del函数指针:我们的event,从io这个event_io_map类型的结构中删除时,也要将与event关联的fd,从IO多路复用器监听列表中删除,这个del函数就是干这件事情的。
- dispatch函数指针:这个函数在event_base_loop中调用,主要作用就是调用select、epoll_wait、poll等函数,在没有IO事件时,将线程投入睡眠,在有IO事件到达,或定时事件触发时,唤醒线程,并且将有IO事件的fd相关的event放入激活列表中。
- dealloc函数指针:在event_base_free函数里调用,也就是在销毁event_base实例时,要顺带将evbase实例释放,释放之前要做一些反注册操作,比如将epoll的事件列表,以及实例销毁等等。
- need_reinit:是否需要重新初始化的变量标记,一般在fork一个进程时,要使用,我们的使用案例中很少用到fork,这里不深入讨论。在epoll的使用范例中,它的值是1。
- feature:我前面说过,libevent整合了多种IO多路复用技术的使用,这些不同的IO多路复用器(事件多路分发器),能够支持的特性也是不同的,这些他们本身就支持的特性就被记录在feature字段中
- EV_FEATURE_ET:支持边缘触发机制
- EV_FEATURE_O1:当有IO事件触发时,epoll获取有效事件的效率接近O(1)(epoll_wait唤醒时,event列表中,小于nevent的事件均是有效事件),而select的效率是O(n),每个都要测试
- EV_FEATURE_EARLY_CLOSE:epoll支持RDHUB事件,即对端关闭连接时,能够被epoll_wait感知
2 event结构–>相当于事件处理器
每个fd都有与之关联的event_map_entry结构,而event_map_entry结构中,又有一个event双向链表,我们创建出来的event,最后就是会被塞入这个链表中,当然,删除一个event时,也会将其从这个链表中移除。

struct event {struct event_callback ev_evcallback;/* for managing timeouts */ union { TAILQ_ENTRY(event) ev_next_with_common_timeout; int min_heap_idx; } ev_timeout_pos; evutil_socket_t ev_fd;struct event_base *ev_base;union { /* used for io events */ struct { LIST_ENTRY (event) ev_io_next; struct timeval ev_timeout; } ev_io;/* used by signal events */ struct { LIST_ENTRY (event) ev_signal_next; short ev_ncalls; /* Allows deletes in callback */ short *ev_pncalls; } ev_signal; } ev_;short ev_events; short ev_res; /* result passed to event callback */ struct timeval ev_timeout;};
- ev_evcallback:其实就是event_callback类型的变量
- ev_timeout_pos:我们默认使用小根堆,因此使用min_heap_idx,它在event是定时事件的时候使用,min_heap_idx的值表示事件位于小根堆数组的哪个位置
- ev_fd:socket的fd
- ev_base:就是前面讨论过的event_base实例的指针
- ev_events:代表事件的类别和属性,类别包括EV_READ(读)、EV_WRITE(写)等
- 属性包括EV_PERSIT(表示是个持续事件,即激活执行后,事件不会从evmap_io_map中删除,如果没设置这个属性,则表示事件只能被激活一次,被激活执行后,就会从evmap_io_map中清除)
- 这里需要注意的是,ev_events设置为EV_PERSIST的时候,ev_evcallback的evcb_closre也会被同时设置为EV_CLOSURE_EVENT_PERSIST
- ev_res:当事件被激活时,事件要被塞入激活列表(activequeue)时,它会被设置,主要分以下几种情况:
- IO多路复用器(select、epoll_wait)被唤醒时,检测到的读(EV_READ)、写(EV_WRITE)和关闭(EV_CLOSE)事件,读写事件可以同时存在
- 定时事件触发时,将其设置为EV_TIMEOUT
- ev_timeout:当event事件是定时事件时,要被使用。它和ev_中的ev_timeout不同的是,它一般记录的是绝对时间,定时器的触发时机,以这个时间为准,并且可以在定时类型为持续触发和非持续触发两种情况下使用
现在来看一下,一个已经创建好的event,是如何添加到event_io_map中的
- 这个操作是通过一个叫做evmap_io_add的函数进行的,
- 它的主要逻辑是:通过hashsocket函数计算出event fd的hash值
- hth_table_idx = hashsocket(ev) % hth_table_length
- 获取首个event_map_entry实例,event_map_entry* head = hth_table[hth_table_idx]
- 遍历event_map_entry,找到fd与event fd相等的event_map_entry实例
- 将event插入event_map_entry的evmap_io实例的event列表中
3 事件循环
接下来,要讨论的则是我们的事件循环,执行这个流程的函数主要有两个,一个是event_base_dishatch,另一个则是event_base_loop函数
int event_base_loop(struct event_base* base, int flags);
它的第一个参数是传入event_base实例,第二个参数是填的flag标记,标记会影响到loop的行为,先来看一下不填写标记,也就是flag为0的伪代码:
function event_base_loop(base, no_flag) {local done = 0;while (!done) {// 从小根堆中,取出根部,作为dispatch的最大等待时间// 如果小根堆为空,则tv为NULL,如果tv为NULL,那么dispatch会一直等待local tv = timeout_next(base)// 没注册事件,也没有激活的事件,直接退出,这种情况下,libevent// 没有运转的需要if (!has_activate_callbacks(base) && has_events(base)) {done = 1;break;}// dispatch会根据实际情况,调用select、poll、或者是// epoll_wait,tv为NULL则表示这里会一直睡眠,直至// 有IO事件触发,tv不为NULL,则它成为最大的睡眠时间// IO事件触发后,相关的event_callback实例,会被塞入// 激活列表中local res = base->evsel->dispatch(base, tv);// 这个函数,不断从小根堆中,抽取超时时间小于当前时间的根节点,// 并满足条件的将定时事件的event_callback塞入激活事件列表中timeout_process(base);// 判断激活队列中是否有事件if (has_activate_callbacks(base)) {// 执行激活队列中,事件的callback函数event_process_activate(base);}}
}
上述的no_flag,其实质就是flags值为0的情况,实际上,libevent为我们提供了另一个函数,用来简化调用,这个函数的定义如下所示:
int event_dispatch(void){ return (event_loop(0));}
我们接下来来看一下,设置flags的情况,主要针对EVENT_LOOP_ONCE和EVENT_LOOP_NONBLOCK,我们来看一下,设置了EVENT_LOOP_ONCE一次性事件循环模式和EVENT_LOOP_NONBLOCK非阻塞模式时的伪代码:
function event_base_loop(base, flags = EVENT_LOOP_ONCE | EVENT_LOOP_NONBLOCK) {local done = 0;while (!done) {local tv = NULL// 如果flags设置了EVENT_LOOP_NONBLOCK的标记,那么dispatch将// 不会进行睡眠,而是立刻被唤醒if (flags & EVENT_LOOP_NONBLOCK)tv = 0;elsetv = timeout_next(base);// 没注册事件,也没有激活的事件,直接退出,这种情况下,libevent// 没有运转的需要if (!has_activate_callbacks(base) && has_events(base)) {done = 1;break;}// dispatch会根据实际情况,调用select、poll、或者是// epoll_wait,tv为NULL则表示这里会一直睡眠,直至// 有IO事件触发,tv不为NULL,则它成为最大的睡眠时间// 如果tv为0,那么dispatch函数将不会进行睡眠,而是立刻被唤醒// IO事件触发后,相关的event_callback实例,会被塞入// 激活列表中local res = base->evsel->dispatch(base, tv);// 这个函数,不断从小根堆中,抽取超时时间小于当前时间的根节点,// 并满足条件的将定时事件的event_callback塞入激活事件列表中timeout_process(base);if (has_activate_callbacks(base)) { // 判断激活队列中是否有事件local n = event_process_activate(base); // 执行激活队列中,事件的callback函数// 如果标记为EVENT_LOOP_ONCE,并且没有更多激活的回调,或者已处理的回调不为0if (flags & EVENT_LOOP_ONCE &&!has_activate_callbacks(base) && n != 0) {done = 1;}}else if (flags & EVENT_LOOP_NONBLOCK) {done = 1;}}
}
4 注册一个读写事件的大致的逻辑流程
我们前面已经了解到了event_base结构,event结构,事件循环流程之类的。我们现在来看一下,注册一个读写事件的大致的逻辑流程是怎样的:
// 读取数据的回调函数
void socket_read_cb(evutil_socket_t fd, short events, void* cbarg) {// 调用read函数,读取数据// 将读出的数据,塞入自定义的buffer列表中// 处理分包粘包,得到完整的请求包
}// 监听连接的回调函数
void listener_cb(evutil_socket_t listener_fd, short events, void* cbarg) {evutil_socket_t fd = accept(listener_fd, NULL, NULL);event_base* base = (event_base*)cbarg;// 创建一个新的事件来监听连接的读取事件event* read_ev = event_new(base, fd, EV_READ, socket_read_cb, base);// 将事件添加到事件队列中event_add(base, read_ev);
}int main() {struct event_base* base;struct sockaddr_in sin;evutil_socket_t listener_fd;// 创建事件基础对象base = event_base_new();// 创建监听套接字listener_fd = socket(AF_INET, SOCK_STREAM, 0);// 设置监听套接字地址memset(&sin, 0, sizeof(sin));// 绑定监听套接字if (bind(listener_fd, (struct sockaddr*)&sin, sizeof(sin)) < 0) {}// 将监听套接字设置为监听状态if (listen(listener_fd, 16) < 0) {}// 为监听套接字创建事件,监听可读事件(有新的连接到达)struct event* listener_ev = event_new(base, listener_fd, EV_READ | EV_PERSIST, listener_cb, base);// 将监听事件添加到事件队列event_add(listener_ev, NULL);// 启动事件循环event_base_dispatch(base);// 释放资源event_base_free(base);return 0;
}
上述伪代码,先是创建了一个监听的socket,并且在绑定了端口和设置为listen状态之后,就开始等待连接到达。
当新的连接到达时,event_base_dispatch内的base->evsel->dispatch函数被唤醒,并且将可读事件塞入激活列表中,最后再调用它们,此时listener_cb函数被触发。
在这个函数,显示accept了一个新的连接,然后为其创建了一个event,并设置了socket_read_cb作为它的读取事件,当该fd有可读事件时,这个函数会被调用,我们可以看到,伪代码中,我们需要自己主动去读取数据,然后自己管理读取buffer列表等等,处理起来非常麻烦。
为此,libevent给我们提供了一个新的结构,这个结构就叫做bufferevent,它为我们管理读写的buffer队列,帮我们处理读写事件,甚至在某种程度上,实现了网络库的proactor模式。同时它也为我们在多线程的情况下,提供了安全的读写操作功能,使得我们不用自己去写加锁解锁逻辑。
5 bufferevent
bufferevent是libevent库中非常核心的结构之一,它提供了高效、简化的网络I/O处理方式。它封装了事件的注册、回调的设置、数据的读写等操作,允许用户专注于业务逻辑的处理,而无需关心低级的I/O事件管理。以下是对bufferevent结构的详细总结:
5.1bufferevent结构体
struct bufferevent {struct event_base *ev_base; // event_base实例,用于事件调度const struct bufferevent_ops *be_ops; // 操作函数集,如enable, disable, destruct等struct event ev_read; // 读取事件的结构,注册和触发读操作struct event ev_write; // 写入事件的结构,注册和触发写操作struct evbuffer *input; // 输入缓存区,存储从网络读取的数据struct evbuffer *output; // 输出缓存区,存储待写入的数据struct event_watermark wm_read; // 读取水位标记,控制何时触发读回调struct event_watermark wm_write; // 写入水位标记,控制何时触发写回调bufferevent_data_cb readcb; // 用户定义的读回调函数bufferevent_data_cb writecb; // 用户定义的写回调函数bufferevent_event_cb errorcb; // 用户定义的错误回调函数void *cbarg; // 回调函数的上下文参数struct timeval timeout_read; // 读取事件的超时时间struct timeval timeout_write; // 写入事件的超时时间short enabled; // 当前启用的事件类型(EV_READ 或 EV_WRITE)
};
-
ev_base:
- 这是指向
event_base的指针,event_base是事件循环的核心,管理和调度所有的事件。
- 这是指向
-
be_ops:
- 这是指向
bufferevent_ops的指针,bufferevent_ops包含了bufferevent操作的函数,比如启用和禁用事件(enable,disable),以及实例销毁(destruct)等。
- 这是指向
-
ev_read和 ev_write:
- 这两个字段分别是
event类型,表示当可读事件或可写事件触发时,调用的回调函数。ev_read用于处理输入数据,ev_write用于处理输出数据。
- 这两个字段分别是
-
input和 output:
input是输入缓冲区,存储从网络读取的数据,使用evbuffer结构来实现数据的管理。output是输出缓冲区,存储待发送的数据,同样通过evbuffer实现。
-
wm_read 和 wm_write:
- 水位标记控制了何时触发读取或写入的回调。
wm_read控制何时触发readcb,wm_write控制何时触发writecb。它们有low和high两个水位,默认情况下设置为0,表示只要数据存在就触发回调。
- 水位标记控制了何时触发读取或写入的回调。
-
readcb 和 writecb:
- 这两个回调函数分别用于处理读取和写入事件。当数据准备好时,libevent会调用这两个回调函数来通知应用层,供其进行数据的处理或发送。
-
errorcb:
- 错误回调函数,在发生错误时会被调用。它可以用于处理错误,例如连接丢失、超时等,并且通常会在该回调中关闭连接。
-
cbarg:
- 用户自定义的参数,可以用于传递上下文信息,传递给回调函数。
-
timeout_read和 timeout_write:
- 设置读取和写入事件的超时时间。如果数据在指定时间内没有到达或无法写入,libevent会调用超时处理回调。
-
enabled:
- 当前启用的事件类型。可以是
EV_READ(读事件)或EV_WRITE(写事件)。通过bufferevent_enable和bufferevent_disable来动态修改。
- 当前启用的事件类型。可以是
5.2 bufferevent_private结构:
除了核心的bufferevent结构外,libevent中还定义了bufferevent_private结构,它包含了一些额外的字段,主要用于处理选项、引用计数和锁等。
struct bufferevent_private {struct bufferevent bev; // 包含核心的bufferevent结构enum bufferevent_options options; // 设置的选项,如BEV_OPT_THREADSAFE等int refcnt; // 引用计数,当为0时,bufferevent实例会被释放void *lock; // 如果启用了BEV_OPT_THREADSAFE,则会分配锁来保护输入输出缓存
};
5.3 重要的API函数:
-
创建bufferevent实例:
struct bufferevent* bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, int options);- 创建一个新的
bufferevent实例,绑定到指定的socket文件描述符fd,并关联到指定的event_base。
- 创建一个新的
-
设置回调函数:
void bufferevent_setcb(struct bufferevent *bufev, bufferevent_data_cb readcb, bufferevent_data_cb writecb, bufferevent_event_cb eventcb, void *cbarg);- 为
bufferevent实例设置读取、写入和错误事件的回调函数。
- 为
-
注册事件:
int bufferevent_enable(struct bufferevent *bufev, short event); int bufferevent_disable(struct bufferevent *bufev, short event);- 注册或注销读写事件。通过
bufferevent_enable将EV_READ和EV_WRITE事件添加到事件循环中,通知libevent开始监听这些事件。
- 注册或注销读写事件。通过
-
写入数据:
int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);- 将数据写入到输出缓冲区,
bufferevent_write会将数据从应用程序传输到网络。
- 将数据写入到输出缓冲区,
-
销毁bufferevent实例:
void bufferevent_free(struct bufferevent *bufev);- 销毁
bufferevent实例并释放相关资源。
- 销毁
总结:
bufferevent通过抽象事件的注册、回调的设置和数据的读写,简化了网络I/O的处理过程,尤其是在多线程或高并发的环境下非常有用。- 它结合了
evbuffer进行数据缓存,支持自动处理粘包和分包问题。 - 通过
bufferevent_enable和bufferevent_disable可以动态管理事件的启用与禁用。 bufferevent的使用可以显著简化基于事件驱动的网络应用开发,提高代码的可维护性和扩展性。
bufferevent 仍然是单线程
bufferevent 仍然是在 libevent 的单线程模型下运行的。
虽然 bufferevent 本身提供了一些更高级的抽象来简化 I/O 操作,但 libevent 本身的事件循环 (event_base) 仍然是基于单线程的。即使你在 bufferevent 中使用了线程安全的选项(如 BEV_OPT_THREADSAFE),它主要是在多个线程间进行同步,而不是完全实现多线程并行处理。
-
事件循环 (event_base) 是单线程的:
- 在 libevent 中,所有事件都通过一个
event_base实例进行调度。默认情况下,event_base是单线程运行的,这意味着所有事件的分发和回调处理都发生在同一个线程中。 - 即使
bufferevent提供了线程安全的缓冲区(如通过BEV_OPT_THREADSAFE选项),它只是确保在多线程环境下的input和output缓冲区操作是线程安全的,并不会改变事件循环的单线程模型。
- 在 libevent 中,所有事件都通过一个
-
事件调度和回调都在一个线程中执行:
- 当
bufferevent注册了读写事件后,event_base会将这些事件添加到事件循环中。当这些事件发生时,相关的回调函数(如readcb和writecb)会被调用,而这些回调函数是在单个线程内执行的。
- 当
-
BEV_OPT_THREADSAFE是为缓存区加锁的:- 当你使用
BEV_OPT_THREADSAFE选项时,libevent 会为bufferevent的input和output缓冲区分配锁,以支持多线程安全的访问。这意味着你可以在多个线程中操作这些缓冲区,而不必担心线程竞争问题。 - 但是,这并不会改变
event_base事件循环的单线程特性。因此,即使多个线程可以访问缓冲区,实际的事件处理和回调仍然是由单线程的event_base进行调度的。
- 当你使用
-
支持多线程的设计:
- libevent 本身也提供了多线程支持,但多线程通常是通过多个 event_base 或 线程池 来实现的。这种设计允许每个线程拥有自己的事件循环实例,而每个
event_base仍然是单线程运行的。多个线程并行处理事件的方式并不意味着单个event_base会变成多线程。
- libevent 本身也提供了多线程支持,但多线程通常是通过多个 event_base 或 线程池 来实现的。这种设计允许每个线程拥有自己的事件循环实例,而每个
如何在 libevent 中实现多线程?
尽管 bufferevent 本身是基于单线程的,但 libevent 允许你通过一些额外的配置来支持多线程:
-
多
event_base:- 你可以创建多个
event_base实例,每个线程使用一个单独的event_base实例。然后,多个线程可以并行处理不同的事件。这种方式通常用于多核处理器,可以提高事件处理的并发性。
event_base *base1 = event_base_new(); event_base *base2 = event_base_new(); // 每个线程创建一个 base 实例并调度事件 - 你可以创建多个
-
线程池:
- libevent 允许你将工作线程池与事件循环结合,多个线程可以共享一个事件循环,分发事件到不同的工作线程处理。例如,libevent 的
evthread模块可以与线程池一起使用。 - 你可以使用
modu库(在 libevent 的基础上扩展)来实现线程池,在这些线程池中处理事件,从而实现并发处理。
- libevent 允许你将工作线程池与事件循环结合,多个线程可以共享一个事件循环,分发事件到不同的工作线程处理。例如,libevent 的
总结:
bufferevent本身是在 单线程 模式下运行的,即使你设置了线程安全选项,也只会对缓存区进行线程安全管理,而不会改变 libevent 的单线程事件处理模型。- libevent 的事件循环 (
event_base) 默认是单线程的,但可以通过创建多个event_base或使用线程池等方式实现多线程的事件处理。
因此,如果你希望利用多核处理器来处理多个事件并行,通常需要使用多个 event_base 实例或结合线程池来实现,而不是依赖单个 event_base来处理所有事件。
参考文件:
https://www.cnblogs.com/secondtonone1/p/5535722.html
http://blog.csdn.net/sparkliang/article/details/4957667
https://mp.weixin.qq.com/s/nFv4B_N_MSOA4eMEwDPR-A
相关文章:
【Linux】【网络】Libevent 内核实现简略版
【Linux】【网络】Libevent 内核实现简略版 1 event_base结构–>相当于Reactor 在使用libevent之前,就必须先创建这个结构。 以epoll为例: 1.1evbase void* evbase-->epollop结构体(以epoll为例) libevent通过一个void…...
大数据学习(49) - Flink按键分区状态(Keyed State)
&&大数据学习&& 🔥系列专栏: 👑哲学语录: 承认自己的无知,乃是开启智慧的大门 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言📝支持一下博主哦ᾑ…...
架构——LVS负载均衡主要模式及其原理、服务水平、优缺点
LVS(Linux Virtual Server)是一款高性能的开源负载均衡软件,支持多种负载均衡模式。以下是其主要模式及其原理、服务水平、优缺点: 1. NAT 模式(Network Address Translation) 原理: 请求流程…...
【React组件通讯双重视角】函数式 vs 类式开发指南
目录 前言 正文 父组件向子组件传值 函数式写法 类式写法 子组件向父组件传值 函数式写法 类式写法 兄弟组件通信 函数式写法 类式写法 跨层级通信(使用Context) 函数式写法 类式写法 进阶通讯方式(补充说明…...
VScode内接入deepseek包过程(本地部署版包会)
目录 1. 首先得有vscode软件 2. 在我们的电脑本地已经部署了ollama,我将以qwen作为实验例子 3. 在vscode上的扩展商店下载continue 4. 下载完成后,依次点击添加模型 5. 在这里可以添加,各种各样的模型,选择我们的ollama 6. 选…...
Ubuntu虚拟机NDK编译ffmpeg
目录 一、ffmpeg源码下载1、安装git(用于下载ffmpeg源码)2、创建源码目录,下载ffmpeg源码 二、下载ubuntu对应的NDK,并解压到opt下1、下载并解压2、配置 ~/.bashrc 三、源码编译、1、创建编译脚本2、脚本文件内容3、设置可执行权限并运行4、编译的结果在…...
机器学习:k近邻
所有代码和文档均在golitter/Decoding-ML-Top10: 使用 Python 优雅地实现机器学习十大经典算法。 (github.com),欢迎查看。 K 邻近算法(K-Nearest Neighbors,简称 KNN)是一种经典的机器学习算法,主要用于分类和回归任务…...
js第十二题
题十二:轮播图 要求: 1.鼠标不在图片上方时,进行自动轮播,并且左右箭头不会显示;当鼠标放在图片上方时,停止轮播,并且左右箭头会显示; 2.图片切换之后,图片中下方的小…...
讯飞唤醒+VOSK语音识别+DEEPSEEK大模型+讯飞离线合成实现纯离线大模型智能语音问答。
在信息爆炸的时代,智能语音问答系统正以前所未有的速度融入我们的日常生活。然而,随着数据泄露事件的频发,用户对于隐私保护的需求日益增强。想象一下,一个无需联网、即可响应你所有问题的智能助手——这就是纯离线大模型智能语音…...
Day4 25/2/17 MON
【一周刷爆LeetCode,算法大神左神(左程云)耗时100天打造算法与数据结构基础到高级全家桶教程,直击BTAJ等一线大厂必问算法面试题真题详解(马士兵)】https://www.bilibili.com/video/BV13g41157hK?p4&v…...
HTML【详解】input 标签
input 标签主要用于接收用户的输入,随 type 属性值的不同,变换其具体功能。 通用属性 属性属性值功能name字符串定义输入字段的名称,在表单提交时,服务器通过该名称来获取对应的值disabled布尔值禁用输入框,使其无法被…...
Jvascript网页设计案例:通过js实现一款密码强度检测,适用于等保测评整改
本文目录 前言功能预览样式特点总结:1. 整体视觉风格2. 密码输入框设计3. 强度指示条4. 结果文本与原因说明 功能特点总结:1. 密码强度检测2. 实时反馈机制3. 详细原因说明4. 视觉提示5. 交互体验优化 密码强度检测逻辑Html代码Javascript代码 前言 能满…...
LeetCode刷题---哈希表---290
单词规律 290. 单词规律 - 力扣(LeetCode) 题目: 给定一种规律 pattern 和一个字符串 s ,判断 s 是否遵循相同的规律。 这里的 遵循 指完全匹配,例如, pattern 里的每个字母和字符串 s 中的每个非空单词…...
用React实现一个登录界面
使用React来创建一个简单的登录表单。以下是一个基本的React登录界面示例: 1. 设置React项目 如果你还没有一个React项目,你可以使用Create React App来创建一个。按照之前的步骤安装Create React App,然后创建一个新项目。 2. 创建登录组…...
图论:tarjan 算法求解强连通分量
题目描述 有一个 n n n 个点, m m m 条边的有向图,请求出这个图点数大于 1 1 1 的强连通分量个数。 输入格式 第一行为两个整数 n n n 和 m m m。 第二行至 m 1 m1 m1 行,每一行有两个整数 a a a 和 b b b,表示有一条…...
Haskell语言的物联网
Haskell语言在物联网中的应用 引言 物联网(IoT,Internet of Things)是现代科技发展的重要领域,它将日常生活中的各种设备通过互联网连接起来,实现智能化的控制与管理。随着设备数量的激增,以及数据处理需…...
Java:单例模式(Singleton Pattern)及实现方式
一、单例模式的概念 单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点来访问该实例,是 Java 中最简单的设计模式之一。该模式常用于需要全局唯一实例的场景,例如日志记录器、配置管理、线程池、数据库…...
Python爬虫实战:股票分时数据抓取与存储 (1)
在金融数据分析中,股票分时数据是投资者和分析师的重要资源。它能够帮助我们了解股票在交易日内的价格波动情况,从而为交易决策提供依据。然而,获取这些数据往往需要借助专业的金融数据平台,其成本较高。幸运的是,通过…...
将图片base64编码后,数据转成图片
将图片数据进行base64编码后,可以在浏览器上查看图片,只需在前端加上data:image/png;base64,即可 在线工具: Base64转图片 - 加菲工具...
天翼云910B部署DeepSeek蒸馏70B LLaMA模型实践总结
一、项目背景与目标 本文记录在天翼云昇腾910B服务器上部署DeepSeek 70B模型的全过程。该模型是基于LLaMA架构的知识蒸馏版本,模型大小约132GB。 1.1 硬件环境 - 服务器配置:天翼云910B服务器 - NPU:8昇腾910B (每卡64GB显存) - 系统内存&…...
Budibase低代码平台体验
低代码平台还是很多的,体验了Nocobase,又开始体验Budibase, 其实Budibase和appsmith更相似一点。 Budibase的安装也很简单。 1.安装好操作系统Debian; 2.安装好docker, docker-compose 3.创建目录/data,在里面参考内容创建文件docker-compos…...
【R语言】GitHub Copilot安装-待解决
参考: 文章目录...
Playwright 自动化测试系统学习
入门 Playwright安装:Playwright入门之---安装-CSDN博客 生成测试:Playwright入门之---生成测试-CSDN博客 命令汇总:Playwright入门之---命令-CSDN博客...
Jetson Agx Orin平台preferred_stride调试记录--1924x720图像异常
1.问题描述 硬件: AGX Orin 在Jetpack 5.0.1和Jetpack 5.0.2上测试验证 图像分辨率在1920x720和1024x1920下图像采集正常 但是当采集图像分辨率为1924x720视频时,图像输出异常 像素格式:yuv_uyvy16 gstreamer命令如下 gst-launch-1.0 v4l2src device=/dev/video0 ! …...
DeepSeek冲击(含本地化部署实践)
DeepSeek无疑是春节档最火爆的话题,上线不足一月,其全球累计下载量已达4000万,反超ChatGPT成为全球增长最快的AI应用,并且完全开源。那么究竟DeepSeek有什么魔力,能够让大家趋之若鹜,他又将怎样改变世界AI格…...
CF 144A.Arrival of the General(Java实现)
题目分析 一个n个身高数据,问最高的到最前面,最矮的到最后面的最短交换次数 思路分析 首先,如果数据有重复项,例如示例二中,最矮的数据就是最后一个出现的数据位置,最高的数据就是最先出现的数据位置&…...
set的使用(c++)
STL里面已经为我们实现了两种红黑树,一种是存储关键字的set,另一种是存储双关键字的map,今天主要来了解set,无论是set还是map后面都跟一个multi,它们区别是set 不能存相同元素, multiset 可以存相同的元素&…...
未加cont修饰的左值引用不能绑定到右值
目录 一、问题背景 二、错误分析 三、警告分析 一、问题背景 在initial value of reference to non-const - C Forum看到如下有问题的代码,编译如下代码看看 #include <iostream> #include <cmath>int g(double x) { return std::floor(x); } int&a…...
5.日常英语笔记
sprouted tater 发芽的土豆 fluid 液体,流体 The doctor recommended drinking plenty of fluids 医生建议多喝流质 适应新环境 adapt to the new environment adjust to the new surroundings get used to the new setting accommodate oneself to the new circu…...
IDEA单元测试插件 SquareTest 延长试用期权限
SquareTest是一款强大的IDEA单元测试生成插件工具,具体使用方法就不过多介绍了,这里主要介绍变更试用期,方便大家使用 配置信息 我的电脑安装前提配置条件 IntelliJ IDEA 2023.2windows 系统 软件安装 IntelliJ IDEA 直接安装插件Squar…...
