首页 > 解决方案 > 如何交换两个打开的文件描述符?

问题描述

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

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

我认为我最好的选择是使用dup()and/or 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.

看起来不错,除了第一个关闭 fd1 的事实dup2(),它也关闭 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 是否可写FD_ISSET(fd1, write_set)api_select()工作方式或多或少类似于select(),但有一个计时器,如果连接花费的时间超过设定的连接时间(因为它是O_NONBLOCK),它可以触发超时。如果发生这种情况,api_select()请在不同的接口上创建一个新连接(调用所有必要的socket(),bind()connect())。此连接由应用程序不知道的新 fd -fd2- 标识,并在 API 中进行跟踪。

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

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

标签: csocketsunixfile-descriptordup2

解决方案


我认为我最好的选择是使用dup()and/or dup2(),但根据他们的手册页,dup()创建传递给函数的 fd 的副本,但它指的是相同的打开文件描述,

是的。

这意味着两者可以互换使用,

也许。这取决于您所说的“可互换”是什么意思。

dup2()关闭替换旧 fd 的新 fd。

dup2()在将源描述符复制到它之前关闭目标文件描述符(如果它是打开的)。也许这就是您的意思,但我无法以这种方式阅读您的描述。

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

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()时,它会向函数传递一个指向 int 的指针(连同所有必要的地址和协议等)。api_connect()会调用socket()、bind()和connect(),重要的是它将socket()的返回值通过指针解析到内存中。

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

这就是我所说的“套接字只知道一个 fd”的意思。然后应用程序将调用FD_SET(fd1, write_set),调用 aapi_select()然后通过调用检查 fd 是否可写FD_ISSET(fd1, write_set)

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

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

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

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

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

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

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


推荐阅读