c++高级篇(三) ——Linux下IO多路复用之poll模型

简介: c++高级篇(三) ——Linux下IO多路复用之poll模型

poll模型

前言

poll模型与select的实现原理相近,所以绝大数的原理其实可以参考select,我们这里对二者的相同点不做过多探究,如果有需要可以去看一下博主的上一篇文章:

c++高级篇(二) ——Linux下IO多路复用之select模型

这里我们只对二者的不同处做说明。

poll结构体

在poll模型中,是利用pollfd结构体数组来储存socket通讯中使用的socket,pollfd的结构体实现如下:

struct pollfd
{
    int fd; //存储的socket
    short events; // socket触发的事件
    short revents; // 返回的事件
}

由于poll使用的是结构体数组,所以相比于select,poll没有1024的数量限制。

poll模型存在的问题与一些细节

  • 在程序中,poll’的数据结构是数组,传入内核里面切换为链表
  • 每次调用select()需要拷贝两次bitmap,poll拷贝一次结构体数组
  • poll监视的连接数没有1024的限制,但是随着socket的增多,poll的效率会降低

poll流程图(这里以服务端监听socket为例,只有只读事件)

代码示例

  • poll.h
#include "data-sharing-center/public/_cmpublic.h"
int initsocket(int port);
  • poll.cpp
#include "poll.h"
using namespace std;
int main(int argc,char* argv[])
{
     if(argc!=2)
    {
        cout<<"using example:./server [port]"<<endl;
        return -1;
    }
    int listensock=initsocket(atoi(argv[1]));
    if(listensock<0)
    {
        perror("initsocket() error");
        return -1;
    }
    //定义poll模型的结构体数组
    struct pollfd fds[2048];   //这里写的数字仅说明可以超过1024,具体情况请根据实际情况来判断
    //初始化结构体数组
    for(int ii=0;ii<2048;ii++)
    {
        fds[ii].fd=-1;
        fds[ii].events=POLLIN;   //POLLIN:读,POLLOUT:写,POLLIN|POLLOUT:读写
    }
    int maxfd=listensock;
    while(true)
    {
        //开始监听
        int infds=poll(fds,maxfd+1,100); //最后的数字是超时机制所需的时间,单位为微秒
        if(infds<0)  //连接失败
        {
            perror("poll() error");
            break;
        }
        else if(infds==0)  //超时
        {
            cout<<"poll() timeout"<<endl;
            continue;
        }
        else  //有事件发生
        {
            for(int ii=0;ii<maxfd+1;ii++)  //遍历结构体数组,寻找发生事件的socket
            {
                if(fds[ii].fd==-1) continue;
                if((fds[ii].events&&POLLIN)==0) continue; //没有读事件
                if(fds[ii].fd==listensock)  // 有客户端发送了连接请求
                {
                    struct sockaddr_in clientaddr;
                    socklen_t len=sizeof(clientaddr);
                    int clientsock=accept(listensock,(struct sockaddr*)&clientaddr,&len);
                    if(clientsock<0)
                    {
                        perror("accept() error");
                        break;
                    }
                    cout<<"new client connect"<<endl;
                    //将新的socket加入到结构体数组中
                    fds[maxfd].fd=clientsock;
                    fds[maxfd].events=POLLIN;
                    if(maxfd<clientsock) maxfd=clientsock;
                }
                else
                {
                    //有客户端发送了数据
                    char buff[1024];
                    memset(buff,0,sizeof(buff));
                    int len=recv(fds[ii].fd,buff,sizeof(buff),0);
                    if(len<0)  //说明是客户端关闭了连接
                    {
                        close(fds[ii].fd);
                        fds[ii].fd=-1;
                        fds[ii].events=0;
                        if(fds[ii].fd==maxfd)
                        {
                            for(int ii=maxfd;ii>0;ii--)
                            {
                                if(fds[ii].fd!=-1)
                                {
                                    maxfd=ii;
                                    break;
                                }
                            }
                        }
                    }
                    cout<<"recv data:"<<buff<<endl;
                    //将数据原封不动的发送给客户端
                    send(fds[ii].fd,buff,len,0);
                }
            }
        }
    }
    return 0;
}
int inintsocket(int port)
{
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        perror("socket() error");
        return -1;
    }
    //设置端口复用
    int opt=1;
    unsigned int len=sizeof(opt);
    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,len);
    //绑定端口
    struct sockaddr_in serveraddr;
    serveraddr.sin_family=AF_INET;
    serveraddr.sin_port=htons(port);
    serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
    if(bind(sock,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0)
    {
        perror("bind() error");
        close(sock);
        return -1;
    }
    //监听
    if(listen(sock,5)<0)
    {
        perror("listen() error");
        close(sock);
        return -1;
    }
    return sock;
}

注意: 这里的头文件是博主自己封装的,大家可以使用’man+函数名的方式查看相关函数所需的头文件以及其帮助文档,示例:

相关文章
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
699 34
|
Linux C语言 网络架构
Linux的基础IO内容补充-FILE
而当我们将运行结果重定向到log.txt文件时,数据的刷新策略就变为了全缓冲,此时我们使用printf和fwrite函数打印的数据都打印到了C语言自带的缓冲区当中,之后当我们使用fork函数创建子进程时,由于进程间具有独立性,而之后当父进程或是子进程对要刷新缓冲区内容时,本质就是对父子进程共享的数据进行了修改,此时就需要对数据进行写时拷贝,至此缓冲区当中的数据就变成了两份,一份父进程的,一份子进程的,所以重定向到log.txt文件当中printf和fwrite函数打印的数据就有两份。此时我们就可以知道,
337 0
|
存储 Linux Shell
Linux的基础IO
那么,这里我们温习一下操作系统的概念我们在Linux平台下运行C代码时,C库函数就是对Linux系统调用接口进行的封装,在Windows平台下运行C代码时,C库函数就是对Windows系统调用接口进行的封装,这样做使得语言有了跨平台性,也方便进行二次开发。这就是因为在根本上操作系统确实像银行一样,并不完全信任用户程序,因为直接开放底层资源(如内存、磁盘、硬件访问权限)给用户程序会带来巨大的风险。所以就向银行一样他的服务是由工作人员隔着一层玻璃,然后对顾客进行服务的。
168 0
|
Linux API C语言
Linux基础IO
Linux基础IO操作是系统管理和开发的基本技能。通过掌握文件描述符、重定向与管道、性能分析工具、文件系统操作以及网络IO命令等内容,可以更高效地进行系统操作和脚本编写。希望本文提供的知识和示例能帮助读者更深入地理解和运用Linux IO操作。
335 14
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
8月前
|
Java Unix Go
【Java】(8)Stream流、文件File相关操作,IO的含义与运用
Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。!但本节讲述最基本的和流与 I/O 相关的功能。我们将通过一个个例子来学习这些功能。
315 1
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用
|
Java 大数据 API
Java 流(Stream)、文件(File)和IO的区别
Java中的流(Stream)、文件(File)和输入/输出(I/O)是处理数据的关键概念。`File`类用于基本文件操作,如创建、删除和检查文件;流则提供了数据读写的抽象机制,适用于文件、内存和网络等多种数据源;I/O涵盖更广泛的输入输出操作,包括文件I/O、网络通信等,并支持异常处理和缓冲等功能。实际开发中,这三者常结合使用,以实现高效的数据处理。例如,`File`用于管理文件路径,`Stream`用于读写数据,I/O则处理复杂的输入输出需求。
956 12
|
Java 数据处理
Java IO 接口(Input)究竟隐藏着怎样的神秘用法?快来一探究竟,解锁高效编程新境界!
【8月更文挑战第22天】Java的输入输出(IO)操作至关重要,它支持从多种来源读取数据,如文件、网络等。常用输入流包括`FileInputStream`,适用于按字节读取文件;结合`BufferedInputStream`可提升读取效率。此外,通过`Socket`和相关输入流,还能实现网络数据读取。合理选用这些流能有效支持程序的数据处理需求。
734 2
|
XML 存储 JSON
【IO面试题 六】、 除了Java自带的序列化之外,你还了解哪些序列化工具?
除了Java自带的序列化,常见的序列化工具还包括JSON(如jackson、gson、fastjson)、Protobuf、Thrift和Avro,各具特点,适用于不同的应用场景和性能需求。