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

标签 c sockets unix file-descriptor dup2

对于我的硕士论文项目,我正在用 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 的指针(连同所有必要的地址和协议(protocol)等)。 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()在不同的接口(interface)上创建一个新连接(调用所有必要的 socket()bind()connect() )。此连接由应用程序不知道的新 fd -fd2- 标识,并在 API 中进行跟踪。

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

在这一点上,我真的很困惑我是否真的需要复制或只是进行整数交换(我对 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,



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

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


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

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 的基础打开文件描述最初提到的是不是 关闭,因为该进程有另一个与之关联的打开文件描述符 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.



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

简而言之,您的 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()通过指针写入文件描述符值或将其作为或在函数的返回值中传递来返回文件描述符值是无关紧要的。关键仍然是重要的是值(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() 的接口(interface)。是……奇怪。

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

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

关于c - 如何交换两个打开的文件描述符?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54116344/

相关文章:

c++ - 从有符号字符转换为无符号字符然后再转换回来?

c - C 指针中不兼容的指针类型

c - 从c中的十进制数中获取2个MSB数字

c# - 服务器客户端发送/接收简单文本

c - 如何将十六进制转换为十进制?

java - 在java中,同一个程序可以同时作为服务器和客户端运行吗?

shell - 通过 Shell 脚本将文件转换为另一个文件

linux - 获取变量中存储的相对路径的绝对路径

perl - grep 或 sed 使用相同的模式匹配开始和结束之间的 block

java - 在java中使用GZIP压缩字节数组