Linux应用--IO多路复用

news2024/9/21 22:38:47

一、I/O多路复用简介

        socket通信,在Linux系统其是就是文件描述符,对应于内核中的缓冲区(包含读缓冲区与写缓冲区),实质上是对读写缓冲区的操作;多路复用,多条路复用成一条路。

        I/O多路复用使得程序能同时监听多个文件描述符,Linux下实现I/O多路复用的系统调用主要有select、poll、epoll。

        例如,有十个客户端连接,我们需要知道那个客户端发送了数据,那个客户端没有发送数据,应该怎么操作?遍历文件描述符的读缓冲区。但是不能同时知道。使用I/O复用可以同时监控多个文件描述符。

        几种常见的IO模型;操作IO实质就是看文件描述符中的读写缓冲区是否有数据。

        利用阻塞等待,来判断对应的读写缓冲区是否有数据,其优点是,不需要占用CPU时间,缺点是,同一时刻只能处理一个操作,效率较低。 

        进一步的,添加进程或者线程,可以同时处理多个操作。其优点就是可以连接多个客户端,实现并发操作。缺点是,线程或者进程会消耗资源,消耗CPU资源(进程调度)。

代码如何写?BIO模型;

1、首先创建一个socket,然后链接,监听,阻塞在accept,当有一个客户端连接的时候,进行通信,阻塞在read或者recv中,当有数据操作时,才继续执行,但是如果阻塞在accept的时候,另外在有客户端进来,就不能再accept。

2、改进采用多线程操作, 在线程中阻塞read或者recv。可以同时使得其他客户端进行连接。

根本问题就是阻塞;

非阻塞,忙轮循

        不存在阻塞技术,因为采用不停的轮循,去判断是否有客户端的连接,是否有数据的收发,不需要阻塞在那一块,但是需要不停的轮循遍历,浪费系统资源。

优点:提高程序的执行效率;

缺点:需要占用更多的CPU和资源管理;

IO多路转接技术

        内核检测数据,可以知道有没有数据到,有没有客户端进行连接,但是具体是哪一个还需要轮循遍历。 但是这个遍历不再是遍历read或者recv函数,而是另外的01效率较比之前是有所提升的。

        内核检测,不仅能检测到是否有数据到,还能告诉你是那几个数据到了。在程序中只需呀检测一次,实现多路复用为一路的。

二、select 技术

    主旨思想:

        1、构造一个关于文件描述符的列表,将要监听的文件描述符添加到该列表中。

        2、调用一个系统API——select,监听该列表中的文件描述,直到这些文件描述符中的一个或者过个进行了IO操作,该函数才返回。

        此函数是阻塞的;函数对文件描述符的检测的操作是由内核完成的。

        3、返回时,会告诉进程有多少描述符要进行IO操作。

 其余相关API:

// 将参数文件描述符fd对应的标志位设置为0
void FD_CLR(int fd, fd_set *set);


// 判断fd对应的标志位是0还是1, 返回值 : fd对应的标志位的值,0,返回0, 1,返回1
int  FD_ISSET(int fd, fd_set *set);


// 将参数文件描述符fd 对应的标志位,设置为1
void FD_SET(int fd, fd_set *set);


// fd_set一共有1024 bit, 全部初始化为0
void FD_ZERO(fd_set *set);

        创建一个文件描述符列表,其大小为1024,代表1024个bit位,对应一个文件描述符。检测四个文件描述符,将对应的文件描述符对应标志位设置为1,调用select函数。nfds+1的作用是让内核遍历的位置。将创建的文件描述符,从用户态拷贝到内核态,由内核进行判断,判断完成后再由内核态拷贝到用户态,进而输出检测结果。 

select.c

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>

int main()
{

    //创建socket
    int lfd=socket(PF_INET,SOCK_STREAM,0);
    struct sockaddr_in saddr;
    saddr.sin_port=htons(9999);
    saddr.sin_family=AF_INET;
    saddr.sin_addr.s_addr=INADDR_ANY;
    //绑定
    bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));

    //监听
    listen(lfd,8);

    //创建一个fd_set集合,存放的是需要检测的文件描述符
    fd_set rdset,tmp;

    //初始化
    FD_ZERO(&rdset);
    //添加要检测的文件描述符
    FD_SET(lfd,&rdset);

    int maxfd=lfd;

    //在循环中监听——调用select函数,让内核帮忙进行检测;
        while(1)
        {
          tmp=rdset;
          int ret = select(maxfd+1,&tmp,NULL,NULL,NULL);
            if(ret==-1)
            {
                perror("select");
                exit(-1);
            }
            else if (ret==0)
            {
                continue;
            }
            else if (ret>0)
            {
                //说明文件描述符对应的缓冲区数据发生了改变;

                if(FD_ISSET(lfd,&tmp))
                {
                   //表示有新的客户端连接进来
                   struct sockaddr_in cliaddr;
                   int len=sizeof(cliaddr);
                   int cfd= accept(lfd,(struct sockaddr *)&cliaddr,&len);
                    printf("新的客户端添加:%d\n",cfd);
                    //将新的文件描述符添加到集合中去;
                     FD_SET(cfd,&rdset);
                    //更新最大的文件描述符
                    maxfd= maxfd>cfd ? maxfd:cfd;
                }

                for(int i=lfd+1;i<=maxfd;i++)
                {
                    if(FD_ISSET(i,&tmp))
                    {
                    //说明这个文件描述符对应的客户端发来了数据;
                    char buf[1024]={0};
                    int len =read(i,buf,sizeof(buf));
                        if(len==-1)
                        {
                            perror("read");
                            exit(-1);
                        } 
                        else if(len==0)
                        {
                            printf("client close...\n");
                            close(i);
                            FD_CLR(i,&rdset);
                        }       
                        else if(len>0)
                        {
                            printf("read buf:%s\n",buf);
                            write(i,buf,strlen(buf+1));
                        }
                    }
                }
            }
        }

        close(lfd);
    return 0;
}

client.c 

#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main() {

    // 创建socket
    int fd = socket(PF_INET, SOCK_STREAM, 0);
    if(fd == -1) {
        perror("socket");
        return -1;
    }

    struct sockaddr_in seraddr;
    inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr);
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(9999);

    // 连接服务器
    int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr));

    if(ret == -1){
        perror("connect");
        return -1;
    }

    int num = 0;
    while(1) {
        char sendBuf[1024] = {0};
        sprintf(sendBuf, "send data %d", num++);
        write(fd, sendBuf, strlen(sendBuf) + 1);

        // 接收
        int len = read(fd, sendBuf, sizeof(sendBuf));
        if(len == -1) {
            perror("read");
            return -1;
        }else if(len > 0) {
            printf("read buf = %s\n", sendBuf);
        } else {
            printf("服务器已经断开连接...\n");
            break;
        }
         sleep(1);
       //usleep(1000);
    }

    close(fd);

    return 0;
}

运行结果

服务器:

客户端1

客户端2 

 缺点:

1、每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。

2、同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。

3、select支持的文件描述符数量太小了,默认是1024。

4、fds集合不能重复使用,每次需要重置。

三、poll技术 

改进select中两个缺点:

1、select支持的文件描述符数量太小,默认是1024;

2、fds集合不能重复使用,每次需要重置;

        1024是因为fd_set就是这种类型,改用结构体数组,可以自定义结构体内部大小,再用结构体成员来扩充文件描述符的标志位,可以不需要重置。

对于events和revents的取值有以下的设置:

利用poll实现:

poll.c

#include <sys/poll.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>

int main()
{

    //创建socket
    int lfd=socket(PF_INET,SOCK_STREAM,0);
    struct sockaddr_in saddr;
    saddr.sin_port=htons(9999);
    saddr.sin_family=AF_INET;
    saddr.sin_addr.s_addr=INADDR_ANY;
    //绑定
    bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));

    //监听
    listen(lfd,8);

    //初始化检测的文件描述符数组
    struct pollfd fds[1024];
    for(int i=0;i<1024;i++)
    {
        fds[i].fd=-1;
        fds[i].events=POLLIN;
    }
    fds[0].fd=lfd;
    int nfds=0;
        while(1)
        {
            //调用poll系统函数,让内核帮忙检测哪些文件描述符有数据
            int ret=poll(fds,nfds+1,-1);
            if(ret==-1)
            {
                perror("poll");
                exit(-1);
            }
            else if (ret==0)
            {
                continue;
            }
            else if (ret>0)
            {
                //说明文件描述符对应的缓冲区数据发生了改变;

                if(fds[0].revents&POLLIN)
                {
                   //表示有新的客户端连接进来
                   struct sockaddr_in cliaddr;
                   int len=sizeof(cliaddr);
                   int cfd= accept(lfd,(struct sockaddr *)&cliaddr,&len);
                    printf("新的客户端添加:%d\n",cfd);
                    //将新的文件描述符添加到集合中去;
                     for(int i=1;i<1024;i++)
                     {
                        if(fds[i].fd==-1)
                        {
                            fds[i].fd=cfd;
                            fds[i].events=POLLIN;
                            break;
                        }
                     }
                    //更新最大的文件描述符
                    nfds= nfds>cfd ? nfds:cfd;
                }

                for(int i=1;i<=nfds;i++)
                {
                    if(fds[i].revents & POLLIN)
                    {
                    //说明这个文件描述符对应的客户端发来了数据;
                    char buf[1024]={0};
                    int len =read(fds[i].fd,buf,sizeof(buf));
                        if(len==-1)
                        {
                            perror("read");
                            exit(-1);
                        } 
                        else if(len==0)
                        {
                            printf("client close...\n");
                            fds[i].fd=-1;
                            close(fds[i].fd);
                        }       
                        else if(len>0)
                        {
                            printf("read buf:%s\n",buf);
                            write(i,buf,strlen(buf+1));
                        }
                    }
                }
            }
        }

        close(lfd);
    return 0;
}

运行结果

服务器:

客户端1: 

 客户端2:

四、epoll技术 

poll技术是对select技术的一个改进,解决了select技术中后两个缺点,但是还存在两个缺点:

1、每次调用都要进行一个拷贝;将要检测的文件描述符的信息进行内核的拷贝。

2、调用时仍然需要进行遍历。只能知道有多少返回值发生了改变,但是不知道是哪一个改变了,因此需要进行遍历。

epoll技术原理:

        在内核区创建一个eventpoll数据,返回一个文件描述符,通过这个文件描述符可以操作这块区域的数据。两个重要的成员:rbr与rdlist,rbr采用红黑树的结构;rdlist采用双链表的结构。

        效率提高:1、没有用户态到内核态的拷贝过程;2、之前是线性数组结构,现在是红黑树的结构,现在遍历的速度更快。 

相关API函数说明:

涉及头文件:#include<sys/epoll>

1、 int epoll_creat(int size) 

//创建一个新的epoll实例,在内核中创建了一个数据,这个数据中有两个主要的数据,一个是需要检测的文件描述符的信息(红黑树),一个是就绪列表,存放检测到数据发生改变的文件描述符信息(双向链表)。

        参数:目前没有意义;随便一个数就行;

        返回值:-1,失败。>0,文件描述符,用来操作epoll实例;

2、int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);

//对epoll实例进行管理,添加、删除、修改文件描述符信息;

        参数:

                epfd:epoll实例对应的文件描述符;

                op:要进行什么操作;

                         EPOLL_CTL_ADD:  添加
                         EPOLL_CTL_MOD: 修改
                         EPOLL_CTL_DEL: 删除

                fd:要检测的文件描述符,

                event:要检测文件描述符什么事情;

             

        struct epoll_event {
                uint32_t     events;      /* Epoll events */
                epoll_data_t data;        /* User data variable */
                                     }

                常见的Epoll检测事件:
                   - EPOLLIN
                   - EPOLLOUT
                   - EPOLLERR

                typedef union epoll_data {
                        void        *ptr;
                        int          fd;
                        uint32_t     u32;
                        uint64_t     u64;
                } epoll_data_t;

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

      //检测函数

        参数:epfd:epoll实例对应的文件描述符;

                events:传出参数,保存了发送了变化的文件描述符信息;

                maxevents:第二个参数结构体数组的大小;

                timeout:阻塞时间;

                        0:不阻塞;-1:阻塞(直到检测到fd数据发生变化,解除阻塞);

                        >0:阻塞的时长(毫秒);

        返回值:

                        成功返回发生变化的描述符的个数;失败返回-1;

epoll.c

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>

int main()
{
    //创建socket
    int lfd=socket(PF_INET,SOCK_STREAM,0);
    struct sockaddr_in saddr;
    saddr.sin_port=htons(9999);
    saddr.sin_family=AF_INET;
    saddr.sin_addr.s_addr=INADDR_ANY;
    //绑定
    bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));

    //监听
    listen(lfd,8);

      //调用epoll_creat 创建一个epoll实例
        int epfd= epoll_create(100);
      //将监听的文件描述符相关的检测信息添加到epoll实例中;
      struct epoll_event epev;
      epev.events=EPOLLIN;
      epev.data.fd=lfd;
      epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);
        struct epoll_event epevs[1024];
      while(1)
      {
        int ret=epoll_wait(epfd,epevs,1024,-1);
        if(ret==-1)
        {
             perror("epoll_wait");
             exit(-1);   
        }
        printf("ret=%d\n",ret);
        for(int i=0;i<ret;i++)
        {
            int curfd=epevs[i].data.fd;

            if(curfd==lfd)
            {
                //有客户端连接
                struct sockaddr_in cliaddr;
                int len=sizeof(cliaddr);
                int cfd= accept(lfd,(struct sockaddr *)&cliaddr,&len);
                printf("新的客户端添加:%d\n",cfd);

                 epev.events=EPOLLIN;
                 epev.data.fd=cfd;
                 epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev);
            }
            else{
                // if(epevs[i].events&EPOLLOUT)
                // {
                //     continue;
                // }
                //有数据到达
                 char buf[1024]={0};
                    int len =read(curfd,buf,sizeof(buf));
                        if(len==-1)
                        {
                            perror("read");
                            exit(-1);
                        } 
                        else if(len==0)
                        {
                            printf("client close...\n");
                            epoll_ctl(epfd,EPOLL_CTL_DEL,curfd,NULL); 
                            close(curfd);
                        }       
                        else if(len>0)
                        {
                            printf("read buf:%s\n",buf);
                            write(curfd,buf,strlen(buf+1));
                        }
            }
        }
      }
    close(lfd);
    close(epfd);
    return 0;
}

 client.c

#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main() {

    // 创建socket
    int fd = socket(PF_INET, SOCK_STREAM, 0);
    if(fd == -1) {
        perror("socket");
        return -1;
    }

    struct sockaddr_in seraddr;
    inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr);
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(9999);

    // 连接服务器
    int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr));

    if(ret == -1){
        perror("connect");
        return -1;
    }

    int num = 0;
    while(1) {
        char sendBuf[1024] = {0};
        sprintf(sendBuf, "send data %d", num++);
        write(fd, sendBuf, strlen(sendBuf) + 1);

        // 接收
        int len = read(fd, sendBuf, sizeof(sendBuf));
        if(len == -1) {
            perror("read");
            return -1;
        }else if(len > 0) {
            printf("read buf = %s\n", sendBuf);
        } else {
            printf("服务器已经断开连接...\n");
            break;
        }
         //sleep(1);
       usleep(1000);
    }

    close(fd);

    return 0;
}

运行结果:

  epoll.c

客户端1

客户端2

 epoll的工作模式:

     LT模式(水平触发)

        假设委托内核检测读事件->检测fd的读缓冲区;

        读缓冲区有数据->epoll检测到了会给用户通知;

                用户不读数据,数据一直在缓冲区,epoll会一直通知;

                用户只读了一部分数据,epoll会一直通知;

                缓冲区数据读完了,不通知。

        LT(level-triggered)是缺省的工作方式(默认工作模式),并且同时支持block与no block socket。内核告诉你一个文件描述符是否就绪,然后你可以对这个就绪的fd进行IO操作。如果你不做任何操作,内核还是继续通知你。

     ET模式(边沿触发)

  假设委托内核检测读事件->检测fd的读缓冲区;

        读缓冲区有数据->epoll检测到了会给用户通知;

                用户不读数据,数据一直在缓冲区,epoll下次检测的时候就不通知;

                用户只读了一部分数据,epoll不通知;

                缓冲区数据读完了,不通知。

        ET(edge-trigged)是高速工作方式,只支持no-clock socket,在这种工作模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你,然后他会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪态,但是请注意,如果一直不对这个fd进行IO操作(从而导致它再次变成未就绪态),内核不会发送更多的通知。

        ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率比LT模式高,epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由一个文件句柄的阻塞读/写操作把处理多个文件描述符的任务饿死。

水平触发测试:

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>

int main()
{
    //创建socket
    int lfd=socket(PF_INET,SOCK_STREAM,0);
    struct sockaddr_in saddr;
    saddr.sin_port=htons(9999);
    saddr.sin_family=AF_INET;
    saddr.sin_addr.s_addr=INADDR_ANY;
    //绑定
    bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));

    //监听
    listen(lfd,8);

      //调用epoll_creat 创建一个epoll实例
        int epfd= epoll_create(100);
      //将监听的文件描述符相关的检测信息添加到epoll实例中;
      struct epoll_event epev;
      epev.events=EPOLLIN;
      epev.data.fd=lfd;
      epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);
        struct epoll_event epevs[1024];
      while(1)
      {
        int ret=epoll_wait(epfd,epevs,1024,-1);
        if(ret==-1)
        {
             perror("epoll_wait");
             exit(-1);   
        }
        printf("ret=%d\n",ret);
        for(int i=0;i<ret;i++)
        {
            int curfd=epevs[i].data.fd;

            if(curfd==lfd)
            {
                //有客户端连接
                struct sockaddr_in cliaddr;
                int len=sizeof(cliaddr);
                int cfd= accept(lfd,(struct sockaddr *)&cliaddr,&len);
                printf("新的客户端添加:%d\n",cfd);

                 epev.events=EPOLLIN;
                 epev.data.fd=cfd;
                 epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev);
            }
            else{
                // if(epevs[i].events&EPOLLOUT)
                // {
                //     continue;
                // }
                //有数据到达
                 char buf[5]={0};
                    int len =read(curfd,buf,sizeof(buf));
                        if(len==-1)
                        {
                            perror("read");
                            exit(-1);
                        } 
                        else if(len==0)
                        {
                            printf("client close...\n");
                            epoll_ctl(epfd,EPOLL_CTL_DEL,curfd,NULL); 
                            close(curfd);
                        }       
                        else if(len>0)
                        {
                            printf("read buf:%s\n",buf);
                            write(curfd,buf,strlen(buf+1));
                        }
            }
        }
      }
    close(lfd);
    close(epfd);
    return 0;
}

服务器

 客户端

边沿触发:

 边沿触发不是默认的,需要进行设置;在哪设置呢?常见的Epoll检测事件,添加EPOLLET;

 

         设置了边沿触发,这种模式你要检测的缓冲区里面有数据,epoll检测的时候会通知你,如果没有读取完,epoll再次检测的时候,就不会通知你了。        

        再次发送数据,他就还能触发一次,读取顺序按照读缓冲区中的内容顺序进行读取的。那么边沿触发如果要一次性将数据读出来,那如何操作呢?

   

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
int main()
{
    //创建socket
    int lfd=socket(PF_INET,SOCK_STREAM,0);
    struct sockaddr_in saddr;
    saddr.sin_port=htons(9999);
    saddr.sin_family=AF_INET;
    saddr.sin_addr.s_addr=INADDR_ANY;
    //绑定
    bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));

    //监听
    listen(lfd,8);

      //调用epoll_creat 创建一个epoll实例
        int epfd= epoll_create(100);
      //将监听的文件描述符相关的检测信息添加到epoll实例中;
      struct epoll_event epev;
      epev.events=EPOLLIN;
      epev.data.fd=lfd;
      epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);
        struct epoll_event epevs[1024];
      while(1)
      {
        int ret=epoll_wait(epfd,epevs,1024,-1);
        if(ret==-1)
        {
             perror("epoll_wait");
             exit(-1);   
        }
        printf("ret=%d\n",ret);
        for(int i=0;i<ret;i++)
        {
            int curfd=epevs[i].data.fd;

            if(curfd==lfd)
            {
                //有客户端连接
                struct sockaddr_in cliaddr;
                int len=sizeof(cliaddr);
                int cfd= accept(lfd,(struct sockaddr *)&cliaddr,&len);
                printf("新的客户端添加:%d\n",cfd);
                //设置cfd非阻塞属性;
               int flag= fcntl(cfd,F_GETFL);
                  flag |=O_NONBLOCK;
                  fcntl(cfd,F_SETFL,flag);

                 epev.events=EPOLLIN | EPOLLET;//设置边沿触发
                 epev.data.fd=cfd;
                 epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev);
            }
            else{
                if(epevs[i].events&EPOLLOUT)
                {
                    continue;
                }
               //循环读取所有数据
               char buf[5]={0};
               int len=0;
               while((len=read(curfd,buf,sizeof(buf)))>0)
               {
               //打印数据
               // printf("recv data:%s\n",buf);
                write(STDOUT_FILENO,buf,len);
                write(curfd,buf,len);
               }
            if(len==0)
            {
              printf("client closed...");
              }
            else if (len==-1)
            {
            if(errno==EAGAIN)
            {
              printf("data over....");
            }else
            {
             perror("read");
              exit(-1);
            }
            }
            }
        }
      }
    close(lfd);
    close(epfd);
    return 0;
}

服务器端: 

 客户端:

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

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

相关文章

爬虫动态http代理ip:提高数据抓取的有效工具

爬虫动态HTTP代理IP的概述与应用 在网络爬虫的世界中&#xff0c;动态HTTP代理IP是一个非常重要的工具。它不仅能帮助用户提高数据抓取的效率&#xff0c;还能有效避免被目标网站封禁。本文将为您详细介绍什么是动态HTTP代理IP、其优势、使用场景及如何获取和配置。 1. 什么是…

NVDLA专题8:具体模块介绍——Convolution Accumulator

概述 卷积累加器(Convolution Accumulator&#xff0c; CACC)是CMAC之后的卷积流水线的一个阶段,CACC的定义在NV_NVDLA_cacc.v&#xff0c;module定义如下&#xff1a; module NV_NVDLA_cacc (cacc2sdp_ready //|< i,csb2cacc_req_pd //|<…

ZooKeeper服务器下载|安装|配置|启动|关闭|端口占用冲突解决

1、下载ZooKeeper ZooKeeper官网&#xff1a;https://zookeeper.apache.org/ 下载ZooKeeper二进制包 2、安装ZooKeeper 对ZooKeeper压缩包解压即可 tar -zxvf apache-zookeeper-3.9.2-bin.tar.gz -C /usr/local/3、配置ZooKeeper 来到ZooKeeper配置文件页面 cd conf复制z…

一文详解ETC1压缩纹理——OpenGL中ETC1纹理加载与渲染实践

ETC1(Ericsson Texture Compression)是一种有损纹理压缩技术,2005年初由爱立信研究院参与研发,目的是用于减少移动设备和嵌入式系统中纹理存储的内存占用,应用场景见于游戏、VR、AR等需要大量的纹理资源来创建高质量的视觉效果以及复杂的动画效果场景。 ETC1可提供RGB888像…

宠物医院收银系统源码

1.系统开发语言 核心开发语言: PHP、HTML5、Dart 后台接口: PHP7.3 后合管理网站: HTML5vue2.0element-uicssjs 线下收银台&#xff08;安卓/Windows版&#xff09;: Dart3 框架&#xff1a;Flutter 3.19.6 助手: uniapp 商城: uniapp 2.系统概况 针对宠物医院的一套一体化收…

Unity 流光shader的思路

先看一下直线方程&#xff1a; 1.x0.5y0: 2.x0.5y20: 3.由上面的函数图像可以看出 zxky (k是常量)&#xff0c;表示所有斜率为k的直线集合&#xff0c;z是直线在x轴的截距&#xff0c;每个z的取值都确定一条唯一的斜率为k的直线。 4.那么给z一个取值范围就可以画出一条斜的条…

vulnhub系列:devguru

vulnhub系列&#xff1a;devguru 靶机下载 一、信息收集 nmap扫描存活&#xff0c;根据mac地址寻找IP nmap 192.168.23.0/24nmap扫描端口&#xff0c;开放端口&#xff1a;22、80、8585 nmap 192.168.23.147 -p- -sV -Pn -O访问80端口 dirb目录扫描&#xff0c;存在 git 源…

c shell 脚本学习使用

1.cd /进入该目录等 2.rm -rf filename 删除文件 3、ctrl allt 打开终端窗口 4、ls 查看该路径下的文件 5 mkdir filename 创建文件夹 6、sudo chmod 777 filename 给予权限 首先对于vcs而言&#xff0c;建立其脚本有以下几个步骤: 1、setup synopsys_sim.setu…

mysql中log

目录 MySQL 日志系统概述 日志类型 日志的作用和重要性 Mermaid图示 1. Undo Log 和 Redo Log 的协同工作图 2. Redo Log 确保持久性的流程图 Undo Log&#xff08;回滚日志&#xff09; 事务的原子性&#xff08;Atomicity&#xff09;保障 事务回滚机制 MVCC&#…

MySQL的初步认识

目录 1、MySQL的定义 2、数据库操作 2.1 创建数据库 2.2 查看数据库 2.3 选中数据库 2.4 删除数据库 3、数据表的操作 3.1 创建表 3.2 查看所有表 3.3 查看指定表结构 3.4 删除表 1、MySQL的定义 数据库技术主要是用来解决数据处理的非数值计算问题&#xff0c;数据处…

植物大战僵尸融合版

1.这是植物大战僵尸融合版 2.百度网盘 链接&#xff1a;https://pan.baidu.com/s/1yUytNeloiQs5tlss16fVOg 提取码&#xff1a;yspa 时间从2024年8月14号开始分享30天&#xff0c;10个人访问&#xff0c;先来先得。

项目管理软件中的项目集是什么?项目集管理哪些人适合学习?

在现今的数字化时代&#xff0c;项目管理软件已经成为企业高效运作不可或缺的工具。其中&#xff0c;项目集这一概念在项目管理领域内越来越受到重视。那么&#xff0c;项目管理软件中的项目集究竟是什么呢&#xff1f;它又适合哪些人进行学习和应用呢&#xff1f; 项目、大项目…

生信技能56 - 去除重复BAM文件的窗口reads计数方法

1. 输入去除重复的BAM文件 一般采用BWA MEM比对到参考基因组,对得到的BAM文件去除PCR重复,将去除重复的BAM文件作为窗口reads计数的输入文件。 去除重复方法参考本人文章: 生信软件23 - Samtools和GATK去除PCR重复方法汇总 2. 窗口文件制作 左到右列分别为: 染色体名…

RCE-无字母数字webshell命令执行

目录 1.源码 2.题目解析 3.利用方法 3.1 PHP7下如何实现 3.2PHP5下如何实现 3.2.1 shell下可以利用. 来执行任意脚本 3.2.2 Linux文件名支持用glob通配符代替 1.题目源码 <?php if(isset($_GET[code])){$code $_GET[code];if(strlen($code)>35){die(&q…

pytorch 3 计算图

计算图结构 分析&#xff1a; 起始节点 ab 5 - 3ac 2b 3d 5b 6e 7c d^2f 2e最终输出 g 3f - o&#xff08;其中 o 是另一个输入&#xff09; 前向传播 前向传播按照上述顺序计算每个节点的值。 反向传播过程 反向传播的目标是计算损失函数&#xff08;这里假设为…

看demo学算法之 线性回归模型

嗨&#xff01;今天我们来聊聊如何用Python构建一个简单的线性回归模型。这个过程就像给数据配对舞一样&#xff0c;让它们在舞池里找到最佳位置。准备好了吗&#xff1f;让我们开始吧&#xff01;&#x1f680; 第一步&#xff1a;数据准备 首先&#xff0c;我们要准备一些数…

基因家族Motif 分析

一、名词解释 Motif分析是一种在生物信息学和计算生物学中广泛应用的技术&#xff0c;用于识别DNA、RNA或蛋白质序列中具有生物学功能的短保守序列模式&#xff08;motif&#xff09;。这些motif通常与特定的生物学功能相关&#xff0c;如DNA中的转录因子结合位点、RNA中的剪接…

vue项目名修改、webstorm和idea创建的项目重命名、重构项目、修改项目名称

一、需求 就是创建了一个项目&#xff0c;后期需要重命名&#xff0c;怎么办&#xff1f;----> 直接修改&#xff1f;肯定不行&#xff0c;因为里面有些配置也需要修改&#xff0c;假如你只改文件夹名称的话&#xff0c;里面配置都没修改&#xff0c;后期可能会出问题。 二…

专栏十七:如何选择你的单细胞亚群的分辨率--chooseR

好久没更,没想到还是有小伙伴订阅,那就更一个最近看到的问题 1.缘起 是因为在文章Single-cell RNA sequencing and spatial transcriptomics reveal cancer-associated fibroblasts in glioblastoma with protumoral effects(https://doi.org/10.1172/JCI147087.)中看到 也…

机械行业数字化生产供应链产品解决方案(三)

在机械行业数字化生产供应链产品解决方案中&#xff0c;通过融合物联网&#xff08;IoT&#xff09;技术、数据分析平台与智能自动化系统&#xff0c;实现生产设备和供应链的全方位数字化管理&#xff0c;能够实时监控生产过程、预测维护需求并优化生产调度&#xff0c;同时利用…