为什么在 C 中读写套接字时要使用循环缓冲区?

Why should I use circular buffers when reading and writing to sockets in C?

我正在做一个任务,目标是用 C 语言创建一个能够同时处理多个客户端的基本 FTP 服务器。 主题告诉我们“明智地使用循环缓冲区”,但我真的不明白为什么或如何?

我已经在使用 select 来了解何时可以无阻塞地读取或写入我的套接字,因为我不允许使用 recvsendO_NONBLOCKING.

每个连接都有一个结构,我在其中存储与此客户端相关的所有内容,如通信文件描述符、网络信息和缓冲区。

为什么我不能在我的套接字上使用 read 到一个固定大小的缓冲区,然后将这个缓冲区传递给解析函数?

写作也是如此:为什么我不能 dprintf 将我的响应直接发送到套接字中?

从我的角度来看,使用循环缓冲区会增加一层无用的复杂性,只是为了将其转换回字符串以解析命令或发回响应。

我是不是理解错了主题?我应该将命令和响应存储为字符串的循环缓冲区,而不是存储单个字符吗?

Why should I use circular buffers when reading and writing to sockets in C?

套接字接口本身并没有提供使用循环缓冲区的原因(a.k.a. 环形缓冲区)。您应该查看使用套接字的应用程序的协议要求——在本例中为 FTP 协议。这将根据底层网络协议(FTP 的 TCP)的特性及其对套接字层行为的影响进行着色。

Why can't I just use read on my socket into a fixed size buffer and then pass this buffer to the parsing function ?

你当然可以不用循环缓冲区,但这并不像你想象的那么简单。无论如何,这不是您应该问的问题:不是 是否需要 循环缓冲区,而是它们可以提供哪些您可能无法获得的好处。稍后会详细介绍。

此外,您当然可以拥有 固定大小 循环缓冲区——“循环”和“固定大小”是正交特性。但是,使用循环缓冲区的目标通常是最小化或消除动态调整缓冲区大小的需要。

Same goes for writing : why can't I just dprintf my response into the socket ?

同样,您可能可以按照您的描述进行操作。问题是你从插入循环缓冲区中得到什么?再一次,稍后再说。

From my point of view using a circular buffer adds a useless layer of complexity just to be translated back into a string to parse the command or to send back the response.

Did I misunderstood the subject ?

你说的是字符串之间的相互转换让我觉得你确实误解了这个主题。

Instead of storing individual characters should I store commands and responses as circular buffers of strings ?

同样,您认为“of strings”从何而来?你为什么假设缓冲区的元素会代表(整个)消息?

A circular buffer 更像是一种 使用方式 普通的,平坦的,通常 fixed-size 缓冲区而不是它自己的单独数据结构.但是,这里涉及到一些额外的簿记数据,因此我不会与任何想称其为数据结构的人争论不休。

输入的循环缓冲区

循环缓冲区有用的主要上下文之一是数据以流语义(如 TCP 提供)而不是消息语义(如 UDP 提供)到达。关于您的任务,考虑一下:当服务器读取命令输入时,它如何知道命令在哪里结束?我怀疑您假设您将获得每个 read() 的一个完整命令,但这绝不是一个安全的假设,无论客户端的实现如何。您可能会在每个 read() 上收到部分命令、多个命令或同时收到这两种命令,您需要做好应对的准备。

所以假设,例如,您在一个 read() 中收到一个半控制消息。您可以解析和响应第一个,但您需要读取更多数据才能对第二个进行操作。你把这些数据放在哪里?好的,您将其读入缓冲区的末尾。如果在下一个 read() 您不仅收到一条消息的其余部分,而且还收到另一条消息的一部分怎么办?

您不能在缓冲区末尾无限期地添加数据,即使您根据需要动态分配更多 space 也是如此。您 可以 在某些时候将未处理的数据从缓冲区的尾部移动到开头,从而在最后打开 space ,但这是昂贵的,并且在这一点上我们远远超出了您心中的简单性。 (这种简单性总是虚构的。)或者,您可以将读取执行到循环缓冲区中,以便从缓冲区的(逻辑)开头使用数据自动使 space 在(逻辑)结尾处可用。

输出的循环缓冲区

类似的情况适用于使用 stream-oriented 网络协议的写入端。考虑到您不能 write() 一次任意数量的数据,并且很难事先确切知道您 可以 写入多少。与控制连接相比,这更有可能在数据连接上咬你,但原则上,它适用于两者。如果您一次只有一个客户端需要提供数据,那么您可以保持 write() 循环,直到您成功传输所有数据,这就是 dprintf() 会做的。但这可能是一个阻塞操作,因此当您同时为多个客户端提供服务时,它会削弱您的响应能力,甚至可能只有一个,如果(与 FTP 一样)每个客户端有多个连接。

你需要在服务器上缓冲数据,尤其是数据连接,现在你遇到了与读取端几乎相同的问题:当你只写入了你想要的部分数据时发送,套接字还没有准备好让你发送更多,你会怎么做?您可以只跟踪您在缓冲区中的位置,并尽可能发送更多片段,直到缓冲区为空。但是这样你就浪费了从源文件中读取更多数据或缓冲更多控制响应的机会,直到你完成缓冲区。再一次,循环缓冲区可以缓解这种情况,它为您提供了一个缓冲更多数据的地方,而不需要它从缓冲区 bei 的开头开始g 受缓冲区物理结束前可用 space 的限制。