【计算机网络】网络编程接口 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/socket.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) |
| EINVAL | nfds 是负值,或者超过了 RLIMIT_NOFILE 资源限制,具体参见getrlimit(2) |
| EINVAL | timeout 中的数值不合法 |
| 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 解读(2)
Socket 是网络协议栈暴露给编程人员的 API,相比复杂的计算机网络协议,API 对关键操作和配置数据进行了抽象,简化了程序编程。 本文讲述的 socket 内容源自 Linux 发行版 centos 9 上的 man 工具,和其他平台(比如 os-x …...
【黄啊码】PHP如何防止重复提交
防抖(Debounce)是一种防止重复提交的策略,它通过延迟一定时间来合并连续的操作,以确保只执行一次。 以下是几种防抖的实现方法以及对应的代码示例: 1. 前端 JavaScript 实现: 在前端使用 JavaScript 实现…...
2594. 修车的最少时间
文章目录 Tag题目来源题目解读解题思路方法一:二分枚举答案 写在最后 Tag 【二分枚举答案】【数组】 题目来源 2594. 修车的最少时间 题目解读 给你一个表示机械工能力的数组 ranks,ranks[i] 表示第 i 位机械工可以在 r a n k s [ i ] ∗ n 2 ranks[…...
vue 使用qrcode生成二维码并可下载保存
安装qrcode npm install qrcode --save代码 <template><div style"display: flex; flex-direction: column; align-items: center; justify-content center;"><div>查看溯源码,<a id"saveLink" style"text-decorati…...
网络融合的发展思路
虽然移动和固定网的融合代表了下一代网络的发展方向,但是目前移动和固定网的 发展还是独立的,有着各自的演进方式,要实现两个网络的完全融合是一个长期 的、逐步发展的过程。 网络融合的体系结构首先应坚持网络分层和功能分离的原则&#…...
报考浙江工业大学MBA项目如何选择合适的辅导班?
浙江工业大学MBA项目每年有数百人报考,在浙江省内除了浙大以外算是人数比较多的一个项目。2023级的招生中第一志愿也通过复试刷掉了百来人,在省内其实作为第一志愿报考的风险在逐渐增大,考生们如果坚持报考,则在针对联考初试的备考…...
算法训练第五十八天
总结:今日事单调栈的开端,还是挺巧妙的。 496. 下一个更大元素 I - 力扣(LeetCode) 代码: class Solution { public:vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& …...
如何快速生成一个H5滑动的卡片(单页和分页都有)
单页 <ul class"combo"><li v-for"(item, index) in arr" :key"index"><div class"combo-name">{{ item.A }}</div><div class"combo-price">{{ item.B }}</div><div class"co…...
嵌入式开发笔试面试
C语言部分: 1.gcc的四步编译过程 1.预处理 展开头文件,删除注释、空行等无用内容,替换宏定义。 gcc -E hello.c -o hello.i 2.编译 检查语法错误,如果有错则报错,没有错误则生成汇编文件。 gcc -S hello.i -o h…...
2023国赛数学建模B题思路分析 - 多波束测线问题
# 1 赛题 B 题 多波束测线问题 单波束测深是利用声波在水中的传播特性来测量水体深度的技术。声波在均匀介质中作匀 速直线传播, 在不同界面上产生反射, 利用这一原理,从测量船换能器垂直向海底发射声波信 号,并记录从声波发射到…...
thinkphp6 入门(5)-- 模型是什么 怎么用
一、模型 MVC架构 之前开发一个功能,后端为在控制器(C)中写 php SQL,前端为在页面(V)中写html css js,这就形成了 VC 架构。 但是发现,相同的数据逻辑(SQL…...
Hadoop HDFS 高阶优化方案
目录 一、短路本地读取:Short Circuit Local Reads 1.1 背景 1.2 老版本的设计实现 1.3 安全性改进版设计实现 1.4 短路本地读取配置 1.4.1 libhadoop.so 1.4.2 hdfs-site.xml 1.4.3 查看 Datanode 日志 二、HDFS Block 负载平衡器:Balan…...
通俗易懂讲解大模型:Tokenizer
Tokenizer Tokenizer 是 NLP pipeline 的核心组件之一。Tokenizer 的目标是:将文本转换为模型可以处理的数据。模型只能处理数字,因此 Tokenizer 需要将文本输入转换为数字输入。 通常而言有三种类型的 Tokenizer :Word-based Tokenizer、Cha…...
nested exception is java.io.FileNotFoundException
完整的错误信息: [main] ERROR o.s.boot.SpringApplication - Application run failed org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [com.heima.article.ArticleApplication]; nested exception is java…...
ARM编程模型-常用指令集
一、ARM指令集 ARM是RISC架构,所有的指令长度都是32位,并且大多数指令都在一个单周期内执行。主要特点:指令是条件执行的,内存访问使用Load/store架构。 二、Thumb 指令集 Thumb是一个16位的指令集,是ARM指令集的功能…...
MAC M2芯片执行yolov8 + deepsort 实现目标跟踪
MAC M2芯片执行yolov8 deepsort 实现目标跟踪 MAC M2 YoloX bytetrack实现目标跟踪 实验结果 MAC mps显存太小了跑不动 还是得用服务器跑 需要实验室的服务器跑 因为网上花钱跑4天太贵了!!! 步骤过程尝试: 执行mot17 数据集 …...
使用Python轻松实现文档编写
大家好,本文将介绍如何使用Python轻松实现文档编写,减少报告撰写的痛苦,使用Microsoft Word、python和python-docx库来简化报告撰写和从报告中提取信息。 案例 读取一个Word文档并进行编辑。 虽然听起来可能不那么令人振奋,但根…...
前后端分离项目,整合成jar包,刷新404或空白页,解决方法
问题解决 1、注销遇到404,或刷新遇到404 # 添加错误跳转 Component public class ErrorConfig implements ErrorPageRegistrar {Overridepublic void registerErrorPages(ErrorPageRegistry registry) {ErrorPage error404Page new ErrorPage(HttpStatus.NOT_FOU…...
前端、后端面试集锦
诸位读者,我们在工作的过程中,经常会因跳槽而面试。 你开发能力很强,懂得技术也很多,若加上条理清晰的面试话术,可以让您的面试事半功倍。 个人博客阅读量破170万,为尔倾心打造的 面试专栏-前端、后端面试…...
Web存储
目录 什么是 HTML5 Web 存储? 方法 cookie webStorage 会话存储 sessionStorage 本地存储localStorage 什么是 HTML5 Web 存储? 使用HTML5可以在本地存储用户的浏览数据。 早些时候,本地存储使用的是 cookie。但是Web 存储需要更加的安全与快速. 这些数据不会被保存在服…...
1987年4月26日下午15-17点出生性格、运势和命运
1987年4月24日晚上出生的人,如今已步入38岁的门槛。在职业生涯中,这是一个承上启下的关键阶段——既脱离了职场新人的青涩,又尚未到达管理者或专家的巅峰位置。从非命理的角度分析,他们的事业运势与时代变迁、个人选择和社会结构密…...
Sunshine游戏串流快速上手:3步搭建你的个人云游戏服务器
Sunshine游戏串流快速上手:3步搭建你的个人云游戏服务器 【免费下载链接】Sunshine Self-hosted game stream host for Moonlight. 项目地址: https://gitcode.com/GitHub_Trending/su/Sunshine 想要在任何设备上玩转PC游戏大作吗?Sunshine作为一…...
瑞芯微RK3568音频调试实战:从procfs到i2cset,手把手教你排查I2S无声问题
RK3568音频调试实战:从无声到有声的完整排查指南 当你在RK3568平台上遇到音频输出无声的问题时,那种挫败感是每个嵌入式工程师都深有体会的。本文将以一个真实的调试案例为线索,带你走完从问题定位到最终解决的完整流程,而不仅仅是…...
Static-Program-Analysis-Book中间表示解析:构建高效静态分析器的核心技术
Static-Program-Analysis-Book中间表示解析:构建高效静态分析器的核心技术 【免费下载链接】Static-Program-Analysis-Book Getting started with static program analysis. 静态程序分析入门教程。 项目地址: https://gitcode.com/gh_mirrors/st/Static-Program-…...
H3CSE 高性能园区网:VRRP 技术详解
H3CSE 高性能园区网:VRRP 技术详解VRRP 技术详解一、VRRP 简介1.1 VRRP 技术背景与定义1.1.1 技术背景1.1.2 VRRP 核心定义1.2 VRRP 核心原理与关键概念1.2.1 主备切换工作流程1.2.2 关键概念解析1.2.3 免费ARP工作原理二、VRRP 核心工作原理2.1 VRRP 基础运行原理概…...
大模型时代,软件开发行业的新玩法(2026 深度复盘)
摘要 2026 年,大模型已从 “辅助工具” 进化为软件开发的核心生产引擎,彻底重构需求、设计、编码、测试、运维全链路逻辑。传统 “人写代码” 的模式被颠覆,人机共生、AI 主导执行、人类决策审核成为行业新常态。本文结合最新行业实践、数据案…...
基于i.MX8M Plus与5G的高性能AI边缘计算网关设计与实践
1. 项目概述:为什么我们需要一个“会思考”的边缘网关?在工业现场待久了,你一定会对几个场景深有感触:产线上几十台PLC和传感器,协议五花八门,Modbus、Profibus、CANopen,想统一采集数据得接一堆…...
基于java的畅阅读系统小程序设计与实现(源码+数据库+文档)
畅阅读系统小程 目录 基于java的畅阅读系统小程序设计与实现 一、前言 二、系统功能设计 三、系统实现 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取: 博主介绍:✌️大厂码农|毕设布道师&a…...
用于参数扫描的自定义工具
能够改变光学系统的参数是任何设置分析的关键部分,以便更好地了解系统在从制造错误到组件潜在错位的任何情况下的行为。设计一个在面对这些不可避免的偏离理想化预期设计时表现出鲁棒性的系统,与找到一个完全满足所有规范的初始设计一样重要,…...
合同系统功能详解:相对方管理
上一期,我们讲解了合同系统的业务功用。本期开始,我们将逐一对合同系统的核心功能进行拆解,结合实际业务场景展开详细讲解。今天,我们重点介绍合同系统中的相对方管理功能。 在实际业务落地过程中,不同企业的经营业态…...
