【网络】【Linux】多路转接技术-2.poll(了解)

时间:2024-10-14 14:05:51

poll也是系统提供的一个多路转接接口。

  • poll系统调用也可以让我们的程序同时监视多个文件描述符上的事件是否就绪,和select的定位是一样的,适用场景也是一样的。

2.1poll系统调用及参数介绍

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数说明:

  • fds:一个poll函数监视的结构列表,每一个元素包含三部分内容:文件描述符、监视的事件集合、就绪的事件集合。
  • nfds:表示fds数组的长度。
  • timeout:表示poll函数的超时时间,单位是毫秒(ms)。

参数timeout的取值:

  • -1:poll调用后进行阻塞等待,直到被监视的某个文件描述符上的某个事件就绪。
  • 0:poll调用后进行非阻塞等待,无论被监视的文件描述符上的事件是否就绪,poll检测后都会立即返回。
  • 特定的时间值:poll调用后在指定的时间内进行阻塞等待,如果被监视的文件描述符上一直没有事件就绪,则在该时间后poll进行超时返回。

返回值说明:

  • 如果函数调用成功,则返回有事件就绪的文件描述符个数。
  • 如果timeout时间耗尽,则返回0。
  • 如果函数调用失败,则返回-1,同时错误码会被设置。

poll调用失败时,错误码可能被设置为:

  • EFAULT:fds数组不包含在调用程序的地址空间中。
  • EINTR:此调用被信号所中断。
  • EINVAL:nfds值超过RLIMIT_NOFILE值。
  • ENOMEM:核心内存不足。

(1)pollfd类型

struct pollfd结构当中包含三个成员:

  • fd:特定的文件描述符,若设置为负值则忽略events字段并且revents字段返回0。
  • events:需要监视该文件描述符上的哪些事件。
  • revents:poll函数返回时告知用户该文件描述符上的哪些事件已经就绪。

image-20240812160137420

events和revents的取值:

事件 描述 是否可作为输入 是否可作为输出
POLLIN(常用) 数据(包括普通数据和优先数据)可读
POLLRDNORM 普通数据可读
POLLRDBAND 优先级带数据可读(Linux不支持)
POLLPRI 高优先级数据可读,比如TCP带外数据
POLLOUT(常用) 数据(包括普通数据和优先数据)可写
POLLWRNORM 普通数据可写
POLLWRBAND 优先级带数据可写
POLLRDHUP TCP连接被对方关闭,或者对方关闭了写操作,它由GNU引入
POLLERR 错误
POLLHUP 挂起。比如管道的写端被关闭后,读端描述符上将收到POLLHUP事件
POLLNVAL 文件描述符没有打开

这些取值实际都是以宏的方式进行定义的,它们的二进制序列当中有且只有一个比特位是1,且为1的比特位是各不相同的。

  • 因此在调用poll函数之前,可以通过或运算符将要监视的事件添加到events成员当中。
  • 在poll函数返回后,可以通过与运算符检测revents成员中是否包含特定事件,以得知对应文件描述符的特定事件是否就绪。

2.2poll技术实现echo服务器

poll与select的区别就在于每次不需要重新将输入输出型参数重置,以及可控监控fd数量。

const static int gdefaultport = 8888;
const static int gbacklog = 8;
const static int gnum = 1024;
const static int gdefaultfd = -1;
const static int gdefultevent = 0;

class PollServer
{
    public:
    PollServer(int port = gdefaultport) : _port(port), _listensock(new TcpSocket()), _isrunning(false), _timeout(-1)
    {
    }
    void HandlerEvents()
    {
        for (int i = 0; i < gnum; i++)
        {
            if (_events[i].fd == gdefaultfd)
                continue;

            int fd = _events[i].fd;
            short revents = _events[i].revents;
            if (revents & POLLIN)
            {
                if (fd == _listensock->GetSockFd())
                {
                    // 新连接到来
                    std::string clientip;
                    uint16_t clientport;
                    int sockfd = _listensock->AcceptConnection(&clientip, &clientport);
                    if (sockfd < 0)
                        continue;
                    lg.LogMessage(Debug, "get a new client, %s:%d\n", clientip.c_str(), clientport);
                    int j = 0;
                    for (; j < gnum; j++)
                    {
                        if (_events[j].fd == gdefaultfd)
                            break;
                    }
                    if (j < gnum)
                    {
                        // 给poll添加新的sockfd
                        _events[j].fd = sockfd;
                        _events[j].events = POLLIN;
                        _events[j].revents = gdefultevent;
                        lg.LogMessage(Debug, "add a new client, fd is : %d\n", sockfd);
                        PrintDebug();
                    }
                    else
                    {
                        // 扩容或者close
                    }
                }
                else
                {
                    char buffer[1024];
                    // fd上面的读事件就绪了
                    ssize_t n = recv(fd, buffer, sizeof(buffer) - 1, 0);
                    if (n > 0)
                    {
                        buffer[n] = 0;
                        lg.LogMessage(Debug, "client info# %s\n", buffer);

                        std::string echo_message = "[server echo]# ";
                        echo_message += buffer;
                        // 写回去 --- epoll会说
                        send(fd, echo_message.c_str(), echo_message.size(), 0);
                    }
                    else if (n == 0)
                    {
                        ::close(fd);
                        _events[i].fd = gdefaultfd;
                        _events[i].events = _events[i].revents = gdefultevent;
                        lg.LogMessage(Info, "client quit..., remove fd: %d\n", fd);
                        PrintDebug();
                    }
                    else
                    {
                        ::close(fd);
                        _events[i].fd = gdefaultfd;
                        _events[i].events = _events[i].revents = gdefultevent;
                        lg.LogMessage(Info, "recv error..., remove fd: %d\n", fd);
                        PrintDebug();
                    }
                }
            }
            if (revents & POLLOUT)
            {
                // TODO
            }
        }
    }
    void InitServer()
    {
        _listensock->BuildListenSocketMethod(_port, gbacklog);
        for (int i = 0; i < gnum; i++)
        {
            _events[i].fd = gdefaultfd;
            _events[i].events = gdefultevent;
            _events[i].revents = gdefultevent;
        }
        _events[0].fd = _listensock->GetSockFd();
        _events[0].events = POLLIN; // 表示对读事件关心(listensock上有新连接,就是读事件就绪)
        lg.LogMessage(Debug, "add listen socket, fd is : %d\n", _listensock->GetSockFd());
    }
    void Loop()
    {
        _isrunning = true;
        while (_isrunning)
        {
            int n = poll(_events, gnum, _timeout);
            switch (n)
            {
                case 0:
                    lg.LogMessage(Info, "poll timeout...\n");
                    break;
                case -1:
                    lg.LogMessage(Error, "poll error!!!\n");
                    break;
                default:
                    // 正常的就绪的fd
                    lg.LogMessage(Info, "get a read event!\n");
                    HandlerEvents();
                    break;
            }
        }
        _isrunning = false;
    }
    void Stop()
    {
        _isrunning = false;
    }

    void PrintDebug()
    {
        std::cout << "current poll rfds list is : ";
        for (int i = 0; i < gnum; i++)
        {
            if (_events[i].fd == gdefaultfd)
                continue;
            else
                std::cout << _events[i].fd << " ";
        }
        std::cout << std::endl;
    }
    ~PollServer()
    {
    }

    private:
    std::unique_ptr<Socket> _listensock;
    int _port;
    int _isrunning;
    struct pollfd _events[gnum];
    int _timeout;
};

2.3poll优缺点

(1)poll的优点

  • struct pollfd结构当中包含了events和revents,相当于将select的输入输出型参数进行分离,因此在每次调用poll之前,不需要像select一样重新对参数进行设置。
  • poll可监控的文件描述符数量没有限制,由传入poll函数的第二个参数决定。

(2)poll的缺点

  • 和select函数一样,当poll返回后,需要遍历fds数组来获取就绪的文件描述符。
  • 每次调用poll,都需要把大量的struct pollfd结构从用户态拷贝到内核态,这个开销也会随着poll监视的文件描述符数目的增多而增大。
  • 同时每次调用poll都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。