为什么要考虑缓冲区而不是线路?fgets() 可以一次读取多行吗?

Yu *_*ing 3 c++ unix network-programming

最近我在读《Unix网络编程第一卷》。在3.9节中,图3.18最后两段说了,这里我引用一下:

...但是我们的建议是从黄油而不是行的角度来思考,编写代码来读取数据的黄油,如果需要一行,请检查缓冲区以查看它是否包含该行。

在下一段中,作者给出了一个更具体的例子,这里我引用一下:

...我们将在第 6.3 节中看到。像 select 这样的系统函数仍然不知道 readline 的内部缓冲区,因此粗心编写的程序很容易发现自己在 select 中等待已经接收并存储在 readline 缓冲区中的数据。

在6.5节中,实际的问题是“stdio和select()的混合”,这将使程序“容易出错”,这里我引用这本书。但如何呢?

我知道作者稍后在同一节中给出了答案,根据我对本书的理解,这是因为 select() 隐藏了数据,因此 select() 无法知道已读取的数据是消耗与否。

答案确实存在,但这里的第一个问题是我真的很难得到它,我无法想象它会对程序造成什么损害,也许我需要一个遇到这个问题的演示程序来帮助我理解它。

还是在第 6.5 节中,作者试图进一步解释这个问题,我在这里引用:

...考虑当标准输入有几行输入可用时的情况。select 将导致第 20 行的代码使用 fgets 读取输入,然后将可用行读入 stdio 使用的缓冲区中。但是,fgets 只返回一行,并将所有剩余数据留在 stdio 缓冲区中......

上面提到的“第20行”是:

if (Fgets(sendline, MAXLINE, fp) == NULL)
Run Code Online (Sandbox Code Playgroud)

其中 sendline 是 char 数组,fp 是指向 FILE 的指针。我查看了 Fgets 的详细实现,它只是用一些额外的错误处理逻辑包装了 fgets() ,仅此而已。

这是我的第二个问题,fgets 如何设法,在这里我再次引用,读取可用的?我的意思是,我查阅了 fgets 的手册页,它说 fgets 通常在第一个换行符处停止。这不是意味着 fgets 只会读取一行吗?更具体地说,如果我在终端中输入一行并按 Enter 键,则 fgets 会读取这一行。我再次执行此操作,然后 fgets 读取下一个新行,并且该点一次一行。

感谢您耐心阅读所有描述,并期待您的答复。

Som*_*ude 5

(当涉及到网络编程时)考虑缓冲区而不是线路的主要原因之一是因为 TCP 是一种流协议,其中数据只是以连接开始并以断开连接结束的字节流。

除了 TCP 之上的应用程序级协议所决定的之外,没有消息边界,也没有“线路”。

这使得从 TCP 连接读取“行”变得不可能,没有这样的原始函数。您必须使用缓冲区进行读取。由于流式传输和缺乏任何类型的边界,单个接收数据的调用可能会给您的应用程序提供比您要求的更少的数据,并且它可能是部分应用程序级消息。或者您可能会收到多条消息,包括最后的部分消息。

另一个重要的注意事项是套接字默认情况下是阻塞的,因此没有准备好接收任何数据的套接字将导致任何读取调用阻塞,并等待直到有数据。该select调用仅告知读取调用现在是否不会阻塞。如果您在循环中多次执行读取调用,则当要接收的数据耗尽时,它可能(并且最终会)阻塞。

所有这些使得使用高级函数(例如fgets(当然在调用之后fdopen)从 TCP 套接字读取数据)变得非常困难,因为如果您使用阻塞套接字,它可能随时阻塞。或者,如果您使用非阻塞套接字,并且读取调用会返回失败,则它会阻塞(是的,作为错误返回)。

如果您使用自己的缓冲,则可以在与或select相同的循环中使用,以确保调用不会阻塞。或者使用非阻塞套接字,您可以通过单个读取调用收集数据(并附加到缓冲区),并在拥有完整消息时添加检测(通过了解其长度或通过检测消息终止符或分隔符,例如新队)。readrecv


至于fgets读取“多行”,它可能会导致底层读取用多行填充缓冲区,但是fgets本身只会用单行填充您提供的缓冲区。

fgets永远不会给你多行。