网络编程 lesson5 IO多路复用

news2024/11/29 12:31:32

select

当需要在一个或多个文件描述符上等待事件发生时,可以使用select函数。

select函数是一个阻塞调用,它会一直等待,直到指定的文件描述符上有事件发生或超时

select函数详解

 int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);
   功能:select用于监测是哪个或哪些文件描述符产生事件;
   参数:nfds:    监测的最大文件描述个数
        (这里是个数,使用的时候注意,与文件中最后一次打开的文件
          描述符所对应的值的关系是什么?)
    readfds:  读事件集合; //读(用的多)
     writefds: 写事件集合;  //NULL表示不关心
     exceptfds:异常事件集合;  
     timeout:超时检测 1
   如果不做超时检测:传 NULL 
   select返回值:  <0 出错
               >0 表示有事件产生;
   如果设置了超时检测时间:&tv
      select返回值:
         <0 出错
        >0 表示有事件产生;
        ==0 表示超时时间已到;

     struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };
 void FD_CLR(int fd, fd_set *set);//将fd从表中清除
 int  FD_ISSET(int fd, fd_set *set);//判断fd是否在表中
 void FD_SET(int fd, fd_set *set);//将fd添加到表中
 void FD_ZERO(fd_set *set);//清空表1

使用步骤

1.准备文件描述符集合:

创建一个文件描述符集合,用于指定你感兴趣的文件描述符。可以使用FD_ZEROFD_SETFD_CLRFD_ISSET宏来操作文件描述符集合。

fd_set readfds;
FD_ZERO(&readfds);
FD_SET(fd1, &readfds);  // 将文件描述符fd1添加到集合中
FD_SET(fd2, &readfds);  // 将文件描述符fd2添加到集合中

2.设置超时时间:

准备一个timeval结构体,指定select函数的超时时间,或设置为NULL表示没有超时限制。

struct timeval timeout;
timeout.tv_sec = 5;  // 设置超时时间为5秒
timeout.tv_usec = 0;

3.调用select函数:

将文件描述符集合和超时时间作为参数传递给select函数,等待事件发生

int numReady = select(maxfd + 1, &readfds, NULL, NULL, &timeout);
if (numReady == -1) {
    // 处理错误
} else if (numReady == 0) {
    // 超时处理
} else {
    // 有事件发生
    // 遍历文件描述符集合,检查哪些文件描述符上有事件发生
    for (int fd = 0; fd <= maxfd; ++fd) {
        if (FD_ISSET(fd, &readfds)) {
            // 该文件描述符上有事件发生
            // 处理事件
        }
    }
}

select实现io多路复用的特点:

  1. 一个进程只能监听1024个文件描述符
  2. select每次唤醒都会轮询驱动下的poll函数,效率低,消耗资源
  3. select每次都会清空表,清空后需要将用户空间的表重新拷贝到内核空间,浪费时间(0-3g是用户态,3-4g是内核态)

练习1:检测终端输入事件(键盘 0),鼠标输入事件

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define N 20
int main(int argc, char const *argv[])
{
    int pd;
    pd = open("/dev/input/mouse0", O_RDONLY);
    if (pd < 0)
    {
        perror("open mouse0 err.");
        return -1;
    }
    //1.create fd_set table
    fd_set readfds, tempfds;
    FD_ZERO(&readfds); //清空
    //2.add care file descriptor
    FD_SET(0, &readfds);
    FD_SET(pd, &readfds);
    //注意参数
    //3.maxfd
    int maxfd = pd;
    char buf[N] = "";
    //4.Calling select functions
    while (1)
    {
        //5.add tempfds
        tempfds = readfds;
        if (select(maxfd + 1, &tempfds, NULL, NULL, NULL) < 0)
        {
            perror("select err.");
            return -1;
        }
        if (FD_ISSET(0, &tempfds))
        {
            fgets(buf, N, stdin);
            printf("key:%s", buf);
        }
        if (FD_ISSET(pd, &tempfds))
        {
            int ret = read(pd, buf, N);
            buf[ret] = '\0';
            printf("mouse:%s\n", buf);
        }
    }
    close(pd);
    return 0;
}

练习2:使用select可以实现tcp链接多个服务器

//使用IO多路实现tcp绑定多个服务器
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <unistd.h>

/* superset of previous */

#define Port 1025
#define N 128

int main(int argc, char const *argv[])
{
    char buf[N] = "";
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err.");
        return -1;
    }
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(Port);
    saddr.sin_addr.s_addr = INADDR_ANY;
    socklen_t len = sizeof(caddr);

    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("bind err.");
        return -1;
    }
    if (listen(sockfd, 10) < 0)
    {
        perror("listen");
        return -1;
    }

    fd_set readfds, tempfds;
    FD_ZERO(&readfds);

    FD_SET(0, &readfds);
    FD_SET(sockfd, &readfds);
    int maxfd = sockfd;
    while (1) //循环位置注意下
    {
        tempfds = readfds;
        if (select(maxfd + 1, &tempfds, NULL, NULL, NULL) < 0)
        {
            perror("select err.");
            return -1;
        }
        if (FD_ISSET(0, &tempfds))
        {
            fgets(buf, N, stdin);
            printf("%s", buf);
        }
        if (FD_ISSET(sockfd, &tempfds))
        {
            int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
            if (acceptfd < 0)
            {
                perror("accept err");
                return -1;
            }
            else if (acceptfd == 0)
            {
                perror("client exit");
            }
            printf("port:%d\n", ntohs(caddr.sin_port));
            printf("ip address:%s\n", inet_ntoa(caddr.sin_addr));
        }
    }
    close(sockfd);
    return 0;
}

poll

poll函数是一种多路复用的机制,用于同时监视多个文件描述符的状态(通过控制静态数组)

poll函数详解

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
   参数:
   struct pollfd *fds
     关心的文件描述符数组struct pollfd fds[N];
   nfds:个数
   timeout: 超时检测
    毫秒级的:如果填10001秒
     如果-1,阻塞

 struct pollfd {
     int   fd;         /* 检测的文件描述符 */
     short events;     /* 检测事件 */
     short revents;    /* 调用poll函数返回填充的事件,poll函数一旦返回,将对应事件自动填充结构体这个成员。只需要判断这个成员的值就可以确定是否产生事件 */
 };
    事件: 	POLLIN :读事件
        	POLLOUT : 写事件
            POLLERR:异常事件

使用步骤

1.创建并初始化pollfd结构数组:

poll函数使用一个名为struct pollfd的结构体数组来表示要监视的文件描述符以及监视的事件。每个结构体包含了一个文件描述符的信息和要监视的事件类型。可以通过创建并初始化一个pollfd结构体数组来准备监视的文件描述符。

2.设置要监视的文件描述符和事件:

对于每个要监视的文件描述符,设置其对应的文件描述符(fd字段)以及要监视的事件类型(events字段),如读事件(POLLIN)、写事件(POLLOUT)等。

3.调用poll函数:

使用创建好的pollfd结构体数组作为参数,调用poll函数来进行多路复用的操作。poll函数的原型如下:

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    • fds:指向pollfd结构体数组的指针。
    • nfds:数组中要监视的文件描述符的数量。
    • timeout:设置超时时间,以毫秒为单位。指定为-1表示无限等待,指定为0表示立即返回,指定为正整数表示等待的毫秒数。

4.检查poll函数的返回值:

poll函数返回时,会修改pollfd结构体数组中的revents字段,指示发生了哪些事件。可以通过检查revents字段来确定哪些文件描述符发生了事件。

5.处理文件描述符的事件:

根据revents字段的值,处理相应文件描述符发生的事件,如读事件或写事件。可以使用条件语句或循环结构来处理多个文件描述符的事件。

6.重复步骤2-5:

如果需要继续监视文件描述符的事件,可以重复执行步骤2-5,以实现多次的多路复用。

poll实现io多路复用的特点:

  1. 优化文件描述数个数限制,个数由程序员自己进行决定
  2. poll被唤醒后需要轮询一遍驱动下的poll函数,效率低,浪费cpu资源(在代码中能看出来遍历数组)
  3. 只需将用户空间的表拷贝一次到内核空间即可,不会清空文件描述符表

练习1:TCP实现多个服务器和客户端连接(基于poll实现)

//服务器端
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    int socket_fd;
    socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_fd < 0)
    {
        perror("socket");
        return -1;
    }
    struct sockaddr_in ip;
    ip.sin_family = AF_INET;
    ip.sin_port = ntohs(1025);
    ip.sin_addr.s_addr = inet_addr("0.0.0.0");
    if(connect(socket_fd, (struct sockaddr *)&ip, sizeof(ip))<0)
    {
        perror("connect");
        return -1;
    }

    char arr[128];
    int send_val;

    while (1)
    {
        scanf("%s", arr);
        send_val = send(socket_fd, arr, sizeof(arr), 0);
        if (send_val < 0)
        {
            perror("send");
            return -1;
        }
        else if (send_val == 0)
        {
            printf("server is exit");
            break;
        }
    }
    close(socket_fd);
    return 0;
}


//poll函数实现tcp处理服务器
//客户端

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
#include <string.h>

#define Port 1025
#define N 128
#define PollN 100
int main(int argc, char const *argv[])
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("perror socket");
        return -1;
    }
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(Port);
    saddr.sin_addr.s_addr = INADDR_ANY;
    socklen_t len = sizeof(caddr);
    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("bind err.");
        return -1;
    }
    if (listen(sockfd, 5) < 0)
    {
        perror("listen err.");
        return -1;
    }
    //1.创建文件描述符表
    struct pollfd fds[PollN];
    memset(fds, 0, (sizeof(struct pollfd) * PollN));
    //2.将关心得文件描述符添加到表中
    fds[0].fd = 0;
    fds[0].events = POLLIN;

    fds[1].fd = sockfd;
    fds[1].events = POLLIN;
    int last = 1; //标记最大元素下标志
    char buf[N];
    while (1)
    {                                    //3.调用poll函数
        if (poll(fds, last + 1, -1) < 0) //-1表示无限阻塞
        {
            perror("poll err");
            return -1;
        }
        //4.遍历结构体数组
        for (int i = 0; i <= last; i++)
        {
            if (fds[i].revents == POLLIN) //fd是0得情况 
            //第二个会赋值第三个
            {
                if (fds[i].fd == 0)
                {
                    fgets(buf, N, stdin);
                    printf("%s", buf);
                }
                else if (fds[i].fd == sockfd)//fd是sockfd情况
                {
                    int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
                    if (acceptfd < 0)
                    {
                        perror("accept err.");
                        return -1;
                    }
                    printf("port:%d\n", ntohs(caddr.sin_port));
                    printf("Ipaddr:%s\n", inet_ntoa(caddr.sin_addr));

                    /***********************************/
                    last++;
                    fds[last].fd = acceptfd;
                    fds[last].events = POLLIN;
                }
                else//fd是acceptfd情况
                {
                    size_t ret = recv(fds[i].fd, buf, N, 0);
                    if (ret < 0)
                    {
                        perror("recv err.");
                    }
                    else if (ret == 0)
                    {
                        //若是对面服务器退出得处理
                        perror("client exit.");
                        close(fds[i].fd);
                        /***************************************/
                        fds[i] = fds[last];
                        //把最后一个值直接拿过来替换就可,这里得数组不注重存储顺序
                        i--;
                        last--;
                        break;
                    }
                    else //链接成功
                    {
                        printf("%s\n", buf);
                    }
                }
            }
        }
    }
    close(sockfd);
    return 0;
}

epoll

epoll介绍

epoll 是一种事件驱动的 I/O 复用机制,用于高效地处理大量的文件描述符(sockets、文件等)的并发 I/O 操作。它在 Linux 操作系统中提供,用于替代旧的 select和 poll 系统调用。

注意:epoll是 Linux 特有的系统调用,无法在其他操作系统上直接使用。其他操作系统通常使用不同的机制,如 kqueue(BSD 系统)和 IOCP(Windows)来实现类似的功能。

epoll底层原理(了解)

认识红黑树

在这里插入图片描述

epoll底层原理和红黑树有关,先了解下红黑树(新手不必深究)。

红黑树(Red-Black Tree)是一种自平衡的二叉搜索树,它在每个节点上增加了一个额外的属性表示节点的颜色,可以是红色或黑色。红黑树满足以下性质:

  1. 每个节点要么是红色,要么是黑色。
  2. 根节点是黑色。
  3. 每个叶节点(NIL节点,空节点)都是黑色。
  4. 如果一个节点是红色的,则它的两个子节点都是黑色的。
  5. 对于每个节点,从该节点到其子孙节点的所有路径上包含相同数量的黑色节点。

这些性质确保了红黑树的平衡性和高效性。由于红黑树是自平衡的,它的插入、删除和查找操作的时间复杂度都是对数时间 O(log n),其中 n 是树中节点的数量。

红黑树在很多编程语言的标准库中被广泛使用,特别适用于需要高效的插入和删除操作,并且需要保持有序性的场景。它常被用作实现映射(Map)和集合(Set)等数据结构的基础。

红黑树的算法相对复杂,包括了节点的插入、删除和旋转等操作。在实际应用中,通常使用现有的红黑树实现,而不需要手动实现它。许多编程语言和算法库都提供了红黑树的实现,可以直接使用这些库来获得红黑树的功能。

关键机制和数据结构

epoll 的底层原理涉及到 Linux 内核中的几个关键组件和数据结构。

  1. 事件表(Event table):epoll 使用一个事件表来存储待处理的事件和相关的文件描述符。事件表是一个红黑树(Red-Black Tree),用于快速查找和插入事件。每个事件项包含了文件描述符、事件类型以及用户定义的数据。
  2. 等待队列(Wait queue):epoll 通过等待队列来管理等待事件的进程或线程。等待队列是一个链表,其中的每个节点代表一个等待事件的进程或线程。当没有事件发生时,进程或线程会被加入到等待队列中,以便在事件就绪时唤醒。
  3. 文件描述符表(File descriptor table):内核维护着一个文件描述符表,用于跟踪和管理所有打开的文件描述符。每个文件描述符表项包含了文件描述符的状态、操作函数指针等信息。
  4. 内核事件结构体(Kernel event structure):内核使用一种特殊的数据结构来表示事件。这个结构体包含了事件的类型、文件描述符等信息。当一个事件发生时,内核会创建这个结构体,并将其插入到事件表中。

底层原理

  1. 创建 epoll 实例:通过调用 epoll_create 系统调用,内核会分配和初始化一个 epoll 实例,并返回一个文件描述符。
  2. 注册事件:使用 epoll_ctl 系统调用将感兴趣的文件描述符添加到 epoll 实例的事件表中。内核会将文件描述符相关的信息创建为一个内核事件结构体,并插入到事件表中。
  3. 等待事件:使用 epoll_wait 系统调用等待事件发生。当没有事件发生时,进程或线程会被放入等待队列中。当有事件发生时,内核会将相应的事件结构体标记为就绪,并唤醒等待队列中的进程或线程。
  4. 处理事件:进程或线程被唤醒后,可以通过 epoll_wait 返回的就绪事件列表,获取每个事件的文件描述符和事件类型。通过事件的回调函数处理相应的操作,如读取、写入等。
  5. 反复等待:重复执行步骤 3 和步骤 4,以实现事件的持续处理。

函数接口

epoll_create:创建红黑树

#include <sys/epoll.h>
int epoll_create(int size); 
功能:创建红黑树根节点
 参数:size:不作为实际意义值 >0 即可
返回值:成功时返回epoll文件描述符,失败时返回-1

epoll_ctl:控制epoll函数

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:控制epoll属性
    epfd:epoll_create函数的返回句柄。
    op:表示动作类型。有三个宏 来表示:
            EPOLL_CTL_ADD:注册新的fd到epfd中
            EPOLL_CTL_MOD:修改已注册fd的监听事件
            EPOLL_CTL_DEL:从epfd中删除一个fd
    Fd:需要监听的fd。
            event:告诉内核需要监听什么事件
            EPOLLIN:表示对应文件描述符可读
            EPOLLOUT:可写
            EPOLLPRI:有紧急数据可读;
            EPOLLERR:错误;
            EPOLLHUP:被挂断;
            EPOLLET:触发方式,边缘触发;(默认使用边缘触发)
             ET模式:表示状态的变化;
返回值:成功时返回0,失败时返回-1

epoll_ctl涉及到的共用体和结构体

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


struct epoll_event {
uint32_t events; / * Epoll事件* /
epoll_data_t data; / *用户数据变量* /
};

epoll_wait:等待事件产生

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
功能:等待事件的产生,类似于select的用法
     epfd:句柄;
     events:用来保存从内核得到事件的集合;
     maxevents:表示每次能处理事件最大个数;
     timeout:超时时间,毫秒,0立即返回,-1阻塞
成功时返回发生事件的文件描述个数,失败时返回-1

epoll实现io多路复用的特点:

  1. 监听的文件描述符个数无限制(取决于自己系统)
  2. 异步I/O,不需要轮询,使用callback(回调函数)直接拿到唤醒的文件描述符。
  3. epoll不需要重构文件描述表,只需将用户空间表拷贝到内核空间一次即可。

练习:使用epoll实现多个客户端和服务器进行连接

//使用epoll得方式完成多个客户端进行通信
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    char buf[128] = "";
    int socked = socket(AF_INET, SOCK_STREAM, 0);
    if (socked < 0)
    {
        perror("socket err.");
        exit(-1);
    }
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(25535);
    saddr.sin_addr.s_addr = INADDR_ANY;
    socklen_t len = sizeof(caddr);
    if (bind(socked, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("bind err.");
        exit(-1);
    }
    if (listen(socked, 10) < 0)
    {
        perror("listen err.");
        exit(-1);
    }
    printf("listen is ok\n");
    //上面得代码都一样。下面开始引入epoll
    //1.创建一个表
    struct epoll_event event;
    struct epoll_event events[10];
    //epoll引入和红黑树得概念
    //>>1创建一颗树
    int epfd = epoll_create(1);
    //>>2添加关心得文件描述符到树中
    event.data.fd = 0; //添加标准输入
    event.events = EPOLLIN | EPOLLET;
    epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &event);

    event.data.fd = socked;
    event.events = EPOLLIN | EPOLLET;
    epoll_ctl(epfd, EPOLL_CTL_ADD, socked, &event);

    while (1)
    {
        //>>3掉用epoll_wait等待事情发生
        int ret = epoll_wait(epfd, events, 10, -1); //-1表示不进行超时检测
        //epoll_wait返回事件得文件描述符个数
        if (ret < 0)
        {
            perror("epoll_wait err.");
            exit(-1);
        }
        for (int i = 0; i < ret; i++)
        {
            if (events[i].data.fd == 0)
            {
                fgets(buf, 128, stdin);
                printf("stdin said:%s\n", buf);
            }
            else if (events[i].data.fd == socked)
            {
                int accepted = accept(socked, (struct sockaddr *)&caddr, &len);
                if (accepted < 0)
                {
                    perror("accept err.");
                    exit(-1);
                }
                printf("client:ip=%s port=%d\n", inet_ntoa(caddr.sin_addr), htons(caddr.sin_port));
                //链接成功后还得添加到树上
                event.data.fd = accepted;
                event.events = EPOLLIN | EPOLLET;
                epoll_ctl(epfd, EPOLL_CTL_ADD, accepted, &event);
            }
            else //用户发送数据
            {
                int recvbyte = recv(events[i].data.fd, buf, sizeof(buf),0);
                if (recvbyte < 0)
                {
                    perror("recv err.");
                    exit(-1);
                }
                else if (recvbyte == 0)
                {
                    printf("%d client exit\n", events[i].data.fd);
                    close(events[i].data.fd);
                    //下树操作
                    epoll_ctl(epfd, EPOLL_CTL_DEL,events[i].data.fd, NULL);
                }
                else
                {
                    printf("%d %s", events[i].data.fd, buf);
                }
            }
        }
    }
    close(socked);

    return 0;
}

//client
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void show(void);
void list_client(int sockfd, char *buf, int size);
void put_client(int sockfd, char *buf, int size);
void get_client(int sockfd, char *buf, int size);
int main(int argc, char const *argv[])
{
    //1.创建套接子
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err.");
        return -1;
    }

    //填充结构体
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(25535);
    saddr.sin_addr.s_addr = INADDR_ANY;

    if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("connect err.");
        return -1;
    }
    //收发消息
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork err");
        return -1;
    }
    else if (pid == 0)
    {
        char buf[128];
        while (1)
        {
            fgets(buf, sizeof(buf), stdin);
            if (buf[strlen(buf) - 1] == '\n')
            {
                buf[strlen(buf) - 1] == '\0';
            }
            send(sockfd, buf, sizeof(buf), 0);
        }
    }
    else
    {
        char buf[128];
        int recvtype;
        while (1)
        {
            recvtype = recv(sockfd, buf, sizeof(buf), 0);
            if (recvtype < 0)
            {
                perror("recv err");
                return -1;
            }
            printf("%s\n", buf);
        }
    }
    close(sockfd);
    return 0;
}

select,poll,epoll伪代码

select伪代码

//函数原型select(nfds,&readfds,&writefds,&exportfds,&timeout) 

  1.fd_set readfds,tempfds;
    FD_ZERO(&readfds);
  2.FD_SET(0,&readfds)
    FD_SET(sockfd,&readfds);
	int maxfd=sockfd;
  3.while(1)
   {
       tempfds=readfds;
       int ret=select(maxfd+1,&tempfds,NULL,NULL,NULL);
	   4.if(FD_ISSET(0,&tempfds))
	   {
	      fgets()
	   }
	   if(FD_ISSET(sockfd,&tempfds))
	   {
	      acceptfd=accept();
		  FD_SET(acceptfd,&readfds);
		  if(maxfd < acceptfd)
		    maxfd=acceptfd;
	   }
	   for(int i=4;i<=maxfd;i++)
	   {
	      if(FD_ISSET(i,&tempfds))
		  {
		    recv();
			if(退出)
			{
			   close(i);
			   FD_CLR(i,&readfds);
			   if(i==maxfd)
			     maxfd--;
			}
		  }
	   }   
   }

poll伪代码

//函数原型int poll(struct pollfd *fds, nfds_t nfds, int timeout);

1.struct pollfd fds[200]={};
 /*struct pollfd 
 {
     int fd;
	 short events;//检测事件  POLLIN  POLLOUT  
	 short revents;//poll函数返回用于判断,无0,有revents=events
 }*/
2.fds[0].fd=0;
  fds[0].events=POLLIN;

  fds[1].fd=sockfd;
  fds[1].events=POLLIN;

  int last=1;

   3.while(1)
   {
       int ret=poll(fds,last+1,-1);
	   for(int i=0;i<=last;i++)
	   {
	      if(fds[i].revents == fds[i].events)
		  { 
		     if(fds[i].fd==0)
			 {
			 }else if(fds[i].fd==sockfd)
			 {
			     acceptfd=accept();
				 last++;
				 fds[last].fd=acceptfd;
				 fds[last].events=POLLIN;
			 }else
			 {
			    recv();
				if(退出)
				{
				   close(fds[i].fd);
				   fds[i]=fds[last];
				   last--;
				   i--;
				}
			 }
		  }
	   }
   }

epoll伪代码

int epfd=epoll_create(1)
	 epoll_ctl(epfd,op,fd,&event)
	 op:EPOLL_CTL_ADD   EPOLL_CTL_DEL 
	 struct epoll_event
	 {
	     int events;
		xxx  data.fd;
	 }

  1.int epfd=epoll_create(1)

  2. struct epoll_event  event;
     event.data.fd=0;
     event.evnets=EPOLLIN|EPOLLET;
     epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event)

     event.data.fd=sockfd;
     event.evnets=EPOLLIN|EPOLLET;
     epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&event)

  3.while(1)
	struct epoll_event events[20];
    int ret=epoll_wait(epfd,events,20,-1);
	for(int i=0;i<ret;i++)
	{
	   //直接处理
	   if(events[i].data.fd==0)
	   {
	   }else if(events[i].data.fd==sockfd)
	   {
	       acceptfd=accept();
		    event.data.fd=acceptfd;
			event.evnets=EPOLLIN|EPOLLET;
			epoll_ctl(epfd,EPOLL_CTL_ADD,acceptfd,&event)
	   }else{
	       recv();
		   if(退出)
		   {
		      close(events[i].data.fd);
			  epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,NULL);
		   }
	   }
	}

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

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

相关文章

初识SPDK,从SPDK的软件架构到使用实操

相信很多做存储的同学都听说过SPDK,它是Intel开发的一套开源存储栈。SPDK的全称为存储高性能开发包(Storage Performance Development Kit),从名称可以看出SPDK其实就是一个第三方的程序库。但是这个程序库却是非常强大的,下图是SPDK的软件模块图,从该图可以看出,几乎囊…

Linux---用户管理命令(useradd、userdel、usermod、passwd、id)

1. 用户与用户组 Linux系统是一个多用户多任务的分时操作系统&#xff0c;任何一个要使用系统资源的用户&#xff0c;都必须首先向 系统管理员申请一个账号&#xff0c;然后以这个账号的身份进入系统。 Linux系统中可以&#xff1a; 配置多个用户、配置多个用户组、用户可以…

什么?电路板上还要喷漆?

什么是三防漆&#xff1f; 三防漆是一种特殊配方的涂料&#xff0c;用于保护线路板及其相关设备免受环境的侵蚀。三防漆具有良好的耐高低温性能&#xff1b;其固化后成一层透明保护膜&#xff0c;具有优越的绝缘、防潮、防漏电、防震、防尘、防腐蚀、防老化、耐电晕等性能。 在…

MIT6824——lab4(实现一个分片kv存储)的一些实现,问题,和思考

Part A 分片控制器 1. 整体思路 和lab3A一样&#xff0c;shardctler也是一个服务&#xff0c;由客户端调用。这个服务建立在raft集群上&#xff0c;保证容错。 shardctler也应该保证线性一致性和重复请求的问题&#xff0c;因此也需要记录clientid和messageid。 shardctler保…

BFT 最前线 | 张一鸣成立个人基金;马斯克:AI是双刃剑;阿里首席安全科学家离职;卡内基梅隆究团队:解决农业虫卵问题的机器人

文 | BFT机器人 名人动态 CELEBRITY NEWS 01 字节跳动创始人张一鸣 在香港成立个人投资基金 在卸任CEO两年后&#xff0c;字节跳动创始人张一鸣在香港成立了一家个人投资基金。香港公司注册处网站显示&#xff0c;该基金名为Cool River Venture&#xff0c;性质是私人股份有限…

doris索引

目前 Doris 主要支持两类索引&#xff1a; - 内建的智能索引&#xff1a;包括前缀索引和 ZoneMap 索引。 - 用户创建的二级索引&#xff1a;包括 Bloom Filter 索引 和 Bitmap倒排索引。其中 ZoneMap 索引是在列存格式上&#xff0c;对每一列自动维护的索引信息&#xff0c;包…

Go 语言实战案例:猜谜游戏在线词典SOCKS5代理服务器 Go学习路线

字节跳动后端入门 - Go 语言原理与实践& vscode配置安装Go 3.1猜谜游戏 3.1.2 生成随机数v2 package mainimport ("fmt""math/rand""time" )func main() {maxNum : 100rand.Seed(time.Now().UnixNano())secretNumber : rand.Intn(maxNum)fmt…

OS之页面置换算法

目录 一、最佳置换算法(OPT) 定义 案例 二、先进先出置换算法(FIFO) 定义 案例 FIFO特有的异常 三、最近最久未使用置换算法(LRU) 定义 案例 四、时钟置换算法(CLOCK) 定义 案例 五、改进型的时钟置换算法 定义 案例 一、最佳置换算法(OPT) 定义 每次选择淘汰…

GoWeb -- gin框架的入门和使用(2)

前言 书接上回&#xff0c;在gin的框架使用中&#xff0c;还有着许多方法以及它们的作用&#xff0c;本篇博客将会接着上次的内容继续记录本人在学习gin框架时的思路和笔记。 如果还没有看过上篇博客的可以点此跳转。 map参数 请求url&#xff1a; http://localhost:8080/us…

全志V3S嵌入式驱动开发(驱动开发准备)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 之前的文章都是教大家怎么搭建环境、看原理图、编译内核和根文件系统、做镜像&#xff0c;直到现在才进入驱动开发的主题。毕竟整个专栏的目的&…

Python 基础(十四):类和对象

❤️ 博客主页&#xff1a;水滴技术 &#x1f338; 订阅专栏&#xff1a;Python 入门核心技术 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; 文章目录 一、面向对象编程二、创建类三、创建实例3.1、访问属性3.2、调用方法 四、属性默认值4…

网络通信协议-ICMP协议

目录 一、ICMP协议 二、ICMP协议通信过程 &#xff08;1&#xff09;机制 &#xff08;2&#xff09;原理 &#xff08;3&#xff09;相关术语 丢包率 网络延时率&#xff08;延迟&#xff09; 请求超时【类似表白对方压根不搭理你】 没有任何回复数据&#xff0c;回复…

DNS/ICMP协议/NAT技术

本博文分享DNS&#xff08;简单认识&#xff09;、ICMP&#xff08;简单认识&#xff09;和NAT技术&#xff08;重点学习&#xff09;。 DNS DNS是一整套从域名映射到IP的系统&#xff0c;TCP/IP中使用IP地址和端口号来确定网络上的一台主机的一个程序。但是IP地址不方便记忆&…

windows下mysql中binlog日志分析和数据恢复

1.首先查看是否开启了binlog show variables like %log_bin%;看到了是没有开启的。 2.开启binlog日志&#xff0c;并重启mysql服务 不能通过命令的方式去打开&#xff0c;因为会提示说这个参数是只读的。如下图&#xff1a; 所以&#xff0c;打开mysql的配置文件&#xff…

Three.js--》实现3d地球模型展示

目录 项目搭建 实现网页简单布局 初始化three.js基础代码 创建环境背景 加载地球模型 实现光柱效果 添加月球模型 今天简单实现一个three.js的小Demo&#xff0c;加强自己对three知识的掌握与学习&#xff0c;只有在项目中才能灵活将所学知识运用起来&#xff0c;话不多…

配置MIT6.S081环境

配置环境 1实现环境2更换源3.安装RISC-V交叉编译工具4.安装QEMU5.检测安装6.下载源码7.检查调试工具8.安装vim9.进行初步调试 1实现环境 虚拟机&#xff08;我用的VMware Workstation Pro&#xff09;Ubantu20.04安装QEMU 注意必须使用ubuntu20.04版本&#xff0c;因为后面安装…

Webpack打包图片-JS-Vue

1 Webpack打包图片 2 Webpack打包JS代码 3 Babel和babel-loader 5 resolve模块解析 4 Webpack打包Vue webpack5打包 的过程&#xff1a; 在webpack的配置文件里面编写rules&#xff0c;type类型有多种&#xff0c;每个都有自己的作用&#xff0c;想要把小内存的图片转成bas…

华为OD机试之不含101的整数(Java源码)

不含101的数 题目描述 小明在学习二进制时&#xff0c;发现了一类不含 101的数&#xff0c;也就是&#xff1a; 将数字用二进制表示&#xff0c;不能出现 101 。 现在给定一个整数区间 [l,r] &#xff0c;请问这个区间包含了多少个二进制不含 101 的整数&#xff1f; 输入描述…

WalkRE--刷图流程(超具体)

1、打开WalkRE软件&#xff0c;界面如下&#xff1a; 2、选择“根据模板新建工程”。操作如下&#xff1a; 3、导入数据。需要入准备好的dxf格式的CAD地形数据。操作如下&#xff1a; 在空白处右键&#xff0c;先关闭所有层&#xff08;大部分层在刷图时用不上&#xff0c;仅打…

自动化测试2:selenium常用API

目录 1.webdirver API 1.1.定位元素 1.2CSS 1.语法 2,使用 1.3XPath定位 1.语法 2.使用 2.操作测试对象 2.1.鼠标点击与键盘输入 2.2submit 提交表单 2.3text 获取元素文本 3.添加等待 3.1.sleep休眠 3.2.智能等待 3.2.1.隐式等待 3.2.2显示等待 4.打印信息 …