为什么限制与套接字交互的 C 标准 I/O 流?

Why the restrictions on C standard I/O streams that interact with sockets?

在 CSAPP 书第 10.9 节中,它说标准 I/O 流有两个限制,它们与套接字的限制交互不良。

Restriction 1: Input functions following output functions. An input function cannot follow an output function without an intervening call to fflush, fseek, fsetpos, or rewind. The fflush function empties the buffer associated with a stream. The latter three functions use the Unix I/O lseek function to reset the current file position.

Restriction 2: Output functions following input functions. An output function cannot follow an input function without an intervening call to fseek, fsetpos, or rewind, unless the input function encounters an end-of-file.

但我不明白为什么要施加限制。那么,我的问题是:是什么因素导致了这两个限制?

它还说“在套接字上使用lseek函数是非法的。”,但是fseekfsetposrewind怎么可能使用lseek 是否重置当前文件位置?

有一个类似的问题here,但我的问题与这个不同。

stdio 函数用于 缓冲文件输入和输出 。套接字不是文件,而是套接字。它甚至没有文件位置,缓冲区要求与普通文件截然不同——套接字可以有独立的输入和输出缓冲区,stdio 文件 I/O 不能!

问题是文件输入文件输出共享相同文件位置, 并且操作系统可能具有(并且实际上将在 Unix 上具有)与由于 C 中的缓冲而导致的文件位置不同的文件位置。

因此,根据 C99 原理

A change of input/output direction on an update file is only allowed following a successful fsetpos, fseek, rewind, or fflush operation, since these are precisely the functions which assure that the I/O buffer has been flushed.

请注意,所有这些仅适用于使用 + 打开的文件 - 对于以任何其他标准模式打开的文件,不可能混合输入和输出。

因为 C 标准要求在 FILE * 上从 input 切换到 output 函数之一fsetposrewindfseek,本质上调用 lseek 必须成功(请注意,调用 fflush 会导致写入缓冲输出,当然不会在尝试输出函数之前丢弃缓冲输入)...但是套接字不可搜索,因此 lseek 总是 失败 - 这意味着您不能使用 FILE * 已打开用于读取和写入包装套接字,实际上 读取和 写入 套接字。


如果您真的需要,可以使用 fdopen 打开带有流套接字的 FILE *:只需打开 两个 个文件 - 一个 "rb" 用于输入, 另一个 "wb" 用于输出。

当它说 "An input function cannot follow an output function without an intervening call to fflush, fseek, fsetpos, or rewind" 时,它的意思是如果您不这样做,它可能无法按您预期的那样工作。但他们主要是在谈论 i/o to/from 普通文件。

如果你有一个连接到套接字的 FILE * 流,并且你想在写入和读取之间来回切换,我希望你调用 fflush 时它会工作得很好从写作转向阅读。我不希望从阅读切换到写作时有必要调用任何东西。

(处理文件时,必须调用 fseek 或其亲属之一才能正确更新文件位置,但流没有要更新的文件位置。)

我认为原因是,在早期,对于大多数实现来说,缓冲区是共享的,用于读取和写入。

道​​理很简单,大部分情况都是uni-direction。并且维护2个buffer分别用于读写浪费space.

如果你只有一个缓冲区,当你改变IO方向时,你需要处理缓冲区。这就是为什么您需要 fflush, fseek, fsetpos, or rewind 将缓冲区写入磁盘或清空缓冲区以准备下一个 IO 操作。

我检查了一种 glibc 实现,它只使用一个缓冲区进行读写。

static void init_stream (register FILE *fp) {
    ...
    fp->__buffer = (char *) malloc (fp->__bufsize);

   if (fp->__bufp == NULL)
    {
      /* Set the buffer pointer to the beginning of the buffer.  */
      fp->__bufp = fp->__buffer;
      fp->__put_limit = fp->__get_limit = fp->__buffer;
    }
}

fseek为例

/* Move the file position of STREAM to OFFSET
   bytes from the beginning of the file if WHENCE
   is SEEK_SET, the end of the file is it is SEEK_END,
   or the current position if it is SEEK_CUR.  */
int
fseek (stream, offset, whence)
     register FILE *stream;
     long int offset;
     int whence;
{
  ...
  if (stream->__mode.__write && __flshfp (stream, EOF) == EOF)
    return EOF;
  ...
  /* O is now an absolute position, the new target.  */
  stream->__target = o;
  /* Set bufp and both end pointers to the beginning of the buffer.
     The next i/o will force a call to the input/output room function.  */
  stream->__bufp
    = stream->__get_limit = stream->__put_limit = stream->__buffer;
  ...
}

如果是写入模式,此实现会刷新缓冲区到磁盘文件。

并且它会重置读写指针。它相当于重置或刷新缓冲区以供读取。

它与 C99 匹配(归功于

A change of input/output direction on an update file is only allowed following a successful fsetpos, fseek, rewind, or fflush operation, since these are precisely the functions which assure that the I/O buffer has been flushed.

有关详细信息,请查看 here