【Linux】多路转接--select、poll、epoll,非阻塞等待

news2024/9/28 17:31:00

1.IO的概念

IO=等+拷贝数据

  • 等:发送缓冲区满了或者接受缓冲区没有数据,就需要等待

高效IO就是:减少单位时间内,"等"的比重 

2. 阻塞IO和非阻塞IO

2.1.阻塞IO

 阻塞等待会在read的地方等待

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <errno.h>

using namespace std;
int main()
{
    char buffer[1024] = {0};
    // 阻塞IO
    while (1)
    {
        ssize_t s = read(0, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            //去掉回车
            buffer[s-1] = 0;
            printf("read success,echo:%s\n",buffer);
        }
        else if (s == 0)
        {
            printf("write closed,errno:%d\n", errno);
        }
        else
        {
            printf("read failed,errno:%d\n", errno);
        }
    }
    return 0;
}

2.2.非阻塞等待

2.2.1.设置非阻塞等待

1.直接在打开文件的时候设置O_NONBLOCK或者O_NDELAY ,返回的文件描述符就是非阻塞的

int fd1=open("test.txt",O_WRONLY|O_CREAT|O_NONBLOCK,0644);

2.使用fcntl系统函数修改文件描述符

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <fcntl.h>
#include <error.h>

using namespace std;
void SetNonBlock(int fd)
{
    int fl = fcntl(fd, F_GETFL);
    if (fl < 0)
    {
        cerr << "fcntl failed" << endl;
        return;
    }
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}
int main()
{
    SetNonBlock(0);
    return 0;
}

2.2.2.非阻塞的特点

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

using namespace std;

void SetNonBlock(int fd)
{
    int fl = fcntl(fd, F_GETFL);
    if (fl < 0)
    {
        cerr << "fcntl failed" << endl;
        return;
    }
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}
int main()
{
    SetNonBlock(0);
    char buf[1024];
    while (1)
    {
        ssize_t s = read(0, buf, sizeof(buf) - 1);
        if (s > 0)
        {
            //去掉回车
            buf[s-1] = 0;
            printf("read success,echo: %s\n", buf);
        }
        else if (s == 0)
        {
            printf("write closed,errno:%d\n", errno);
        }
        else
        {
            printf("read failed,errno:%d\n", errno);
        }
        sleep(1);
    }
    return 0;
}

结果如下:非阻塞等待,也就是轮询检测,当如果等待没就绪的话read的返回值是<0的,和错误的返回值都是<0的;我们怎么区分read错误和等待没就绪了 

 区分错误和等待未就绪方法

  • 错误都会被设置错误码,为什么会设置C语言的错误码因为Linux是使用C语言编写的,EAGAIN/EWOULDBLOCK的值都是11;

优化后的代码

    while (1)
    {
        ssize_t s = read(0, buf, sizeof(buf) - 1);
        if (s > 0)
        {
            //去掉回车
            buf[s-1] = 0;
            printf("read success,echo: %s\n", buf);
        }
        else if (s == 0)
        {
            printf("write closed,errno:%d\n", errno);
        }
        else
        {
            //等待未就绪,这两个宏都被定义为11
            if(errno==EAGAIN||errno==EWOULDBLOCK)
            {
                cout<<"time out"<<endl;
            }
            else
            {
                printf("read failed,errno:%d\n", errno);
            }
        }
        sleep(1);
    }

 3.多路转接--select

select就是可以同是等待一批IO,这样单位时间内的等待时间减少了;

select函数

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

返回值:等待就绪的个数

int nfds参数 :等待位图中最大的一个文件描述符+1,因为OS执行select使用循环的方式来判断是否等待就绪的:for(int i=0;i<nfds;i++)

fd_set *readfds/writefds/exceptfds参数 :fd_set是一个位图结构,这些参数都是输入输出型参数,需要用户输入需要等待哪些文件描述符,select等待就绪成功,也是把等待就绪的文件描述符写入到这个fd_set位图结构的

struct timeval *timeout参数 :传值nullptr表示阻塞等待,{0,0}表示非阻塞等待,{1,0}两个参数有不为0表示对应的时间没有等待就绪就返回一次;{s,us}前一个是秒,后一个是微秒

3.1.select的执行过程

3.1.1.fd_set位图

因为是一个位图结构,所以需要使用对应函数来操作;

 void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位
 int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真
 void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位
 void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位

3.1.2.执行过程

 3.2.写一个代码:

封装的套接字接口socket.hpp

#pragma once
#include<iostream>
#include<cstdlib>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>

namespace ns_socket{
    class sock{
    public:
        static int Socket()
        {
            int sock=socket(AF_INET,SOCK_STREAM,0);
            if(sock<0)
            {
                std::cerr<<"socket"<<std::endl;
                exit(1);
            }
            return sock;
        }
        static void Bind(int sock,uint16_t port)
        {
            struct sockaddr_in local;
            local.sin_family=AF_INET;
            local.sin_addr.s_addr=INADDR_ANY;
            local.sin_port=htons(port);
            if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
            {
                std::cerr<<"bind"<<std::endl;
                exit(2);
            }
        }
        static void Listen(int sock)
        {
            if(listen(sock,5)<0)
            {
                std::cerr<<"listen"<<std::endl;
                exit(3);
            }
        }
        static int Accept(int sock) 
        {
            struct sockaddr_in tmp;
            socklen_t tlen=sizeof(tmp);
            int new_sock=accept(sock,(struct sockaddr*)&tmp,&tlen);
            if(new_sock<0)
            {
                std::cerr<<"accept"<<std::endl;
                exit(4);
            }
            return new_sock;  
        }
        static void Connect(int sock,char* server_ip,char* server_port)
        {
            struct sockaddr_in local;
            local.sin_family=AF_INET;
            local.sin_addr.s_addr=inet_addr(server_ip);
            local.sin_port=htons(atoi(server_port));
            if(connect(sock,(struct sockaddr*)&local,sizeof(local))<0)
            {
                std::cerr<<"connect"<<std::endl;
                exit(5);
            }
            else
            {
                std::cout<<"connet success"<<std::endl;
            }
        }
    };
}

 建立创建、bind、监听;

#include "socket.hpp"
#include <unistd.h>
#include <sys/select.h>

using namespace ns_socket;
using namespace std;

#define NUM (sizeof(fd_set)*8)//保存数组的大小
void Usage()
{
    std::cout<<"Usage:"<<"./select_server port"<<std::endl;
}
int main(int argc,char* argv[])
{
    if(argc!=2){
        Usage();
        return -2;
    }
    //fd_set readfds;//读等待位图
    //std::cout<<sizeof(readfds)<<std::endl;//测试fd_set位图结构有多少个数

    int sock_listen=sock::Socket();
    cout<<"sock_liste: "<<sock_listen<<endl;
    sock::Bind(sock_listen,uint16_t(atoi(argv[1])));
    sock::Listen(sock_listen);
}

 创建保存数组和fd_set

    fd_set readfds;//读等待位图
    int* fds_array=new int[NUM];//记录select的文件描述符
    for(int i=0;i<NUM;i++)
    {
        fds_array[i]=-1;
    }
    fds_array[0]=sock_listen;

select的注意要有新fd或者删除fd都要添加到或者删除保存数组 

while(true)
    {
        FD_ZERO(&readfds);//情况位图结构
        int max_fd=fds_array[0];//最大文件描述符
        for(int i=0;i<NUM;i++)
        {
            if(fds_array[i]!=-1){
                max_fd=fds_array[i];
                FD_SET(fds_array[i],&readfds);
            }
        }

        timeval tm={1,0};
        int select_retrun=select(max_fd+1,&readfds,nullptr,nullptr,&tm);//第一个参数为最大文件描述符+1;
        if(select_retrun>0)
        {
            for(int sock=0;sock<NUM;sock++)//哪些文件描述符等待成功了并处理他
            {
                if(FD_ISSET(sock,&readfds))
                {
                    if(sock==sock_listen)//新链接
                    {
                        int new_sock=sock::Accept(sock_listen);
                        if(new_sock>=0)
                        {
                            int tmp=0;
                            for(int tmp=0;tmp<NUM;tmp++)//新链接的文件描述符添加会fds数组
                            {
                                if(fds_array[tmp]==-1)
                                {
                                    fds_array[tmp]=new_sock;
                                    cout<<"获得新链接: "<<new_sock<<endl;
                                    break;
                                }
                            }
                            if(tmp==NUM)
                            {
                                close(new_sock);
                                cout<<"链接已满,请重试,关闭描述符: "<<new_sock<<endl;
                            }
                        }
                    }
                    else//新数据读取
                    {
                        cout<<sock<<"号文件描述符等待成功"<<endl;
                        char buffer[1024]{0};
                        ssize_t s=read(sock,buffer,sizeof(buffer)-1);
                        if(s>0)
                        {
                            buffer[s]=0;
                            cout<<buffer<<endl;
                        }
                        else if(s==0)
                        {
                            cout<<"对端关闭,关闭文件描述符: "<<sock<<endl;
                            close(sock);
                            for(int i=0;i<NUM;i++)//fds数组移除对应文件描述符
                            {
                                if(fds_array[i]==sock)
                                    fds_array[i]=-1;
                            }
                        }
                        else
                        {
                            cout<<"读取数据失败,关闭文件描述符: "<<sock<<endl;
                            close(sock);
                            for(int i=0;i<NUM;i++)//fds数组移除对应文件描述符
                            {
                                if(fds_array[i]==sock)
                                    fds_array[i]=-1;
                            }
                        }
                    }
                }
            }
        }
        else if(select_retrun==0)
        {
            cout<<"没有等待成功文件描述符,继续"<<endl;
            continue;
        }
        else
        {
            cout<<"select失败,终止进程"<<endl;
            return -1;
        }
    }
    close(sock_listen);
    return 0;

 3.3.select的优缺点

优点:

  1. 在单进程就可以等待一批进程,单位时间内减少等待的时间

缺点:

  1. 每次等待就绪fd_set位图结构等待的fd就会改变,需要用保存fd的数组再次设置;
  2. fd_set位图结构的大小有上限,所以同时检测的fd是有限的;
  3. select底层需要轮询检测哪些fd的事件就绪了;select的第一个参数要加一的原因;
  4. select可能会较高频率的从用户到内核,从内核到用户的频繁拷贝问题;每次都要重新设置fd_set和就绪改变fd_set;

4.多路转接--poll

poll函数

  • 第一个参数是一个结构体,有文件描述符和short位图;
  • 第二个参数数组的元素多少个;
  • 第三个参数以毫秒为单位;1000就是1s;
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

// pollfd结构
struct pollfd {
 int fd; /* file descriptor */
 short events; /* requested events */
 short revents; /* returned events */
};

struct pollfd:

event和revent的每个位置代表的事件:

写一个poll代码: 

#include "socket.hpp"
#include <unistd.h>
#include <poll.h>

using namespace ns_socket;
using namespace std;
#define NUM 128

void Usage()
{
    std::cout << "Usage:"<< "./poll_server port" << std::endl;
}
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage();
        return -2;
    }

    int sock_listen = sock::Socket();
    cout << "sock_liste: " << sock_listen << endl;
    sock::Bind(sock_listen, uint16_t(atoi(argv[1])));
    sock::Listen(sock_listen);

    struct pollfd poll_arr[NUM];
    for (int i = 0; i < NUM; i++)
    {
        poll_arr[i].fd = -1;
        poll_arr[i].events = 0;
        poll_arr[i].revents = 0;
    }

    poll_arr[0].fd = sock_listen;
    poll_arr[0].events = POLLIN;
    poll_arr[0].revents = 0;

    while (true)
    {
        int ret = poll(poll_arr, sizeof(poll_arr) / sizeof(poll_arr[0]), 2000);
        if (ret > 0)
        {
            for (int i = 0; i < NUM; i++) // 哪些文件描述符等待成功了并处理他
            {
                if (poll_arr[i].revents & POLLIN)
                {
                    if (poll_arr[i].fd == sock_listen) // 新链接
                    {
                        int new_sock = sock::Accept(sock_listen);
                        if (new_sock >= 0)
                        {
                            int tmp=0;
                            for (int tmp = 0; tmp < NUM; tmp++) // 新链接的文件描述符添加会fds数组
                            {
                                if (poll_arr[tmp].fd == -1)
                                {
                                    poll_arr[tmp].fd = new_sock;
                                    poll_arr[tmp].events = POLLIN;
                                    poll_arr[tmp].revents = 0;
                                    cout << "获得新链接: " << new_sock << endl;
                                    break;
                                }
                            }
                            if (tmp == NUM)
                            {
                                close(new_sock);
                                cout << "链接已满,请重试,关闭描述符: " << new_sock << endl;
                            }
                        }
                    }
                    else // 新数据读取
                    {
                        cout << poll_arr[i].fd << "号文件描述符等待成功" << endl;
                        char buffer[1024]{0};
                        ssize_t s = read(poll_arr[i].fd, buffer, sizeof(buffer) - 1);
                        if (s > 0)
                        {
                            buffer[s] = 0;
                            cout << buffer << endl;
                        }
                        else if (s == 0)
                        {
                            cout << "对端关闭,关闭文件描述符: " << poll_arr[i].fd << endl;
                            close(poll_arr[i].fd);
                            for (int j = 0; j < NUM; j++) // fds数组移除对应文件描述符
                            {
                                if (poll_arr[j].fd == poll_arr[i].fd)
                                {
                                    poll_arr[j].fd = -1;
                                    poll_arr[j].events = 0;
                                    poll_arr[j].revents = 0;
                                }
                            }
                        }
                        else
                        {
                            cout << "读取数据失败,关闭文件描述符: " << poll_arr[i].fd << endl;
                            close(i);
                            for (int j = 0; j < NUM; j++) // fds数组移除对应文件描述符
                            {
                                if (poll_arr[j].fd == poll_arr[i].fd)
                                {
                                    poll_arr[j].fd = -1;
                                    poll_arr[j].events = 0;
                                    poll_arr[j].revents = 0;
                                }
                            }
                        }
                    }
                }
            }
        }
        else if (ret == 0)
        {
            cout << "没有等待成功文件描述符,继续" << endl;
            continue;
        }
        else
        {
            cout << "poll失败,终止进程" << endl;
            return -1;
        }
    }
    close(sock_listen);
    return 0;
}

较于select的优点:

  • 就绪了不用再重新拷贝
  • 等待的IO数量没有限制了
  • 代码的书写更简单

5.多路转接--epoll 

5.1.epoll函数

1.创建一个epoll模型,这个模型有3部分:一颗红黑树、回调机制、就绪队列

  • 返回值:一个文件描述符,用完记得关闭;
  • 参数:自从linux2.6.8之后,size参数是被忽略的,当时还是要填一个非零值;
int epoll_create(int size);

 2.用户告诉内核等待那些fd和对应的事件

  • 参数1:创建epoll模型的返回值文件描述符
  • 参数2:删除、修改、增加struct epoll_event结构体的;
  • 参数3:文件描述符,便于查找插入红黑树
  • 参数4:一个结构体,包含时间位图由一个int实现,和fd
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

删除、修改、增加struct epoll_event结构体的宏;

EPOLL_CTL_ADD :注册新的fd到epfd中
EPOLL_CTL_MOD :修改已经注册的fd的监听事件
EPOLL_CTL_DEL :从epfd中删除一个fd

struct epoll_event结构体定义: 

 3.内核告诉用户哪些文件描述符就绪了

  • 返回值:就绪fd的个数;
  • 参数1:创建epoll模型的返回值文件描述符;
  • 参数2:输出型参数需要自己创建一个struct epoll_event数组;
  • 参数3:上面数组的个数;
  • 参数4:毫秒为单位,1000表示1s
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

 5.2.epoll原理:

epoll_create:创建一个epoll模型,这个模型有3部分:一颗红黑树、回调机制、就绪队列

epoll_ctl(epfd,  EPOLL_CTL_ADD,fd, event):添加一个需要检测的fd;添加到红黑树并建立对应的回调机制;

 epoll_wait:轮询检测等待fd,就绪回调机制会知道,并把对应的struct epoll_event添加到就绪队列;

 5.3.写一份代码:

#include "socket.hpp"
#include <unistd.h>
#include <sys/epoll.h>

using namespace ns_socket;
using namespace std;

#define SIZE 128
#define NUM 68

void Usage()
{
    cout << "usage: ./epoll_server port" << endl;
}
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage();
        return -1;
    }
    int listen_sock = sock::Socket();
    sock::Bind(listen_sock, uint16_t(atoi(argv[1])));
    sock::Listen(listen_sock);

    // 建立epoll模型
    int epfd = epoll_create(SIZE);
    cout << "epfd: " << epfd << endl;
    // 设置fd对应的event的事件和fd
    struct epoll_event epevent;
    epevent.events = EPOLLIN | EPOLLOUT;
    epevent.data.fd = listen_sock;

    if (epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &epevent) != 0)
    {
        cout << "listen_sock epoll_ctl fail" << endl;
        return -2;
    }

    volatile bool quit = false;
    struct epoll_event events[NUM];
    while (!quit)
    {
        int timeout = 1000;
        int wait_num = epoll_wait(epfd, events, NUM, timeout);
        if (wait_num > 0)
        {
            // cout<<"有事件就绪了"<<endl;
            for (int i = 0; i < wait_num; i++)
            {
                // 等待成功先拿出fd和判断是哪个事件成功
                int fd = events[i].data.fd;
                if (events[i].events & EPOLLIN)
                {

                    cout << fd << "号文件描述符读就绪" << endl;
                    // 新链接
                    if (fd == listen_sock)
                    {
                        cout << fd << "号文件描述符获取新链接" << endl;
                        int new_sock = sock::Accept(fd);
                        if (new_sock >= 0)
                        {
                            struct epoll_event add_event;
                            add_event.events = EPOLLIN;
                            add_event.data.fd = new_sock;

                            if (epoll_ctl(epfd, EPOLL_CTL_ADD, new_sock, &add_event) == 0)
                                cout << new_sock << "号链接被添加到epoll" << endl;
                            else
                            {
                                close(new_sock);
                                cout << "epoll_ctl fail,close" << new_sock << endl;
                            }
                        }
                    }
                    // 读取数据
                    else
                    {
                        cout << fd << "号文件描述符读取数据" << endl;
                        char buffer[1024] = {0};
                        ssize_t s = read(fd, buffer, sizeof(buffer) - 1);
                        if (s > 0)
                        {
                            buffer[s] = 0;
                            cout << fd << "client: " << buffer << endl;
                        }
                        else if (s == 0)
                        {
                            epoll_ctl(epfd, EPOLL_CTL_DEL, fd, nullptr);
                            close(fd);
                            cout << "对端关闭,已关闭文件描述符和在epoll中去除" << endl;
                        }
                        else
                        {
                            epoll_ctl(epfd, EPOLL_CTL_DEL, fd, nullptr);
                            close(fd);
                            cout << "读取失败,已关闭文件描述符和在epoll中去除" << endl;
                        }
                    }
                }
            }
        }
        else if (wait_num == 0)
            cout << "timeout ..." << endl;
        else
            cout << "epoll error" << endl;
    }
    close(epfd);
    close(listen_sock);

    return 0;
}

5.4.epoll的两种工作模式:LT(水平触发 level drigger)和ET(边缘触发edge drigger) (重要)

LT(水平触发):epoll的默认工作模式

  • 当epoll检测到socket上事件就绪的时候, 可以不立刻进行处理. 或者只处理一部分,后序还会通知;
  • 直到缓冲区上所有的数据都被处理完, epoll_wait 才不会立刻返回;
  • 支持阻塞读写和非阻塞读写

ET(边缘触发):socket新链接添加到epoll描述符的时候设置了EPOLLET标志, epoll进入ET工作模式.

  • 通知场景:从无到有,从有到多,就是必须有新数据才会通知一次
  • epoll检测到socket文件描述符必须立即处理并且必须把数据处理完,因为不会再次通知;
  • 支持非阻塞读写:如果阻塞等待;有100字节的数据,一次读100字节,一次刚好读完阻塞等待还是会再次读读,但是已经没有数据了,那么就会阻塞在这个读取的地方直到有新数据来到;

ET模式较于LT模式的通知效率更高,实际的情况还是要看具体情况,整体效率不仅仅看通知效率,还有对端的发送效率(一次发多少,多少时间发一次)和自己的接受效率(一次读多少);

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

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

相关文章

JavaScript实现输入数字,输出是几月份的代码

以下为实现输入数字&#xff0c;输出是几月份的代码和运行截图 目录 前言 一、实现输入数字&#xff0c;输出是几月份的 1.1 运行流程及思想 1.2 代码段 1.3 JavaScript语句代码 1.4 运行截图 前言 1.若有选择&#xff0c;您可以在目录里进行快速查找&#xff1b; 2.本…

1699_simulink代码生成配置初级方案

全部学习汇总&#xff1a; GreyZhang/g_matlab: MATLAB once used to be my daily tool. After many years when I go back and read my old learning notes I felt maybe I still need it in the future. So, start this repo to keep some of my old learning notes servral …

数据库篇:初始化、建表、配置及调用

微信小程序云开发实战-答题积分赛小程序 数据库篇:初始化、建表、配置及调用 开通云开发服务 点击【云开发】,开通云开发服务; 开通服务完成后,方可继续往下操作; 题库数据表初始化 创建数据表 点击【数据库】,然后点击【+】创建数据表;

彻底告别手动配置任务,魔改xxl-job!

分析 改造 1、接口调用 2、创建新注解 3、自动注册核心 4、自动装配 测试 测试后 XXL-Job是一款非常优秀的任务调度中间件&#xff0c;其轻量级、使用简单、支持分布式等优点&#xff0c;被广泛应用在我们的项目中&#xff0c;解决了不少定时任务的调度问题。不仅如此&a…

RabbitMQ 简单模型

MQ引言 1.1 什么是MQ ​ MQ(Message Quene) : 翻译为消息队列,通过典型的生产者和消费者模型,生产者不断向消息队列中生产消息&#xff0c;消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的&#xff0c;而且只关心消息的发送和接收&#xff0c;没有业务逻辑的侵…

chatGPT+Midjourney制作绘画本

chatGPTMidjourney制作绘画本 灵感来源&#xff1a;https://www.bilibili.com/video/BV1N24y1F7ga/?spm_id_from888.80997.embed_other.whitelist&vd_source6dd97671c42eb7cf111063714216bd0b 最终效果&#xff1a; 绘本故事 故事塑造能力弱的人可以使用chatGPT来帮助编…

wait/waitpid函数等待子进程状态发生改变

&#x1f38a;【进程通信与并发】专题正在持续更新中&#xff0c;进程&#xff0c;线程&#xff0c;IPC&#xff0c;线程池等的创建原理与运用✨&#xff0c;欢迎大家前往订阅本专题&#xff0c;获取更多详细信息哦&#x1f38f;&#x1f38f;&#x1f38f; &#x1fa94;本系列…

【自看】2023前端面试上岸手册——VUE部分

目录 Vue 的基本原理双向数据绑定的原理MVVM、MVC、MVP 的区别slot 是什么&#xff1f;有什么作用&#xff1f;原理是什么&#xff1f;\$nextTick 原理及作用Vue 单页应用与多页应用的区别Vue 中封装的数组方法有哪些&#xff0c;其如何实现页面更新Vue data 中某一个属性的值发…

商品管理系统【控制台+MySQL】(Java课设)

系统类型 控制台类型Mysql数据库存储数据 使用范围 适合作为Java课设&#xff01;&#xff01;&#xff01; 部署环境 jdk1.8Mysql8.0Idea或eclipsejdbc 运行效果 本系统源码地址&#xff1a;https://download.csdn.net/download/qq_50954361/87738976 更多系统资源库地…

辅助驾驶功能开发-功能规范篇(16)-2-领航辅助系统NAP-匝道跟车基础功能

书接上回 2.3.3匝道辅助驾驶 匝道辅助驾驶功能根据导航引导在ODD范围内辅助驾驶车辆进出匝道,主动变道并入或开出主路,并可根据导航路线引导车辆通过跨高速连接路。 前置条件: 1)驾驶员设置导航目的地及导航路线 2)开启辅助驾驶功能,系统进入NOA功能 2.3.3.1.上下匝道…

如何设计一个可扩展的优惠券功能

本文主要分享了如何设计一个可扩展的优惠券功能。 一、功能特性介绍 1.每个条件的代码独立&#xff0c;相当于单独的实现类实现接口&#xff0c;就能通过配置添加到优惠券条件校验当中&#xff0c;支持多种条件灵活组合 2.新增一种使用条件可以不修改核心流程代码&#xff0…

Angular 与PDF之二:打印预览的实现

如何在angular中实现打印和预览pdf的功能, 使用print.js这个包就可实现这个功能 Print.js介绍 Print.js可以打印pdf文件&#xff0c;html元素&#xff0c;图片。官网 https://printjs.crabbly.com/ Print.js使用 首先新建一个angular项目&#xff0c;在项目里下载print.js n…

[JS每M日N练] [格物] - 你所不知道的toString

文章目录 导读Object.prototype.toString常见类型转换结果Object.toString ! Object.prototype.toString对Object.prototype.toString.call(obj)的理解 .toString.toString TypeError误区tostring被改写了定义在原型链的什么位置上方法重写 文章小结参考资料 导读 开发过程中经…

同时使用注解和 xml 的方式引用 dubbo 服务产生的异常问题排查实战

文章目录 一、现象二、问题排查三、结论四、解决方案 一、现象 使用 nacos 作注册中心的线上 dubbo 消费端应用每隔 1 分钟就会抛出以下异常&#xff08;为使描述简单化&#xff0c;文章中使用本地 demo 来复现&#xff09;&#xff0c;该异常表示无法连接到 172.17.0.1:20881…

JavaWeb( 二 ) URL

1.4.URL统一资源定位符 URL代表Uniform Resource Locator 统一资源定位符&#xff0c;也叫 URL地址 。是用于标识和定位Web上资源的地址&#xff0c;通常用于在Web浏览器中访问网站和文件。 URL由若干部分组成&#xff0c;scheme:// host : port / path 例如&#xff1a; htt…

Contest3111 - 计科2101~2104算法设计与分析上机作业07

问题 A: 有重复元素的排列问题 题目描述 设R{ r 1 , r 2 , …, r n }是要进行排列的n个元素。其中元素r 1 , r 2 , …, r n 可能相同。试设计一个算法&#xff0c; 列出R的所有不同排列。给定n 以及待排列的n 个元素。计算出这n 个元素的所有不同排列。 输入 第1 行是元素个…

android四大组件之一-Activity实现原理分析

前言&#xff1a; 这篇文章是我花费时间最久的一篇文章&#xff0c;整整的两个月。整个流程繁琐是一个方面的原因&#xff0c;另外一个原因是我想尽可能的把整个流程的逻辑尽可能详细的一一描述出来&#xff0c;以及结合到我们项目中遇到的一些问题来进行解释&#xff0c;毕竟…

【五一创作】VS+Qt主界面内嵌自定义控件的四种方法以及不同自定义控件数据交互

前言 在Qt界面开发过程中&#xff0c;一个主界面或者主窗口看成是各个控件排列组合后的集合&#xff0c;对于一些项目而言&#xff0c;有些常用的控件可以封装成自己想要的控件样式并且复用&#xff0c;比如说&#xff0c;log显示控件&#xff0c;图像/视频显示控件等&#xf…

【ros2】ros melodic迁移到ros2 dashing过程中碰到的问题及解决方法

序言 总结踩坑经历&#xff0c;以利他人 1. error: forming pointer to reference type … & 报错原因&#xff1a; ros2回调函数的参数不能是引用形式 &&#xff0c;需要去除& 解决方法&#xff1a; 如果是指针引用&#xff0c;直接去除引用 void Callback(con…

【Java开发】Spring Cloud 11:Gateway 配置 ssl 证书(https、http、域名访问)

最近研究给微服务项目配置 ssl 证书&#xff0c;如此才可以对接微信小程序&#xff08;需要使用 https 请求&#xff09;。传统单体项目来说&#xff0c;首先往项目中添加证书文件&#xff0c;然后在配置文件中配置 ssl 证书路径、密码等相关信息&#xff1b;那么微服务这么多项…