如何交换两个打开的文件描述符?

How to swap two open file descriptors?

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

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

我认为我最好的选择是使用 dup() and/or dup2(),但是根据他们的手册页,dup() 创建了传递给功能,但它指的是相同的打开文件描述,这意味着两者可以互换使用,并且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 为应用程序提供了调用非常 "fancy" 版本的 connect() select()close().

的方法

当应用程序调用 api_connect() 时,它向函数传递一个指向 int 的指针(连同所有必要的地址和协议等)。 api_connect()会调用socket()bind()connect(),重要的是会把socket()的return值写入解析后的内存中通过指针。这就是我所说的 "The socket is only aware of one 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。

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

I think my best options are using dup() and/or dup2(), but according to the their man page, dup() creates a copy of the fd passed to the function, but which refers to the same open file description,

是的。

meaning that the two can be used interchangeably,

也许吧。这取决于你所说的 "interchangeably".

是什么意思

and dup2() closes the new fd which replaces the old fd.

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

So my assumptions on what would happen are (excuse my crappy pseudo code)

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 最初引用的底层打开文件描述是 not closed,因为该进程有另一个与之关联的打开文件描述符,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.

Which looks fine, except for the fact that the first dup2() closes fd1,

是的。

which closes also fd3

不,不是。

since they are identifying the same file description.

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

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

结尾
close(fd3);

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

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

关于编辑,你写,

When the application calls api_connect(), it passes to the function a pointer to an int (together with all the necessary addresses and protocols etc). api_connect() will call socket(), bind() and connect(), the important part is that it will write the return value of socket() in the memory parsed through the pointer.

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

This is what I mean by "The socket is only aware of one fd". The application will then call FD_SET(fd1, write_set), call a api_select() and then check if the fd is writable by calling FD_ISSET(fd1, write_set).

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

[Under some conditions,] api_select() creates a new connection on a different interface (calling all the necessary socket(), bind() and connect()). This connection is identified by a new fd -fd2- the application doesn't know about, and which is tracked in the API.

Now, if the application calls api_select() with FD_SET(fd1, write_set) and the API realises that is the second connection that has completed, thus making fd2 writable, I want the application to use fd2. The problem is that the application will only call FD_ISSET(fd1, write_set) and write(fd1) afterwards, that's why I need to replace fd2 with fd1.

请注意,即使您按照本答案第一部分中的描述交换文件描述符,这也不会影响 FD 在任何 fd_set 中的成员资格,因为此类成员资格是 逻辑,不是物理的。如果呼叫者依赖于此,您将必须手动管理 fd_set 成员资格。

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

既然如此,我强烈建议您设计一个更合适table的界面。除其他外,这样的接口应该可以解决交换 FD 的问题。相反,它可以直接告诉调用者什么 FD(如果有的话)可以使用,方法是 return 调用它或通过指向调用者指定的变量的指针写入它。

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