io多路复用:epoll水平触发(LT)和边沿触发(ET)的区别和优缺点

news2024/9/21 20:06:33

在进行ET模式的正式分析之前,我们来举个例子简单地了解下ET和LT:

假设我们通过fork函数创建了父子两个进程,并通过匿名管道来通信,在子进程中,我们一次向管道写入10个字符数据,为"aaaa\nbbbb\n";每隔5s写入10个字符数据;在父进程中,我们从管道中一次读取5个字符数据:

若我们采用的是LT模式,则在5s的周期内,会先读取5个字符数据,读完之后,因为文件描述符中仍然有数据,epoll_wait会立即返回,会继续读取接下来的5个数据,之后在5s周期以内的剩余时间内,管道中的数据都为空,如下图1。

若我们采用的是ET模式,则当父进程先读完管道中的5个字符后,由于子进程没有立即向管道中写入字符(间隔5s后才会第二次写入),所以此时父进程会先读到5个字符"aaaa\n",隔5s之后,再读到5个字符"bbbb\n",如下图2,可以看到ET模式下,随时间推移,管道中数据会越来越多。

图1

图2

epoll工作流程

想要了解ET模式和LT模式的区别,首先需要熟悉epoll的工作流程:

【注:】上图的poll不要理解成和select相似那个poll,这是通过epoll_ctl调用的,进行回调函数注册。

下面简要分析一下epoll的工作过程:

1)epoll_wait调用ep_poll,当rdlist为空(无就绪fd)时挂起当前进程,直到rdlist不空时进程才被唤醒。

2)****文件fd状态改变(buffer由不可读变为可读或由不可写变为可写),导致相应fd上的回调函数ep_poll_callback()被调用。

ep_poll_callback将相应fd对应epitem加入rdlist,导致rdlist不空,进程被唤醒,epoll_wait得以继续执行。

3)ep_events_transfer函数将rdlist中的epitem拷贝到txlist中,并将rdlist清空。

4)ep_send_events函数(很关键),它扫描txlist中的每个epitem,调用其关联fd对应的poll方法(图中蓝线)。此时对poll的调用仅仅是取得fd上较新的events(防止之前events被更新),之后将取得的events和相应的fd发送到用户空间(封装在struct epoll_event,从epoll_wait返回)。

5)之后如果这个epitem对应的fd是LT模式监听且取得的events是用户所关心的,则将其重新加入回rdlist(图中蓝线),否则(ET模式)不在加入rdlist。

ET模式 vs LT模式

区别

通过上图epoll的工作流程描述,可以看出ET模式和LT模式的区别主要发生在第5步:是否将文件描述符fd放回rdlist中,而rdlist的是否为空决定了epoll_wait函数的阻塞和非阻塞。因此,总结下二者的区别:

ET

如果是ET, epitem是不会再进入到readly list;除非fd再次发生了状态改变, ep_poll_callback被调用。【因此,ET模式可减少epoll_wait函数的调用,从而减少系统开销,提高效率。】

LT

如果是非ET, 不管你还有没有有效的事件或者数据,都会被重新插入到ready list, 再下一次epoll_wait时, 会立即返回, 并通知给用户空间。当然如果这个被监听的fds确实没事件也没数据了, epoll_wait会返回一个0,空转一次。

加入rdlist途径分析

红线:fd状态改变才会触发,那什么情况会导致fd状态的改变呢?

对于读取操作:

1)当buffer由不可读状态变为可读的时候,即由空变为不空的时候。

2)当有新数据到达时,即buffer中的待读内容变多的时候。

对于写操作:

1)当buffer由不可写变为可写的时候,即由满状态变为不满状态的时候。

2)当有旧数据被发送走时,即buffer中待写的内容变少得时候。

蓝线:fd的events中有相应的时间(位置1)即会触发。那什么情况下会改变events的相应位呢?

对于读操作:

1)buffer中有数据可读的时候,即buffer不空的时候,fd的events的可读为就置1。

对于写操作:

2) buffer中有空间可写的时候,即buffer不满的时候fd的events的可写位就置1。

【说明:】

  • 红线是时间驱动被动触发(fd状态改变);

  • 蓝线是函数查询主动触发(LT和ET判断是否加入rdlist)。

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

代码实践

基础代码

以下为一个epoll触发模式测试的基础代码,也不算太长,直接拿来就可以测试:

#include <sys/socket.h> //for socket
#include <arpa/inet.h>  //for htonl htons
#include <sys/epoll.h>  //for epoll_ctl
#include <unistd.h>     //for close
#include <fcntl.h>      //for fcntl
#include <errno.h>      //for errno
#include <iostream>     //for cout

class fd_object
{
public:
    fd_object(int fd) { listen_fd = fd; }
    ~fd_object() { close(listen_fd); }
private:
    int listen_fd;
};

/*
./epoll for lt mode
and
./epoll 1 for et mode
*/
int main(int argc, char* argv[])
{
    //create a socket fd
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd == -1)
    {
        std::cout << "create listen socket fd error." << std::endl;
        return -1;
    }
    fd_object obj(listen_fd);

    //set socket to non-block
    int socket_flag = fcntl(listen_fd, F_GETFL, 0);
    socket_flag |= O_NONBLOCK;
    if (fcntl(listen_fd, F_SETFL, socket_flag) == -1)
    {
        std::cout << "set listen fd to nonblock error." << std::endl;
        return -1;
    }

    //init server bind info
    int port = 51741;
    struct sockaddr_in bind_addr;
    bind_addr.sin_family = AF_INET;
    bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    bind_addr.sin_port = htons(port);
    if (bind(listen_fd, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) == -1)
    {
        std::cout << "bind listen socket fd error." << std::endl;
        return -1;
    }

    //start listen
    if (listen(listen_fd, SOMAXCONN) == -1)
    {
        std::cout << "listen error." << std::endl;
        return -1;
    }
    else
        std::cout << "start server at port [" << port << "] with [" << (argc <= 1 ? "LT" : "ET") << "] mode." << std::endl;

    //create a epoll fd
    int epoll_fd = epoll_create(88);
    if (epoll_fd == -1)
    {
        std::cout << "create a epoll fd error." << std::endl;
        return -1;
    }

    epoll_event listen_fd_event;
    listen_fd_event.data.fd = listen_fd;
    listen_fd_event.events = EPOLLIN;
    if (argc > 1) listen_fd_event.events |= EPOLLET;

    //add epoll event for listen fd
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &listen_fd_event) == -1)
    {
        std::cout << "epoll ctl error." << std::endl;
        return -1;
    }

    while (true)
    {
        epoll_event epoll_events[1024];
        int n = epoll_wait(epoll_fd, epoll_events, 1024, 1000);

        if (n < 0)
            break;
        else if (n == 0) //timeout
            continue;

        for (int i = 0; i < n; ++i)
        {
            if (epoll_events[i].events & EPOLLIN)//trigger read event
            {
                if (epoll_events[i].data.fd == listen_fd)
                {
                    //accept a new connection
                    struct sockaddr_in client_addr;
                    socklen_t client_addr_len = sizeof(client_addr);
                    int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);
                    if (client_fd == -1)
                        continue;

                    socket_flag = fcntl(client_fd, F_GETFL, 0);
                    socket_flag |= O_NONBLOCK;
                    if (fcntl(client_fd, F_SETFL, socket_flag) == -1)
                    {
                        close(client_fd);
                        std::cout << "set client fd to non-block error." << std::endl;
                        continue;
                    }

                    epoll_event client_fd_event;
                    client_fd_event.data.fd = client_fd;
                    client_fd_event.events = EPOLLIN | EPOLLOUT;
                    if (argc > 1) client_fd_event.events |= EPOLLET;

                    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &client_fd_event) == -1)
                    {
                        std::cout << "add client fd to epoll fd error." << std::endl;
                        close(client_fd);
                        continue;
                    }

                    std::cout << "accept a new client fd [" << client_fd << "]." << std::endl;
                }
                else
                {
                    std::cout << "EPOLLIN event triggered for client fd [" << epoll_events[i].data.fd << "]." << std::endl;

                    char recvbuf[1024] = { 0 };
                    int m = recv(epoll_events[i].data.fd, recvbuf, 1, 0); // only read 1 bytes when read event triggered

                    if (m == 0 || (m < 0 && errno != EWOULDBLOCK && errno != EINTR))
                    {
                        if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)
                            std::cout << "the client fd [" << epoll_events[i].data.fd << "] disconnected." <<  std::endl;
                        close(epoll_events[i].data.fd);
                    }

                    std::cout << "recv data from client fd [" << epoll_events[i].data.fd << "] and data is [" << recvbuf << "]." << std::endl;
                }
            }
            else if (epoll_events[i].events & EPOLLOUT)
            {
                if (epoll_events[i].data.fd == listen_fd) //trigger write event
                    continue;

                std::cout << "EPOLLOUT event triggered for client fd [" << epoll_events[i].data.fd << "]." << std::endl;
            }
        }
    }

    return 0;
}

简单说下这段代码的测试方法,可以使用g++ testepoll.cpp -o epoll进行编译,编译后通过./epoll运行为LT模式,通过./epoll et模式运行为ET模式,我们用编译好的epoll程序作为服务器,使用nc命令来模拟一个客户端。

测试分类

1.编译后直接./epoll,然后在另一个命令行窗口用 nc -v 127.0.0.1 51741 命令模拟一次连接,此时 ./epoll 会产生大量的 EPOLLOUT event triggered for client fd ...,那是因为在LT模式下,EPOLLOUT会被一直触发。

albert@home-pc:/mnt/d/data/cpp/testepoll$ ./epoll
start server at port [51741] with [LT] mode.
accept a new client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
...

2.注释包含 EPOLLOUT event triggered for client fd 输出内容的第152行代码,编译后 ./epoll运行,然后在另一个命令行窗口用 nc -v 127.0.0.1 51741 模拟一次连接后,输入abcd回车,可以看到服务器./epoll输出内容,EPOLLIN被触发多次,每次读取一个字节。

albert@home-pc:/mnt/d/data/cpp/testepoll$ ./epoll
start server at port [51741] with [LT] mode.
accept a new client fd [5].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [a].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [b].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [c].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [d].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].

3.还原刚才注释的那行代码,编译后执行 ./epoll et 启动服务器,然后在另一个命令行窗口用 nc -v 127.0.0.1 51741 模拟一次连接后,然后在另一个命令行窗口用 nc -v 127.0.0.1 51741 模拟一次连接,服务器窗口显示触发了EPOLLOUT事件

albert@home-pc:/mnt/d/data/cpp/testepoll$ ./epoll et
start server at port [51741] with [ET] mode.
accept a new client fd [5].
EPOLLOUT event triggered for client fd [5].

在此基础上,从刚刚运行nc命令的窗口中输入回车、输入回车、输出回车,那么epoll服务器窗口看到的是触发了三次EPOLLIN事件,每次收到一个回车:

albert@home-pc:/mnt/d/data/cpp/testepoll$ ./epoll et
start server at port [51741] with [ET] mode.
accept a new client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].

但是如果在nc模拟的客户端里输出abcd回车,那么在epoll服务器窗口触发一次EPOLLIN事件接收到一个a之后便再也不会触发EPOLLIN了,即使你在nc客户端在此输入也没有用,那是因为在接受的缓冲区中一直还有数据,新数据来时没有出现缓冲区从空到有数据的情况,所以在ET模式下也注意这种情况。

albert@home-pc:/mnt/d/data/cpp/testepoll$ ./epoll et
start server at port [51741] with [ET] mode.
accept a new client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [a].

怎么解决ET触发了一次就不再触发了

改代码呗,ET模式在连接后触发一次EPOLLOUT,接收到数据时触发一次EPOLLIN,如果数据没收完,以后这两个事件就再也不会被触发了,要想改变这种情况可以再次注册一下这两个事件,时机可以选择接收到数据的时候,所以可以修改这部分代码:

else
{
    std::cout << "EPOLLIN event triggered for client fd [" << epoll_events[i].data.fd << "]." << std::endl;

    char recvbuf[1024] = { 0 };
    int m = recv(epoll_events[i].data.fd, recvbuf, 1, 0); // only read 1 bytes when read event triggered

    if (m == 0 || (m < 0 && errno != EWOULDBLOCK && errno != EINTR))
    {
        if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)
            std::cout << "the client fd [" << epoll_events[i].data.fd << "] disconnected." <<  std::endl;
        close(epoll_events[i].data.fd);
    }

    std::cout << "recv data from client fd [" << epoll_events[i].data.fd << "] and data is [" << recvbuf << "]." << std::endl;
}

添加再次注册的逻辑:

else
{
    std::cout << "EPOLLIN event triggered for client fd [" << epoll_events[i].data.fd << "]." << std::endl;

    char recvbuf[1024] = { 0 };
    int m = recv(epoll_events[i].data.fd, recvbuf, 1, 0); // only read 1 bytes when read event triggered

    if (m == 0 || (m < 0 && errno != EWOULDBLOCK && errno != EINTR))
    {
        if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)
            std::cout << "the client fd [" << epoll_events[i].data.fd << "] disconnected." <<  std::endl;
        close(epoll_events[i].data.fd);
    }

    epoll_event client_fd_event;
    client_fd_event.data.fd = epoll_events[i].data.fd;
    client_fd_event.events = EPOLLIN | EPOLLOUT;
    if (argc > 1) client_fd_event.events |= EPOLLET;

    epoll_ctl(epoll_fd, EPOLL_CTL_MOD, epoll_events[i].data.fd, &client_fd_event);

    std::cout << "recv data from client fd [" << epoll_events[i].data.fd << "] and data is [" << recvbuf << "]." << std::endl;
}

这次以./epoll et方式启动服务器,使用nc -v 127.0.0.1 51741模拟客户端,输入abc回车发现,epoll服务器输出显示触发的事件变了:

albert@home-pc:/mnt/d/data/cpp/testepoll$ ./epoll et
start server at port [51741] with [ET] mode.
accept a new client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [a].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [b].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [c].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].
EPOLLOUT event triggered for client fd [5].

为什么使用边缘触发必须使用非阻塞

在设置边缘触发时,因为每次发消息只会触发一次(不管缓存区是否还留有数据),所以必须把数据一次性读取出来,否则会影响下一次消息

下面的代码实现的是监听文件描述符,每次固定读取5个字节

先看下面这段代码

int main(int argc, char *argv[])
{
    int epfd, nfds;
    int i;
    struct epoll_event event, events[10];
    char buf[5];
    int flag;
	
    epfd = epoll_create(10);
    event.data.fd = 0; /* 监听标准输入 */
    event.events = EPOLLIN | EPOLLET; /* 读监听、边缘触发 */
    //event.events = EPOLLIN; /* 读监听、边缘触发 */
    epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &event);
 
#if 0
	flag = fcntl(0, F_GETFL);
	fcntl(0, F_SETFL, flag | O_NONBLOCK);
#endif	
 
    while (1)
    {
        int i;
        int num = 0;
        char c;
		
        nfds = epoll_wait(epfd, events, 5, -1); /* 返回就绪的文件描述符 */
		
        for (i = 0; i < nfds; ++i) 
        {
            if (events[i].data.fd == STDIN_FILENO) 
            {
                for(i = 0; i < 5; i++)
                {
                    buf[i] = getc(stdin);
                    if(buf[i] == -1)
                        break;
                }
 
                printf("hello world\n");
            }
            
        }
    }
	
    return 0;
}

这段代码的目的是,使用边沿触发,每次触发读取5个字节,此时getc函数是阻塞读取,这就会引起一个问题,当缓存中的数据小于5时,就会在这里阻塞等待,导致无法处理其他IO,这是非常错误的行为,在并发的服务器中,会导致服务器阻塞,无法处理其他客户端

正确的处理方法是将读取的文件描述符设置为非阻塞,循环读取,如果没有数据了就放回错误,这样就不会让服务器阻塞

可以添加下面这两行代码,将标准输入设置为非阻塞IO

flag = fcntl(0, F_GETFL);
fcntl(0, F_SETFL, flag | O_NONBLOCK);

这就解释了为什么边沿触发必须使用非阻塞的问题。

水平触发和边沿触发的优缺点

水平触发LT:

优点:程序简单,会完整地读取所有数据。

缺点:重复地事件触发会影响高并发服务器地性能,因为epoll监控事件涉及到系统调用,需要用户态-内核态的转换。LT消耗了大量的系统资源,影响服务器性能;

边沿触发ET:

优点:每次epoll_wait只用触发一次,通过程序逻辑实现读取缓冲区的所有数据,工作效率高,大大提升了服务器性能;

缺点(没归纳出来,随便写一个):不能保证数据的完整。(这个可以通过上面提到的程序逻辑实现完整地读取数据)

边沿触发没什么缺点?那是不是用epoll就用ET边沿触发就好了?

我的理解是,YES。在日常用epoll实现并发处理,可以优先使用“边沿触发(EPOLLET)+非阻塞IO”模式。

在高并发服务器中边沿触发(ET)的效率更高

因为边沿触发只在数据到来的一刻才触发,很多时候服务器在接受大量数据时会先接受数据头部(水平触发在此触发第一次,边沿触发第一次)。

接着服务器通过解析头部决定要不要接这个数据。此时,如果不接受数据,水平触发需要手动清除(水平触发当有数据时,会一直触发,直到没有数据可读),而边沿触发可以将清除工作交给一个定时的清除程序去做(只触发一次,不需要的数据可以不读),自己立刻返回。

但是如果sockfd中发送的数据较小,我一次recv就能全部读完,这样LT也不会重复触发epoll事件,和边沿触发的性能差不多,那我们为什么不用更简单的水平触发呢,当然可以使用。那其实就可以理解水平触发和边沿触发是有一个分界点,就是看sockfd的数据是小数据还是大数据。recv的BUFFER_LENGTH如果一次能接收完recv buffer中的数据,就是小数据,一次接收不完就是大数据。小数据就用水平触发,大数据就用边沿触发

但但但但是LT还在一种场景有使用,就是Nginx服务器中listenfd是用的水平触发 。(网络服务器中一般涉及两类sockfd,一种是用于监听是否有连接请求的socket,一种是用于传输数据的socket(上面讲的sockfd都是用于传输数据的))

有一些解释是listenfd用水平触发,如果多个client同时连接进来,listenfd里面积攒多个连接的话,accept一次只处理一个连接,防止漏掉连接,选择水平触发。

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

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

相关文章

深入解析JSP会话跟踪:从原理到实践

什么是会话? 生活中:一次电话.一次取款过程Web应用一次会话:一个客户端浏览器与web服务器之间连续发生的一系列请求和相应的过程 什么是会话状态&#xff1f; 会话状态是指Web服务器与浏览器在会话过程中产生的状态信息。 什么是Cookie&#xff1f; 引言&#xff1a;浏览购…

windows使用tcpdump.exe工具进行抓包教程

windows主机安装一些抓包工具可能有些不方便&#xff0c;这里有一个tcpdump.exe工具直接免安装&#xff0c;可以直接使用进行抓包。&#xff08;工具下载见 附件&#xff09; tcpdump.exe使用教程 如下&#xff1a; 1&#xff1a;tcpdump -D 可查看网络适配器(注意前面的编号)…

htaccess转换nginx工具

115工具网为您提供htaccess与nginx在线转换,apache伪静态文件转为nginx重写规则,htaccess伪静态规则换nginx,apache RewriteRule转rewrite,apache伪静态文件转nginx重写,apache转nginx重写规则&#xff0c;本工具支持所有的htaccess伪静态、基本的配置规则、重定向等转换为ngin…

java多线程编程示例

程序功能 程序展示了 Java 中如何使用多线程来并行执行任务。具体功能如下&#xff1a; 程序创建了三个线程&#xff0c;每个线程执行相同的任务类 Task。 每个线程在运行时输出自身名称&#xff0c;并模拟执行五次任务&#xff0c;每次任务间隔 1 秒。 主线程在启动这三个线程…

技术周总结 09.09~09.15周日(C# WinForm WPF 软件架构)

文章目录 一、09.09 周一1.1) 问题01: Windows桌面开发中&#xff0c;WPF和WinForm的区别和联系&#xff1f;联系&#xff1a;区别&#xff1a; 二、09.12 周四2.1&#xff09;问题01&#xff1a;visual studio的相关快捷键有哪些&#xff1f;通用快捷键编辑导航调试窗口管理 2…

C++:日期类的实现

目录 一、前言 二、头文件 三、各个函数的实现 打印、检查日期及获取日期 、、-、-、 、<、<、>、>、 &#xff01; 日期-日期 >>、<< 一、前言 前面几篇讲了关于类和对象的一些知识&#xff0c;本篇就来实现一下前面用到的日期类。 二、头文…

Java 中 List 常用类和数据结构详解及案例示范

1. 引言 在 Java 开发中&#xff0c;List 是最常用的数据结构接口之一&#xff0c;它用于存储有序的元素集合&#xff0c;并允许通过索引进行随机访问。电商系统中&#xff0c;如购物车、订单列表和商品目录等功能都依赖 List 进行数据管理。选择适当的 List 实现类能够显著提…

三菱变频器变更电流最大输入(20mA 初始值)时的频率(60Hz初始值)

变更最高频率。变更示例 在4~ 20mA 输入频率设定器中&#xff0c;将 20mA 时的频率从 60Hz(初始值)变更为 50Hz。 输入 20mA 电流时调整为输出 50Hz。 将Pr.126 设定为“50Hz” NOTE 4mV 时的频率设定可通过校正参数 C5 设定。 其他的频率设定电流增益的调整方法&#xff0c;还…

计算机网络(七) —— https协议与网络安全证书

目录 一&#xff0c;关于https 二&#xff0c;关于加密 2.1 明文&#xff0c;密钥 2.2 对称和非对称加密 2.3 数据摘要&#xff0c;数据指纹&#xff0c;数字签名 三&#xff0c;https过程过程探究 四&#xff0c;证书 4.1 CA认证 4.2 证书大致内容和申请流程 4.3 签…

试用完几十款ETL工具后的经验总结,ETL工具用这三款就足够了

1.ETL选型前言 市面上ETL工具国内外加起来估计得有30种之多&#xff0c;其中近20款工具都花时间试用过&#xff0c;现在把试用后总结出来的经验分享一下&#xff0c;目前很多企业在选择ETL工具时不知道怎么选择适合自己的工具也不可能一款一款的去试用&#xff0c;试用成本非常…

3.postman脚本语言、接口关联(json引用(变量)、脚本用正则表达式)、断言封装、自动化构造接口请求(Postman工具)

一、Postman的脚本语言 1.使用js语言 2.pm变量 pm.sendRequest():发送HTTP请求 二、自动化实现接口关联 1.JSON引用 2.正则表达式&#xff08;在test下编写如下脚本&#xff09; //获取响应 console.log(responseBody) //re的方式获取token let token responseBody.match(&quo…

佰朔资本:股票中什么叫龙头?怎么找龙头股?

龙头&#xff0c;也便是龙头股&#xff0c;指的是某一工作中有必定影响力和号召力的股票&#xff0c;龙头股的涨跌一般对其他同工作板块股票的涨跌有必定演示和引导效果&#xff0c;是一种风向标一般的存在。龙头股的技能面表现和成交量都会比一同间的大盘和地块要强。 龙头股…

三菱变频器以模拟量电流进行频率设定(电流输入)

POINT 1、在 STF(STR)信号 ON 时&#xff0c;发出启动指令。2、请将 AU 信号置为 ON。 3、请设定 Pr.79 运行模式选择 “2”(外部运行模式)。 接线示例 重点&#xff1a;请将 AU 信号置为 ON。 操作示例&#xff1a;以 60Hz 运行。 1、接通电源时的画面&#xff0c;监视器显…

伪工厂模式制造敌人

实现效果 1.敌人方实现 敌人代码 using UnityEngine; using UnityEngine.UI;public class EnemyBasics : MonoBehaviour {public int EnemySpeed { get; internal set; }public int EnemyAttackDistance { get; internal set; }public int EnemyChaseDistance { get; interna…

YOLOv8 OBB win10+ visual 2022移植部署

前言 想做一个目标旋转角度检测的工程&#xff0c;但是网上多少python的&#xff0c;或者linux的。在win10 visual 2022移植部署&#xff0c;记录一下。 参考 这篇文章没有C win10 环境下的部署教程&#xff0c;我相对于是对此做了补充。 1、下载工程 https://github.com/sh…

Matlab 的.m 文件批量转成py文件

在工作中碰到了一个问题&#xff0c;需要将原来用matlab gui做出来的程序改为python程序&#xff0c;因为涉及到很多文件&#xff0c;所以在网上搜了搜有没有直接能转化的库。参考了【Matlab】一键Matlab代码转python代码详细教程_matlab2python-CSDN博客 这位博主提到的matla…

Redis安装 ▎Redis详细知识点

前言: Redis是一个开源的内存数据结构存储&#xff0c;支持丰富的数据类型&#xff0c;如字符串、哈希、列表、集合和有序集合,作为一个键值对数据库&#xff0c;Redis能提供毫秒级的响应时间&#xff0c;适合高并发应用场景。它还支持持久化&#xff0c;将内存数据定期保存到…

路由器接口配置DHCP实验简述

一、路由器配置 [Huawei]undo info-center enable Info: Information center is disabled. [DHCP-SERVER]sysname DHCP-Server [DHCP-Server]dis this sysname DHCP-Server undo info-center enable return [DHCP-Server]dhcp enable Info: The operation may take a few secon…

【pytorch学习笔记,利用Anaconda安装pytorch和paddle深度学习环境+pycharm安装---免额外安装CUDA和cudnn】

作者链接: link 一、安装pytorch环境 1.打开打开anaconda的终端后 conda env list然后创建一个名字叫pytorch&#xff0c;python是3.8版本的环境 conda create -n pytorch python3.8再次看环境 conda env list# conda environments: #显示如下环境 base …

Rust GUI框架Tauri V1 入门

文章目录 Tauri介绍Vite开始创建 Rust 项目 调用指令window.__TAURI_INVOKE__.invoke is undefined 问题 参考资料JavaScript 模块Vue 框架Vue RouteviteNuxt gitignore文件上传到csdn gitcode网站端本地端 gitcode发布 Tauri介绍 Tauri是一款用Rust构建的开源框架&#xff0c…