Libevent学习

news2024/12/24 8:48:00

一、Libevent概述

1、简介

  • Libevent 是一个用C语言编写的、轻量级的开源高性能事件通知库,主要有以下几个亮点:事件驱动( event-driven),高性能;轻量级,专注于网络,不如 ACE 那么臃肿庞大;源代码相当精炼、易读;跨平台,支持 Windows、 Linux、 *BSD 和 Mac Os;支持多种 I/O 多路复用技术, epoll、 poll、 dev/poll、 select 和 kqueue 等;支持 I/O,定时器和信号等事件;注册事件优先级。

  • Libevent 已经被广泛的应用,作为底层的网络库;比如 memcached、 Vomit、 Nylon、 Netchat等等。

2、安装

  • 官网:官网连接
    源码安装:libevent-2.1.11-stable.tar.gz

  • Ubuntu安装:apt-get install libevent-dev

二、两个重要的结构体

1、struct event

2、struct event_base

在这里插入图片描述

三、libevent常用接口

1、event_init( )函数:初始化事件集合

(1)函数原型

原型:struct event_base *event_init(void)

(2)函数实现

struct event_base *event_init(void)
{
  struct event_base *base = event_base_new_with_config(NULL);
 
  if (base == NULL) {
     event_errx(1, "%s: Unable to construct event_base", __func__);
     return NULL;
  }
 
  current_base = base;
 
  return (base);
}

(3)函数作用

初始化事件集合,其实就是调用了event_base_new_with_config( )函数,创建event_base对象,并且赋值给了全局变量struct evdns_base *current_base

2、event_set( )函数:初始化事件

(1) 函数原型

原型:void event_set(struct event *ev, evutil_socket_t fd, short events, void (*callback)(evutil_socket_t, short, void *), void *arg);
参数:
1)事件
2)关联的文件描述符
3)事件类型
4)回调函数
5)回调函数的参数

(2)函数实现:

void event_set(struct event *ev, evutil_socket_t fd, short events, void (*callback)(evutil_socket_t, short, void *), void *arg)
{
  int r;
  r = event_assign(ev, current_base, fd, events, callback, arg);
  EVUTIL_ASSERT(r == 0);
}

(3)函数作用:

初始化event事件(其实就是给结构体ev的成员赋值)

  • fd表示事件对应的文件描述符,
  • events表示事件的类型,
  • callback是回调函数(即当fd满足条件时调用该函数),
  • arg表示给回调函数传递的参数。

3、event_add( )函数:把事件添加到集合

(1)函数原型

原型:int event_add(struct event *ev, const struct timeval *tv);

(2)函数实现

int event_add(struct event *ev, const struct timeval *tv)
{
  int res;
  if (EVUTIL_FAILURE_CHECK(!ev->ev_base)) {
    event_warnx("%s: event has no event_base set.", __func__);
    return -1;
  }
  EVBASE_ACQUIRE_LOCK(ev->ev_base, th_base_lock);
  res = event_add_nolock_(ev, tv, 0);
  EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);
  return (res);
}

(3)函数作用

  • 1.将event注册到event_base的I/O多路复用要监听的事件中;
  • 2.将event注册到event_base的已注册事件链表中;
  • 3.如果传入了超时时间,则删除旧的超时时间,重新设置,并将event添加到event_base的小根堆中;
  • 如果没有传入超时时间,则不会添加到小根堆中。
  • 只有步骤1成功,才会执行步骤2和3;否则什么都没做,直接返回,保证不会改变event的状态。

4、event_dispatch( )函数:监听事件

这个函数会让程序陷入死循环, 如果集合中没有事件可以监听,则返回

例子1:IO事件

  • 1-fifo.c
#include <stdio.h>                                                                    
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <event.h>
#include <fcntl.h>
/*
   当监听的事件满足条件的时候,会触发回调函数,通过回调函数读取数据
 */
void fifo_read(evutil_socket_t fd, short events, void *arg) {
    char buf[32] = {0};
    int ret = read(fd, buf, sizeof(buf));
    if (-1 == ret) {
        perror("read");
        exit(1);
    }   

    printf("从管道读取: %s\n", buf);
}

int main() {
    int ret = mkfifo("fifo.tmp", 00700);
    if (-1 == ret) {
        perror("mkfifo");
        exit(1);
    }

    int fd = open("fifo.tmp", O_RDONLY);
    if (-1 == fd) {
        perror("open");
        exit(1);
    }
    
    // 创建事件
    struct event ev;

    // 初始化事件集合
    event_init();

    // 初始化事件(把fd和事件ev绑定)
    // 参数:事件、关联的文件描述符、事件类型、回调函数、回调函数参数
    event_set(&ev, fd, EV_READ | EV_PERSIST, fifo_read, NULL);

    //把事件添加到集合
    event_add(&ev, NULL);

    //开始监吿
    event_dispatch();  //死循玿 如果集合中没有事件可以监听,则返囿
    
    exit(0);
} 
  • 1-write_fifo.c
#include <stdio.h>                                                                    
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    int fd = open("fifo.tmp", O_WRONLY);
    if (-1 == fd) 
    {   
        perror("open");
        exit(2);
    }   

    char buf[128] = {0};

    while (1)
    {
        scanf("%s", buf);

        if (write(fd, buf, strlen(buf)) == -1)
        {
            perror("write");
            break;
        }

        if (!strcmp(buf, "bye"))
            break;

        memset(buf, 0, 128);
    }

    close(fd);

    return 0;
}
  • 运行结果:
    在这里插入图片描述

5、event_base_new( )函数:创建事件集合

(1)函数原型

原型:struct event_base *event_base_new(void);

(2)函数实现

struct event_base *event_base_new(void)
{
  struct event_base *base = NULL;
  struct event_config *cfg = event_config_new();
  if (cfg) {
    base = event_base_new_with_config(cfg);
    event_config_free(cfg);
  }
  return base;
}

(3)函数作用

创建event_base对象

注意:采用event_base_new( )创建出来的事件集合,最后要用 event_base_free(base)释放掉,因为event_base_new( )是在堆空间上进行创建的。

(4)event_base_new( )与event_init( )的区别

  • 这两个函数都会创建一个事件集合
  • event_init( )创建的是一个全局的事件集合
  • event_base_new( )创建的是一个局部的事件集合
  • event_init( )函数实现是由event_base_new( )封装而成的

6、event_base_free( )函数:释放事件集合

用于释放event_base_new( )函数创建的集合

7、event_assign( )函数:初始化事件

(1)函数原型:

原型:int event_assign(struct event *ev, struct event_base *base, evutil_socket_t fd, short events, void (*callback)(evutil_socket_t, short, void *), void *arg)

(2)参数介绍

1)事件
2)事件集合
3)关联的文件描述符
4)事件类型
5)回调函数
6)回调函数的参数

(3)函数作用

将指定事件集合中的事件与某一文件描述符进行关联

(4)event_assign( )与event_set( )的区别

  • event_assign( )可以指定事件集合;
  • event_set( )不能指定事件集合,默认采用event_init( )创建的全局的事件集合;

8、event_base_dispatch( )函数:监听事件

可监听事件集合当中的事件,和event_dispatch( )的作用相同

(1)event_base_dispatch( )与event_dispatch( )的区别

  • event_base_dispatch( )可以指定监听哪个事件集合;
  • event_dispatch( )不能指定事件集合,默认监听event_init( )创建的全局的事件集合;

9、event_del( )函数:把事件从集合中删除

原型:event_del(struct event *ev);

例子2:信号事件

  • 2-signal.c
#include <stdio.h>                                                                    
#include <stdlib.h>
#include <signal.h>
#include <event.h>

int signal_count = 0;

void signal_handler(evutil_socket_t fd, short events, void *arg) {
    struct event *ev = (struct event *)arg;
    printf("收到信号 %d\n", fd);

    signal_count++;
    if (signal_count >= 5) {
        // 把事件从集合中删除
        event_del(ev);
    }   
}

int main() {
    // 创建事件集合
    struct event_base *base = event_base_new();

    // 创建事件
    struct event ev;

    // 把事件和信号进行绑定
    event_assign(&ev, base, SIGINT, EV_SIGNAL | EV_PERSIST, signal_handler, &ev);

    // 把事件添加到集合中
    event_add(&ev, NULL);
    
    // 监听集合
    event_base_dispatch(base);

    // 释放集合
    event_base_free(base);

    exit(0);
}  

运行结果(收到一次Ctrl+C就打印信息,收到五次之后退出):
在这里插入图片描述

四、libevent高并发服务器

常见步骤:

  • 创建socket对象
  • 绑定 bind
  • 监听 listen
  • 接受连接 accept

1、evconnlistener_new_bind( )函数

(1)作用:用于创建一个evconnlistener对象的函数之一,用于监听指定地址和端口上的连接请求。

在这里插入图片描述

(2)函数原型:

struct evconnlistener *evconnlistener_new_bind(struct event_base *base,
                                               evconnlistener_cb cb,
                                               void *ptr,
                                               unsigned flags,
                                               int backlog,
                                               const struct sockaddr *sa,
                                               int socklen);

(3)参数介绍

  • 1)base
    struct event_base类型的指针,表示事件集合
  • 2)cb
    evconnlistener_cb类型的回调函数指针,用于处理新连接的事件。其中:evconnlistener_cb类型
    在这里插入图片描述
  • 3)ptr
    传递给回调函数的参数指针
  • 4)flags
    标志位,可以是LEV_OPT_REUSEABLELEV_OPT_CLOSE_ON_FREE等选项
    其中:
    在这里插入图片描述
    在这里插入图片描述
  • 5)backlog:监听队列的长度。
  • 6)sa:指向struct sockaddr类型的指针,表示要监听的地址和端口。
  • 7)socklen:sa指向的地址结构体的长度。

(4)例子

在这里插入图片描述

2、evconnlistener_free( )函数

(1)作用:释放 TCP 监听器

  • 具体来说,evconnlistener_free() 函数用于释放由 evconnlistener_new() 或 evconnlistener_new_bind() 函数创建的 TCP 监听器。在释放监听器之前,应该确保已经停止了监听器上的事件循环,并且不再有任何活动的连接。

(2)函数原型

void evconnlistener_free(struct evconnlistener *listener);

参数 listener 是指向要释放的 TCP 监听器的指针

(3)注意事项

  • 使用 evconnlistener_free() 函数释放监听器时,libevent 库将自动关闭监听器的文件描述符,并释放监听器所占用的内存。在释放监听器之后,应该将指向监听器的指针设置为 NULL,以避免出现悬空指针的问题。

  • 需要注意的是,如果在释放监听器之前仍然有活动的连接,则这些连接将被关闭,并且可能会因为连接未正常关闭而导致数据丢失。因此,在释放监听器之前,应该确保已经停止了所有连接的事件循环,并且数据已经被正确地处理和发送。

3、bufferevent_socket_new( )函数

(1)作用:对新出现的socket创建一个bufferevent

  • bufferevent_socket_new() 是 libevent 库中用于创建基于套接字的缓冲区事件处理器的函数。
  • 套接字缓冲区事件处理器是一种基于 libevent 库的高级 I/O 抽象,它可以将套接字的读写操作转换为事件驱动的方式。使用套接字缓冲区事件处理器,应用程序可以方便地进行非阻塞 I/O 操作,避免了使用底层的 socket API 进行复杂的事件循环编程。

(2)函数原型

struct bufferevent *bufferevent_socket_new(
    struct event_base *base,
    evutil_socket_t fd,
    enum bufferevent_options options
);

(3)参数介绍

  • 1)base
    事件集合
  • 2)fd
    表示要创建缓冲区事件处理器的套接字文件描述符
  • 3)options
    表示创建缓冲区事件处理器的选项,可以是多个选项的组合,例如 BEV_OPT_CLOSE_ON_FREEBEV_OPT_THREADSAFE
    在这里插入图片描述
    在这里插入图片描述

(4)注意事项

  • bufferevent_socket_new() 函数将返回一个指向新创建的套接字缓冲区事件处理器的指针。应用程序可以使用返回的指针来操作缓冲区事件处理器,例如注册事件回调函数、发送和接收数据等。
  • 套接字缓冲区事件处理器提供了一些高级功能,例如数据读写缓冲区、自动调整缓冲区大小、高效的数据传输等。开发人员可以通过设置选项来控制这些功能的行为,从而满足不同的应用场景需求

(5)例子

在这里插入图片描述

4、bufferevent_setcb( )函数

(1)改变bufferevent的回调函数

bufferevent_setcb() 函数是 libevent 库中用于设置套接字缓冲区事件处理器回调函数的函数。

(2)函数原型

void bufferevent_setcb(
    struct bufferevent *bev,
    bufferevent_data_cb readcb,
    bufferevent_data_cb writecb,
    bufferevent_event_cb eventcb,
    void *cbarg
);

(3)参数介绍

  • 1)bev:指向要设置回调函数的套接字缓冲区事件处理器的指针。
  • 2)readcb:表示在缓冲区有数据可读时调用的回调函数。
  • 3)writecb:表示在缓冲区可写时调用的回调函数。
  • 4)eventcb:表示在事件处理器发生错误或状态变化时调用的回调函数。
  • 5)cbarg:表示传递给回调函数的参数。

(4)注意

  • 使用 bufferevent_setcb() 函数,应用程序可以指定回调函数来处理套接字缓冲区事件处理器上的事件,例如接收数据、发送数据、处理错误等。回调函数应该是线程安全的,并且需要尽快返回,以避免阻塞事件循环。
  • 需要注意的是,bufferevent_setcb() 函数必须在事件处理器开始运行之前调用,否则设置的回调函数可能无法生效。而且,在设置回调函数之后,应用程序应该尽快启动事件处理器的事件循环,以便及时处理事件。

(5)例子

在这里插入图片描述
在这里插入图片描述

5、bufferevent_read( )函数

(1)作用:读取套接字缓冲区事件处理器中的数据

  • bufferevent_read() 是 libevent 库中的一个函数,用于读取套接字缓冲区事件处理器中的数据。

(2)函数原型

int bufferevent_read(struct bufferevent *bev, void *data, size_t size);

(3)参数介绍

  • 1)bev:指向要读取数据的套接字缓冲区事件处理器的指针;
  • 2)data:指向存储读取数据的缓冲区的指针;
  • 3)size:表示要读取的数据的最大字节数。

(4)注意事项

  • 使用 bufferevent_read() 函数,应用程序可以从套接字缓冲区事件处理器中读取数据到指定的缓冲区中,并返回实际读取的字节数。如果没有数据可读,函数将立即返回,并返回 0。如果读取时发生错误,函数将返回 -1,并设置 errno 变量来指示错误的原因。
  • 需要注意的是,使用 bufferevent_read() 函数只会从套接字缓冲区中读取数据,并不会阻塞等待数据到达。如果没有数据可读,函数将立即返回,因此需要在事件处理器的读回调函数中多次调用该函数,以确保读取所有可用的数据。

(5)例子

在这里插入图片描述

6、bufferevent_free( )函数

(1)作用:释放套接字缓冲区事件处理器的内存资源

(2)函数原型:

void bufferevent_free(struct bufferevent *bev);

(3)注意事项

  • 使用 bufferevent_free() 函数,应用程序可以释放套接字缓冲区事件处理器占用的内存资源,包括套接字缓冲区、读写缓冲区、回调函数等。在释放套接字缓冲区事件处理器之前,应用程序应该确保已经停止所有的 I/O 操作,并且没有剩余的事件需要处理。
  • 需要注意的是,如果套接字缓冲区事件处理器是使用 bufferevent_socket_new() 函数创建的,则在释放套接字缓冲区事件处理器之前,应用程序通常需要先关闭套接字。可以使用 bufferevent_free() 函数的前一个参数 bev 来访问套接字缓冲区事件处理器的套接字描述符,然后使用 close() 函数来关闭套接字。

完整的例子

服务端

#include <stdio.h>                                                                                                                  
#include <stdlib.h>
#include <event.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <event2/listener.h>


//读取数据
void read_cb(struct bufferevent *bev, void *ctx) {
    char buf[128] = {0};
    size_t ret = bufferevent_read(bev, buf, sizeof(buf));
    if (ret < 0) {
        printf("bufferevent_read error!\n");
    } else {
        printf("read %s\n", buf);
    }   
}
void event_cb(struct bufferevent *bev, short what, void *ctx) {
    if (what & BEV_EVENT_EOF) {
        printf("客户端下线\n");
        bufferevent_free(bev);     //释放bufferevent对象
    } else {
        printf("未知错误\n");
    }
}

void listener_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int socklen, void *arg) {
    printf("接受%d的连接\n", fd);

    struct event_base *base = arg;

    //针对已经存在的socket创建bufferevent对象
    //事件集合(从主函数传递来)、fd(代表TCP连接)、BEV_OPT_CLOSE_ON_FREE(如果释放bufferevent对象则关闭连接)
    struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    if (NULL == bev) {
        printf("bufferevent_socket_new error!\n");
        exit(1);
    }

    //给bufferevent设置回调函数
    //bufferevent对象、读事件回调函数、写事件回调函数、其他事件回调函数、参数
    bufferevent_setcb(bev, read_cb, NULL, event_cb, NULL);

    //使能bufferevent对象
    bufferevent_enable(bev, EV_READ);
}


// 常见步骤:
/*
    socket
    bind
    listen
    accept
*/

int main() {
    //创建一个事件集合
    struct event_base *base = event_base_new();
    if (NULL == base) {
        printf("event_base_new error\n");
        exit(1);
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8000);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    //创建socket、绑定、监听、接受连接
    //创建监听对象,在指定的地址上监听接下来的TCP连接
    //事件集合、当有连接时调用的函数、回调函数参数、释放监听对象关闭socket|端口重复使用、监听队列长度、绑定信息
    //返回值:监听对象
    struct evconnlistener *listener = evconnlistener_new_bind(base, listener_cb, base, 
            LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 10, (struct sockaddr *)&server_addr,sizeof(server_addr));
    if (NULL == listener) {
        printf("evconnlistener_new_bind error\n");
        exit(1);
    }    

	//监听集合中的事件
    event_base_dispatch(base);

    //释放两个对象
    evconnlistener_free(listener);
    event_base_free(base);

    exit(0);
}      

客户端:

#include <stdio.h>                                                                                                                  
#include <stdlib.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main()
{
    //创建socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0); 
    if (-1 == sockfd)
    {   
        perror("socket");
        exit(1);
    }  

    //发起连接请求
    struct sockaddr_in server_info;    //保存服务器的信息
    bzero(&server_info, sizeof(server_info));
    server_info.sin_family = AF_INET;
    server_info.sin_port = htons(8000);
    server_info.sin_addr.s_addr = inet_addr("127.0.0.1");
    if (connect(sockfd, (struct sockaddr *)&server_info, sizeof(server_info)) == -1)
    {
        perror("connect");
        exit(2);
    }

    char buf[1024] = {0};
    while (1)
    {
        scanf("%s", buf);

        if (send(sockfd, buf, strlen(buf), 0) == -1)
        {
            perror("send");
            break;
        }

        if (!strcmp(buf, "bye"))
            break;

        bzero(buf, 1024);
    }

    close(sockfd);

    return 0;
}

运行结果:

在这里插入图片描述

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

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

相关文章

C++必背基础知识点总结

重点&#xff1a;不借助任何外部资料就能掌握&#xff0c;考试面试大概率涉及。 掌握&#xff1a;不借助任何外部资料就能掌握&#xff0c;考试面试有可能涉及。 熟悉&#xff1a;可以适当参考资料掌握&#xff0c;考试面试有可能涉及。 了解&#xff1a;可是参考资料掌握&…

iptables的四表五链

文章目录 1. Iptables的链2.Iptables的表3.数据包过滤的匹配流程3.1 规则表之间的顺序3.2 规则链之间的顺序3.3 规则链内部各条防火墙规则之间的顺序3.4如何应用 每个 规则表&#xff0c;其实就相当于一个内核空间的容器&#xff0c; 按照规则集的不同用途进行划分为默认的四…

w11+wsl+3060安装cuda等深度学习环境

把一切都重新又梳理学习了一遍&#xff0c;然后发现很多事情其实没必要弄 0. 显卡驱动、nvidia-smi、cuda、nvcc、cuDNN、pytorch、cudatoolkit与它们之间的关系 笔者本人鸟枪换炮了&#xff0c;还是wsl环境香&#xff0c;但是按照官方教程跑完后&#xff0c;遇到了bugRuntim…

RTMPose关键点检测实战——笔记3

文章目录 摘要安装MMPose安装虚拟环境安装pytorch安装MMCV安装其他的安装包下载 MMPose下载预训练模型权重文件和视频素材 安装MMDetection安装Pytorch安装MMCV安装其它工具包下载 MMDetection安装MMDetection下载预训练模型权重文件和视频素材 MMPose预训练模型预测命令行的方…

关于render: h => h(App)的解释

当我们第一次安装完脚手架&#xff0c;打开 的时候&#xff0c;我相信&#xff0c;一定有小伙伴和我一样&#xff0c;看到main.js里面的render: h > h(App),感觉懵懵的。 因为&#xff0c;在刚开始接触vue的时候&#xff0c;我们这里是这样写的&#xff1a; 而使用了脚手…

iOS性能优化-异步绘制与异步底层View处理

前言&#xff1a; 基于UIKit的性能优化似乎已经到了瓶颈&#xff0c;无论是使用frame代理snpakit&#xff0c;缓存高度&#xff0c;减少布局层次&#xff0c;diff刷新&#xff0c;压缩图片&#xff0c;选择合适队列&#xff0c;选择高性能锁&#xff0c;也不能满足当前庞大而又…

SpringBoot 整合 MongoDB 实现数据的增删改查功能

1、介绍说明 在 MongoDB 中有三个比较重要的名词&#xff1a;数据库、集合、文档 数据库&#xff08;Database&#xff09;&#xff1a;和关系型数据库一样&#xff0c;每个数据库中有自己的用户权限&#xff0c;不同的项目组可以使用不同的数据库 集合&#xff08;Collectio…

汽车新能源 - 单体电压值为什么通常是5V以内

常见蓄电池单体电压的值&#xff08;25℃&#xff09;&#xff0c;如下表&#xff1a; 蓄电池类型单体电压&#xff08;V&#xff09;铅酸 蓄电池2.08镍金属氢 蓄电池&#xff08;NIMH&#xff09;1.32锂离子 蓄电池2.5~4.2&#xff08;典型3.6&#xff09; 单体电压为什么不…

安卓学习笔记(一)

从今天开始我们开始学习安卓的知识&#xff1a; 1.0 Android基础入门教程 1.Android背景与当前的状况 Android系统是由Andy Rubin创建的&#xff0c;后来被Google收购了&#xff1b;最早的版本是:Android 1.1版本 而现在最新的版本是今年5.28&#xff0c;Google I/O大会上推…

Logstash部署与使用

ElasticSearch 1、ElasticSearch学习随笔之基础介绍 2、ElasticSearch学习随笔之简单操作 3、ElasticSearch学习随笔之java api 操作 4、ElasticSearch学习随笔之SpringBoot Starter 操作 5、ElasticSearch学习随笔之嵌套操作 6、ElasticSearch学习随笔之分词算法 7、ElasticS…

123网盘在线解析PHP版源码

123网盘解析PHP版本源码是一种非常实用的工具&#xff0c;可以方便地帮助用户在网页上直接解析出其它网站中的资源&#xff0c;并提供下载链接。当用户需要获取某些资源时&#xff0c;往往需要通过各种搜索引擎或者专业的资源网站进行查找&#xff0c;而且很多时候找到了资源链…

Android NDK集成OpenCV使用C++的.h和.so库函数

Android NDK集成OpenCV使用C的.h和.so库函数 opencv可以作为一个单独的Android module库&#xff0c;被工程下的其他模块使用&#xff0c;但是这样就没法在Android NDK项目的c代码直接使用opencv的.h文件和.so文件。要在Android NDK项目C代码文件中使用&#xff0c;则需要以An…

Nautilus Chain上首个DEX PoseiSwap即将开启IDO

据悉&#xff0c;Nautilus Chain 上的首个 DEX PoseiSwap 即将开启 IDO &#xff0c;根据官方的最新公告显示&#xff0c;PoseiSwap 即将于 6 月 13 日至 6 月 14 日期间&#xff0c;在 Bounce 平台开启其治理通证 $POSE 的 IDO&#xff08;Initial DEX Offering&#xff09;&a…

数据分析--Numpy初级(一)

Numpy初级 Ndarray对象dtype对象 Numpy是数据分析的基础库&#xff0c;它支持大量的维度计算与矩阵运算。同时他也是一个运行速度非常快的数学库&#xff0c;主要用于数组计算&#xff0c;具有线性代数、傅里叶变换、随机数生成等功能。 Ndarray对象 Numpy最重要的一个特点就…

【内部类】

目录 1.什么是内部类2.内部类分类2.1静态内部类2.非静态内部类2.3 局部内部类&#xff08;几乎不用&#xff0c;大家了解&#xff09; 3.匿名内部类 1.什么是内部类 在Java中&#xff0c;可以将一个类定义在一个类中或者在一个方法中&#xff0c;前者称为内部类&#xff0c;后…

JAVA打印Hello World的底层实现

任何一个学过JAVA的人应该都对这段代码非常熟悉。空闲时间翻了下代码&#xff0c;看看它的底层是怎么实现的 public class HelloWorld {public static void main(String[] args) {System.out.print("Hello, World!");} }首先点开out&#xff0c;发现它是System类中的…

JAVA微服务_网关

服务网关 什么是服务网关/API网关 API Gateway&#xff08;APIGW / API 网关&#xff09;&#xff0c;顾名思义&#xff0c;是系统对外的唯一入口。API网关封装了系统内部架构&#xff0c;为每个客户端提供定制的API。 近几年来移动应用与企业间互联需求的兴起。从以前单一的…

数据结构学习记录——图-最短路径问题(无权图单源最短路径算法、有权图单源最短路径算法、多源最短路径算法、Dijkstra(迪杰斯特拉)算法、Floyd算法)

目录 问题分类 无权图单源最短路径算法 思路 伪代码 时间复杂度 代码实现&#xff08;C语言&#xff09; 有权图单源最短路径算法 Dijkstra&#xff08;迪杰斯特拉&#xff09;算法 伪代码 时间复杂度 代码实现&#xff08;C语言&#xff09; 多源最短路径算法 …

《Apollo 智能驾驶进阶课程》四、感知

1. 感知概貌 2. 传感器和标定 激光雷达&#xff1a;主动式&#xff0c;发射功率限制 Camera: 被动式&#xff0c;受到光照影响大 Radar : 多普勒效率 相对速度 超声波: 感知距离有限&#xff0c;倒车时使用。 … 最后设备还在研发过程中。 PnP问题&#xff0c;解决标定。 IC…

chatgpt赋能python:Python实现字符串匹配的SEO优化

Python实现字符串匹配的SEO优化 在现代网络中&#xff0c;SEO&#xff08;搜索引擎优化&#xff09;已成为一项必不可少的技能。它涉及到网站的排名、用户的流量和营销策略等方面。关键字匹配是一种常见的SEO技术&#xff0c;它可以帮助你的网站在搜索引擎中排名更高。 本篇文…