【问题标题】:Possible Bug - Associating a stream with a FD that already has one associated with it?可能的错误 - 将流与已关联的 FD 关联?
【发布时间】:2010-02-06 18:28:19
【问题描述】:

你好。我正在学习网络课程,我们正在使用迄今为止在课堂上学到的套接字函数创建自己的“网络 API”。

对于这个任务,教授提供了已经完整的聊天和服务器程序,我们要填写一个空白的 .c 文件,该文件有一个相关的头文件,描述了聊天和服务器在我们的小“网络 API”中的函数调用使用的程序。

我即将完成,但遇到了问题。我打算进去弄乱我的代码,但是我的“网络 API”的所有其他部分都可以工作,所以当我快要完成并且有一点改变导致其他部分没有的时候,我不想搞砸它去工作。另外,我不能 100% 确定我确实知道我的错误是什么。

我的问题在于我的名为 recv_line 的函数应该模仿 recv()。

我的函数 recv_line 从客户端获得文件描述符,用于与服务器建立的连接。然后我继续使用 fdopen 将流与文件描述符相关联。这段代码看起来像:

  // Associate a stream with the sock_fd
  if (NULL == (fdstream = fdopen(sock_fd, "r"))) {
    return -1;
  }

现在这就是我认为我的错误所在的地方,但我不能 100% 确定,正如我所说的。首先,我第一次使用 recv_line 函数时效果很好。它实际上按预期进行了 recv_line 行。但问题又出现了第二次。

我使用 GDB 逐步完成了我的函数,上面的代码确实可以毫无问题地执行。然后我继续从我的流中剥离一个字符(使用 fgetc)并检查它是否等于 EOF 字符。如果是我返回,否则我将处理它。我第二次调用 recv_line 它返回因为它每次都读取 EOF 字符。

我假设发生这种情况是因为发送到客户端的数据位于原始流中(第一次创建的流),而第二次调用 fdopen 时,我创建的第二个流中没有任何数据?我不完全确定当我第二次调用 fdopen 时它是如何工作的?

在我的旁注中,我以这种方式读取数据,因为我们应该继续读取数据,直到我们读取 EOF 或字符序列 \r\n。我将 getc 和 ungetc 与流一起使用,以提供将检查 EOF 或 \r\n 序列的前瞻机制。

说了这么多,有谁知道我的大量代码和程序到底发生了什么?我在谷歌上搜索了一种方法来检查 FD 是否有关联的流,并且没有成功地找到/看到任何东西。我做了一些研究,看看是否有一些 fstat 时间函数可以让我看到相关的蒸汽并且什么都没有。我可以只复制传入函数 recv_line 的 FD,然后在复制的 FD 上 fdopen 并避免所有这些问题吗?

感谢所有帮助。如果我不够清楚,我很抱歉。如果您要求我这样做,我会尽力澄清任何事情。抱歉我的问题太长了。 =*(

再次感谢。非常感谢您的帮助。

【问题讨论】:

    标签: c unix networking sockets


    【解决方案1】:

    我假设这是因为发送到客户端的数据在原始流中(第一次创建的流),而我第二次调用 fdopen 时没有任何数据 留在 第二个流读取 我创建的文件描述符?

    这是非常正确的(有一些小的更正)。流对象的主要职责是将文件描述符数据流从内核读取到用户空间,因此您不需要系统调用的开销。但是一旦流对象读取了数据流,内核就会推进它的“读指针”,所以下次你尝试从文件描述符中读取数据时,它会从你离开的地方继续。

    对于给定的描述符,您只能一次调用 fdopen。如果你可以重构你的代码,以便有一个地方你可以轻松地调用一次 fdopen,那就是要走的路。

    一旦你调用 fdopen,你不应该再对文件描述符做任何事情 - 甚至不关闭它(当你调用 fclose 时,底层描述符将被关闭)。

    根据 Jonathon Leffler 的评论,我猜教授的代码也会关闭描述符。如果是这样,你就不能使用 fdopen。

    一些想法:

    1. 只需逐字节读取 fd。性能不是很好,但可以工作。
    2. 您是否可以假设您的代码一次只处理一个连接?如果是这样,只需在 recv_line 中有一个静态缓冲区来缓冲 fd 数据。当缓冲区为空时填充缓冲区,然后从中读取数据。
    3. 如果您需要处理多个连接,您可以扩展#2 以使每个文件描述符拥有一个缓冲区。您将需要某种地图来处理此问题。

    根据评论更新

    @Chris - 让我解释一下使用 fdopen 的风险。在文件描述符上使用 fdopen 后,您必须调用 fclose(否则可能会泄漏为 FILE * 分配的资源)并且您不能在描述符上调用 close(因为fclose 描述符,您可能会在多次关闭描述符时遇到问题)。因此,除非您的代码已经被委派了关闭描述符的职责(在这种情况下,您可以只调用 fclose),否则您不能使用 fdopen。

    没有内置方法来检测是否在描述符上调用了 fdopen。您需要自己添加逻辑。您需要保留 fdopen 返回的 FILE * 的集合;如果您已经有一个给定描述符的 FILE *,您将使用它,否则您将调用 fdopen。

    【讨论】:

    • 从问题的声音来看,教授创建了一个标头,它定义了 recv_line() 的接口,并且更改它不是一个选项。
    • recv_line 实际上不必关闭 FD。 recv_line 应该能够在任何被调用的连接上工作,无论是一个连接还是多个连接。至于只调用一次 fdopen,我该如何检查我是否已经在我的 FD 上调用了 fdopen?这似乎是我的问题。我可以创建一张地图,但它似乎必须是动态的,我不知道该怎么做(还没有用谷歌搜索它......)。我假设我会继续分配/重新分配一个静态的数组?编辑 - 看起来我会继续分配/重新分配东西:D
    【解决方案2】:

    这是来自fdopen 手册页:

    fdopen() 函数将流与现有文件相关联 描述符,fd。流的模式(值“r”,“r+”之一, "w", "w+", "a", "a+") 必须与文件的模式兼容 描述符。新流的文件位置指示器设置为 属于 fd 的,错误和文件结束指示符是 清除。模式“w”或“w+”不会导致文件截断。 该 文件描述符不会被复制,并且会在 fdopen() 创建的流关闭时关闭。 [emph.我的]

    换句话说,据我了解(这不是我的专业领域),您一遍又一遍地“劫持”套接字文件描述符的想法实际上是问题所在,因为当您完成这是第一次,您不仅关闭了流,还关闭了与它一起的套接字连接。

    【讨论】:

      【解决方案3】:

      我不确定我是否真的理解您的问题,但我建议在创建套接字时直接打开输入/输出流,并将它们与套接字一起保存在一个结构中,只要套接字有效(连接的)。这样您就不需要为每一行打开新的流。然后recv_line 函数将输入流作为参数而不是套接字。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-04-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-08-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多