libevent

news2024/11/25 20:40:38

libevent

库概念和特点

        开源。精简。跨平台(Windows、Linux、maxos、unix)。专注于网络通信(不一定非用在网络当中,比如下面的读写管道)。

        libevent特性:基于"事件",面向“文件描述符”的异步(回调)通信模型。

        异步:函数"注册"时间和函数真正被执行的时间不同,函数真正是被内核调用(等待某一条件满足)

事件库:就干一件事,监听文件描述符的库机制。

        先创建一个事件底座,然后创建你想监听的时间,插到底座上,libevent底座就会帮忙监听事件,当事件发生时,就会自动调用事件的回调函数。

libevent使用框架

常规事件

常规事件函数
//创建event_base(事件底座)
struct event_base* base = event_base_new();

//创建事件event
常规事件event:event_new();
例如:
struct event* signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);

buffer event:bufferevent_socket_new();

//将事件添加到base上
int event_add(struct event* ev, const struct timeval* tv);

//循环监听事件满足
int event_base_dispatch(struct event_base* base);
event_base_dispatch(base);					//调用

//释放event_base
event_base_free(base);
其他函数

 

 libevent函数使用的一个简单的例子:
/*
  This example program provides a trivial server program that listens for TCP
  connections on port 9995.  When they arrive, it writes a short message to
  each client connection, and closes each connection once it is flushed.

  Where possible, it exits cleanly in response to a SIGINT (ctrl-c).
*/

#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#ifndef _WIN32
#include <netinet/in.h>
# ifdef _XOPEN_SOURCE_EXTENDED
#  include <arpa/inet.h>
# endif
#include <sys/socket.h>
#endif

#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/util.h>
#include <event2/event.h>

static const char MESSAGE[] = "Hello, World!\n";

static const int PORT = 9995;

static void listener_cb(struct evconnlistener *, evutil_socket_t,
    struct sockaddr *, int socklen, void *);
static void conn_writecb(struct bufferevent *, void *);
static void conn_eventcb(struct bufferevent *, short, void *);
static void signal_cb(evutil_socket_t, short, void *);

//main函数中是使用框架,剩下外面的函数就是事件的回调
int main(int argc, char **argv)
{//创建事件底座指针
	struct event_base *base;
	struct evconnlistener *listener;
//创建事件指针
	struct event *signal_event;
//创建套接字的地址结构
	struct sockaddr_in sin;
#ifdef _WIN32
	WSADATA wsa_data;
	WSAStartup(0x0201, &wsa_data);
#endif
//真正创建事件底座
	base = event_base_new();
	if (!base) {
		fprintf(stderr, "Could not initialize libevent!\n");
		return 1;
	}

	memset(&sin, 0, sizeof(sin));
//初始化套接字的地址结构
	sin.sin_family = AF_INET;
	sin.sin_port = htons(PORT);
//一个函数干了套接字通信的 socket、listen、bind、accept几个函数的事情
	listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
	    LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -1,
	    (struct sockaddr*)&sin,
	    sizeof(sin));

	if (!listener) {
		fprintf(stderr, "Could not create a listener!\n");
		return 1;
	}
//注册一个信号事件
	signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);

	if (!signal_event || event_add(signal_event, NULL)<0) {
		fprintf(stderr, "Could not create/add a signal event!\n");
		return 1;
	}
//循环监听,相当于 while循环加里面的epoll_wait;下面的循环有很大概率没有机会调用
	event_base_dispatch(base);
//释放之前创建的事件
	evconnlistener_free(listener);
	event_free(signal_event);
	event_base_free(base);

	printf("done\n");
	return 0;
}

//下面是千篇一律,定义了三个回调,分别独立对应三个事件。看一个就行
static void listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
    struct sockaddr *sa, int socklen, void *user_data)
{
	struct event_base *base = user_data;
	struct bufferevent *bev;

	bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
	if (!bev) {
		fprintf(stderr, "Error constructing bufferevent!");
		event_base_loopbreak(base);
		return;
	}
	bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL);
	bufferevent_enable(bev, EV_WRITE);
	bufferevent_disable(bev, EV_READ);

	bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
}

static void conn_writecb(struct bufferevent *bev, void *user_data)
{
	struct evbuffer *output = bufferevent_get_output(bev);
	if (evbuffer_get_length(output) == 0) {
		printf("flushed answer\n");
		bufferevent_free(bev);
	}
}

static void conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{
	if (events & BEV_EVENT_EOF) {
		printf("Connection closed.\n");
	} else if (events & BEV_EVENT_ERROR) {
		printf("Got an error on the connection: %s\n",
		    strerror(errno));/*XXX win32*/
	}
	/* None of the other events can happen here, since we haven't enabled
	 * timeouts */
	bufferevent_free(bev);
}

static void signal_cb(evutil_socket_t sig, short events, void *user_data)
{
	struct event_base *base = user_data;
	struct timeval delay = { 2, 0 };

	printf("Caught an interrupt signal; exiting cleanly in two seconds.\n");

	event_base_loopexit(base, &delay);
}
常规事件函数使用详解
*事件创建 event_new:
struct event* event_new(struct event_base* base,evutil_socket_t fd,short what,event_callback_fd cb,void* arg);
//
参数:
base:基事件, 也就是event_base_new()的返回值
fd:绑定到event上的文件描述符,监听的对象
what:文件描述符对应的事件(r/w/e),监听类型是什么
    what的取值:
        EV_READ:读一次后,退出循环
        EV_WRITE:写一次,退出循环
        EV_PERSIST:持续触发,可以理解为while(read())或while(write())
cb:一旦满足监听条件,回调的函数
arg:回调函数的参数

//返回值:成功返回创建的事件对象event

事件回调函数:

参数跟上面一致(实际用时候不用传参)

typedef void (*event_callback_fn)(evutil_socket_t fd,short what,void* arg);
f
事件添加(到event_base上)
int event_add(struct event* ev,const strcut timeval* tv);
//参数:
ev是要添加的事件对象,就是event_new的返回值
tv一般传NULL,表示一直等到事件被触发,回调函数才会被调用。如果传非0,会等待事件被触发,如果事件一直不触发,时间到,回调函数依然会被调用

//返回值:成功返回0;失败返回-1
事件删除(对应add,从event_base上摘下事件(不常用)):
int event_del(struct event* ev);
//ev是要摘下的事件对象,就是event_new的返回值
事件销毁:
int event_free(strcut event* ev);
//ev是要销毁的事件对象,就是event_new的返回值
基于libevent实现的FIFO的读写
读FIFO进程:
void perr_exit(const char* str) {
    perror(str);
    exit(1);
}
//读回调
void read_cb(evutil_socket_t fd, short what, void* arg) {
    char buf[BUFSIZ] = {0};
    read(fd, buf, sizeof(buf));

    printf("Read from writer:%s\n", buf);
    printf("what=%s\n", what & EV_READ ? "Yes" : "No");
    sleep(1);

    return;
}

int main(int argc, char* argv[]) {
    int ret = 0;
    int fd = 0;

    unlink("myfifo");
    mkfifo("myfifo", 0644);

    fd = open("myfifo", O_RDONLY | O_NONBLOCK);//借助epoll反应堆,默认都是非阻塞
    if (fd == -1)
        perr_exit("open error");
//读管道时候,管道中不一定有数据,此时就需要有事件概念介入了
//创建事件基座
    struct event_base* base = event_base_new();

    struct event* ev = NULL;
//创建事件
    ev = event_new(base, fd, EV_READ | EV_PERSIST, read_cb, NULL);
//添加到事件基座
    event_add(ev, NULL);
//开启事件监听
    event_base_dispatch(base);
//释放事件
    event_base_free(base);
    close(fd);
    return 0;
}
写FIFO进程:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <event2/event.h>

// 对操作处理函数
void write_cb(evutil_socket_t fd, short what, void *arg)
{
    // write管道
    char buf[1024] = {0};
    
    static int num = 0;
    sprintf(buf, "hello,world-%d\n", num++);
    write(fd, buf, strlen(buf)+1);
    
    sleep(1);
}


// 写管道
int main(int argc, const char* argv[])
{
    // open file
    //int fd = open("myfifo", O_WRONLY | O_NONBLOCK);
    int fd = open("myfifo", O_WRONLY);
    if(fd == -1)
    {
        perror("open error");
        exit(1);
    }

    // 写管道
    struct event_base* base = NULL;
    base = event_base_new();

    // 创建事件
    struct event* ev = NULL;
    // 检测的写缓冲区是否有空间写
    //ev = event_new(base, fd, EV_WRITE , write_cb, NULL);
    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);
    close(fd);
    
    return 0;
}
 重点关注对写事件event_new函数入参,写监听时长的定义,写一次退出循环和一直触发的区别:

libevent模型,事件的运行状态:未决和非未决

未决:有资格被处理,但还没有被处理 (只剩数据没到达了)

非未决:没有资格被处理 (除了数据没到达,还有其他监听条件没满足,比如事件没add)

小注意:事件被处理完,如果设置了persist,add会被再调用一次
在这里插入图片描述

bufferevent

bufferevent概念

bufferevent的事件对象fd有两个缓冲区。

读:读缓冲区有数据,读回调函数被调用,使用bufferevent_read()读数据

写:使用bufferevent_write,向写缓冲中写数据,该缓冲区中有数据自动写出,写完后,回调函数被调用(鸡肋)

在这里插入图片描述

原理:bufferent利用队列实现两个缓冲区(数据只能读一次,读走就没, FIFO)

bufferevent事件对象创建和销毁
创建socket事件对象 bufferevent_socket_new:
struct bufferevent* bufferevent_socket_new(struct event_base* base,
                                           evutil_socket_t fd,
                                           enum bfferevent_options options)
//入参
base:基事件,event_base_new函数的返回值
fd:封装到bufferevent内的fd(绑定在一起),即lfd
enum表示枚举类型,一般取BEV_OPT_CLOSE_ON_FREE(关闭时释放底层套接字)

//返回
成功返回bufferevent事件对象

对比event,有区别。这个不是在 new的时候设置的回调,而是借助后面的 bufferevent_setcb函数专门设置回调。

销毁事件对象 bufferevent_socket_free:
void bufferevent_socket_free(struct bufferevent* ev)
//入参
bufev:bufferevent_socket_new()函数的返回值

readcb:读缓冲对应的回调,自己封装,在其内部读数据(注意是用bufferevent_read()读,而不是read())

writecb:鸡肋,传NULL即可

eventcb:可传NULL

cbarg:回调函数的参数
给bufferevent事件设置回调 bufferevent_setcb
void bufferevent_setcb(struct bufferevent* bufev,
                      bufferevent_data_cb readcb,
                      bufferevent_data_cb writecb,
                      bufferevent_event_cb eventcb,
                      void* cbarg);
//入参:
bufev:bufferevent_socket_new()函数的返回值

readcb:读缓冲对应的回调,自己封装,在其内部读数据(注意是用bufferevent_read()读,而不是read())

writecb:鸡肋,传NULL即可

eventcb:备用回调,比如异常回调,也可传NULL

cbarg:三个回调函数的(相同)参数
 readcb对应的回调函数:

void read_cb(struct bufferevent* bev,void* arg){
    ...
    bufferevent_read();
}

为什么 read_cb里面不封装read,因为没有fd给read传。fd通过new函数封装好的bufferevent传。

size_t bufferevent_read(struct bufferevent* bufev,void* data,size_t size);
writecb对应的回调函数:

int bufferevent_write(struct bufferevent* bufev,const void* data,size_t size)
eventcb对应的回调函数:
typedef void (*bufferevent_event_cb)(struct bufferevent* bev,short events,void* ctx)

buffer缓冲区开启和关闭

默认新建的bufferevent写缓冲是enable的,而读缓冲是disable的

通过下面函数操作缓冲区读写使能:

 

void bufferevent_enable(struct bufferevent* bufev,short events);		//启用缓冲区
void bufferevnet_disable(struct bufferevent* bufev,short events);		//禁用

/*例如:开启读缓冲*/
void bufferevent_enable(bufev,EV_READ);

//events的值可传入三个宏:
EV_READ
EV_WRITE
EV_READ|EV_WRITE

也可获取缓冲区的禁用状态:

short bufferevent_get_enable(struct bufferevent* bufev)
客户端和服务器连接和监听函数详解
客户端建立连接 bufferevent_socket_connect:
int bufferevent_socket_connect(struct bufferevent* bev,struct sockaddr* address,int addrlen);

//入参
bev:bufferevent事件对象(封装了fd)
address,len:等同于connect()的参2和参3
服务器创建监听器对象 evconnlistener_new_bind:

这一个函数可以完成socket(),bind(),listen(),accept()四个函数的作用

struct evconnlistener* evconnlistener_new_bind(struct event_base* base,
                                               evconnlistener_cb cb,
                                               void* ptr,
                                               unsigned flags,
                                               int backlog,
                                               const struct sockaddr* sa,
                                               int socklen);
//入参
cb:监听回调函数(建立连接后用户要做的操作)
ptr:回调函数的参数
flags:可识别的标志,通常传:
​ LEV_OPT_CLOSE_ON_FREE(释放bufferevent时关闭底层传输端口, 这将关闭底层套接字, 释放底层bufferevent等)
​ LEV_OPT_REUSEABLE(可以设置端口复用)
backlog:相当于listen的参2,最大连接数, 传-1表示使用默认的最大值
sa:服务器自己的地址结构
socklen:sa的大小

回调函数 evconnlistener_cb cb:

监听成功后,说明客户端链接成功,原来的accept传出的客户端地址结构,现在在这里传出。

typedef void (*evconnlistener_cb)(struct evconnlistener* listener,
                                 evutil_socker_t sock,
                                 struct sockaddr* addr,
                                 int len,
                                 void* ptr);
//入参:
listener:evconnlistener_new_bind 的返回值
sock:用于通信的文件描述符,即cfd
addr:传出,客户端的地址结构
len:客户端地址结构的长度
ptr:外部ptr传进来的值
libevent实现TCP服务器流程

感觉这个思路和epoll反应堆还有些距离??? 

  • 创建基事件event_base
  • 使用evconlistener_new_bind()创建监听服务器,用来专门监听客户端链接事件,有新链接上来,就调用其回调函数(完成socket(),bind(),listen(),accept()四个函数的作用
  • 设置evconlistener_new_bind()回调函数listner_cb(),该回调函数被调用,说明有一个新的客户端连接上来,会得到一个新的fd,用于跟客户端通信。
  • 创建bufferevent事件对象:bufferevent_socket_new(),将fd封装到这个事件对象中
  • 使用bufferevent_setcb()函数给bufferevent的read,write,event设置回调函数
  • 设置读写缓冲enable
  • 启动循环event_base_dispath(),监听通信事件()
  • 当监听的事件满足时,read_cb会被调用,在其内部bufferevent_read(),读
  • 释放连接
libevent实现TCP服务器源码分析
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>

// 读缓冲区回调
void read_cb(struct bufferevent *bev, void *arg)
{
    char buf[1024] = {0};   
    bufferevent_read(bev, buf, sizeof(buf));
    printf("client say: %s\n", buf);

    char *p = "我是服务器, 已经成功收到你发送的数据!";
    // 发数据给客户端
    bufferevent_write(bev, p, strlen(p)+1);//执行完后回调write_cb
    sleep(1);
}

// 写缓冲区回调
void write_cb(struct bufferevent *bev, void *arg)
{
    printf("I'm服务器, 成功写数据给客户端,写缓冲区回调函数被回调...\n"); 
}

// 事件
void event_cb(struct bufferevent *bev, short events, void *arg)
{
    if (events & BEV_EVENT_EOF)
    {
        printf("connection closed\n");  
    }
    else if(events & BEV_EVENT_ERROR)   
    {
        printf("some other error\n");
    }
    
    bufferevent_free(bev);    
    printf("buffevent 资源已经被释放...\n"); 
}



void cb_listener(
        struct evconnlistener *listener, 
        evutil_socket_t fd, 
        struct sockaddr *addr, 
        int len, void *ptr)
{
   printf("connect new client\n");
   struct event_base* base = (struct event_base*)ptr;

//创建出用于通信的socket事件对象bufferevent,并给bufferevent缓冲区设置回调和读使能
   struct bufferevent *bev;
   //2参fd被封装在新的事件对象 bufferevent中
   bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
   bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);
   bufferevent_enable(bev, EV_READ);
}


int main(int argc, const char* argv[])
{

    // init server 
    struct sockaddr_in serv;

    memset(&serv, 0, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(9876);
    serv.sin_addr.s_addr = htonl(INADDR_ANY);

    struct event_base* base;
    base = event_base_new();
    // 创建套接字
    // 绑定
    // 接收连接请求
    struct evconnlistener* listener;
    listener = evconnlistener_new_bind(base, cb_listener, base, 
                                  LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 
                                  36, (struct sockaddr*)&serv, sizeof(serv));

    event_base_dispatch(base);

    evconnlistener_free(listener);
    event_base_free(base);

    return 0;
}
客户端流程简析和回顾
  1. 创建event_base
  2. 使用bufferevent_socket_new()创建一个用跟服务器通信的bufferevent事件对象
  3. 使用bufferevent_socket_connect连接服务器
  4. 使用bufferevent_setcb()给bufferevent对象的read,write,event设置回调
  5. 设置bufferevent对象的读写缓冲区使能 (4、5放connect前应该也没问题)
  6. 接受,发送数据bufferevent_read()/bufferevent_write()
  7. 启动循环监听event_base_dispath()
  8. 释放资源
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <arpa/inet.h>

void read_cb(struct bufferevent *bev, void *arg)
{
    char buf[1024] = {0}; 
    bufferevent_read(bev, buf, sizeof(buf));

    printf("fwq say:%s\n", buf);

    bufferevent_write(bev, buf, strlen(buf)+1);
    sleep(1);
}

void write_cb(struct bufferevent *bev, void *arg)
{
    printf("----------我是客户端的写回调函数,没卵用\n"); 
}

void event_cb(struct bufferevent *bev, short events, void *arg)
{
    if (events & BEV_EVENT_EOF)
    {
        printf("connection closed\n");  
    }
    else if(events & BEV_EVENT_ERROR)   
    {
        printf("some other error\n");
    }
    else if(events & BEV_EVENT_CONNECTED)
    {
        printf("已经连接服务器...\\(^o^)/...\n");
        return;
    }
    
    // 释放资源
    bufferevent_free(bev);
}

// 客户端与用户交互,从终端读取数据写给服务器
void read_terminal(evutil_socket_t fd, short what, void *arg)
{
    // 读数据
    char buf[1024] = {0};
    int len = read(fd, buf, sizeof(buf));

    struct bufferevent* bev = (struct bufferevent*)arg;
    // 发送数据
    bufferevent_write(bev, buf, len+1);
}

int main(int argc, const char* argv[])
{
    struct event_base* base = NULL;
    base = event_base_new();

    int fd = socket(AF_INET, SOCK_STREAM, 0);

    // 通信的fd放到bufferevent中
    struct bufferevent* bev = NULL;
    bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);

    // init server info
    struct sockaddr_in serv;
    memset(&serv, 0, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(9876);
    inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);

    // 连接服务器
    bufferevent_socket_connect(bev, (struct sockaddr*)&serv, sizeof(serv));

    // 设置回调
    bufferevent_setcb(bev, read_cb, write_cb, event_cb, NULL);

    // 设置读回调生效
	// bufferevent_enable(bev, EV_READ);

    // 创建事件
    struct event* ev = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST,
                                 read_terminal, bev);
    // 添加事件                     
    event_add(ev, NULL);

    event_base_dispatch(base);

    event_free(ev);
    
    event_base_free(base);

    return 0;
}

 

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

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

相关文章

C++算法:第N位数的原理、源码及测试用例

本文涉及知识点 简单的数学知识。 本博文对应源码&#xff0c;审核比较慢&#xff0c;请耐心等待&#xff1a;https://download.csdn.net/download/he_zhidan/88504919 本博文在CSDN 学院有对应课程。 题目 给你一个整数 n &#xff0c;请你在无限的整数序列 [1, 2, 3, 4, 5…

编译原理(1)----LL(1)文法(首符号集,后跟符号集,选择符号集)

一.首符号集&#xff08;First()&#xff09; 技巧&#xff1a;找最左边可能出现的终结符 例&#xff1a; 1.First(E) E->T,最左边为T&#xff0c;又因为T->F,最左边为F&#xff0c;F->(E)|i,则最左边为{&#xff08;&#xff0c;i } 2.First(T):只需要看符号串最左…

Mac VsCode g++编译报错:不支持C++11语法解决

编译运行时报错&#xff1a; [Running] cd “/Users/yiran/Documents/vs_projects/c/” && g 1116.cpp -o 1116 && "/Users/yiran/Documents/vs_projects/c/"1116 1116.cpp:28:22: warning: range-based for loop is a C11 extension [-Wc11-extensi…

pda条码二维码扫描数据采集安卓手持终端扫码热敏标签打印一体机

HT800新一代移动物联终端是深圳联强优创信息科技有限公司自主研发的基于Android11操作系统的高性能、高可靠的工业级手持数据终端&#xff0c;能与其它设备进行无线通讯&#xff0c;提供良好的操作界面&#xff0c;支持条码扫描、RFID读写&#xff08;NFC&#xff09;、GPS定位…

ch579串口编程笔记

“CH579SFR.h”库文件&#xff0c;关于串口中断部分 /* UART interrupt identification values for IIR bits 3:0 */ #define UART_II_SLV_ADDR 0x0E // RO, UART0 slave address match #define UART_II_LINE_STAT 0x06 // R…

云服务器哪家便宜靠谱 | 简单了解亚马逊云科技发展史

云服务器哪家便宜又靠谱呢&#xff1f;为什么说亚马逊云科技在这道题答案的第一行&#xff0c;一篇故事告诉你。 1994年&#xff0c;杰夫贝索斯在西雅图创建了亚马逊&#xff0c;最初只是一个在线书店。 1997年&#xff0c;亚马逊在纳斯达克交易所上市&#xff0c;成为一家公…

基于 Odoo + Python 的网站快速开发指南

基于 Odoo Python 的网站快速开发指南 下载根据本指南开发的主题模块源码 Odoo 网站生成器是一个灵活的工具&#xff0c;可以轻松构建与 Odoo 应用完全集成的网站。使用其提供的主题选项 (options) 和构建块 (blocks) 很容易定制网站。然而&#xff0c;你还可以更进一步深度定…

JavaEE进阶3

传递数组: 当我们请求中,同一个参数有多个时,浏览器就会帮我们封装成一个数组 用逗号进行分割也是可以的(有的浏览器不能直接使用逗号,需要我们去转码) 传递集合: HTTP 状态码(不是后端自定义的) 2XX:成功 3XX:重定向 4XX:客户端错误 5XX:服务器错误 业务状态码:HTTP响应…

第十章 Python 自定义模块及导入方法

系列文章目录 第一章 Python 基础知识 第二章 python 字符串处理 第三章 python 数据类型 第四章 python 运算符与流程控制 第五章 python 文件操作 第六章 python 函数 第七章 python 常用内建函数 第八章 python 类(面向对象编程) 第九章 python 异常处理 第十章 python 自定…

Cookie、Session、Token、JWT 一篇就够了

什么是认证&#xff08;Authentication&#xff09; 通俗地讲就是验证当前用户的身份&#xff0c;证明“你是你自己”&#xff08;比如&#xff1a;你每天上下班打卡&#xff0c;都需要通过指纹打卡&#xff0c;当你的指纹和系统里录入的指纹相匹配时&#xff0c;就打卡成功&a…

git工作流(待续)

一、git git是分布式管理系统和集中式的区别在于每个人具有的本地份数不同&#xff0c;集中式只有一份 分布式 主要是 协同工作 gitlab github 等是git仓库的一个托管平台 二、git安装、初始化 基础配置 第一次需要对身份进行说明 git config --global user.name "xx&…

阿里云安全恶意程序检测(速通二)

阿里云安全恶意程序检测 高阶数据探索变量分析连续数值变量与连续数值变量单个类别变量与连续数值变量两个类别变量与连续数值变量两个变量线性关系探索查看多个双变量关系的技巧 高阶数据探索多变量交叉探索 高阶数据探索 变量分析 连续数值变量与连续数值变量 分析连续数值…

SQL第三次上机作业

1.查询与王利就读同一专业学生的借书证号和姓名 SELECT Lno,Rname FROM Reader WHERE Dept(SELECT DeptFROM ReaderWHERE Rname王利)2.查询比希望出版社出版的所有图书价格都高的图书信息 SELECT * FROM Book WHERE Price>(SELECT MAX(Price)FROM BookWHERE Press希望出版…

【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)

C 入门 四 1.内联函数1.1前言&#xff08;引出内联函数&#xff09;①写一个Add函数的宏定义②宏的缺点③C对宏的态度 1.2内联函数①概念②内联函数特性 2.auto关键字(C11)① 类型别名思考② auto简介③ auto的使用细则④ auto不能推导的场景 3. 基于范围的for循环(C11)① 范围…

【计算机网络笔记】TCP的拥塞控制机制

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能&#xff08;1&#xff09;——速率、带宽、延迟 计算机网络性能&#xff08;2&#xff09;…

3.27每日一题(常系数线性非齐次方程的特解)

常系数非齐次线性方程的特解如何假设&#xff08;两种&#xff09;形式&#xff1a; 1、题目中 e 的 x 次幂以及 1&#xff0c;都是第一种&#xff1a;1可以看成为e的0次幂 注&#xff1a;题目给的多项式是特殊的形式&#xff0c;我们要设为一般的形式的多项式 2、题目中sin…

git使用全解析 | git的原理 配置 基础使用 分支 合并

文章目录 1 git初步了解1.1 git的安装1.2 git原理模型1.3 git基础配置1.4 git基础用法1 将文件加入暂存区2 查看当前的git仓库状态3 删除文件4 commit 将暂存区文件加入本地git版本仓库5 查看提交历史 更改 2 分支2.1 创建分支2.2 查看分支2.3 切换分支2.4 内容比较 3 合并 本文…

【单片机基础小知识-如何通过指针来读写寄存器】

寄存器的本质就是内存&#xff0c;RAM&#xff0c;而指针是可以对内存进行操作的&#xff0c;因此可以通过指针来读写寄存器。 如何读取以下一片地址&#xff1a; 步骤1、首地址 结构体&#xff0c;它所占用的内存空间大小与它内部成员有关。 构造一个28字节的类型 type…

nginx知识点-1

#因为是最小化安装&#xff0c;先安装vim编辑器&#xff0c;net-tools查看端口&#xff0c;psmisc可以使用killall命令bash-completion tab补全命令(需要重启生效)[rootlocalhost ~]# yum -y install net-tools psmisc vim bash-completion [rootlocalhost ~]# tar zxvf nginx-…

报错Could not resolve placeholder ‘driver‘ in value “${driver}“

这是我的报错&#xff1a; 原因是我的applicationContext.xml文件加载properties文件径错误&#xff1a; 应该把路径改成这样就可以了&#xff1a;