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

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

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


select

遵循 POSIX.1 - 2008

1.库

标准 c 库,libc, -lc

2.头文件

<sys/select.h>

3.接口定义

int select(int nfds, fd_set *_Nullable restrict readfds,fd_set *_Nullable restrict writefds,fd_set *_Nullable restrict exceptfds,struct timeval *_Nullable restrict timeout);

4.接口描述

        首先,我们需要注意 select 只能监听少于 FD_SETSIZE(1024)  个文件描述符,这在现在看来是非常不合理的,如果想不受这个限制,需要使用 poll 或者 epool。

        select 可以同时监听多个文件描述符,只要有一个文件描述符有操作需求时即返回。文件描述符有操作需求指的是可以马上进行相关的 I/O 操作,比如 read 或者少量的写操作。

    fd_set

        一个表示一组文件描述符的结构体,根据 POSIX 要求,结构中最大文件描述符数量为 FD_SETSIZE。

    File descriptor set

        select() 接口重要的参数是 3 个文件描述符集合(以 fd_set 类型声明),这允许调用者在指定的文件描述符集合上等待 3 种类型的事件。每个 fd_set 参数都可以是 NULL,只要没有文件描述符集需要监听对应的事件。

        值得注意的是,一旦接口返回,每个文件描述符集都被更新,来指示哪些文件描述符就绪了。因此,如果在一个循环中使用 select(),集合必须每次调用前重新初始化。

        文件描述符集的内容可以使用以下宏来操作:

        FD_ZERO()

        这个宏用来清除集合中的所有文件描述符,是初始化文件描述符集的第一步。

        FD_SET()

        这个宏用来向集合中添加文件描述符,如果文件描述符已经存在,那么也不会报错,只是不进行任何操作。

        FD_CLR()

        这个宏用来从集合中移除指定文件描述符,如果文件描述符不存在,则不进行任何操作。

        FD_ISSET()

        select() 根据如下规则更新集合内容:select() 调用结束后,FD_ISSET() 宏用来检测指定文件描述符是否还位于集合中,如果存在则返回非 0 值,否则返回 0。

5.参数

(1)readfds

        这个集合中的文件描述符用来监测其是否已经读就绪。一个文件描述读就绪指的是读操作不会阻塞,特别的是,EOF 也算是读就绪。

        select() 函数返回后,readfds 中只会保留读就绪的文件描述符,其他都会被删除。

(2)writefds

        这个集合中的文件描述符用来监测其是否已经写就绪。一个文件描述写就绪指的是写操作不会阻塞。不过即使一个文件描述符已经写就绪,但是大块的写操作可能也会阻塞。

        select() 函数返回后,writefds 中只会保留写就绪的文件描述符,其他都会被删除。

(3)eceptfds

        这个集合中的文件描述符用来监测其异常情况,一些异常情况的示例,在 poll() 的 POLLPRI 中会有讨论。

        select() 返回后,exceptfds 中只保留发生异常情况的文件描述符。

(4)nfds

        这个参数应该被设置为 3 个集合中文件描述符的最大值加 1。

(5)timeout

        timeout 是一个 timeval 的结构,指定了 select() 等待文件描述符就绪的时间,这个接口会一直阻塞直到以下事件发生:

  • 文件描述符就绪
  • 调用被信号处理打断
  • timeout 超时

        值得注意的是,timeout 值会向上(rounded up)近似到系统时钟粒度,另外由于系统调度延迟,可能会导致阻塞间隔比 timeout 稍微大一些。

        如果 timeout 的两个成员都为 0,那么 select 会立即返回(通常用于轮询)。

        如果 timeout 是 NULL,select 会无限期等待直到有文件描述符就绪。

6.pselect()

        pselect() 系统调用能够允许应用更安全的等待文件描述符就绪或者信号发生。

        它和 select() 是一样的,除了以下几个地方:

  • select() 使用 timeval 结构的 timeout,而 pselect() 使用 timespec 结构 的timeout
  • select() 可能会更新 timeout 参数来指示还有多少剩余时间,而 pselect() 不会
  • select() 没有信号屏蔽 sigmask 参数,相当于 pselect 的sigmask 参数为 NULL

        sigmask 是一个指向信号屏蔽的指针。如果它不为空,那么 pselect() 首先会使用它代替当前的信号屏蔽,然后在进行 select(),最后再恢复原来的信号屏蔽。如果是 NULL,那么 pselect() 调用过程并不会改变信号屏蔽值。

        除了时间精度上的差异,下面两端代码等效:

  ready = pselect(nfds, &readfds, &writefds, &exceptfds,timeout, &sigmask);

        

sigset_t origmask;pthread_sigmask(SIG_SETMASK, &sigmask, &origmask);
ready = select(nfds, &readfds, &writefds, &exceptfds, timeout);
pthread_sigmask(SIG_SETMASK, &origmask, NULL);

        设计 pselect() 的原因是想要等待信号发生或者文件描述符就绪,那么就需要一个原子测试来解决数据竞争问题。比如,一个信号处理函数设置了一个标志并返回,如果信号刚好在测试的附近到达导致数据竞争时, select() 后面测试这个标志有可能无限期卡住。而 pselect() 允许先屏蔽信号,处理已经发生的信号,然后使用指定 sigmask 来调用 pselect() ,避免了数据竞争。

        timeout

        select() 的 timeout 结构体定义如下:

           struct timeval {time_t      tv_sec;         /* seconds */suseconds_t tv_usec;        /* microseconds */};

        pselect() 对应的结构体时 timespec。

Linux 系统上 select() 会修改 timeout 值来反映未睡眠的时间,其他实现不是这么做的。POSIX.1 认为任何行为都是合法的。这就会导致 Linux 系统和其他系统之间的移植问题,所以,我们应该认为 timeout 在 select() 后是未知的值。

7.返回值

        成功时,select() 和 pselect() 返回三个返回文件描述符集中的文件描述符总数(也就是 redfds、writefds、exceptfds 的中设置为 1 位数)。返回值可以为 0,表示在有文件描述符就绪前 timeout 超时。

        发生错误时,返回 -1,并设置errno 来指示错误类型。文件描述符集并不会被修改,timeout 值是未定义的。

        错误值定义如下:

EBADF集合中存在不合法的文件描述符,比如已经关闭的文件描述符或者发生错误的文件描述符),具体参见 BUGS
EINTR捕获了一个信号,具体参见 signal(7)
EINVALnfds 是负值,或者超过了 RLIMIT_NOFILE 资源限制,具体参见getrlimit(2)
EINVALtimeout 中的数值不合法
ENOMEM没有足够内存来分配内部表

在其他 UNIX 系统上,如果系统无法分配内核资源,select() 可能会返回 EAGAIN 错误而不是 ENOMEM。POSIX 为 poll() 定义了该错误,但是并没有为 select() 定义。考虑到程序的移植性,应该检查 EGAIN 并重新调用,就行 EINTR 处理一样。

8.注意

        <sys/time.h> 也提供了 fd_set 的定义,fd_set 是一个固定大小的缓冲区,执行 FD_CLR 和 FD_SET 传入一个负值或者大于 FD_SETSIZE 的 fd 会导致不可预期的结果。此外,POSIX 要求 fd 是一个可用的文件描述符。

        select() 和 pselect() 操作不受 O_NONBLOCK 标志的影响。

        self-pipe 小技巧

        在没有 pselect() 实现的系统上,可靠(更具有移植性)的信号捕捉可以通过 self-pipe 小技巧实现。这个技术在信号处理函数中向一个 pipe 中写入 1 字节,而该 pipe 的另一端由 select() 监听。为了防止满写阻塞和空读阻塞,pipe 的读写应采用非阻塞 I/O 方式。

        模拟 usleep

        在 usleep 出现前,一些代码使用 select() 来实现一种可移植的亚秒精度延迟,将所有集合设置为空,nfds 为 0,非空的 timeout值。

        select() 和 poll() 间通知的映射

        在 linux 代码树中,我们可以发现 select() 读、写、异常通知和 poll()/epoll() 事件通知之间的联系:

           #define POLLIN_SET  (EPOLLRDNORM | EPOLLRDBAND | EPOLLIN |EPOLLHUP | EPOLLERR)/* Ready for reading */#define POLLOUT_SET (EPOLLWRBAND | EPOLLWRNORM | EPOLLOUT |EPOLLERR)/* Ready for writing */#define POLLEX_SET  (EPOLLPRI)/* Exceptional condition */

        多线程应用

        如果一个线程通过 select() 监听的文件描述符被另一个现场关闭,那么结果是未知的。在一些 UNIX 系统上,select() 会停止阻塞并返回,告知文件描述符就绪(后续操作会出错,除非刚好其他线程又打开了文件描述符并且就绪了)。在 Linux 及其他系统上,其他线程关闭文件描述符对 select() 没有任何影响。总结起来,应用如果依赖这些具体的行为的话,就会产生 bug。

        C 库和内核的差异

        Linux 内核允许文件描述符集是任意大小的,由 nfds 的值来决定具体的大小。而 glibc 将fs_set 类型设置为固定值。参考 BUGS。

        我们这里讲述的 pselect() 接口是 glibc 实现的,底层系统调用名字是 pselect6(),系统调用的行为和 pselect() 有些许不同。

        Linux 的 pselect6() 系统调用修改 timeout 参数,然而 glibc 通过本地缓存 timeout 值隐藏了该行为。因此,glibc  pselect6() 没有修改 timeout 参数,这也符合 POSIX.1-2001 要求。

        pselect6() 系统调用的最后一个参数不是 sigset_t * 指针类型,而是如下格式:

           struct {const kernel_sigset_t *ss;   /* Pointer to signal set */size_t ss_len;               /* Size (in bytes) of objectpointed to by 'ss' */};

        这使得系统调用可以获取信号集指针及其大小,并考虑到大多数系统支持最大 6 个系统调用参数这个事实。关于信号处理的差异之处,可以参考 sigprocmask 的讨论。

        glibc 历史细节

        gblic 2.0 提供了 pselect() 的错误版本,它并没有 sigmask 参数。

        glibc 2.1 到 2.2.1,为了获得 <sys/select.h> 中的 pselect() 声明,必须定义 _GNU_SOURCE 宏。

9.BUGS

        POSIX 允许实现通过 FD_SETSIZE 来定义文件描述符集中文件描述符的上限,Linux 内核并没有限制,但是 glibc 实现将 fd_set 定为固定长度并将 FD_SETSIZE 设置为 1024,FD_*() 宏根据这个限制操作。为了能够监测多余 1023 个文件描述符,可以使用 poll() 或者 epoll。

        fd_set 参数的输入输出属性是一个错误的设计,已经在 poll() 和 epoll() 改正过来。

        根据 POSIX 要求,select() 应该检查所有集合中的文件描述符不能超过 nfds - 1,但是,当前实现会忽略掉那些文件描述符值大于当前进程打开的最大文件描述符值。根据 POSIX 要求,这些文件描述符会导致 EBADF 错误。

        从 glibc 2.1 开始,glibc 使用 sigprocmask() 和 select() 实现了 pselect() 模拟,这个实现却遗留了 pselect() 解决的数据竞争问题。现在版本的 glibc 通常使用内核提供的不受数据竞争影响的 pselect() 系统调用。

        Linux 上,select()可能报告 socket 文件描述符读就绪,但是后续的读却会阻塞,这个常发生在数据已达到但是数据的校验和不对,数据被丢弃。当然,也可能是误报。所以使用 O_NONBLOCK 的 sockets 更安全些。

        Linux 上的 select() 会在被信号打断的情况下更新 timeout 值,POSIX.1 并不允许这样做。Linux 的 pselect() 是同样的行为,但是 glibc 隐藏了这种行为。 

10.代码实例

       #include <stdio.h>#include <stdlib.h>#include <sys/select.h>intmain(void){int             retval;fd_set          rfds;struct timeval  tv;/* Watch stdin (fd 0) to see when it has input. */FD_ZERO(&rfds);FD_SET(0, &rfds);/* Wait up to five seconds. */tv.tv_sec = 5;tv.tv_usec = 0;retval = select(1, &rfds, NULL, NULL, &tv);/* Don't rely on the value of tv now! */if (retval == -1)perror("select()");else if (retval)printf("Data is available now.\n");/* FD_ISSET(0, &rfds) will be true. */elseprintf("No data within five seconds.\n");exit(EXIT_SUCCESS);}

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

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

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

相关文章

使用 uni-app 开发app时 中使用的 @longpress 含义及使用方法

学习目标&#xff1a; 目标&#xff1a; 了解 longpress 事件的含义及使用方法 学习内容&#xff1a; 内容&#xff1a; longtap, longpress 都代表长按事件在浏览器上&#xff0c;longtap 事件会覆盖longpress&#xff1b;在手机端的时候&#xff0c;2个事件都存在&#x…

Vue错误记录

文章目录 1. 项目build的时候报错Warning: Accessing non-existent property cat of module exports inside circular dependency2. WebpackOptionsValidationError: Invalid configuration object. Webpack has been initialised using a configuration object that does not …

Docker底层实现

Docker采用c/s架构&#xff0c;Docker守护进程&#xff08; Daemon &#xff09;作为服务端&#xff0c;接受来自客户端&#xff08;命令行&#xff09;的请求&#xff0c;并处理这些请求&#xff08;创建、运行、分发容器&#xff09; 。客户端和服务端既可以运行在一个机器上…

数据结构与算法之(赫夫曼树,哈夫曼树,压缩软件)

一&#xff1a;思考 1.电报发送&#xff1a;二战的时候大家都知道那时候普遍会应用电报&#xff0c;如果让你来设计一个电报的发送编码你该如何设计呢&#xff1f; 2.压缩算法&#xff1a;给你10000个字符&#xff08;每个字符1btye&#xff0c;也就是8bit&#xff09;的文件&a…

GitHub星标超70K,阿里大佬的架构总结“分布式全解”笔记霸榜

分布式架构与微服务平台是当今IT界的关键技术&#xff0c;也是资深软件工程师和系统架构师必须掌握的核心技术。 因此小编为各位粉丝朋友带来这份阿里大佬的分布式笔记&#xff1a;从传统分布式架构迁移到基于容器技术的微服务架构为主线&#xff0c;全面、透彻地介绍了与分布…

linux中常用shell脚本整理

linux常见shell脚本整理 备份日志 #!/bin/bash # 每日创建新的备份日志-根据日期备份 tar -czf log-date %Y%m%d.tar.gz /var/log # 通过crontab 每日定时启动 00 03 * * 5 /root/logbak.sh 监控内存和磁盘容量&#xff0c;小于给定值时报警 #!/bin/bash # 实…

华为回击:制裁无法阻挡中国科技创新 | 百能云芯

华为最新推出的Mate 60 Pro手机引发了中国市场的抢购热潮&#xff0c;这一成功的举措为华为带来了信心。华为在这个背景下再度推出两款新机&#xff0c;其中包括高阶版的Mate 60 Pro和折叠式手机Mate X5。这两款手机在首批预购开始后迅速售罄&#xff0c;不仅取得了市场的热烈欢…

JavaScript基础

1 JavaScript概述 1.1 什么是JavaScript 1.1.1 概念 JavaScript 是脚本语言&#xff0c;是一种解释性脚本语言&#xff08;代码不进行预编译&#xff09; JavaScript 是一种轻量级的编程语言。 JavaScript 是可插入 HTML 页面的编程代码。 JavaScript 插入 HTML 页面后&a…

CANoe中的工作模式之争:由一段简单的代码引出的问题

1、引子 有网友问我一个CAPL中timer定时器的代码问题。他在CANoe工程中写了一段代码:每5秒循环触发一次定时器事件程序,输出一句文本信息到Write窗口。但是执行后发现并不是每5秒触发一次定时器事件程序,而是非常快的触发定时器事件程序。当他把这段代码复制到一个新的CANo…

【开发】安防监控/视频汇聚/云存储/AI智能视频融合平台页面新增地图模式

AI智能分析网关包含有20多种算法&#xff0c;包括人脸、人体、车辆、车牌、行为分析、烟火、入侵、聚集、安全帽、反光衣等等&#xff0c;可应用在安全生产、通用园区、智慧食安、智慧城管、智慧煤矿等场景中。将网关硬件结合我们的视频汇聚/安防监控/视频融合平台EasyCVR一起使…

2023-简单点-怎么知道树莓派是什么cpu架构?

树莓派是几位&#xff1f; getconf LONG_BIT https://qengineering.eu/install-ncnn-on-raspberry-pi-4.html

算法随笔:各种经典最短路算法的简要比较总结

有多种最短路径的应用场景&#xff0c;它们需要用到不同的算法来解决。除了贪心最优搜索之外&#xff0c;其他都是最优性算法&#xff0c;即得到的解都是最短路径。其中m是边的数量&#xff0c;n是点的数量。 问题边权算法时间复杂度一个起点&#xff0c;一个终点非负数&#…

【C#】C#调用进程打开一个exe程序

文章目录 一、过程二、效果总结 一、过程 新建WinForm程序&#xff0c;并写入代码&#xff0c;明确要调用的程序的绝对路径&#xff08;或相对路径&#xff09;下的exe文件。 调用代码&#xff1a; 这里我调用的另一个程序的路径是&#xff1a; F:\WindowsFormsApplication2…

uni-app--》基于小程序开发的电商平台项目实战(一)

&#x1f3cd;️作者简介&#xff1a;大家好&#xff0c;我是亦世凡华、渴望知识储备自己的一名在校大学生 &#x1f6f5;个人主页&#xff1a;亦世凡华、 &#x1f6fa;系列专栏&#xff1a;uni-app &#x1f6b2;座右铭&#xff1a;人生亦可燃烧&#xff0c;亦可腐败&#xf…

ES6之 变量的解构赋值 ➕ 扩展运算符(…)

ES6之 变量的解构赋值 ➕ 扩展运算符 1. 变量的解构赋值1.1 直接解构赋值1.2 解构赋值——修改属性名1.3 连续解构赋值 2. 扩展运算符2.1 简介&#xff08;官网&#xff09;2.2 应用例子2.2.1 简单例子12.2.2 数组拷贝2.2.3 连接多个数组2.2.4 拷贝对象&#xff08;多层和多维数…

PhotoShop 快捷键及技巧

复制图层&#xff1a; 图层-新建-复制图层 图层上右键&#xff0c;点击复制图层 ctrlJ 删除图层&#xff1a; 选中图层&#xff0c;按del键删除图层 对图像实现自由变换&#xff1a; ctrlT 同过图像边框八个方块对图像实现大小变化 按回车键确认变换 按住shift键实现等比例…

可以用Python实现RPC协议的接口自动化测试

基于RPC协议的接口自动化测试可以用Python语言实现。下面是实现步骤&#xff1a; 1、安装依赖库&#xff0c;如protobuf、grpc。 2、编写.proto文件定义接口参数和返回值。 3、使用protoc编译.proto文件生成Python代码。 4、编写客户端代码调用远程接口进行测试。 具体实现…

数据结构-堆的实现及应用(堆排序和TOP-K问题)

数据结构-堆的实现及应用[堆排序和TOP-K问题] 一.堆的基本知识点1.知识点 二.堆的实现1.堆的结构2.向上调整算法与堆的插入2.向下调整算法与堆的删除 三.整体代码四.利用回调函数避免对向上和向下调整算法的修改1.向上调整算法的修改2.向下调整算法的修改3.插入元素和删除元素函…

操作系统——复习笔记

主要是按照王道考研408的ppt进行复习。 概述 操作系统概览 操作系统&#xff08;Operating System&#xff0c; OS&#xff09;是指控制和管理整个计算机系统的硬件和软件资源&#xff0c;并合理地组织调度计算机的工作和资源的分配&#xff1b;以提供给用户和其他软件方便的…

Unity Shader 溶解效果

一、效果图 二、原理分析 实现原理就是在片元着色器中&#xff0c;对像素点进行丢弃不显示。借助美术做的噪点图(利用噪点图中rgb中r值来做计算)。比如噪点图r值从0-1。我们从小到大让r值逐渐丢弃&#xff0c;比如刚开始r < 0.1丢弃&#xff0c;然后t < 0.2丢弃...知道t…