一篇文章搞懂Libevent网络库的原理与应用

news2024/11/28 0:42:55

1. Libevent介绍

Libevent 是一个用C语言编写的、轻量级的开源高性能事件通知库,主要有以下几个亮点:

  • > - 事件驱动( event-driven),高性能;

  • > - 轻量级,专注于网络;

  • > - 源代码相当精炼、易读;

  • > - 跨平台,支持 Windows、 Linux、 BSD(是Unix的衍生系统) 和 Mac OS;

  • > - 支持多种 I/O 多路复用技术, epoll、 poll、 select 和 kqueue 等;

  • > - 支持 I/O,定时器和信号等事件;

  • > - 支持注册事件优先级。

1.1 安装Libevent

下载地址: libevent

安装步骤 -> 源码安装的方式(终端打开下载目录)

      1. 可执行程序: configure
          ./configure         // 检测安装环境, 并且生成一个makefile文件
 
      2. 根据makefile中的构建规则编译源代码
          make     
 
      3. 安装
          sudo make install   //将得到可执行程序/动态库/静态库/头文件拷贝到系统目录

动态库找不到问题解决:

1. 通过find命令查找对应的库的位置

find 搜索目录 -name "libevent.so"

得到结果: /usr/local/lib/libevent.so

2. 通过vi 打开/etc/ld.so.conf文件

sudo /etc/ld.so.conf

将/usr/local/lib/放到文件的最后一行, 保存

3. 执行命令: sudo ldconfig

编译要加上动态库event hello.c

gcc hello.c -o hello -levent

2. 事件处理框架 - event_base

使用 libevent函数之前需要分配一个或者多个 event_base 结构体。每个event_base 结构体持有一个事件集合,可以检测以确定哪个事件是激活的。每个 event_base 都有一种用于检测哪种事件已经就绪的 “方法”。

2.1 event_base API函数

// 头文件
  #include <event2/event.h>
  // 操作函数
  struct event_base * event_base_new(void);          //创建事件处理框架
  void event_base_free(struct event_base * base);    //释放事件处理框架
  
  // 检查event_base的后端方法
  const char** event_get_supported_methods(void);
  const char *event_base_get_method(const struct event_base *base);

event_base和fork(进程)关系:

  1. 子进程创建成功之后, 父进程可以继续使用event_base

  2. 子进程中需要继续使用event_base需要用下面函数,重新初始化

int event_reinit(struct event_base* base);

例子:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <event2/event.h>
 
int main()
{
    // 1. 创建事件处理框架
    struct event_base* base = event_base_new();
    
    // 打印支持的IO转接函数
    const char** method = event_get_supported_methods();
    for(int i=0; method[i] != NULL; ++i)
    {
        printf("%s\n", method[i]);
    }
    printf("current method: %s\n", event_base_get_method(base));
 
    // 创建子进程
    pid_t pid = fork();
    if(pid == 0)
    {
        // 子进程中event_base也会被复制,在使用这个base时候要重新初始化
        event_reinit(base); 
    }
 
    // 2. 释放资源
    event_base_free(base);
    return 0;
}

相关视频推荐

从4个方面深度理解libevent的原理及使用

libevent 解决了网络编程中哪些痛点?

C++后端必读7个开源项目源码(redis、mysql、nginx、protobuf、libevent、cjson、log4cpp)

免费学习地址:c/c++ linux服务器开发/后台架构师

需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

 

3. 事件循环

event_base不停的检测委托的检测是实际是不是发生了, 如果发生了, event_base会调用对应的回调函数, 这个回调函数的用户委托检测事件的时候给的.

3.1 设置事件循环

如果委托了event_base检测某些事件, 不停的进行循环检测 结束检测时间: 所有要检测的事件都被触发, 并且处理完毕。

// 头文件
  #include <event2/event.h>
  
  // 操作函数
  #define EVLOOP_ONCE 			0x01
  #define EVLOOP_NONBLOCK 		0x02
  #define EVLOOP_NO_EXIT_ON_EMPTY 0x04
 
  int event_base_loop(struct event_base *base, int flags);
  	参数:
  		- base: 通过 event_base_new(void)得到的
  		- flags:
  			- EVLOOP_ONCE: 一直检测某个事件, 当事件被触发了, 停止事件循环
  			- EVLOOP_NONBLOCK: 非阻塞的方式检测, 当事件被触发了, 停止事件循环
  			- EVLOOP_NO_EXIT_ON_EMPTY: 一直进行事件检测, 如果没有要检测的事件, 不退出
 
  int event_base_dispatch(struct event_base* base); 	// 一般使用这个函数
  	参数:
  		- base: 通过 event_base_new(void)得到的

3.2 终止事件循环

// 头文件
  #include <event2/event.h>
  
  struct timeval {
  	long    tv_sec;                    
  	long    tv_usec;    // 微秒        
  };
 
  // 在 tv 时长之后退出循环, 如果这个参数为空NULL, 直接退出事件循环
  // 事件循环: 检测对应的事件是否被触发了
  // 如果事件处理函数正在被执行, 执行完毕之后才终止
  int event_base_loopexit(struct event_base * base, const struct timeval * tv);
  
// 马上终止
  int event_base_loopbreak(struct event_base * base);

4. 事件

4.1 事件基本操作

  • 事件的创建 event_new

 #include <event2/event.h>
  
//要检测事件   what:
  #define EV_TIMEOUT 	0x01
  #define EV_READ 	    0x02
  #define EV_WRITE 	    0x04
  #define EV_SIGNAL 	0x08
  #define EV_PERSIST 	0x10	// 修饰某个事件是持续触发的
  #define EV_ET 		0x20	// 边沿模式
 
//回调函数格式:
  typedef void (*event_callback_fn)(evutil_socket_t,short,void *);
  	参数:
  		- 第一个参数: event_new的第二个参数
  		- 第二个参数: 实际触发的事件
  		- 第三个参数: event_new的最后一个参数
 
// 创建事件
  struct event* event_new(struct event_base * base,evutil_socket_t fd,
       					 short what,event_callback_fn cb,void * arg);
  	参数:
  		- base: event_base_new得到的
  		- fd: 文件描述符, 检测这个fd对应的事件
  		- what: 监测fd的什么事件 
  		- cb: 回调函数, 当前检测的事件被触发, 这个函数被调用
  		- arg: 给回调函数传参
  • 事件的释放

#include <event2/event.h>
// 释放事件资源
  void event_free(struct event * event);
  • 事件的添加、删除

事件被new出之后, 不能直接被event_base进行检测event_add之后event_base就可以对事件进行检测

#include <event2/event.h>
int  event_add(struct event * ev,const  struct timeval * tv);
  	参数: tv-> 超时时间, 
          如果这个值> 0, 比如 == 3
          检测fd的读事件, 在三秒之内没有触发该事件 -> 超时-> 超时之后, 事件对应的回调函数会被强制调用
          如果该参数为NULL, 不会做超时检测
  
// 删除要检测的事件
  int  event_del(struct event * ev);

4.2 事件的优先级设置

// 头文件
  #include <event2/event.h>
 
  // EVENT_MAX_PRIORITIES == 256     最大的初始化事件优先级
 
  int event_base_priority_init(struct event_base * base,int n_priorities);
  	参数:
  		- n_priorities: 等级的个数, 假设 == 6
             也就是说有6个等级: 0,1,2,3,4,5, 0优先级最高
  
  // 获取当前可用的等的个数
  int event_base_get_npriorities(struct event_base * base);
  // 给事件设置等级
  int event_priority_set(struct event *event, int priority);
  	参数:
  		- event: 创建的事件
  		- priority: 要设置的等级

例子:使用event实现有名管道的进程间通信myfifo

myfifo管道文件要提前创造好

读端:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <event2/event.h>
#include <fcntl.h>
 
//读的回调函数
void read_cb(evutil_socket_t fd, short what, void* arg)
{
    char buf[128];
    int count = read(fd, buf, sizeof(buf)+1);
    printf("read data: %s, %d\n", buf, count);
    printf("read event: %s\n", what & EV_READ ? "yes" : "no");
    printf("what: %d\n\n", what);
}
 
int main()
{
    int fd = open("myfifo", O_RDONLY);
    if (fd == -1)
    {
        perror("open");
        exit(0);
    }
    
    struct event_base* base = event_base_new();
    //创建事件
    struct event* ev = event_new(base, fd, EV_READ|EV_PERSIST, read_cb, NULL);
    //添加事件
    event_add(ev, NULL);
    //事件循环检测
    event_base_dispatch(base);
 
    //释放资源
    event_free(ev);
    event_base_free(base);
    return 0;
}

写端:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <event2/event.h>
#include <fcntl.h>
 
//读的回调函数
void write_cb(evutil_socket_t fd, short what, void* arg)
{
    char buf[128];
    static int num = 0;
    sprintf(buf, "hello, %d\n", num++);
    int count = write(fd, buf, sizeof(buf)+1);
    printf("wirte data: %s, %d\n", buf, count);
    printf("wirte event: %s\n", what & EV_WRITE ? "yes" : "no");
    printf("what: %d\n\n", what);
    sleep(3);
}
 
int main()
{
    int fd = open("myfifo", O_WRONLY);
    if (fd == -1)
    {
        perror("open");
        exit(0);
    }
    
    struct event_base* base = event_base_new();
    //创建事件
    struct event* ev = event_new(base, fd, EV_WRITE|EV_PERSIST, write_cb, NULL);
    //添加事件
    event_add(ev, NULL);
    //事件循环检测
    event_base_dispatch(base);
 
    //释放资源
    event_free(ev);
    event_base_free(base);
    return 0;
}

5. 带缓冲区的事件

概念理解:

1. bufferevent 理解:

  • > - 是libevent为IO缓冲区操作提供的一种通用机制

  • > - bufferevent 由一个底层的传输端口(如套接字 ), 一个读取缓冲区和一个写入缓冲区组成。

  • > - 与通常的事件在底层传输端口已经就绪, 可以读取或者写入的时候执行回调不同的是, bufferevent在读取或者写入了足够量的数据之后调用用户提供的回调。

2. - 每个 bufferevent 有两个数据相关的回调

  • > - 读取回调: 从底层传输端口读取了任意量的数据之后会调用读取回调(默认)

  • > - 写入回调: 输出缓冲区中足够量的数据被清空到底层传输端口后写入回调会被调用(默认)

创建/释放基于套接字的bufferevent bufferevent_socket_new

主要应用于网络套接字通信 -> socket()

struct bufferevent *bufferevent_socket_new(
  	struct event_base *base,
  	evutil_socket_t fd,
  	enum bufferevent_options options
  ); 
  	参数:
  		- base: 处理事件的
  		- fd: 通信的文件描述符
  		- options: BEV_OPT_CLOSE_ON_FREE -> 自动释放底层资源
  	返回值: 得到带缓冲区的事件变量
  
// 释放资源
  void bufferevent_free(struct bufferevent *bev);

在bufferevent上启动连接服务器函数 bufferevent_socket_connect

1. 如果还没有为bufferevent 设置套接字,调用函数将为其分配一个新的流套接字,并且设置为非阻塞的

例子:bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);

2. 如果已经为 bufferevent 设置套接字,调用bufferevent_socket_connect() 将告知 libevent 套接字还未连接,直到连接成功之前不应该对其进行读取或者写入操作。

3. 连接完成之前可以向输出缓冲区添加数据。

int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address, int addrlen); 
  	参数:
  		- bev: 带缓冲区的事件, 里边封装 fd
  		- address: 要连接的服务器的IP和端口
  		- addrlen: address结构体的内存大小

bufferevent读写缓冲区回调操作 bufferevent_setcb

//读、写事件触发之后的回调函数格式
typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
  	参数:
  		- bev: 从bufferevent_setcb函数中的第一个参数传入的
  		- ctx: 从bufferevent_setcb函数中的最后第一个参数传入的
 
//特殊事件的回调函数格式 		
typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short events, void *ctx);
  	参数:
  		- bev: 从bufferevent_setcb函数中的第一个参数传入的
  		- events: 可以检测到的事件
  			EV_EVENT_READING:读取操作时发生某事件,具体是哪种事件请看其他标志。
  			BEV_EVENT_WRITING:写入操作时发生某事件,具体是哪种事件请看其他标志。
  			BEV_EVENT_ERROR:操作时发生错误。关于错误的更多信息,请调用 EVUTIL_SOCKET_ERROR()。
  			BEV_EVENT_TIMEOUT:发生超时。
  			BEV_EVENT_EOF:遇到文件结束指示。
  			BEV_EVENT_CONNECTED:请求的连接过程已经完成 
 
void bufferevent_setcb(struct bufferevent *bufev, 
                       bufferevent_data_cb readcb, 		
                       bufferevent_data_cb writecb, 
                       bufferevent_event_cb eventcb, void *cbarg
);
  	参数:
  		- bufev: 带缓冲区的事件
  		- readcb: 读事件触发之后的回调函数
  		- writecb: 写事件触发之后的回调函数
  		- eventcb: 特殊事件的回调函数
  		- cbarg: 给回调函数传参

禁用、启用缓冲区

可以启用或者禁用 bufferevent 上的 EV_READ、EV_WRITE 或者 EV_READ | EV_WRITE 事件。 没有启用读取或者写入事件时, bufferevent 将不会试图进行数据读取或者写入。

写缓冲区默认是有效的,读缓冲区默认无效

// 设置某个事件有效
  void bufferevent_enable(struct bufferevent *bufev, short events); 
  
// 设置某个事件无效
  void bufferevent_disable(struct bufferevent *bufev, short events);
  
// 获取缓冲区对应的有效事件
  short bufferevent_get_enabled(struct bufferevent *bufev);

操作bufferevent中的数据 bufferevent_write bufferevent_read

// 向bufferevent的输出缓冲区添加数据
  int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);
  
// 从bufferevent的输入缓冲区移除数据
  size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);

6. 链接监听器

  • 创建和释放evconnlistener

#include <event2/listener.h> 
 
//回调函数格式
  typedef void (*evconnlistener_cb)(
  			struct evconnlistener *listener,   
  			evutil_socket_t sock,   
  			struct sockaddr *addr, 
  			int len, 
  			void *ptr
  ); 
  	参数:
  		- listener: evconnlistener_new_bind 返回的地址
  		- sock: 用于通信的fd
  		- addr: 客户端的地址信息
  		- ptr: 外部传进来的参数, evconnlistener_new_bind的第三个参数
  		
 
// 创建监听的套接字, 绑定, 设置监听, 等待并接受连接请求
  struct evconnlistener *evconnlistener_new_bind(
  			struct event_base *base,    
  			evconnlistener_cb cb,	        // 接受新连接之后的回调函数
  			void *ptr,                      // 回调函数参数
  			unsigned flags, 
  			int backlog,                   // listen()中的第二参数,最多的监听数量,小于128的整数
  			const struct sockaddr *sa,     // 本地的IP和端口
  			int socklen				   // struct sockaddr结构体大小
  );
  	参数:
  		- flags:
  			LEV_OPT_CLOSE_ON_FREE: 自动关闭底层套接字
  			LEV_OPT_REUSEABLE: 设置端口复用
// 释放
  void evconnlistener_free(struct evconnlistener *lev); 
  • 启用和禁用 evconnlistener

设置无效之后, 就不监听连接请求了

  #include <event2/listener.h> 
 
  int evconnlistener_disable(struct evconnlistener *lev);
  int evconnlistener_enable(struct evconnlistener *lev);
  • 调整 evconnlistener 的回调函数

#include <event2/listener.h>
 
void evconnlistener_set_cb(struct evconnlistener *lev, evconnlistener_cb cb, void *arg);

7. 例子:用event实现服务器和客户端tcp通信

服务器使用链接监听器、带缓冲区的事件

客户端使用带缓冲区的事件

服务器server代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/listener.h>
 
// read缓冲区的回调
void read_cb(struct bufferevent* bev, void* arg)
{
    // 读缓冲区的数据
    char buf[128];
    int len = bufferevent_read(bev, buf, sizeof(buf));
    printf("read data: len = %d, str = %s\n", len, buf);
 
    // 回复数据
    bufferevent_write(bev, buf, len);
    printf("数据发送完毕...\n");
}
 
// 写缓冲区的回调
// 调用的时机: 写缓冲区中的数据被发送出去之后, 该函数被调用
void write_cb(struct bufferevent* bev, void* arg)
{
 
    printf("arg value: %s\n", (char*)arg);
    printf("数据已经发送完毕...xxxxxxxxxxxx\n");
}
 
// 事件回调
void events_cb(struct bufferevent* bev, short event, void* arg)
{
    if(event & BEV_EVENT_ERROR)
    {
        printf("some error happened ...\n");
    }
    else if(event & BEV_EVENT_EOF)
    {
        printf("server disconnect ...\n");
    }
    // 终止连接
    bufferevent_free(bev);
}
 
// 接收连接请求之后的回调
void listener_cb(struct evconnlistener *listener,   
                evutil_socket_t sock,   
                struct sockaddr *addr, 
                int len, 
                void *ptr)
{
    // 通信
    // 使用带缓冲区的事件对sock进行包装
    struct event_base* base = (struct event_base*)ptr;
    struct bufferevent* bev = bufferevent_socket_new(base, sock, BEV_OPT_CLOSE_ON_FREE);
    // 设置回调
    bufferevent_setcb(bev, read_cb, write_cb, events_cb, NULL);
    bufferevent_enable(bev, EV_READ);
}
 
int main()
{
    struct event_base * base = event_base_new();
    // 1. 创建监听的套接字, 绑定, 设置监听, 等待并接受连接请求
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9898);    // 服务器监听的端口
    addr.sin_addr.s_addr = INADDR_ANY;
    struct evconnlistener* listener = evconnlistener_new_bind(base, listener_cb, base, 
                                                               LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
                                                               100, (struct sockaddr*)&addr, sizeof(addr)); 
 
    event_base_dispatch(base);
    evconnlistener_free(listener);
    event_base_free(base);
 
 
    return 0;
}

客户端client代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
 
// read缓冲区的回调
void read_cb(struct bufferevent* bev, void* arg)
{
    printf("arg value: %s\n", (char*)arg);
    // 读缓冲区的数据
    char buf[128];
    int len = bufferevent_read(bev, buf, sizeof(buf));
    printf("read data: len = %d, str = %s\n", len, buf);
 
    // 回复数据
    bufferevent_write(bev, buf, len);
    printf("数据发送完毕...\n");
}
 
// 写缓冲区的回调
// 调用的时机: 写缓冲区中的数据被发送出去之后, 该函数被调用
void write_cb(struct bufferevent* bev, void* arg)
{
 
    printf("arg value: %s\n", (char*)arg);
    printf("数据已经发送完毕...xxxxxxxxxxxx\n");
}
 
// 事件回调
void events_cb(struct bufferevent* bev, short event, void* arg)
{
    if(event & BEV_EVENT_ERROR)
    {
        printf("some error happened ...\n");
    }
    else if(event & BEV_EVENT_EOF)
    {
        printf("server disconnect ...\n");
    }
    // 终止连接
    bufferevent_free(bev);
}
 
void send_msg(evutil_socket_t fd, short ev, void * arg)
{
    // 将写入到终端的数据读出
    char buf[128];
    int len = read(fd, buf, sizeof(buf));
    // 发送给服务器
    struct bufferevent* bev = (struct bufferevent*)arg;
    bufferevent_write(bev, buf, len);
}
 
int main()
{
    struct event_base * base = event_base_new();
    // 1. 创建通信的套接字
    struct bufferevent* bufev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
    
    // 2. 连接服务器
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9898);    // 服务器监听的端口
    inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr.s_addr);
    // 这个函数调用成功, == 服务器已经成功连接
    bufferevent_socket_connect(bufev, (struct sockaddr*)&addr, sizeof(addr));
    
    // 3. 通信
    // 给bufferevent的缓冲区设置回调
    bufferevent_setcb(bufev, read_cb, write_cb, events_cb, (void*)"hello, world");
    bufferevent_enable(bufev, EV_READ);
 
    // 创建一个普通的输入事件
    struct event* myev = event_new(base, STDIN_FILENO, EV_READ|EV_PERSIST, send_msg, bufev);
    event_add(myev, NULL);
    
    
    event_base_dispatch(base);
    event_free(myev);
    event_base_free(base);
 
 
    return 0;
}

8. 总结:

处理不带缓冲区的事件:

 

  1. 创建事件处理框架event_base event_base_new()

  2. 创建新事件event event_new()

  3. 将事件添加到事件处理框架event_base上 event_add()

  4. 启动事件循环检测 event_base_dispatch()

  5. 循环结束之后释放资源 event_base_free() 、event_free()

处理带缓冲区的事件:

1、创建事件处理框架event_base event_base_new()

2、服务器端:

  1. 创建连接监听器(在回调函数得到fd) evconnlistener_new_bind()

  2. 将通信fd包装 bufferevent_socket_new()

  3. 使用bufferevent通信:给bufferevent读写缓冲区设置回调函数 bufferevent_setcb()

  4. 设置读缓冲区可用 bufferevent_enable()

  5. 对缓冲区数据操作 bufferevent_write()、bufferevent_rea()

3、客户端:

  1. 创建通信用的fd并且使用bufferevent包装 bufferevent_socket_new()

  2. 连接服务器 bufferevent_socket_connect()

  3. 使用bufferevent通信:给bufferevent读写缓冲区设置回调函数 bufferevent_setcb()

  4. 设置读缓冲区可用 bufferevent_enable()

  5. 对缓冲区数据操作 bufferevent_write()、bufferevent_read()

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

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

相关文章

前端(五)——从 Vue.js 到 UniApp:开启一次全新的跨平台开发之旅

&#x1f642;博主&#xff1a;小猫娃来啦 &#x1f642;文章核心&#xff1a;从 Vue.js 到 UniApp&#xff1a;开启一次全新的跨平台开发之旅 文章目录 UniApp和vue.js什么是UniApp&#xff1f;UniApp的写法什么是vue.js&#xff1f;UniApp与vue.js是什么关系&#xff1f; 为什…

Python+Appium+Pytest自动化测试-参数化设置

来自APP Android端自动化测试初学者的笔记&#xff0c;写的不对的地方大家多多指教哦。&#xff08;所有内容均以微博V10.11.2版本作为例子&#xff09; 在自动化测试用例执行过程中&#xff0c;经常出现执行相同的用例&#xff0c;但传入不同的参数&#xff0c;导致我们需要重…

【Redis基础】快速入门

一、初识Redis 1. 认识NoSQL 2. 认识Redis Redis诞生于2009年&#xff0c;全称是Remote Dictionary Server&#xff08;远程词典服务器&#xff09;&#xff0c;是一个基于内存的键值型NoSQL数据库特征 &#xff08;1&#xff09;键值&#xff08;key-value&#xff09;型&am…

测试员如何突破自我的瓶颈?我有几点看法

前阵子我自己也对如何“突破瓶颈”思考过&#xff0c;我觉得“突破瓶颈”、“弥补短板”等等都大同小异&#xff0c;从古至今就是测试员们津津乐道的话题。我也对自己该如何“突破瓶颈”总结了几点&#xff0c;跟大家分享下&#xff1a; 1、“常立志、立长志”。“立志”就是目…

Vue脚手架使用【快速入门】

一、使用vue脚手架创建工程 在黑窗口中输入vue ui命令 再更改完路径地址后需要按回车 二、vue工程中安装elementui 第一种可以在黑窗口输入命令安装 npm install -s element-ui第二种使用图形化安装 三、 在vue工程中安装axios 第一种可以在黑窗口输入命令安装 npm inst…

ECMAScript6之一

目录 一、介绍 二、新特性 2.1 let 和 const 命令 2.2 es6的模板字符串 2.3 增强的函数 2.4 扩展的字符串、对象、数组功能 2.5 解构赋值 2.6 Symbol 2.7 Map 和 Set 2.8 迭代器和生成器 2.9 Promise对象 2.10 Proxy对象 2.11 async的用法 2.22 类class 2.23 模块…

linux内核中kmalloc与vmalloc

kmalloc 和 vmalloc 是 Linux 内核中的两种内存分配方法&#xff0c;它们都用于为内核分配内存&#xff0c;但它们在使用和管理内存方面存在一些重要差异。下面我们详细讨论这两种内存分配方法的异同。 相同点&#xff1a; 都是内核空间的内存分配方法。都可以用于动态分配内…

anaconda目录下的pkgs文件夹很大,可以删除吗?

pkgs这个目录占用了6GB的硬盘空间。 其实里面是conda安装第三方包的时候保存在本地的下载文件&#xff0c;大部分是可以删除的。 只是删除后&#xff0c;后续你需要创建虚拟环境的时候或者在虚拟环境下pip安装第三方库的时候&#xff0c;会从网络去下载&#xff0c;没法直接从…

Jmeter的常用设置(一)

文章目录 前言一、Jmeter设置中文 方法一&#xff08;临时改为中文&#xff09;方法二&#xff08;永久改成中文&#xff09;二、启动Jmeter的两种方式 方法一&#xff08;直接启动&#xff0c;不打开cmd窗口&#xff09;方法二&#xff08;带有cmd窗口的启动&#xff09;三、调…

【xxl-job】本地部署并接入xxl-job到项目中

本地部署并接入xxl-job到项目中 一、xxl-job简介 XXL-JOB是一个分布式任务调度平台&#xff0c;其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线&#xff0c;开箱即用。 什么是分布式任务调度 通常任务调度的程序是集成在应用…

SparkCoreDAG

DAG有向无环图 倒推 故推导程序的执行计划时&#xff0c;先看代码有几个action算子&#xff0c;从action倒推 一个action会产生一个JOB&#xff08;DAG&#xff09;&#xff08;即一个应用程序内的子任务&#xff09; 一个action一个Job一个DAG 一个application里面可以有多…

Latex:画图识别符号

http://detexify.kirelabs.org/classify.html

RDMA RoCev2 CM建链和Socket建链测试

前言 RDMA在高性能计算&#xff0c;AI大模型训练中发挥着重要的作用。 主流支持RDMA的协议有IB、RoCev1、RoCev2、iWARP。 其中RoCev2是应用最广泛的协议&#xff0c;因为其RDMA over UDP/IP&#xff0c;不依赖昂贵的IB网络设备&#xff0c;同时支持路由&#xff0c;性能上也…

Azure Kinect 之 Note(一)

Azure Kinect Azure Kinect DK 是一款开发人员工具包&#xff0c;配有先进的AI 传感器&#xff0c;提供复杂的计算机视觉和语音模型。 Kinect 将深度传感器、空间麦克风阵列与视频摄像头和方向传感器整合成一体式的小型设备&#xff0c;提供多种模式、选项和软件开发工具包(S…

Web开发模式

Web开发介绍 1 什么是web开发 Web&#xff1a;全球广域网&#xff0c;也称为万维网(www World Wide Web)&#xff0c;能够通过浏览器访问的网站。 所以Web开发说白了&#xff0c;就是开发网站的&#xff0c;例如下图所示的网站&#xff1a;淘宝&#xff0c;京东等等 那么我们…

【*2400 线段树】CF444 C

Problem - C - Codeforces 题意&#xff1a; 思路&#xff1a; 首先询问的是权值和&#xff0c;那么维护一个区间和sum&#xff0c;因此pushup部分就好了 考虑修改&#xff0c;区间修改&#xff0c;因此要打标记 一次修改对区间和的贡献不能直接计算&#xff0c;因此我们考…

8-1、Deployment运行应用的机制

Kubernetes 通过各种 Controller 来管理 Pod 的生命周期。为了满足不同业务场景,Kubernetes 开发了 Deployment、ReplicaSet、DaemonSet、StatefuleSet、Job 、 CronJob 等多种 Controller。 用户通过 kubectl 创建 Depl…

ENSP模拟器如何设置命令行和描述框的背景颜色及字体

ENSP模拟器如何设置命令行和描述框的背景颜色及字体 选择“菜单 > 工具 > 选项”&#xff0c; 在弹出界面中选择“字体设置”。 单击“字体”后的“选择”设置字体&#xff0c;单击“字体颜色”后的“选择”设置字颜色&#xff0c;单击“背景颜色”后的“选择”设置…

UnityVR--机械臂场景11-简单流水线应用3

目录 一. 前言 二. 设置一个定时器 三. 添加机械臂事件 四. 机械臂控制函数OnArmCtrl 五. 定义上面的3个机械臂移动方法 六. 机械臂各关节转动控制 七. 场景实现 八. 完整代码 一. 前言 上一篇使用了DoTween插件&#xff0c;并且改写了事件的相关参数&#xff0c;本篇…

jenkins 关闭关闭CSRF Protection(跨站请求伪造保护)

jenkins版本 我的jenkins版本是&#xff1a;2.332.4 背景 Jenkins版本自2.204.6以来的重大变更有&#xff1a;删除禁用 CSRF 保护的功能。 从较旧版本的 Jenkins 升级的实例将启用 CSRF 保护和设置默认的发行者&#xff0c;如果之前被禁用。 解决方法 老版本Jenkins的CSRF…