Linux I/O多路复用——poll模型实现服务端Socket通信

2023-11-16

目录

poll函数

参数说明

events相关

与select的不同

程序流程

程序实例


poll函数

     poll模型在实现服务端时思路是和select类似的,可以说poll是select的加强版,poll函数原型如下:

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

参数说明

fds:文件描述符结构体指针,实际传参时应传入该结构体数组。通过该结构体可以设置文件描述符及其对应的感兴趣事件以及最终返回的事件,该结构体定义如下:

struct pollfd {
               int   fd;         /* file descriptor */      
               short events;     /* requested events */
               short revents;    /* returned events */
           };

nfds: fds所指结构体数组的元素个数,即需要监听的描述符个数;

timeout:设置超时事件。

返回值:超时返回0;失败返回-1;成功返回大于0的整数,这个整数表示就绪描述符的数目。

events相关

POLLIN:有数据可读;

POLLPRI:有优先级数据可读;

POLLOUT:数据可写;

POLLERR:发生错误;

POLLHUP:发生挂起;

POLLNVAL:描述符未打开;

POLLRDNORM:同POLLIN

POLLRDBAND:优先级数据可读;

POLLWRNORM:同 POLLOUT;

 POLLWRBAND:优先级数据可写。

与select的不同

       poll与select的不同之处在于:

1.监听描述符的个数由结构体数组决定,因此没有select的限制;

2.对于select函数,需要监控的描述集合与成功监控的描述集合是同一个参数,而poll则将二者分离到了events与revents中;

3.select不同类型的描述符需要放到不同的集合中,而poll则直接以events和revents来分辩不同类型的文件描述符。

      poll依旧保留了select的一些缺点:大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。并且返回之后需要对fds数组的元素进行遍历然后再处理。

程序流程

1.绑定、监听.....

2.创建fds数组,数组每个元素都是一个结构体,结构体中存放描述符,感兴趣事件以及返回事件并将所有元素的fd成员初始化为-1,意为无效;

3.将fds数组第一个元素fds[0]结构体设置为监听描述符fds[0].fd = lfd,感兴趣事件为fds[0].events = POLLIN,使用变量fdcount记录当前需要监控的描述符数;

4.创建while循环,调用poll函数开始阻塞等待;

5.poll函数返回后,先判断监听描述符fds[0]的返回情况,查看是否发生了POLLIN事件,用if(fds[0].revents&POLLIN)判断;

6.如果判断为真,说明有新连接,则调用accept函数新连接的文件描述符并存在变量connfd中,然后再在fds数组中找到第一个“有效”的元素,将connfd作为该元素的fd,然后设置events为POLLIN,用来监听是否收到数据;

7.然后处理除监听描述符以外的描述符。遍历fds数组,判断数组中的元素是否发送了感兴趣事件,用if(fds[0].revents&POLLIN),如果发生了,就用read和write进行数据读取;

8.继续下一次循环....

程序实例

#include <iostream>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <poll.h>

using namespace std;

#define SERV_IP "127.1.2.3"
#define SERV_PORT 8888
#define MAX_CONN 1024

int main()
{
    sockaddr_in servaddr,clitaddr;
    sockaddr_in clit_info[MAX_CONN];  //存放成功连接的客户端地址信息

    char buf[1024];  //读写缓冲区
    int lfd;      //用于监听
    int connfd;   //连接描述符
    int readyfd;  //保存select返回值
    int fdcount = 0;  //fds数组中有效的文件描述符个数
    int maxi = 0;  //maxi反映了fds中最后一个成功连接的文件描述符的索引
    socklen_t addr_len = sizeof(clitaddr);

    struct pollfd fds[MAX_CONN];  //创建描述符结构体,其中events用于设置监控事件,revents用于返回发生的事件
    
    for(int i=0;i<MAX_CONN;i++)fds[i].fd = -1;  //将所有描述符都置为失效状态    


    if((lfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
    {
        cout<<"creat socket fault : "<<strerror(errno)<<endl;
        return 0;
    }

    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    servaddr.sin_addr.s_addr = inet_addr(SERV_IP);

    if(bind(lfd,(sockaddr *)&servaddr,sizeof(servaddr)) == -1)
    {
        cout<<"bind fault : "<<strerror(errno)<<endl;
        return 0;
    }

    if(listen(lfd,128) == -1)
    {
        cout<<"listen fault : "<<strerror(errno)<<endl;
        return 0;
    }

    fds[0].fd = lfd ; //添加监听连接描述符到数组中
    fds[0].events = POLLIN ;  //监控事件为数据可读

    fdcount++;  //因为添加了lfd,因此加1;

    cout<<"Init Success ! "<<endl;
    cout<<"host ip : "<<inet_ntoa(servaddr.sin_addr)<<"  port : "<<ntohs(servaddr.sin_port)<<endl;

    cout<<"Waiting for connections ... "<<endl;

    while(1)
    {
        readyfd = poll(fds,fdcount,-1); 
        //执行到这里,说明poll返回,返回值保存在readyfd中,表示有多少个文件描述符被监控成功
        if(readyfd == -1)
        {
            cout<<"poll fault : "<<strerror(errno)<<endl;
            return 0;
        }

        if(fds[0].revents&POLLIN)  //如果监听描述符上发生的事件是POLLIN,说明有连接请求
        {
            int i=1;
            connfd = accept(lfd,(sockaddr *)&clitaddr,&addr_len);
            if(connfd == -1)
            {
                cout<<"accept fault : "<<strerror(errno)<<endl;
                continue ;
            }
            cout<<inet_ntoa(clitaddr.sin_addr)<<":"<<ntohs(clitaddr.sin_port)<<" connected ...  "<<endl;
            //成功连接后,就将connfd加入监控描述符数组中

            for(;i<MAX_CONN;i++)
            {
                if(fds[i].fd == -1)
                {
                    fds[i].fd = connfd;
                    fds[i].events = POLLIN;   //继续设置为读事件监控
		    clit_info[i] = clitaddr;  //保存新连接的客户端地址信息
                    break;
                }
            }
		
            fdcount++;  //有效描述符计数加1
            if(i>maxi)maxi = i;  //更新maxi

            readyfd --;
            if(readyfd == 0)continue;  //如果只有lfd被监控成功,那么就重新poll
        }
        //处理lfd之外监控成功的文件描述符
        for(int i=1;i<=maxi;i++)
        {
            if(fds[i].fd == -1)continue; //等于-1说明这个描述符已经无效
            if(fds[i].revents&POLLIN)   //在fds数组中寻找是否有被监控成功的文件描述符
            {
                int readcount = read(fds[i].fd,buf,sizeof(buf));
                if(readcount == 0)  //对方客户端关闭
                {
                    close(fds[i].fd);
                    fds[i].fd = -1;
		    fdcount--;
                    cout<<inet_ntoa(clit_info[i].sin_addr)<<":"<<ntohs(clit_info[i].sin_port)<<" exit ... "<<endl;
                }
                else if(readcount == -1)
                {
                    cout<<"read fault : "<<strerror(errno)<<endl;
                    continue;
                }
                else
                {
                    cout<<"(From "<<inet_ntoa(clit_info[i].sin_addr)<<":"<<ntohs(clit_info[i].sin_port)<<")";
		    for(int j=0;j<readcount;j++)cout<<buf[j];
		    cout<<endl;
                    for(int j=0;j<readcount;j++)buf[j] = toupper(buf[j]);
                    write(fds[i].fd,buf,readcount);
                }
                readyfd--;
                if(readyfd == 0)break;
            }
        }
    }
    close(lfd);
    return 0;
}

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux I/O多路复用——poll模型实现服务端Socket通信 的相关文章

  • SSH,运行进程然后忽略输出

    我有一个命令可以使用 SSH 并在 SSH 后运行脚本 该脚本运行一个二进制文件 脚本完成后 我可以输入任意键 本地终端将恢复到正常状态 但是 由于该进程仍在我通过 SSH 连接的计算机中运行 因此任何时候它都会登录到stdout我在本地终
  • 相当于Linux中的导入库

    在 Windows C 中 当您想要链接 DLL 时 您必须提供导入库 但是在 GNU 构建系统中 当您想要链接 so 文件 相当于 dll 时 您就不需要链接 为什么是这样 是否有等效的 Windows 导入库 注意 我不会谈论在 Win
  • 如何使用 JSch 将多行命令输出存储到变量中

    所以 我有一段很好的代码 我很难理解 它允许我向我的服务器发送命令 并获得一行响应 该代码有效 但我想从服务器返回多行 主要类是 JSch jSch new JSch MyUserInfo ui new MyUserInfo String
  • FileOutputStream.close() 中的设备 ioctl 不合适

    我有一些代码可以使用以下命令将一些首选项保存到文件中FileOutputStream 这是我已经写了一千遍的标准代码 FileOutputStream out new FileOutputStream file try BufferedOu
  • 在 C 中使用单个消息队列是否可以实现双向通信

    我希望服务器向客户端发送一些消息 并让客户端确认它 我被分配了这个任务 我可以在 C linux 中使用单个消息队列来完成它还是我需要创建两个 谢谢 是的 可以使用 sysV 消息队列来做到这一点 从您之前的问题来看 您正在使用该队列 您可
  • python获取上传/下载速度

    我想在我的计算机上监控上传和下载速度 一个名为 conky 的程序已经在 conky conf 中执行了以下操作 Connection quality alignr wireless link qual perc wlan0 downspe
  • tcpdump 是否受 iptables 过滤影响?

    如果我的开发机器有iptables规则到FORWARD一些数据包 这些数据包是否被 tcpdump 捕获 我有这个问题 因为我知道存在其他链称为INPUT如果数据包路由到 它会过滤发往应用程序的数据包FORWARD链 它会到达吗tcpdum
  • 就分页分段内存而言的程序寿命

    我对 x86 Linux 机器中的分段和分页过程有一个令人困惑的概念 如果有人能澄清从开始到结束所涉及的所有步骤 我们将很高兴 x86 使用分页分段内存技术进行内存管理 任何人都可以解释一下从可执行的 elf 格式文件从硬盘加载到主内存到它
  • 如何在linux中以编程方式获取dir的大小?

    我想通过 C 程序获取 linux 中特定目录的确切大小 我尝试使用 statfs path struct statfs 但它没有给出确切的大小 我也尝试过 stat 但它返回任何目录的大小为 4096 请建议我如何获取 dir 的确切大小
  • GMail 421 4.7.0 稍后重试,关闭连接

    我试图找出为什么它无法使用 GMail 从我的服务器发送邮件 为此 我使用 SwiftMailer 但我可以将问题包含在以下独立代码中
  • 如何使用waf构建共享库?

    我想使用构建一个共享库waf http code google com p waf 因为它看起来比 GNU 自动工具更容易 更简洁 到目前为止 我实际上有几个与我开始编写的 wscript 有关的问题 VERSION 0 0 1 APPNA
  • 错误:“rjags”的包或命名空间加载失败

    在终端的 conda 环境之一中 我能够成功安装包 rjags 但是 当我在该环境中运行 R 并运行库 rjags 时 出现以下错误 加载所需的包 coda 错误 rjags 的包或命名空间加载失败 rjags 的 loadNamespac
  • 在生产服务器上使用 Subversion 使文件生效的最佳方法是什么?

    目前我已经设置了 subversion 这样当我在 Eclipse PDT 中进行更改时 我可以提交更改 它们将保存在 home administrator 中项目文件 该文件具有 subversion 推荐的 branches tags
  • 如何使用Android获取Linux内核的版本?

    如何在 Android 应用程序中获取 Linux 内核的版本 不是 100 确定 但我认为调用 uname r 需要 root 访问权限 无论如何 有一种不太肮脏的方法可以做到这一点 那就是 System getProperty os v
  • 使用 gdb 调试 Linux 内核模块

    我想知道 API 在内核模块 中返回什么 从几种形式可以知道 这并不是那么简单 我们需要加载符号表来调试内核模块 所以我所做的就是 1 尝试找到内核模块的 text bss和 data段地址 2 在 gdb 中使用 add symbol f
  • Apache 访问 Linux 中的 NTFS 链接文件夹

    在 Debian jessie 中使用 Apache2 PHP 当我想在 Apache 的文档文件夹 var www 中创建一个新的小节时 我只需创建一个指向我的 php 文件所在的外部文件夹的链接 然后只需更改该文件夹的所有者和权限文件夹
  • 复制目录内容

    我想将目录 tmp1 的内容复制到另一个目录 tmp2 tmp1 可能包含文件和其他目录 我想使用C C 复制tmp1的内容 包括模式 如果 tmp1 包含目录树 我想递归复制它们 最简单的解决方案是什么 我找到了一个解决方案来打开目录并读
  • x86-64 AMD 上 CALL 指令的操作数生成

    以下是示例程序 objdump 的输出 080483b4
  • ansible unarchive 模块如何查找 tar 二进制文件?

    我正在尝试执行一个 ansible 剧本 该剧本的任务是利用unarchive模块 因为我是在 OSX 上执行此操作 所以我需要使用它gnu tar 而不是bsd tar通常与 OSX 一起提供 因为BSD tar 不受官方支持 https
  • 无法显示 Laravel 欢迎页面

    我的服务器位于 DigitalOcean 云上 我正在使用 Ubuntu 和 Apache Web 服务器 我的家用计算机运行的是 Windows 7 我使用 putty 作为终端 遵循所有指示https laracasts com ser

随机推荐

  • STM32G031 HAL库TIM2 PWM使用 占空比可调

    废话不多直接上代码 void HAL TIM MspPostInit GPIO InitTypeDef GPIO InitStruct 0 HAL RCC GPIOA CLK ENABLE GPIO InitStruct Pin GPIO
  • 飞凌RZ/G2L的开发板实上的时视频编码推流设计与实现

    飞凌RZ G2L的开发板测评 实时视频编码推流设计与实现 大信 QQ 8125036 在完成RZ G2L板上视频采集的试用测试基础上 逐渐熟悉了RZ G2L开发板的SDK 在研究过它的音视频硬件与软件包后 想进一步利用该开发板做音视频的深度
  • YOLOv5算法原理与网络结构

    YOLOv5算法原理与网络结构 1 1 YOLOv5算法 YOLOv5算法共有4种网络结构 分别是YOLOv5s YOLOv5m YOLOv5l和YOLOv5x 这四种网络结构在宽度和深度上不同 原理上基本一样 接下来以 YOLOv5s 为
  • 【MySQL】 Linux平台MySQL安装

    Linux平台MySQL安装 太淦了 Linux平台MySQL安装 方式1 使用包管理器进行自动安装 方式2 编译源代码安装 1 下载源代码 1 1在 官网 https dev mysql com downloads mysql 下载源代码
  • 黑盒测试靶机(cmcc)以及对应系统加固方案

    目录 一 信息收集 二 进入web界面 三 redis 6379连接 四 连接蚁剑成功 这是个沙箱 五 redis入手 使用密钥连接 六 尝试docker越狱 七 针对docker进行入侵排查 从系统层面 1 检查进程 2 木马样本可以保留
  • 高通MDM平台-ChargeIC

    Charge IC 是负责管理外部供电以及电池充放电 在MDM平台上采用的是比较便宜的ChargeIC MP2617 其作用也仅限于控制对电池充放电 下面就简单的介绍下内核中关于chargeIC的驱动 以及dts配置 内核DTS 关于cha
  • python中close函数的用法_python中调用open函数打开文件,使用close函数关闭文件,调用什么函数可以实现对文件内容的读取。...

    参考答案如下 中调范仲淹任杭州太守时 遭遇饥荒 他采取的方式是 函e函函数茶多酚 TP 主要包括 数打使用数关什实现茶多酚的主要作用是 开文茶在植物学上的分类位置属于 件内茶多酚对人体的功效 茶太烫时不要用嘴去吹 闭文为了快速冷却你可以用另
  • 成员内部类、静态内部类、局部内部类、匿名内部类的精髓与应用

    目录 1 成员内部类 坐拥外部类 1 1 定义和使用 1 2 优势 2 静态内部类 悠然独立 2 1 定义和使用 2 2 优势 3 局部内部类 精巧隐藏 3 1 定义和使用 3 2 优势 4 匿名内部类 神秘而优雅 4 1 定义和使用 4
  • Java-使用线程池创建多线程

    Java 使用线程池创建多线程 1 概念 提前创建好多个线程 放入线程池总 使用时直接获取 使用完后放入池中 可以避免频繁创建销毁 实现重复利用 corePoolSize 核心池的大小 maximumPoolSize 最大线程数 keepA
  • Angular-组件

    Angular 组件 一 组件是Angular应用的主要构造块 组件包括 1 一个HTML模板 用于声明页面要渲染的内容 2 一个用于定义行为的TypeScript的类 3 一个CSS选择器 定义组件在模板中的使用方式 4 要应用在模板上的
  • 配置Log4j(很详细)

    来自 http www blogjava net zJun archive 2006 06 28 55511 html Log4J的配置文件 Configuration File 就是用来设置记录器的级别 存放器和布局的 它可接key va
  • 机器学习sklearn之贝叶斯网络实战(一)

    贝叶斯网络 贝叶斯网络 信念网络 贝叶斯模型或概率定向无环图形模型是一种概率图形模型 一种统计模型 通过有向无环图 DAG 表示一组随机变量及其条件依赖关系 当我们想要表示随机变量之间的因果关系时 主要使用贝叶斯网络 贝叶斯网络使用条件概率
  • linux查看某天的日志,LINUX查看某段时间的日志

    其一 sed 截选时间段日志 sed n 开始时间 结束时间 p 日志文件 使用sed命令如下 sed n 2020 05 04 09 25 55 2015 05 04 09 28 55 p logfile 这样可以精确地截取出来某个时间段
  • python学习笔记---常用内建模块【廖雪峰】

    常用内建模块 datetime Python处理日期和时间的标准库 datetime表示的时间需要时区信息才能确定一个特定的时间 否则只能视为本地时间 如果要存储datetime 最佳方法是将其转换为timestamp再存储 因为times
  • GD32F303调试小记(九)之FreeRTOS移植

    前言 距离上一次更新GD32系列的文章已经过了一年有余 按照之前的想法 仅仅介绍到GD32中常用的模块就结束了 在后续的开发中 有幸再次能使用这颗IC作为主控 所以既为了自己做个随笔 也为方便各位同行或是同学借鉴 这段时间我会编写几篇文章主
  • freeRTOS出现任务卡死的情况。

    最近在做一个产品二代升级的项目 代码是上一任工程师留下的 很多BUG 而且融合了HAL库和LL库 以及github上下载的GSM源码 很不好用 我这边是将2G模块换成了4G 且添加了单独的BLE模块 因此只在源码的基础上 去除2G和BLE代
  • 论坛直击

    关注ITValue 看企业级最新鲜 最价值报道 2020年对中国而言 是极其特殊的一年 新年伊始 一场突如其来的疫情席卷全国 各行各业均受到不同程度的冲击 房地产开发由于不能实地展业 影响尤甚 为此 在中国房地产业协会的支持下 中国房协数字
  • nginx配置多个前端项目,使用同一个后端

    这几天一直在搞nginx配置的东西 踩了很多坑 特此记录 前后端分离的项目有两种部署方案 第一种是把前端打包好的dist文件夹放在后端的static下面 然后打包后端 在服务器运行后端的这个jar包 没什么好说的 第二种是使用nginx配置
  • key-value store

    1 key value store key value分布式存储系统查询速度快 存放数据量大 支持高并发 非常适合通过主键进行查询 但不能进行复杂的条件查询 数据存储形式为
  • Linux I/O多路复用——poll模型实现服务端Socket通信

    目录 poll函数 参数说明 events相关 与select的不同 程序流程 程序实例 poll函数 poll模型在实现服务端时思路是和select类似的 可以说poll是select的加强版 poll函数原型如下 int poll st