【计算机网络】网络编程接口 Socket API 解读(3)

  Socket 是网络协议栈暴露给编程人员的 API,相比复杂的计算机网络协议,API 对关键操作和配置数据进行了抽象,简化了程序编程。

        本文讲述的 socket 内容源自 Linux 发行版 centos 9 上的 man 工具,和其他平台(比如 os-x 及不同版本会有些出入)。本文主要对各 API 进行详细介绍,从而更好的理解 socket 编程。


poll

poll()           遵循 POSIX.1 - 2008

ppoll()         遵循 Linux

1.库

标准 c 库,libc, -lc

2.头文件

<poll.h>

3.接口定义

       int poll(struct pollfd *fds, nfds_t nfds, int timeout);int ppoll(struct pollfd *fds, nfds_t nfds,const struct timespec *_Nullable tmo_p,const sigset_t *_Nullable sigmask);

4.接口描述

        poll() 和 select() 做的事情差不多,它等待一个文件描述符集 I/O 就绪。Linux 的 epoll() 也是类似的,只是比 poll() 提供多了一些特性。

        fds 参数是要监控的文件描述符集,是下面结构体的一个数组:

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

        由调用者指定 fds 的项数。

        结构体中 fd 包含了一个打开的文件描述符,如果它是负值,那么 events 参数将被忽略,revents 返回 0。(也就是说 可以将 fd 设置为其补码就可以忽略它)。

        events 参数是一个输入参数,通过按位掩码来标识应用感兴趣的文件描述符上的事件。参数可以设置为 0,那么就只能返回 POLLHUP/POLLERR/POLLNVAL 事件。

        revents 是一个输出参数,由内核填充实际发生的事件。这些事件可以是 events 中指定的事件,也可以是  POLLHUP/POLLERR/POLLNVAL 中的一个。(events 中这三个事件对应的位并没有什么意义,只要对应的条件发生,revents 就会返回该事件。)

        如果没有请求的事件(包括错误)发生,那么 poll() 会一直阻塞,直到有事件发生。

        timeout 参数指定了 poll() 等待文件描述符就绪的毫秒数,该调用会一直阻塞直到:

  • 文件描述符就绪
  • 调用被信号打断
  • 发生超时

        同样,timeout 值也会向上近似到系统时钟粒度,由于内核调度延迟阻塞的事件可能会稍微多一点。如果 timeout 是负值,表示超时时间是无限长。如果 timeout 设置为 0,那么 poll() 会马上返回,即使没有任何文件描述符就绪。

        events 和 revents 中各个位在 poll.h 中定义:

    POLLIN

       有数据可以读。

    POLLPRI

        文件描述符上有异常发生,可能是(1)TCP socket 上有带外数据(2)处于报文模式的伪终端主机发现了从机状态变化(3)cgroup.events 文件被修改了。

        POLLOUT

       当前可写,但是写大于 socket 或 pipe 中可用空间的数据仍然会导致阻塞(除非设置了 O_NONBLOCK)。

        POLLRDHUP

        流 socket 对端关闭了连接或者在写半连接时关机。这个定义依赖于 _GNU_SOURCE 宏定。

        POLLERR

       发生错误。如果文件描述符指向了 pipe 的写端,而读端关闭了,那么也会返回这个错误。

        POLLHUP

        挂断。在读取 pipe 或者流 socket 时,这个事件只表示对端关闭了其通道,后面的数据读取时,在通道中数据读尽后再继续读会返回 0(EOF)。

        POLLNVAL

        请求不合法:fd 没有打开。

        在使用 _XOPEN_SOURCE 宏编译时,还会有以下一些事件,不过也没有提供太多信息:

        POLLRDNORM

        等同于 POLLIN。

        POLLRDBAND

        优先带宽数据可以读(通常在 Linux 上用)

        POLLWRNORM

        等同于 POLLOUT

        POLLWRBAND

        可能写了优先数据

ppoll()

        ppoll() 和 poll() 的关系就像 select() 和 pselect() 的关系一样,ppoll() 为应用提供了等待信号或者就绪事件的安全方法。

        除了 timeout 时间精度上的差异,以下两段代码几乎等效

           ready = ppoll(&fds, nfds, tmo_p, &sigmask);
           sigset_t origmask;int timeout;timeout = (tmo_p == NULL) ? -1 :(tmo_p->tv_sec * 1000 + tmo_p->tv_nsec / 1000000);pthread_sigmask(SIG_SETMASK, &sigmask, &origmask);ready = poll(&fds, nfds, timeout);pthread_sigmask(SIG_SETMASK, &origmask, NULL);

          上面代码说成几乎等效而不是等效主要是因为负值的 timeout 会被 poll() 解释为一直等待,而 ppoll() 中负值的 *tmo_p 会报错。

        可以参考 pselect(2) 来看为什么 ppoll 是必要的。

        如果 sigmask 参数为 NULL,那么就不会有任何信号屏蔽操作,这时这两个接口唯一的区别就是时间精度。

        tmo_p 指定了 ppoll() 会阻塞的时间上限,它是指向 timespec 结构体的指针,指针为空时,ppoll() 会一直阻塞。

5.返回值

        成功时,poll() 返回一个非负数表示 pollfds 中有多少个文件描述符上有事件发生,即对应的 revents 有被更新为非 0 值。返回 0 表示没有任何文件描述符就绪并超时。

        发生错误时,返回 -1,并设置errno 来指示错误类型。

        错误值定义如下:

EFAULTfds 指向了进程外的地址空间
EINTR请求事件发生前,发生了信号,具体参见 signal(7)
EIVALnfds 值超出了 RLIMIT_NOFILE 限制
EINVALppoll() 中的 *tmo_P 是一个非法值(负数)
ENOMEM没有足够内存来分配内核数据结构

一些其他 UNIX 系统上,如果内核无法发分配内核资源,poll() 可能会产生 EAGAIN 类的错误,而不像 Linux 上的 ENOMEM。POSIX 允许这种行为。所以,一个可移植的程序需要检测该错误,并重试,就像处理 EINTR 一样。

一些实现定义了非标准常量 INFTIM(-1),用作 poll() 的 timeout,但是这个常量并没有被被 glibc 提供。

6.注意

       poll() 和 ppoll() 的行为不受 O_NONBLOCK 标志影响。

        对于一个文件描述符正在被 poll() 监听却被另一个线程关闭了这种情况的讨论,可以参考 select(2)。

7.BUGS

        可以参考 select(2) 中关于虚假就绪通知的讨论。 

8.代码实例

        该程序会打开命令行参数传进来的文件名并监听其 POLLIN 事件,程序会循环调用 poll() 来监听文件描述符,打印已经就绪的文件描述符数。对于每个就绪的文件描述符,程序会:

  • 以可读的格式显示返回的 revents
  • 如果文件描述符就绪,那么就从中读一些数据出来并打印
  • 如果文件描述符不可读,但是发生了一些其他事件(比如 POLLHUP),就关闭文件描述符

        假定我们在一个终端运行程序,让他打开一个 FIFO:

       $ mkfifo myfifo$ ./poll_input myfifo

        在另一个终端打开 FIFO,并写入一些数据,然后关闭 FIFO:

       $ echo aaaaabbbbbccccc > myfifo

                 我们将在运行程序的终端上看到如下信息:

           Opened "myfifo" on fd 3About to poll()Ready: 1fd=3; events: POLLIN POLLHUPread 10 bytes: aaaaabbbbbAbout to poll()Ready: 1fd=3; events: POLLIN POLLHUPread 6 bytes: cccccAbout to poll()Ready: 1fd=3; events: POLLHUPclosing fd 3All file descriptors closed; bye

         从上面我们可以看到 poll() 返回了三次:

  • 第一次返回是 POLLIN,表示文件描述符可读,另一个是 POLLHUP 表示文件描述符的另一个端关闭了。程序接着读取了一些可用的输入数据
  • 第二次返回同样是这两个事件,依然消费了一些可用数据
  • 最后一次返回,poll() 只有 POLLHUP 事件,然后关闭文件描述符并结束了程序。
       /* poll_input.cLicensed under GNU General Public License v2 or later.*/#include <fcntl.h>#include <poll.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \} while (0)intmain(int argc, char *argv[]){int            ready;char           buf[10];nfds_t         num_open_fds, nfds;ssize_t        s;struct pollfd  *pfds;if (argc < 2) {fprintf(stderr, "Usage: %s file...\n", argv[0]);exit(EXIT_FAILURE);}num_open_fds = nfds = argc - 1;pfds = calloc(nfds, sizeof(struct pollfd));if (pfds == NULL)errExit("malloc");/* Open each file on command line, and add it to 'pfds' array. */for (nfds_t j = 0; j < nfds; j++) {pfds[j].fd = open(argv[j + 1], O_RDONLY);if (pfds[j].fd == -1)errExit("open");printf("Opened \"%s\" on fd %d\n", argv[j + 1], pfds[j].fd);pfds[j].events = POLLIN;}/* Keep calling poll() as long as at least one file descriptor isopen. */while (num_open_fds > 0) {printf("About to poll()\n");ready = poll(pfds, nfds, -1);if (ready == -1)errExit("poll");printf("Ready: %d\n", ready);/* Deal with array returned by poll(). */for (nfds_t j = 0; j < nfds; j++) {if (pfds[j].revents != 0) {printf("  fd=%d; events: %s%s%s\n", pfds[j].fd,(pfds[j].revents & POLLIN)  ? "POLLIN "  : "",(pfds[j].revents & POLLHUP) ? "POLLHUP " : "",(pfds[j].revents & POLLERR) ? "POLLERR " : "");if (pfds[j].revents & POLLIN) {s = read(pfds[j].fd, buf, sizeof(buf));if (s == -1)errExit("read");printf("    read %zd bytes: %.*s\n",s, (int) s, buf);} else {                /* POLLERR | POLLHUP */printf("    closing fd %d\n", pfds[j].fd);if (close(pfds[j].fd) == -1)errExit("close");num_open_fds--;}}}}printf("All file descriptors closed; bye\n");exit(EXIT_SUCCESS);}

下一篇 【计算机网络】网络编程接口 Socket API 解读(4)​​​​​​​

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/77230.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux-Day10-信号量,共享内存,消息队列】

信号量 信号量描述 信号量是一个特殊的变量&#xff0c;一般取正数值。它的值代表允许访问的资源数目&#xff0c;获取资源 时&#xff0c;需要对信号量的值进行原子减一&#xff0c;该操作被称为 P 操作。 当信号量值为 0 时&#xff0c;代表没有资源可用&#xff0c;P 操作…

2022年全国研究生数学建模竞赛华为杯B题方形件组批优化问题求解全过程文档及程序

2022年全国研究生数学建模竞赛华为杯 B题 方形件组批优化问题 原题再现&#xff1a; 背景介绍   智能制造被“中国制造2025”列为主攻方向, 而个性化定制、更短的产品及系统生命周期、互联互通的服务模式等成为目前企业在智能制造转型中的主要竞争点。以离散行业中的产品为…

20230912java面经整理

1.gc算法有哪些 引用计数&#xff08;循环引用&#xff09;和可达性分析找到无用的对象 标记-清除&#xff1a;简单&#xff0c;内存碎片&#xff0c;大对象找不到空间 标记-复制&#xff1a;分成两半&#xff0c;清理一半&#xff0c;没有碎片&#xff0c;如果存活多效率低&a…

Python之离线安装第三方库

1、场景介绍 在一些服务器上&#xff0c;我们搭建完Python环境之后&#xff0c;因为服务器的网络限制原因&#xff0c;不能直接通过pip命令下载安装Python的依赖包。 因此&#xff0c;我们需要在可以正常上网的服务器上下载好所需的依赖包文件&#xff0c;然后拷贝到目标服务器…

嘉泰实业:真实低门槛,安全有保障

在互联网金融大行其道的当下&#xff0c;无论用户是多么的青睐、喜爱这种便捷的理财方式&#xff0c;也一定得把资金安全放在心上。要投就投那些实力背景雄厚&#xff0c;诚信经营的平台&#xff0c;可以选择投资用户基数庞大的理财老品牌&#xff0c;也可以选择发展势头迅猛的…

OneFormer: One Transformer to Rule Universal Image Segmentation论文笔记

论文https://arxiv.org/pdf/2211.06220.pdfCodehttps://github.com/SHI-Labs/OneFormer 文章目录 1. Motivation2. 方法2.1 与Mask2Former的相同之处2.2 OneFormer创新之处2.3 Task Conditioned Joint Training2.4 Query Representations2.4 Task Guided Contrastive Queries 3…

备忘录模式简介

概念&#xff1a; 备忘录模式&#xff08;Memento Pattern&#xff09;是一种行为型设计模式&#xff0c;它允许在不暴露对象内部状态的情况下捕获和恢复对象之前的状态。该模式通过将对象的状态封装到备忘录中&#xff0c;并将其保存在一个管理者类中&#xff0c;从而实现了对…

【Redis】为什么要学 Redis

文章目录 前言一、Redis 为什么快二、Redis 的特性2.1 将数据储存到内存中2.2 可编程性2.3 可扩展性2.4 持久性2.5 支持集群2.6 高可用性 三、Redis 的应用场景四、不能使用 Redis 的场景 前言 关于为什么要学 Redis 这个问题&#xff0c;一个字就可以回答&#xff0c;那就是&…

学习记忆——宫殿篇——记忆宫殿——数字编码——记忆数字知识点

面对错综复杂的数字信息&#xff0c;我们想要记住可以通过以下三点&#xff1a; 1、首先找到关键词 2、数字编码牢记 3、关键词跟编码链接 案例&#xff1a;会计考试-时间期限为 3、7、10 日、1 年的知识点 3 天 (1)托收承付的承付期验单付款为 3 天。 (2)失票人应当在通…

帝国cms后台访问链接提示“非法来源”解决方法

提示“非法来源”的原因 帝国CMS更新升级7.2后,新增了后台安全模式,后台推出了金刚模式来验证链接来源。后台所有链接都需要登录后才能访问,直接强制访问后台页面链接都会提示“非法来源”。不是正常登录后台的用户无法直接访问到内容,保证了后台数据安全。 那么我们在日常…

【设计模式】三、概述分类+单例模式

文章目录 概述设计模式类型 单例模式饿汉式&#xff08;静态常量&#xff09;饿汉式&#xff08;静态代码块&#xff09;懒汉式(线程不安全)懒汉式(线程安全&#xff0c;同步方法)懒汉式(线程安全&#xff0c;同步代码块)双重检查静态内部类枚举单例模式在 JDK 应用的源码分析 …

SpringMVC实现文件上传和下载功能

文件下载 ResponseEntity用于控制器方法的返回值类型&#xff0c;该控制器方法的返回值就是响应到浏览器的响应报文。具体步骤如下&#xff1a; 获取下载文件的位置&#xff1b;创建流&#xff0c;读取文件&#xff1b;设置响应信息&#xff0c;包括响应头&#xff0c;响应体以…

04-Redis哨兵高可用架构

上一篇&#xff1a;03-Redis主从架构 架构说明 sentinel哨兵是特殊的redis服务&#xff0c;不提供读写服务&#xff0c;主要用来监控redis实例节点。 哨兵架构下client端第一次从哨兵找出redis的主节点&#xff0c;后续就直接访问redis的主节点&#xff0c;不会每次都通过s…

【2023最新B站评论爬虫】用python爬取上千条哔哩哔哩评论

文章目录 一、爬取目标二、展示爬取结果三、爬虫代码四、同步视频五、附完整源码 您好&#xff0c;我是 马哥python说&#xff0c;一枚10年程序猿。 一、爬取目标 之前&#xff0c;我分享过一些B站的爬虫&#xff1a; 【Python爬虫案例】用Python爬取李子柒B站视频数据 【Pyt…

Java实现合并多个excel操作

涉及较多封装的工具类&#xff0c;所有依赖的工具类均提供代码&#xff0c;根据名称新建对应的类&#xff0c;在每个工具类中再引入相应的依赖即可 首先需要明确的是&#xff0c;需要合并的每个excel的表头名称必须是相同的&#xff0c; 针对表头&#xff0c;建立传输的dto&a…

【Spring】Ioc容器

&#x1f388;博客主页&#xff1a;&#x1f308;我的主页&#x1f308; &#x1f388;欢迎点赞 &#x1f44d; 收藏 &#x1f31f;留言 &#x1f4dd; 欢迎讨论&#xff01;&#x1f44f; &#x1f388;本文由 【泠青沼~】 原创&#xff0c;首发于 CSDN&#x1f6a9;&#x1f…

IDEA设置Maven 镜像

第一步&#xff1a;右键项目&#xff0c;选择Maven->Create ‘settings.xml’ 已经存在的话是Open ‘settings.xml’&#xff1a; 第二步&#xff1a;在settings.xml文件中增加阿里云镜像地址&#xff0c;代码如下&#xff1a; <?xml version"1.0" encodin…

Android Ble蓝牙App(七)扫描过滤

Ble蓝牙App&#xff08;七&#xff09;扫描过滤 前言目录正文一、增加菜单二、使用MMKV① 添加依赖② 封装MMKV③ 使用MMKV 三、过滤空设备名四、过滤Mac地址五、过滤RSSI六、源码 前言 在上一篇文章中了解了MTU的相关知识以及对于设备操作信息的展示&#xff0c;本篇文章中将增…

Electron和vue3集成(推荐仅用于开发)

本篇我们仅实现Electron和vue3通过先运行起vue3项目&#xff0c;再将vue3的url地址交由Electron打开的方案&#xff0c;仅由Electron在vue3项目上套一层壳来达到脱离本机浏览器运行目的 1、参考快速上手 | Vue.js搭建起vue3初始项目 npm install -g vue npm install -g vue/c…

Android 文字转语音播放实现

1&#xff0c;TextToSpeech类是android自带的&#xff0c;但是部分设备需要支持TTS需要增加语音库&#xff0c;我使用的是讯飞语音&#xff08;离线的哦&#xff09;。请自行下载并安装讯飞语音APK&#xff0c;然后到系统设置中设置TTS功能默认使用该选项。有自带TTS库的可以省…