在套接字文件描述符中,“可读/可写”是什么意思?为什么常规文件不需要考虑这个问题?

6

最近我在学习libev,发现在io_watcher中有一个可读/可写的概念,但我并不太理解。据我所知,在Linux系统编程中有一个参数:

O_ASYNC

当指定的文件变得可读或可写时,将生成一个信号(默认为SIGIO)。此标志仅适用于终端和套接字,而不适用于常规文件。

因此,由于常规文件不会受到可读/可写的影响,那么在套接字编程中,可读/可写实际上是什么意思?内核是如何确定套接字文件描述符是否可读的呢?

考虑到“一切皆文件”的哲学,每个具有不同描述符编号的套接字描述符实际上指向同一个文件吗?如果是这样,我可以认为可读/可写问题是由同步引起的吗?

好吧,看起来我问了一个愚蠢的问题。我的真正意思是,套接字和常规文件都通过文件描述符进行读写,那么为什么套接字描述符具有可读/可写的概念,而常规文件没有呢?由于EJP告诉我这是因为缓冲区和每个描述符都有自己的一对缓冲区,因此我的结论是:可读/可写的概念是针对缓冲区的,如果缓冲区为空,则不可读,而如果缓冲区已满,则不可写。可读性和可写性与同步无关,由于常规文件没有缓冲区,因此它始终是可读和可写的。

还有更多问题:当说到接收缓冲区时,这个缓冲区与int recv(SOCKET socket,char FAR * buf,int len,int flags);中的缓冲区不是同一个东西,对吗?


每个连接对应一个文件描述符。如果10个客户端连接到服务器的套接字,服务器将看到10个文件描述符。int recv()读取系统缓冲区并将内容复制到应用程序传递的用户缓冲区中。 - alvits
@alvits 所以即使有10个套接字,也只有一个系统缓冲区,对吧?如果一个套接字可读,它是在系统缓冲区上可读还是在用户缓冲区上可读? - user2269707
它可以在系统缓冲区上进行读取,因此可以调用系统调用recv()将内容传输到用户缓冲区。看起来你对学习系统方面很感兴趣。我建议阅读有关内核模块和驱动程序的资料。 - alvits
你不能比较不同设备的读写行为。以键盘为例,当没有人在键盘上打字时,就没有可读取的内容。但这并不意味着文件结束了。另一方面,常规文件始终准备好读取数据。即使文件为空,你也总是能到达文件末尾。套接字与键盘类似。如果数据还没有到达,就没有可读取的内容。 - alvits
谢谢,回答很清晰。我正在检查ldd的细节。 - user2269707
2个回答

5

这个问题在Unix网络编程卷1:套接字网络API第3版[W.Richard Stevens,Bill Fenner,Andrew M.Rudoff]中有专门的讨论(点击这里)。为了更好地阅读,我会添加一些小修改:

描述符何时处于可用状态?

[...] 造成 select 返回 socket 的“可读”或“可写”状态的条件如下:

1. 若满足以下四个条件之一,则该 socket 处于可读状态:

  • 在 socket 接收缓冲区中的数据字节数大于等于当前的接收缓冲区低水位标记。对该 socket 进行读操作不会阻塞,并将返回一个大于 0 的值(即准备好读取的数据)。[...]
  • 读半部分连接已关闭(即收到了 FIN 的 TCP 连接)。对该 socket 进行读操作不会阻塞,将返回 0(即 EOF)。
  • 该 socket 是监听 socket,且已完成的连接数不为零。[...]
  • 该 socket 存在待处理错误。对该 socket 进行读操作不会阻塞,并将返回带有特定错误条件的错误码(-1),同时设置错误号errno。[...]

2. 若满足以下四个条件之一,则该 socket 处于可写状态:

  • 在 socket 发送缓冲区中的可用空间字节数大于等于当前的发送缓冲区低水位标记,且:(i) 该 socket 已连接,或(ii) 该 socket 不需要连接(例如 UDP)。这意味着,如果将该 socket 设置为非阻塞模式,则写操作不会阻塞,并将返回一个正值(如传输层接受的字节数)。[...]
  • 写半部分连接已关闭。对该 socket 进行写操作将生成 SIGPIPE 信号。
  • 使用非阻塞连接的 socket 已经完成连接,或连接失败。
  • 该 socket 存在待处理错误。对该 socket 进行写操作不会阻塞,并将返回一个带有特定错误条件的错误码(-1),同时设置错误号errno。[...]

3. 若该 socket 存在异常情况,则表示该 socket 具有待处理的带外数据或仍处于带外标记状态。

[注:]

  • 我们对“可读”和“可写”的定义直接采用自内核的 soreadablesowriteable 宏定义(详见 TCPv2 的第 530-531 页)。类似地,对于 socket 的“异常情况”,我们采用了这些页面上的 soo_select 函数中的定义。

  • 请注意,当 socket 发生错误时,select 将其标记为可读和可写。

  • 接收和发送低水位标记的目的是让应用程序控制在 select 返回可读或可写状态之前必须准备多少数据进行读取,或者必须有多少空间可用于写入。例如,如果我们知道除非至少有 64 个字节的数据存在,否则我们的应用程序没有任何有效工作可做,我们可以将接收低水位标记设置为 64,以防止在准备好读取的数据少于 64 字节时唤醒我们。

  • 只要 UDP socket 的发送低水位标记小于发送缓冲区大小(这应始终是默认关系),UDP socket 就始终是可写的,因为不需要连接。

<

同书相关阅读:TCP套接字发送缓冲区和UDP套接字(伪)发送缓冲区

请注意,这是一个关于IT技术的文章,介绍了TCP套接字和UDP套接字的发送缓冲区。要理解这篇文章,您需要具备一定的计算机网络知识。

1
可读表示套接字接收缓冲区中存在数据或者FIN。
可写表示套接字发送缓冲区中有空间可用。
文件没有套接字发送或接收缓冲区。
考虑“一切皆文件”的哲学。
那是什么哲学?
每个具有不同描述符编号的套接字描述符实际上指向同一个文件吗?
什么文件?为什么它们会指向同一个东西?问题毫无意义。
我对一件事感到困惑:当套接字被创建时,描述符实际上是指向套接字的接收和发送缓冲区。
它“指向”很多东西:源地址、目标地址、源端口、目标端口、一对缓冲区、一组计数器和定时器...
并没有所谓的“文件代表网络硬件”,除非你说的是/dev/...中的设备驱动程序入口,但这基本上与此无关。TCP套接字是连接的端点。它特定于该连接、TCP、源和目标地址和端口等。

这是否意味着不同描述符号的所有套接字文件描述符实际上指向同一个缓冲区? - user2269707
不,每个套接字都有自己的一对。 - user207421
3
这是哪种哲学?这是一个严肃的问题吗?https://en.wikipedia.org/wiki/Everything_is_a_file - 我猜你一定是Windows用户,但如果你要回答有关套接字的问题,你应该知道套接字接口起源于BSD和Unix哲学,术语有时会很相关。套接字就是一个文件;你所说的“文件”应该称为常规文件,两者都通过文件描述符进行访问。 - user2404501
拥有一个通用的“可以消耗和/或生成字节序列”的类非常有用,为该通用类创建一个共同接口(readwrite),然后再从中进行专门化,形成提供其他操作的各种子类。这种设计是Unix中最好的事情之一。而该类层次结构的顶层恰好被称为“文件”。 - user2404501
1
各位,没有必要为我的愚蠢问题争论,我已经更新了更多细节的问题。 - user2269707
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接