IO多路复用-epoll

IO多路复用-epoll

1. 概述

epoll 全称 eventpoll,是 linux 内核实现IO多路转接/复用(IO multiplexing)的一个实现。

epoll是select和poll的升级版,相较于这两个前辈,epoll改进了工作方式,因此它更加高效

  • 对于待检测集合select和poll是基于线性方式处理的,epoll是基于红黑树来管理待检测集合的
  • select和poll每次都会线性扫描整个待检测集合,集合越大速度越慢,epoll使用的是回调机制,效率高,处理效率也不会随着检测集合的变大而下降
  • 程序猿需要对select和poll返回的集合进行判断才能知道哪些文件描述符是就绪的,通过epoll可以直接得到已就绪的文件描述符集合,无需再次检测
  • 使用epoll没有最大文件描述符的限制,仅受系统中进程能打开的最大文件数目限制

当多路复用的文件数量庞大、IO流量频繁的时候,推荐使用epoll()。

2. 函数说明

在epoll中一共提供是三个API函数,分别处理不同的操作

#include <sys/epoll.h>
// 创建epoll实例,通过一棵红黑树管理待检测集合
int epoll_create(int size);
// 管理红黑树上的文件描述符(添加、修改、删除)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 检测epoll树中是否有就绪的文件描述符
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

select/poll低效的原因之一是将“添加/维护待检测任务”和“阻塞进程/线程”两个步骤合二为一。每次调用select都需要这两步操作,然而大多数应用场景中,需要监视的socket个数相对固定,并不需要每次都修改。epoll将这两个操作分开,先用epoll_ctl()维护等待队列,再调用epoll_wait()阻塞进程(解耦)。通过下图的对比显而易见,epoll的效率得到了提升。

在这里插入图片描述

epoll_create()函数的作用是创建一个红黑树模型的实例,用于管理待检测的文件描述符的集合。

int epoll_create(int size);

函数参数 size:指定一个大于0的数值就可以

函数返回值:

  • 失败:返回-1
  • 成功:返回一个有效的文件描述符,通过这个文件描述符就可以访问创建的epoll实例了

epoll_ctl()函数的作用是管理红黑树实例上的节点,可以进行添加、删除、修改操作

// 联合体, 多个变量共用同一块内存        
typedef union epoll_data {void        *ptr;int          fd;	// 通常情况下使用这个成员, 和epoll_ctl的第三个参数相同即可uint32_t     u32;uint64_t     u64;
} epoll_data_t;struct epoll_event {uint32_t     events;      /* Epoll events */epoll_data_t data;        /* User data variable */
};
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

函数参数:

  • epfd:epoll_create() 函数的返回值,通过这个参数找到epoll实例
  • op:这是一个枚举值,控制通过该函数执行什么操作
    • EPOLL_CTL_ADD:往epoll模型中添加新的节点
    • EPOLL_CTL_MOD:修改epoll模型中已经存在的节点
    • EPOLL_CTL_DEL:删除epoll模型中的指定的节点
  • fd:文件描述符,即要添加/修改/删除的文件描述符
  • event:epoll事件,用来修饰第三个参数对应的文件描述符的,指定检测这个文件描述符的什么事件
    • events:委托epoll检测的事件
      • EPOLLIN:读事件, 接收数据, 检测读缓冲区,如果有数据该文件描述符就绪
      • EPOLLOUT:写事件, 发送数据, 检测写缓冲区,如果可写该文件描述符就绪
      • EPOLLERR:异常事件
    • data:用户数据变量,这是一个联合体类型,通常情况下使用里边的fd成员,用于存储待检测的文件描述符的值,在调用epoll_wait()函数的时候这个值会被传出

函数返回值:

  • 失败:返回-1
  • 成功:返回0

epoll_wait()函数的作用是检测创建的epoll实例中有没有就绪的文件描述符

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

函数参数:

  • epfd:epoll_create() 函数的返回值, 通过这个参数找到epoll实例
  • events:传出参数, 这是一个结构体数组的地址, 里边存储了已就绪的文件描述符的信息
  • maxevents:修饰第二个参数, 结构体数组的容量(元素个数)
  • timeout:如果检测的epoll实例中没有已就绪的文件描述符,该函数阻塞的时长, 单位ms 毫秒
    • 0:函数不阻塞,不管epoll实例中有没有就绪的文件描述符,函数被调用后都直接返回
    • 大于0:如果epoll实例中没有已就绪的文件描述符,函数阻塞对应的毫秒数再返回
    • -1:函数一直阻塞,直到epoll实例中有已就绪的文件描述符之后才解除阻塞

函数返回值:

  • 成功:
    • 等于0:函数是阻塞被强制解除了, 没有检测到满足条件的文件描述符
    • 大于0:检测到的已就绪的文件描述符的总个数
  • 失败:返回-1

3. epoll的使用

3.1 操作步骤

服务器端

  1. 创建监听的套接字
  2. 使用本地的IP与端口和监听的套接字进行绑定
  3. 给监听的套接字设置监听
  4. 创建epoll实例对象
  5. 将用于监听的套接字添加到epoll实例中
  6. 检测添加到epoll实例中的文件描述符是否已就绪,并将这些已就绪的文件描述符进行处理
  • 如果是监听的文件描述符,和新客户端建立连接,将得到的文件描述符添加到epoll实例中
  • 如果是通信的文件描述符,和对应的客户端通信,如果连接已断开,将该文件描述符从epoll实例中删除
  1. 重复第6步的操作
3.2 示例代码

服务器:

//
// Created by 47468 on 2024/1/26.
//
#include "arpa/inet.h"
#include "unistd.h"
#include <cstdio>
#include "cstdlib"
#include "iostream"
#include <sys/epoll.h>
#include <cstring>using namespace std;int main(){// 1.创建套接字int lfd = socket(AF_INET, SOCK_STREAM, 0);if(lfd == -1){perror("socket");exit(0);}// 2. 绑定 ip, portstruct sockaddr_in saddr{};saddr.sin_family = AF_INET;saddr.sin_port = htons(9999);saddr.sin_addr.s_addr = INADDR_ANY;int res = bind(lfd, (struct sockaddr*)&saddr, sizeof(saddr));if(res == -1){perror("bind");exit(0);}// 3. 监听res = listen(lfd, 128);if(res == -1){perror("listen");exit(0);}// 4. 创建epoll实例对象int epfd = epoll_create(1);// 5. 将用于监听的套接字添加到epoll实例中epoll_event ev{};ev.events = EPOLLIN;ev.data.fd = lfd;res = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);if(res == -1){perror("epoll_ctl");exit(0);}// 6. 检测添加到epoll实例中的文件描述符是否已就绪// 并将这些已就绪的文件描述符进行处理epoll_event evs[1024];int size = sizeof(evs) / sizeof(evs[0]);while (true){int num = epoll_wait(epfd, evs, size, -1);for (int i = 0; i < num; ++i){// 取出当前的文件描述符int fd = evs[i].data.fd;if(fd == lfd){// 有新客户端建立连接sockaddr_in saddr{};int len = sizeof(saddr);int cfd = accept(lfd, (sockaddr *) &saddr, (socklen_t *) &len);// 打印客户端信息char ip[32];cout << "有客户端建立连接, ip: "<< inet_ntop(AF_INET, &saddr.sin_addr.s_addr, ip, sizeof(ip))<< ", port: "<< ntohs(saddr.sin_port)<< endl;// 把用于通信的套接字放到epoll实例中去ev.data.fd = cfd;ev.events = EPOLLIN;res = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);if(res == -1){perror("epoll_ctl-accept");exit(0);}}else{// 是通信的文件描述符就绪// 通信char buf[1024];memset(buf, 0, sizeof(buf));ssize_t len = read(fd, buf, sizeof(buf));if(len == 0){cout << "客户端断开了连接" << endl;epoll_ctl(epfd, EPOLL_CTL_DEL, fd, nullptr);close(fd);}else if(len > 0){cout << "client say: " << buf << endl;for(int i = 0; i < len; ++i){buf[i] = toupper(buf[i]);}write(fd, buf, len);}else{perror("recv");exit(0);}}}}close(lfd);return 0;
}

客户端代码不变

4. epoll的工作模式

4.1 水平模式

LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket。

特点:

  • 读事件:如果文件描述符对应的读缓冲区还有数据,读事件就会被触发,epoll_wait()解除阻塞

也就是说只要都缓冲区里面有数据, 即使没处理, 他会一直通知

写事件也是一样, 只要写缓冲区可写, 就会一直触发

4.2 边沿模式

ET(edge-triggered)是高速工作方式,只支持no-block socket

当文件描述符从未就绪变为就绪时,内核会通过epoll通知使用者。然后它会假设使用者知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知(only once)

ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。

特点:

读事件:当读缓冲区有新的数据进入,读事件被触发一次,没有新数据不会触发该事件

  • 读事件被触发,可以通过调用read()/recv()函数将缓冲区数据读出
  • 如果数据没有被全部读走,并且没有新数据进入,读事件不会再次触发,只通知一次
  • 如果数据被全部读走或者只读走一部分,此时有新数据进入,读事件被触发,并且只通知一次

写事件:当写缓冲区状态可写,写事件只会触发一次

  • 写缓冲区从不满到被写满,期间写事件只会被触发一次
  • 写缓冲区从满到不满,状态变为可写,写事件只会被触发一次

综上所述:epoll的边沿模式下 epoll_wait()检测到文件描述符有新事件才会通知,如果不是新的事件就不通知,通知的次数比水平模式少,效率比水平模式要高。

4.3 边沿模式的设置

epoll管理的红黑树示例中每个节点都是struct epoll_event类型,只需要将EPOLLET添加到结构体的events成员中即可:

struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;	// 设置边沿模式

在服务器端的代码改动:

// 把用于通信的套接字放到epoll实例中去ev.data.fd = cfd;ev.events = EPOLLIN | EPOLLET;res = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);if(res == -1){perror("epoll_ctl-accept");exit(0);}

这样的话, 把服务器端的buf该校一下, 每次只接受5个字节, 这样服务器就无法一次把数据全部接收完

客户端:

在这里插入图片描述

服务器:

在这里插入图片描述

也就是说每次有新的数据发送的时候, 服务器才能把原来缓冲区的数据读出来

4.4 边沿模式非阻塞设置

第一种方式是把read函数放到while循环中一直读取, 只有有数据就读取

int len = 0;
while((len = recv(curfd, buf, sizeof(buf), 0)) > 0)
{// 数据处理...
}

但这样的话有一个问题就是数据读取完了之后, 线程就阻塞在read函数中了, 无法继续向下运行, 所以我们需要把cfd这个文件描述符的状态设置为非阻塞

需要使用fcntl()函数进行处理:

// 设置完成之后, 读写都变成了非阻塞模式
int flag = fcntl(cfd, F_GETFL);
flag |= O_NONBLOCK;                                                        
fcntl(cfd, F_SETFL, flag);

就是这样:

// 有新客户端建立连接
sockaddr_in saddr{};
int len = sizeof(saddr);
int cfd = accept(lfd, (sockaddr *) &saddr, (socklen_t *) &len);
// 把cfd设置为非阻塞模式
auto flag = fcntl(cfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd, F_SETFL, flag);
// 打印客户端信息
char ip[32];

也就是说把解说数据的代码部分都放到一个while循环中

在这里插入图片描述

这是还存在一个问题就是, 当循环读取完客户端发来的数据后, 没数据的话read也不会阻塞, 而是直接返回-1, 这样的话会直接打印错误信息recv, 并退出程序, 这并不是我们想要的, 我们想在接受完一部分数据后, 跳出while循环, 并继续走上一层for循环, 检测有没有新的文件描述符就绪

我们先来运行程序看是什么错误信息, 根据错误信息进行判断什么时候break

在这里插入图片描述

我们可以看出来, 客户端之发送了一个dsa, 服务器接收到之后, 报错并直接退出

我们查一下这个错误信息, 看一下read函数的error

在这里插入图片描述

能看出是这个原因导致的, 所以我们在len==-1里面判断一下错误号即可:

在这里插入图片描述

这样就ok了

运行:

在这里插入图片描述

在这里插入图片描述

最后的服务器端的代码:

//
// Created by 47468 on 2024/1/26.
//
#include "arpa/inet.h"
#include "unistd.h"
#include <cstdio>
#include "cstdlib"
#include "iostream"
#include <sys/epoll.h>
#include <cstring>
#include <fcntl.h>
#include <cerrno>using namespace std;int main(){// 1.创建套接字int lfd = socket(AF_INET, SOCK_STREAM, 0);if(lfd == -1){perror("socket");exit(0);}// 2. 绑定 ip, portstruct sockaddr_in saddr{};saddr.sin_family = AF_INET;saddr.sin_port = htons(9999);saddr.sin_addr.s_addr = INADDR_ANY;int res = bind(lfd, (struct sockaddr*)&saddr, sizeof(saddr));if(res == -1){perror("bind");exit(0);}// 3. 监听res = listen(lfd, 128);if(res == -1){perror("listen");exit(0);}// 4. 创建epoll实例对象int epfd = epoll_create(1);// 5. 将用于监听的套接字添加到epoll实例中epoll_event ev{};ev.events = EPOLLIN;ev.data.fd = lfd;res = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);if(res == -1){perror("epoll_ctl");exit(0);}// 6. 检测添加到epoll实例中的文件描述符是否已就绪// 并将这些已就绪的文件描述符进行处理epoll_event evs[1024];int size = sizeof(evs) / sizeof(evs[0]);while (true){int num = epoll_wait(epfd, evs, size, -1);for (int i = 0; i < num; ++i){// 取出当前的文件描述符int fd = evs[i].data.fd;if(fd == lfd){// 有新客户端建立连接sockaddr_in saddr{};int len = sizeof(saddr);int cfd = accept(lfd, (sockaddr *) &saddr, (socklen_t *) &len);// 把cfd设置为非阻塞模式auto flag = fcntl(cfd, F_GETFL);flag |= O_NONBLOCK;fcntl(cfd, F_SETFL, flag);// 打印客户端信息char ip[32];cout << "有客户端建立连接, ip: "<< inet_ntop(AF_INET, &saddr.sin_addr.s_addr, ip, sizeof(ip))<< ", port: "<< ntohs(saddr.sin_port)<< endl;// 把用于通信的套接字放到epoll实例中去ev.data.fd = cfd;ev.events = EPOLLIN | EPOLLET;res = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);if(res == -1){perror("epoll_ctl-accept");exit(0);}}else{// 是通信的文件描述符就绪// 通信char buf[5];memset(buf, 0, sizeof(buf));while (true) {ssize_t len = read(fd, buf, sizeof(buf));if (len == 0) {cout << "客户端断开了连接" << endl;epoll_ctl(epfd, EPOLL_CTL_DEL, fd, nullptr);close(fd);break;} else if (len > 0) {buf[len] = '\0';cout << "client say: " << buf << endl;for (int i = 0; i < len; ++i) {buf[i] = toupper(buf[i]);}write(fd, buf, len);} else {// len == -1if(errno == EAGAIN){cout << "数据接收完毕..." << endl;break;}perror("recv");exit(0);}}}}}close(lfd);return 0;}

5. 多线程+epoll边沿模式

大致思想跟select多线程通信其实是一样的

直接上代码

服务器端:

//
// Created by 47468 on 2024/1/26.
//
#include "arpa/inet.h"
#include "unistd.h"
#include <cstdio>
#include "cstdlib"
#include "iostream"
#include <sys/epoll.h>
#include <cstring>
#include <fcntl.h>
#include <cerrno>
#include <pthread.h>
using namespace std;struct socketInfo{int fd;int epfd;
};void* acceptConn(void* arg){// 打印一下线程idcout << "acceptConn id: " << pthread_self() << endl;auto* info = (socketInfo*)arg;int lfd = info->fd;int epfd = info->epfd;// 有新客户端建立连接sockaddr_in saddr{};int len = sizeof(saddr);int cfd = accept(lfd, (sockaddr *) &saddr, (socklen_t *) &len);// 把cfd设置为非阻塞模式auto flag = fcntl(cfd, F_GETFL);flag |= O_NONBLOCK;fcntl(cfd, F_SETFL, flag);// 打印客户端信息char ip[32];cout << "有客户端建立连接, ip: "<< inet_ntop(AF_INET, &saddr.sin_addr.s_addr, ip, sizeof(ip))<< ", port: "<< ntohs(saddr.sin_port)<< endl;// 把用于通信的套接字放到epoll实例中去epoll_event ev{};ev.data.fd = cfd;ev.events = EPOLLIN | EPOLLET;int res = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);if(res == -1){perror("epoll_ctl-accept");exit(0);}delete info;return nullptr;
}void* conmmunication(void* arg){// 打印一下线程idcout << "conmmunication id: " << pthread_self() << endl;auto* info = (socketInfo*)arg;int fd = info->fd;int epfd = info->epfd;// 是通信的文件描述符就绪// 通信char buf[1024];memset(buf, 0, sizeof(buf));while (true) {ssize_t len = read(fd, buf, sizeof(buf));if (len == 0) {cout << "客户端断开了连接" << endl;epoll_ctl(epfd, EPOLL_CTL_DEL, fd, nullptr);close(fd);break;} else if (len > 0) {buf[len] = '\0';cout << "client say: " << buf << endl;for (int i = 0; i < len; ++i) {buf[i] = toupper(buf[i]);}write(fd, buf, sizeof(buf));} else {// len == -1if(errno == EAGAIN){cout << "数据接收完毕..." << endl;break;}perror("recv");break;}}delete info;return nullptr;
}int main(){// 1.创建套接字int lfd = socket(AF_INET, SOCK_STREAM, 0);if(lfd == -1){perror("socket");exit(0);}// 2. 绑定 ip, portstruct sockaddr_in saddr{};saddr.sin_family = AF_INET;saddr.sin_port = htons(9999);saddr.sin_addr.s_addr = INADDR_ANY;int res = bind(lfd, (struct sockaddr*)&saddr, sizeof(saddr));if(res == -1){perror("bind");exit(0);}// 3. 监听res = listen(lfd, 128);if(res == -1){perror("listen");exit(0);}// 4. 创建epoll实例对象int epfd = epoll_create(1);// 5. 将用于监听的套接字添加到epoll实例中epoll_event ev{};ev.events = EPOLLIN;ev.data.fd = lfd;res = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);if(res == -1){perror("epoll_ctl");exit(0);}// 6. 检测添加到epoll实例中的文件描述符是否已就绪// 并将这些已就绪的文件描述符进行处理epoll_event evs[1024];int size = sizeof(evs) / sizeof(evs[0]);while (true){int num = epoll_wait(epfd, evs, size, -1);for (int i = 0; i < num; ++i){auto* info = new socketInfo;info->epfd = epfd;info->fd = evs[i].data.fd;pthread_t tid;// 取出当前的文件描述符int fd = evs[i].data.fd;if(fd == lfd){pthread_create(&tid, nullptr, acceptConn, info);pthread_detach(tid);}else{pthread_create(&tid, nullptr, conmmunication, info);pthread_detach(tid);}}}close(lfd);return 0;}

客户端

//
// Created by 47468 on 2024/1/26.
//
#include "arpa/inet.h"
#include "unistd.h"
#include <cstdio>
#include <cstring>
#include "cstdlib"
#include "iostream"
using namespace std;int main(){// 1. 创建用于通信的套接字int fd = socket(AF_INET, SOCK_STREAM, 0);if(fd == -1){perror("socket");exit(0);}// 2. 连接服务器sockaddr_in saddr{};saddr.sin_family = AF_INET;saddr.sin_port = htons(9999);inet_pton(AF_INET, "192.168.110.129", &saddr.sin_addr.s_addr);int res = connect(fd, (sockaddr *) &saddr, sizeof(saddr));if(res == -1){perror("connet");exit(0);}// 通信while(true){// 读数据char readBuf[1024];// 写数据cout << "请输入要发送的字符串: " << endl;cin.getline(readBuf, sizeof(readBuf));// 发送数据到客户端write(fd, readBuf, strlen(readBuf));// 接收服务器发送的数据ssize_t len = read(fd, readBuf, sizeof(readBuf));// readBuf[len] = '\0';cout << readBuf << endl;}close(fd);return 0;
}

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

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

相关文章

WorkPlus AI智能客服解决方案,提升企业服务质量

在当今竞争激烈的商业环境中&#xff0c;提供卓越的客户服务成为企业赢得市场竞争的关键。而AI智能客服技术的不断发展&#xff0c;则成为了提高服务效率和满意度的利器。作为一款领先的AI助理解决方案&#xff0c;WorkPlus AI助理以其出色的性能和智能化的功能&#xff0c;助力…

c++入门学习(十八)赋值运算符

简单赋值运算符&#xff08;&#xff09;&#xff1a; 最基本的赋值运算符是“”。它表示将右侧的值赋给左侧的变量。例如&#xff0c;x 5意味着将值5赋给变量x。 增量赋值运算符&#xff1a; 这是一组在赋值的同时对变量进行递增操作的运算符。常见的有、-、*、/等。例如&…

THM学习笔记——网络工具

ping 当我们想要测试是否可以连接到远程资源时&#xff0c;会使用 ping 命令。 ping 的基本语法&#xff1a; ping <target>。 测试是否可以与百度建立网络连接&#xff1a; traceroute 互联网由许多个不同的服务器和端点组成&#xff0c;它们都相互联网。这意味着&a…

Soul CEO张璐团队布局AIGC领域,打造数智化社交新体验

作为互联网社交领域的领军企业,Soul App近日再次受到广泛关注,因其在生成式人工智能(AIGC)领域的前沿布局和创新。随着数据积累、算力提升和算法不断迭代,AIGC技术正逐渐成为推动产业创新的重要工具之一。2023年被誉为AIGC元年,而Soul App在CEO张璐的带领下,在这个领域的不懈努…

计算CNN卷积层和全连接层的参数量

计算CNN卷积层和全连接层的参数量 先前阅读 CNN ExplainerA Comprehensive Guide to Convolutional Neural Networks — the ELI5 way 本文主旨意在搞明白2个问题&#xff1a; 第一个问题 一个卷积操作&#xff0c;他的参数&#xff0c;也就是我们要训练的参数&#xff0c;也…

Navigation 2 学习01 介绍及安装及运行示例

Navigation 2 是什么 Nav2 是 ROS 导航 的综合控制服务&#xff0c;类似人类的小脑控制人类的行走及身体平衡&#xff0c;Nav2 针对移动和地面机器人提供支持的自动驾驶车辆的相同类型的技术&#xff0c;经过优化和改造。该项目旨在找到一种安全的方法&#xff0c;使移动机器人…

nginx离线部署-aarch64架构

nginx离线部署-aarch64架构 服务器环境: 架构&#xff1a;aarch64&#xff0c; 系统&#xff1a;Red Hat &#xff08;CentOS 7&#xff09; nginx 1.24 需要准备这些&#xff1a; 可以先尝试安装 Nginx 安装NGINX 内网是没有网络的需要使用 RPM 包安装 gcc&#xff0c; g…

绘制太极图 - 使用 PyQt

大家好&#xff01;今天我们将一起来探讨一下如何使用PyQt&#xff0c;这是一个强大的Python库&#xff0c;来绘制一个传统的太极图。这个图案代表着古老的阴阳哲学&#xff0c;而我们的代码将以大白话的方式向你揭示它的奥秘。 PyQt&#xff1a;是什么鬼&#xff1f; 首先&a…

架构师之路(十六)计算机网络(传输层)

前置知识&#xff08;了解&#xff09;&#xff1a;计算机基础。 作为架构师&#xff0c;我们所设计的系统很少为单机系统&#xff0c;因此有必要了解计算机和计算机之间是怎么联系的。局域网的集群和混合云的网络有啥区别。系统交互的时候网络会存在什么瓶颈。 既然网络层已经…

.net访问oracle数据库性能问题

问题&#xff1a; 生产环境相同的inser语句在别的非.NET程序相应明显快于.NET程序&#xff0c;执行时间相差比较大&#xff0c;影响正常业务运行&#xff0c;测试环境反而正常。 问题详细诊断过程 问题初步判断诊断过程&#xff1a; 查询插入慢的sql_id 检查对应的执行计划…

直播间流程解析基础

通过用户心理需求引导用户行为 贯穿内容和产品牵引想要和需要 直播间内流程解析 分为播前准备、开播暖场、产品介绍、穿插活动、结尾预告 &#xff08;1&#xff09;直播间内流程解析----播前准备 &#xff08;2&#xff09;直播间内流程解析----开播暖场 &#xff08;3&…

互联网加竞赛 基于机器视觉的银行卡识别系统 - opencv python

1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学习的银行卡识别算法设计 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f9ff; 更多资料, 项目分享&#xff1a; https://gitee.com/dancheng…

电涌保护器(SPD)、后备保护器(SCB)、断路器(CB)的区别与应用

随着现代电力系统的不断发展&#xff0c;电力设备的保护显得愈发重要。其中&#xff0c;电涌保护器&#xff08;SPD&#xff09;、后备保护器&#xff08;SCB&#xff09;和断路器&#xff08;CB&#xff09;是三种常见的保护设备&#xff0c;但它们各自具有不同的功能和特点。…

【渗透测试】借助PDF进行XSS漏洞攻击

简介 在平时工作渗透测试一个系统时&#xff0c;常常会遇到文件上传功能点&#xff0c;其中大部分会有白名单或者黑名单机制&#xff0c;很难一句话木马上传成功&#xff0c;而PDF则是被忽略的一个点&#xff0c;可以让测试报告更丰富一些。 含有XSS的PDF制作步骤 1. 编辑器…

论文阅读《thanking frequency fordeepfake detection》

项目链接&#xff1a;https://github.com/yyk-wew/F3Net 这篇论文从频域的角度出发&#xff0c;提出了频域感知模型用于deepfake检测的模型 整体架构图&#xff1a; 1.FAD&#xff1a; 频域感知分解&#xff0c;其实就是利用DCT变换&#xff0c;将空间域转换为频域&#xff…

element+vue 之 v-limit 按钮操作权限

1.新建一个permission.js文件 import store from /storeexport default {inserted: function (el, binding) {const { perms: limits } store.state.userconst { value: params } bindingif (!limits.length) returnif (params && Array.isArray(params)) {if (!limi…

08.Elasticsearch应用(八)

Elasticsearch应用&#xff08;八&#xff09; 1.为什么需要相关性算分 我们在文档搜索的时候&#xff0c;匹配程度越高的相关性算分越高&#xff0c;算分越高的越靠前&#xff0c;但是有时候我们不需要算分越高越靠前我们可能需要手动影响算分来控制顺序比如广告&#xff08…

2016年认证杯SPSSPRO杯数学建模A题(第一阶段)洗衣机全过程文档及程序

2016年认证杯SPSSPRO杯数学建模 A题 洗衣机 原题再现&#xff1a; 洗衣机是普及率极高的家用电器&#xff0c;它给人们的生活带来了很大的方便。家用洗衣机从工作方式来看&#xff0c;有波轮式、滚筒式、搅拌式等若干种类。在此基础上&#xff0c;各厂商也推出了多种具体方案…

微信小程序(十五)自定义导航栏

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.组件文件夹创建方法 2.自定义组件的配置方法 3.外部修改组件样式&#xff08;关闭样式隔离或传参&#xff09; 创建组件文件夹 如果是手动创建建议注意在json文件声明&#xff1a; mynav.json {//声明为组件可…

从CDN了解到的边缘计算与前端渲染

文章概叙 本文代码量较少&#xff0c;讲的是在云开发的基础上使用边缘计算的&#xff0c;代码量不高&#xff0c;​建议看完理解下就可以丢了&#xff0c;知道个概念就好。 废话1 第一次接触边缘计算是在2020年的时候&#xff0c;公司的cloud课程中&#xff0c;有一些相关概…