IO多路转接(二)

简介: IO多路转接

二、poll

2.1 poll函数

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:核心内存不足

struct pollfd结构


fd:特定的文件描述符,若设置为负值则忽略events字段并且revents字段返回0

events:需要监视该文件描述符上的哪些事件

revents:poll函数返回时告知用户该文件描述符上的哪些事件已经就绪

6fcbea78e24a41f985086f444da98150.png


events和revents的取值:


a30f23dc9f954536ab1ac0bcfdb85555.png


这些值都以宏的方式定义,二进制序列中有且只有一个bit位是1,且为1的bit位各不相同


809a7dbf615246c7a8693f90200df251.png


在调用poll函数之前,可以通过或运算符将要监视的事件添加到events成员中

在poll函数返回后,可以通过与运算符检测revents成员中是否包含特定事件,以得知对应文件描述符的特定事件是否就绪

2.2 poll服务器

poll的工作流程和select基本类似,下面也实现一个简单poll服务器,只读取客户端发来的数据并进行打印


PollServer类

#ifndef __POLL_SVR_H__
#define __POLL_SVR_H__
#include <iostream>
#include <string>
#include <poll.h>
#include "Socket.hpp"
#include "Log.hpp"
#include <unistd.h>
#include <cstring>
#include <cerrno>
using namespace std;
#define FD_NONE -1
// 只完成读取,写入和异常不做处理
class PollServer
{
public:
    PollServer(const nfds_t nfds, const uint16_t &port = 9090) : _port(port), _nfds(nfds), _fds(nullptr)
    {
        _listenSocketFd = Socket::SocketCreate();
        Socket::Bind(_listenSocketFd, _port);
        Socket::Listen(_listenSocketFd);
        LogMessage(DEBUG, "create base socket success");
        _fds = new struct pollfd[_nfds];
        _fds[0].fd = _listenSocketFd;
        _fds[0].events = POLLIN;
        for(int i = 1; i < _nfds; ++i) {
            _fds[i].fd = FD_NONE;
            _fds[i].events = _fds[i].revents = 0;
        }
        _timeout = 1000;
    }
    ~PollServer() { 
        if (_listenSocketFd > 0) close(_listenSocketFd); 
        if (_fds != nullptr) delete[] _fds;
    }
public:
    void Start()
    {
        while (true)
        {
            DebugPrint();
            int number = poll(_fds, _nfds, _timeout);
            switch (number)
            {
            case 0:
                LogMessage(DEBUG, "%s", "Time Out ...");
                break;
            case -1:
                LogMessage(WARNING, "Poll Fail: %d : %s", errno, strerror(errno));
                break;
            default:
                HandlerEvent();
                break;
            }
        }
    }
private:
    void Accepter()
    {
        string clientIp;
        uint16_t clientPort = 0;
        int socketfd = Socket::Accept(_listenSocketFd, &clientIp, &clientPort);
        if (socketfd < 0)
        {
            LogMessage(ERROR, "accept error");
            return;
        }
        LogMessage(DEBUG, "Get a link success : [%s : %d] , socketFd : %d", clientIp.c_str(), clientPort, socketfd);
        int pos = 1;
        for (; pos < _nfds; ++pos)
            if (_fds[pos].fd == FD_NONE) break;
        if (pos == _nfds) { // 满了
            //可以进行自动扩容
            LogMessage(ERROR, "%s:%d", "PollServer already full, close:", socketfd);
            close(socketfd);
        }
        else { // 找到空位置
            _fds[pos].fd = socketfd;
            _fds[pos].events = POLLIN;
        }
    }
    void Recver(int i) 
    {
        LogMessage(DEBUG, "message in , get IO event:%d", _fds[i].fd);
        char buffer[1024];
        int num = recv(_fds[i].fd, buffer, sizeof(buffer) - 1, 0);
        if(num > 0) {
            buffer[num] = 0;
            LogMessage(DEBUG, "client[%d]#%s", _fds[i].fd, buffer);
        }
        else if(num == 0) {
            LogMessage(DEBUG, "client[%d] link close, me too...", _fds[i].fd);
            close(_fds[i].fd);
            _fds[i].fd = FD_NONE;
            _fds[i].events = _fds[i].revents = 0;
        }
        else {
            LogMessage(WARNING, "%d recv error, %d : %s", _fds[i].fd, errno, strerror(errno));
            close(_fds[i].fd);
            _fds[i].fd = FD_NONE;
            _fds[i].events = _fds[i].revents = 0;
        }
    }
    void HandlerEvent()
    {
        for (int i = 0; i < _nfds; ++i)
        {
            // 去掉不合法的fd
            if (_fds[i].fd == FD_NONE) continue;
            // 判断是否就绪
            if (_fds[i].revents & POLLIN)
            {
                if (_fds[i].fd == _listenSocketFd) Accepter(); //链接事件
                else  Recver(i);// 读事件
            }
        }
    }
    void DebugPrint()
    {
        cout << "fds[]:";
        for(int i = 0; i < _nfds; ++i) {
            if(_fds[i].fd == FD_NONE) continue;
            cout << _fds[i].fd << " ";
        }
        cout << endl;
    }
private:
    uint16_t _port;
    int _listenSocketFd;
    struct pollfd* _fds; 
    nfds_t _nfds = 100;
    int _timeout;
};
#endif


_fds数组的大小是固定设置的,因此在将新获取连接对应的文件描述符添加到fds数组时,可能会因为fds数组已满而添加失败,这时poll服务器只能将刚刚获取上来的连接对应的套接字进行关闭


poll服务器测试


在调用poll函数时,将timeout的值设置成1000,因此运行服务器后每隔1000毫秒没有客户端发来连接请求,那么服务器就会超时返回

c0f4fbad2b554a119c00a7812ffe0174.png



用telnet工具连接poll服务器后,poll函数在检测到监听套接字的读事件就绪后就会调用accept获取建立好的连接,并打印输出客户端的IP和端口号等信息,此时客户端发来的数据也能成功被poll服务器收到并进行打印输出


e16f6b6aca254ffa8909016651c97cf9.png


poll服务器也是单进程、单线程服务器,同样可以为多个客户端服务

0ecfdb1bd5fb45eda82d88c5c0589d60.png



当服务器端检测到客户端退出后,也会关闭对应的连接,并将对应的套接字从_fds数组中清除


60abc2c89009492c8146cd7092527430.png


2.3 poll的优点 && 缺点

优点


struct pollfd结构中包含了events和revents,相当于将select的输入输出型参数进行分离,因此在每次调用poll之前,不需像select一样重新对参数进行设置

poll可监控的文件描述符数量没有限制

poll也可以同时等待多个文件描述符,提高IO效率

说明一下:


虽然代码中将_fds数组的元素个数定义为100,但_fds数组的大小可以增大,poll函数能监视多少文件描述符由poll函数的第二个参数决定

而fd_set类型只有1024个bit位,因此select函数最多只能监视1024个文件描述符

缺点


和select函数一样,当poll返回后,需要遍历_fds数组来获取就绪的文件描述符

每次调用poll,都需将大量struct pollfd结构从用户态拷贝到内核态,这个开销会随着poll监视的文件描述符数目增多而增大

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


目录
相关文章
|
6月前
IO多路转接 ——— select、poll、epoll(下)
IO多路转接 ——— select、poll、epoll
IO多路转接 ——— select、poll、epoll(下)
|
6月前
|
数据处理 C语言
网络IO 多路IO复用 之 epoll
网络IO 多路IO复用 之 epoll
|
6月前
网络IO 多路IO复用 之 select
网络IO 多路IO复用 之 select
|
6月前
IO多路转接 ——— select、poll、epoll(上)
IO多路转接 ——— select、poll、epoll
|
7月前
|
存储 NoSQL Linux
计算机网络 | IO多路转接技术 | select详解
计算机网络 | IO多路转接技术 | select详解
44 0
|
10月前
|
安全 应用服务中间件 Linux
IO多路转接(三)
IO多路转接
43 0
|
10月前
|
存储 监控 网络协议
IO多路转接(一)
IO多路转接
52 0
|
2天前
|
存储 缓存 安全
Java 中 IO 流、File文件
Java 中 IO 流、File文件
|
2天前
|
Java Unix Windows
|
2天前
|
监控 Java
Java一分钟之-NIO:非阻塞IO操作
【5月更文挑战第14天】Java的NIO(New IO)解决了传统BIO在高并发下的低效问题,通过非阻塞方式提高性能。NIO涉及复杂的选择器和缓冲区管理,易出现线程、内存和中断处理的误区。要避免这些问题,可以使用如Netty的NIO库,谨慎设计并发策略,并建立标准异常处理。示例展示了简单NIO服务器,接收连接并发送欢迎消息。理解NIO工作原理和最佳实践,有助于构建高效网络应用。
8 2

热门文章

最新文章