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

这篇具有很好参考价值的文章主要介绍了【计算机网络】网络编程接口 Socket API 解读(2)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

        本文讲述的 socket 内容源自 Linux man。本文主要对各 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)
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 object
                                               pointed 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>

       int
       main(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. */
           else
               printf("No data within five seconds.\n");

           exit(EXIT_SUCCESS);
       }

下一篇 【计算机网络】网络编程接口 Socket API 解读(3)​​​​​​​文章来源地址https://www.toymoban.com/news/detail-700239.html

到了这里,关于【计算机网络】网络编程接口 Socket API 解读(2)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

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

             Socket 是网络协议栈暴露给编程人员的 API,相比复杂的计算机网络协议,API 对关键操作和配置数据进行了抽象,简化了程序编程。         本文讲述的 socket 内容源自 Linux man。本文主要对各 API 进行详细介绍,从而更好的理解 socket 编程。 续  【计算机网络】网

    2024年02月08日
    浏览(49)
  • 【计算机网络】网络编程接口 Socket API 解读(11)

             Socket 是网络协议栈暴露给编程人员的 API,相比复杂的计算机网络协议,API 对关键操作和配置数据进行了抽象,简化了程序编程。         本文讲述的 socket 内容源自 Linux man。本文主要对各 API 进行详细介绍,从而更好的理解 socket 编程。 遵循 POSIX.1-2008      

    2024年02月08日
    浏览(46)
  • 【计算机网络】网络编程接口 Socket API 解读(4)

             Socket 是网络协议栈暴露给编程人员的 API,相比复杂的计算机网络协议,API 对关键操作和配置数据进行了抽象,简化了程序编程。         本文讲述的 socket 内容源自 Linux man。本文主要对各 API 进行详细介绍,从而更好的理解 socket 编程。 poll()           遵

    2024年02月09日
    浏览(58)
  • 【计算机网络】网络编程接口 Socket API 解读(1)

             Socket 是网络协议栈暴露给编程人员的 API,相比复杂的计算机网络协议,API 对关键操作和配置数据进行了抽象,简化了程序编程。         本文讲述的 socket 内容源自 Linux man。本文主要对各 API 进行详细介绍,从而更好的理解 socket 编程。 遵循 POSIX.1 - 2001、POS

    2024年02月09日
    浏览(44)
  • 【计算机网络】网络编程接口 Socket API 解读(3)

             Socket 是网络协议栈暴露给编程人员的 API,相比复杂的计算机网络协议,API 对关键操作和配置数据进行了抽象,简化了程序编程。         本文讲述的 socket 内容源自 Linux man。本文主要对各 API 进行详细介绍,从而更好的理解 socket 编程。 poll()           遵

    2024年02月09日
    浏览(49)
  • 计算机网络--网络编程(1)

    简单认识一下传输层中的UDP和TCP: TCP:有链接,可靠传输,面向字节流,全双工 UDP:无连接,不可靠传输,面向数据报,全双工 有链接类似于打电话,通了就是有链接。没通就一直在等待。 无连接类似于发短信,只管发,不管到。 可靠传输就是保证信息传输的可靠性。就

    2024年02月11日
    浏览(46)
  • 【计算机网络】4 Socket网络编程

    目录 写在前面的话 概览 环境 URL请求程序: 2. 系统时间查询 服务端 T_TCPServer.py代码 客户端 T_TCPClient.py代码 运行效果 3. 网络文件传输 服务端 TF_TCPServer.py代码 运行效果(后面加了远程功能,效果图暂时还在本地) 4. 网络聊天室 服务端 UDPServer.py代码 客户端 UDPClient.py代码 运

    2024年02月01日
    浏览(45)
  • 【计算机网络】网络编程套接字(一)

    目录 1.预备知识 1.1.理解源IP地址和目的IP地址 1.2.认识端口号 1.2.1.理解\\\"端口号\\\"和\\\"进程ID\\\" 1.2.2.理解源端口号和目的端口号 1.3.认识TCP/UDP协议 1.3.1.TCP协议 1.3.2.UDP协议 1.4.网络字节序 网络字节序和主机字节序的转换 2.socket编程接口 2.1.sockaddr结构 struct sockaddr_in 的具体结构: 2.

    2024年02月08日
    浏览(53)
  • 计算机网络---网络编程套接字(一)

    ✨ 计算机网络—网络编程套接字之UDP数据报套接字编程 作者介绍: 🎓作者:偷偷敲代码的青花瓷🐱‍🚀 👀作者的Gitee:代码仓库 📌系列文章推荐:计算机网络 ——网络原理之初识 ✨✨我和大家一样都是热爱编程✨,很高兴能在此和大家分享知识,希望在分享知识的同时,能和大

    2023年04月09日
    浏览(37)
  • 【计算机网络】网络编程套接字(二)

    简单TCP服务器实现 我们将会使用到的头文件放在 comm.h 文件中 创建套接字 创建过程和UDP服务器几乎完全一样,除了使用的是TCP服务器使用的是流式服务(SOCK_STREAM),UDP使用的是数据包服务(SOCK_DGRAM) 服务器绑定 绑定的过程和UDP服务器也是相同的,可以看着复习一下 定义好 st

    2024年02月13日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包