【问题标题】:How to swap two open file descriptors?如何交换两个打开的文件描述符?
【发布时间】:2019-06-04 14:16:02
【问题描述】:

对于我的硕士论文项目,我正在用 C 语言构建一个适用于 Unix 套接字的 API。简而言之,我有两个由它们的两个 fd 标识的套接字,我在其上调用了 O_NONBLOCK connect()。此时,我正在调用select() 来检查哪个先连接并准备好写入。

问题从现在开始,因为使用此 API 的应用程序只知道其中一个套接字,比如说 fd1 标识的那个。如果 fd2 标识的套接字是第一个连接的,则应用程序无法知道它可以写入该套接字。

我认为我最好的选择是使用dup() 和/或dup2(),但根据他们的手册页,dup() 创建了传递给函数的 fd 的副本,但它指的是同一个打开的文件描述,表示两者可以互换使用,dup2() 关闭新的 fd 替换旧的 fd。

所以我对会发生什么的假设是(在伪代码中)

int fd1, fd2, fd3;

fd1 = socket(x); // what the app is aware of
fd2 = socket(y); // first to connect

fd3 = dup(fd1); // fd1 and fd3 identify the same description
dup2(fd2, fd1); // The description identified by fd2 is now identified by fd1, the description previously identified by fd1 (and fd3) is closed
dup2(fd3, fd2); // The description identified by fd3 (copy of fd1, closed in the line above) is identified by fd2 (which can be closed and reassigned to fd3) since now the the description that was being identified by fd2 is being identified by fd1.

这看起来不错,除了第一个dup2() 关闭 fd1,它也关闭 fd3,因为它们标识相同的文件描述。第二个dup2() 工作正常,但它正在替换已被第一个关闭的连接的 fd,而我希望它继续尝试连接。

谁能帮助我更好地理解 Unix 文件描述符?

编辑:我想详细说明一下 API 的作用以及为什么应用程序只能看到一个 fd。

API 为应用程序提供了调用 connect() select()close() 的“花哨”版本的方法。

当应用程序调用api_connect() 时,它会向函数传递一个指向 int 的指针(连同所有必要的地址和协议等)。 api_connect()会调用socket()bind()connect(),重要的是它会将socket()的返回值写入通过指针解析的内存中。这就是我所说的“套接字只知道一个 fd”的意思。然后应用程序将调用FD_SET(fd1, write_set),调用 api_select(),然后通过调用FD_ISSET(fd1, write_set) 检查 fd 是否可写。 api_select() 的工作方式或多或少类似于select(),但有一个计时器,如果连接花费的时间超过设定的连接时间(因为它是O_NONBLOCK),它可以触发超时。如果发生这种情况,api_select() 在不同的接口上创建一个新连接(调用所有必要的socket()bind()connect())。此连接由应用程序不知道的新 fd -fd2- 标识,并在 API 中进行跟踪。

现在,如果应用程序用FD_SET(fd1, write_set) 调用api_select() 并且API 意识到这是已完成的第二个连接,从而使fd2 可写,我希望应用程序使用fd2。问题是应用程序之后只会调用FD_ISSET(fd1, write_set)write(fd1),这就是为什么我需要用fd1 替换fd2。

在这一点上,我真的很困惑我是真的需要复制还是只进行整数交换(我对 Unix 文件描述符的理解只是比基本的多一点)。

【问题讨论】:

  • "第一个 dup2() 关闭了 fd1,它也关闭了 fd3" - 您是否真的证实了这种情况? fd1fd3 指向同一个打开的文件这一事实意味着关闭一个会自动关闭另一个。如果是这样,重定向将是不可能的。
  • 什么是“意识到”?低级文件描述符只是一个数字。你可以复制过来。
  • API 听起来容易出错;什么时候应用程序在api_select() 之前生成dup(fd1) 并在此副本上工作?如果可能,您应该更改 API,例如api_select() 返回一个 fd,和/或提供在你的 fd 上运行的函数(例如 api_read()
  • 应用程序不会调用 dup(),api_select() 会。此外,api_select() 需要对应用程序透明,它为什么返回与 select() 将返回的代码相同的代码。
  • en.wikipedia.org/wiki/File_descriptorstackoverflow.com/q/5256599/1358308 对文件描述符的工作方式进行了合理的描述……我建议只跟踪“正确的”文件描述符,而不是使用倾向于使用的 dup*() 函数当您实现 shell 并需要确保“FD1”实际上是标准输入时很有用

标签: c sockets unix file-descriptor dup2


【解决方案1】:

我认为我最好的选择是使用dup() 和/或dup2(),但根据 到他们的手册页,dup() 创建一个传递给 函数,但它引用相同的打开文件描述,

是的。

含义 这两者可以互换使用,

也许吧。这取决于您所说的“可互换”。

dup2() 关闭新的fd 它取代了旧的 fd。

dup2() 关闭 target 文件描述符(如果它是打开的),然后再将源描述符复制到它上面。也许这就是您的意思,但我无法以这种方式阅读您的描述。

所以我对会发生什么的假设是(请原谅我蹩脚的伪 代码)

int fd1, fd2, fd3;

fd1 = socket(x); // what the app is aware of
fd2 = socket(y); // first to connect

fd3 = dup(fd1); // fd1 and fd3 indentify the same description

到目前为止还不错。

dup2(fd2, fd1); // The description identified by fd2 is now identified by fd1, the description previously identified by fd1 (and fd3) is closed

不,评论不正确。 文件描述符首先关闭fd1,然后使其成为fd2 的副本。 fd1 最初引用的底层打开文件描述关闭,因为该进程有另一个与之关联的打开文件描述符 fd3

dup2(fd3, fd2); // The description identified by fd3 (copy of fd1, closed in the line above) is identified by fd2 (which can be closed and reassigned to fd3) since now the thescription that was being identified by fd2 is being identified by fd1.

这看起来不错,除了第一个 dup2() 关闭的事实 fd1,

是的。

也关闭 fd3

没有。

因为他们识别的是同一个文件 描述。

无关紧要。关闭是文件描述符上的函数,而不是直接在底层打开文件描述上的函数。事实上,最好不要在这里使用“识别”一词,因为这表明文件描述符是打开文件描述的某种标识符或别名。他们不是。文件描述符标识与打开文件描述的关联表中的条目,但它们本身不是打开文件描述。

简而言之,您的dup()dup2()dup2() 调用序列应该会影响您想要的那种交换,只要它们都成功。但是,它们确实会留下一个额外的打开文件描述符,这在许多情况下会导致文件描述符泄漏。因此,不要忘记完成一个

close(fd3);

当然,所有假设是应用程序特有的fd1,而不是包含它的变量。文件描述符只是数字。包含它们的对象本身并没有什么特别之处,所以如果是应用程序需要使用的变量fd1,不管它的具体值是什么,那么你需要做的就是执行一个普通的整数交换:

fd3 = fd1;
fd1 = fd2;
fd2 = fd3;

关于编辑,你写,

当应用程序调用api_connect()时,它传递给函数a 指向 int 的指针(连同所有必要的地址和 协议等)。 api_connect() 将调用 socket()、bind() 和 connect(),重要的是它会写入返回值 通过指针解析内存中的socket()。

api_connect() 是通过指针写入文件描述符值还是将其作为或在函数的返回值中传送来返回文件描述符值是无关紧要的。重点在于,重要的是,而不是包含它的对象(如果有的话)。

这就是我 意思是“套接字只知道一个 fd”。该应用程序将 然后拨打FD_SET(fd1, write_set),拨打api_select()然后检查 如果 fd 可通过调用 FD_ISSET(fd1, write_set) 写入。

根据您的其余描述,这听起来有问题。

[在某些情况下,] api_select() 在不同的接口上创建一个新连接 (调用所有必要的 socket()、bind() 和 connect())。这 连接由新的 fd -fd2 标识-应用程序没有 知道,并在 API 中跟踪。

现在,如果应用程序使用 FD_SET(fd1, write_set) 调用 api_select() API 意识到这是已完成的第二个连接, 因此使 fd2 可写,我希望应用程序使用 fd2。这 问题是应用程序之后只会调用FD_ISSET(fd1, write_set)write(fd1),这就是为什么我需要替换 fd2 与 fd1。

请注意,即使您按照本答案的第一部分所述交换文件描述符,这也不会影响任何一个 FD 在任何fd_set 中的成员身份,因为这样的成员身份是合乎逻辑的 ,而不是物理的。如果调用者依赖它,您将必须手动管理 fd_set 成员资格。

我不清楚api_select() 是否打算同时为多个(调用者指定的)文件描述符提供服务,就像select() 可以做的那样,但我想它需要的簿记这样做会很可怕。另一方面,如果事实上该函数一次只处理一个调用者提供的 FD,那么模仿select() 的接口是……奇怪。

在这种情况下,我强烈建议您设计一个更合适的界面。除其他外,这样的界面应该解决交换 FD 的问题。相反,它可以通过返回或通过指向调用者指定的变量的指针将其写入,直接告诉调用者什么 FD(如果有)可以使用。

此外,如果您确实以一种或另一种方式切换到替代 FD,请不要忽视管理旧的 FD,以免泄漏文件描述符。每个进程的可用进程数量都非常有限,因此文件描述符泄漏可能比内存泄漏更麻烦。如果您确实进行了切换,那么您确定您真的需要交换,而不是仅仅 dup2()将新的 FD 放到旧的 FD 上,然后关闭新的?

【讨论】:

  • 感谢您的回答。最后一部分让我有些困惑,所以我想详细说明一下 API 的作用。请在问题中查看我的编辑。
  • @Muffin,我添加了针对您的编辑的评论。
  • 好的,我可以看到问题出在哪里,我只是想澄清api_select() 的工作原理。在 API 中,每个 fd 只是一个更复杂的数据结构的成员,称为 socketlist,顾名思义,它是一个链表。列表的第一个元素有调用者知道的 fd,所有新的连接尝试都跟随着他们的 fd。当 api_select() 被调用时,它会遍历列表,并且在实际 select() 对列表中的每个 fd 调用 FD_SET() 之前。因此,例如,如果 fd2 是连接 FD_ISSET(fd2, write_set) 的那个,则为真。
  • 很好,@Muffin,但是在select() 返回之后,只有那些它确定准备就绪的文件描述符才会被设置。如果您然后交换文件描述符,调用者将看不到预期的 FD 集,即使由于交换,它实际上已经准备好。
  • 抱歉,我们花了这么长时间才真正向您更新这件事。无论如何,我知道试图解释我的 API 是如何工作的是不可能的,但我想让你知道,由于你的回答,我所做的一些调整是我想做的。谢谢!
猜你喜欢
  • 1970-01-01
  • 2011-03-31
  • 2011-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多