第四章:Linux中I/O多路复用的poll技术--对select技术的改进

poll

poll技术是对select技术进行改进,所以select技术肯定具有缺点

select技术的缺点

当客户端多了的时候,也就是fd多了的时候,就会出现如下的一系列问题

其中的第四条就是不使用临时 tmp_set 的问题,read_set应该要继续检测的部分被置为0了,就因为这个时候没有数据进来,所以言下之意就是不能重用,每次都需要重置

第四章:Linux中I/O多路复用的poll技术--对select技术的改进_第1张图片

poll()

使用时引头文件

#include 
struct pollfd {
    int fd; /* 委托内核检测的文件描述符 */
    short events; /* 委托内核检测文件描述符的什么事件 */
    short revents; /* 文件描述符实际发生的事件 */
};

struct pollfd myfd;
myfd.fd = 5;
myfd.events = POLLIN | POLLOUT;

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    - 参数:
        - fds : 是一个struct pollfd 结构体数组,这是一个需要检测的文件描述符的集合
        - nfds : 这个是第一个参数数组中最后一个有效元素的下标 + 1
        - timeout : 阻塞时长
            0 : 不阻塞
            -1 : 阻塞,当检测到需要检测的文件描述符有变化,解除阻塞
            >0 : 阻塞的时长,单位是毫秒
    - 返回值:
        -1 : 失败
        >0(n) : 成功,n表示检测到集合中有n个文件描述符发生变化

第四章:Linux中I/O多路复用的poll技术--对select技术的改进_第2张图片

代码

代码的架构和前面的几乎没有区别,只有server.cpp进行了修改,这里只放出server.cpp

select技术的链接:

https://blog.csdn.net/m0_61588837/article/details/132411683?spm=1001.2014.3001.5501

// server.cpp
#include 
#include 
using namespace std;
#include 
#include 
#include 

#include "Client_Info.h"

#define MAXSIZE 1024
#define MAX_CLIENT_SIZE 1024
#define MAX_POLLFD_SIZE 1025

// 全局存放客户端连接的IP和端口
class Client_Info cli_infos[MAX_CLIENT_SIZE];

// 全局存放需要检测的文件描述符数组
struct pollfd fds[MAX_POLLFD_SIZE];

int bigger(const int& val1, const int& val2) {
    return val1 > val2 ? val1 : val2;
}

void Communicate(const int& _index) {
    int _connect_fd = fds[_index].fd;

    char* _client_ip = cli_infos[_connect_fd].client_ip;
    in_port_t& _client_port = cli_infos[_connect_fd].client_port;

    char buf[MAXSIZE] = {0};
    // 读
    bzero(buf, sizeof(buf));
    int len = read(_connect_fd, buf, sizeof(buf) - 1);
    if (-1 == len) {
        perror("read");
        exit(-1);
    }
    if (len > 0)
        printf("recv client (ip : %s , port : %d) : %s", _client_ip, _client_port, buf);
    else if (0 == len) {  // 客户端关闭
        printf("client ip : %s , port : %d has closed...\n", _client_ip, _client_port);
        // 关闭文件描述符
        close(_connect_fd);
        // 将对应的文件描述符置为-1
        fds[_index].fd = -1;
        return;
    }
    // 写
    write(_connect_fd, buf, strlen(buf));
}

int main() {
    // 1.创建socket
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == listen_fd) {
        perror("socket");
        return -1;
    }

    // 设置一下端口复用
    int _optval = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEPORT, &_optval, sizeof(_optval));

    // 2.绑定IP和端口
    struct sockaddr_in server_addr;
    // 地址族
    server_addr.sin_family = AF_INET;
    // IP
    server_addr.sin_addr.s_addr = INADDR_ANY;
    // 端口
    server_addr.sin_port = htons(9999);

    int ret = bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    if (-1 == ret) {
        perror("bind");
        return -1;
    }

    printf("server has initialized.\n");

    // 3.开始监听
    ret = listen(listen_fd, 8);
    if (-1 == ret) {
        perror("listen");
        return -1;
    }

    // 使用NIO模型,使用poll解决问题
    // 初始化检测的文件描述符数组
    for (int i = 0; i < MAX_POLLFD_SIZE; ++i) {
        fds[i].fd = -1;
        fds[i].events = POLLIN;  // 表示一会儿要去检测读事件
    }
    // 加入监听的文件描述符
    fds[0].fd = listen_fd;

    // 定义最大的文件描述符的fds[]数组的索引
    int nfds = 0;

    while (1) {
        // 调用poll()函数,这是select()函数的改进版本
        ret = poll(fds, nfds + 1, -1);
        if (-1 == ret) {
            perror("select");
            return -1;
        } else if (0 == ret)
            // 为0表示超时并且没有检测到有改变的
            continue;  // 这里我们的设置因为是阻塞的,所以不会走到这里
        else if (ret > 0) {
            // 说明检测到了有文件描述符对应缓冲区的数据发生了改变
            if (fds[0].revents & POLLIN == POLLIN) {
                // 表示有新的客户端连接进来了
                struct sockaddr_in client_addr;
                socklen_t client_addr_len = sizeof(client_addr);
                int connect_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);

                if (-1 == connect_fd) {
                    perror("accept");
                    return -1;
                }

                // 获取客户端的信息
                char ip[MAX_IPV4_STRING] = {0};
                inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip));

                in_port_t port = ntohs(client_addr.sin_port);

                // 打印信息
                printf("client ip : %s , port : %d has connected...\n", ip, port);

                // 将客户端的信息保存到全局数组中
                cli_infos[connect_fd] = Client_Info(ip, port);

                // 将新的文件描述符加入到事件中,注意文件描述符的优先用小的机制
                for (int i = 1; i < MAX_POLLFD_SIZE; ++i)
                    if (fds[i].fd == -1) {
                        fds[i].fd = connect_fd;
                        fds[i].events = POLLIN;
                        // 更新nfds
                        nfds = bigger(nfds, i);
                        break;
                    }
            }

            // 看完监听的文件描述符,看其他的文件描述符是否收到数据
            for (int i = 1; i < nfds + 1; ++i) {
                if (fds[i].revents & POLLIN == POLLIN)
                    Communicate(i);
            }
        }
    }

    // 4.关闭连接
    close(listen_fd);

    return 0;
}
代码分析

首先我们要理解结构体 pollfd 的含义

这是用来保存委托内核检测的文件描述符;委托内核检测的文件描述符的什么事件,比如读写,类似于select中的read_set;还有检测过后实际发生的事件,比如没有读,就修改,类似于select中的 tmp_set;的一个结构体

struct pollfd {
    int fd; /* 委托内核检测的文件描述符 */
    short events; /* 委托内核检测文件描述符的什么事件 */
    short revents; /* 文件描述符实际发生的事件 */
};

值得注意的是这些事件的类型和存储方法,是short类型的,我们来看它可以描述哪些事件

第四章:Linux中I/O多路复用的poll技术--对select技术的改进_第3张图片

其实他和文件属性stat变量里面st_mode(表示文件类型和权限)是一个道理,一个bit位表示一个权限,1表示有,0表示没有,因此添加权限应该用 按位或 | , 这里的事件也是一样的道理,我们一般判断读事件就POLLIN,写事件就POLLOUT

第三个参数就是经过检测之后的状态,可以用它来判断是否有检测到读;由于我们设置的event没有变化,所以相对于select()还是好了很多

其次,我们查看poll()接口的第一个参数是: struct pollfd *fds,需要一个结构体的数组传入进来,每一个元素就封装了一个文件描述符对应的信息,我们从0开始依次记录,如果该元素的fd为-1就表示没有使用,可以存放新的元素,注意这个下标,或者我们称他为索引,索引的值和文件描述符的值是不同的,为了提高效率我们这么设计,在代码中一定要注意,其他的逻辑没什么区别

还有一点,我们看如何判断最后的 revents 检测到读信息

还是前面的思想,每一位对应一个,读对应一位为1,其他为0;当然为什么不是直接相等呢?可能我们设置了其他性质也需要检测,内核处理后还是有了其他的性质为1,我们最好不要冒险,所以这里我们用 &

if (fds[i].revents & POLLIN == POLLIN)
    //下面的操作

你可能感兴趣的:(牛客Linux,linux,c++)