• 网络编程 lesson5 IO多路复用


    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    使用步骤

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

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

    fd_set readfds;
    FD_ZERO(&readfds);
    FD_SET(fd1, &readfds);  // 将文件描述符fd1添加到集合中
    FD_SET(fd2, &readfds);  // 将文件描述符fd2添加到集合中
    
    • 1
    • 2
    • 3
    • 4
    2.设置超时时间:

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

    struct timeval timeout;
    timeout.tv_sec = 5;  // 设置超时时间为5秒
    timeout.tv_usec = 0;
    
    • 1
    • 2
    • 3
    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)) {
                // 该文件描述符上有事件发生
                // 处理事件
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

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

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

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

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

    //使用IO多路实现tcp绑定多个服务器
    #include 
    #include  /* See NOTES */
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    /* 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    使用步骤

    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);
    
    • 1
      • 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 
    #include  /* See NOTES */
    #include 
    #include 
    #include 
    #include 
    #include 
    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 
    #include  /* See NOTES */
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167

    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 
    int epoll_create(int size); 
    功能:创建红黑树根节点
     参数:size:不作为实际意义值 >0 即可
    返回值:成功时返回epoll文件描述符,失败时返回-1
    • 1
    • 2
    • 3
    • 4
    • 5

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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; / *用户数据变量* /
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    epoll_wait:等待事件产生

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

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

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

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

    //使用epoll得方式完成多个客户端进行通信
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    //client
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    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--;
    			}
    		  }
    	   }   
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    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--;
    				}
    			 }
    		  }
    	   }
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    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
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

  • 相关阅读:
    自定义hooks
    初级算法_字符串 --- 实现 strStr()
    shell脚本的国际化——gettext与pot、po、mo
    大厂 Framework 面试必备 Handler&Binder 面试题
    数据结构:二叉排序树
    EM聚类(下):用EM算法对王者荣耀英雄进行划分
    sql行转列三个方法
    面向对象编程思维(软件工程)
    创建线程:Thread类和Runnable接口
    mysql 索引选取规则
  • 原文地址:https://blog.csdn.net/weixin_42352787/article/details/130903869