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

时间:2019-01-09 18:36:27

标签: c sockets unix file-descriptor dup

对于我的硕士论文项目,我正在用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,fd1也关闭fd3,因为它们标识相同的文件描述,这看起来还不错。第二个dup2()可以正常工作,但是它代替了第一个已关闭的连接的fd,而我希望它继续尝试连接。

任何对Unix文件描述符有更好理解的人都可以帮助我吗?

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

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

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

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

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

1 个答案:

答案 0 :(得分:3)

  

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

是的

  

含义   两者可以互换使用,

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

  

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

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

  

所以我对将会发生的假设是(对不起,我pseudo脚的伪   代码)

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 value 对应用程序来说是特殊的,而不是包含它的变量 < / strong>。文件描述符只是数字。包含它们的对象本质上没有什么特别的,因此,如果应用程序需要使用变量fd1,而不论其具体值如何,那么您要做的就是执行普通的整数交换:

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

关于修改,您写

  

应用程序调用{​​{1}}时,它将传递给函数a   指向int的指针(以及所有必要的地址和   协议等)。 api_connect()将调用socket(),bind()和   connect(),重要的是它将写入返回值   指针解析的内存中的socket()数量。

api_connect()是通过指针写入文件描述符值还是在函数的返回值无关时传递它来返回文件描述符值。重点仍然是,重要的是 value ,而不是包含它的对象(如果有)。

  

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

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

  

[在某些情况下,]   FD_ISSET(fd1, write_set)在其他界面上创建新连接   (调用所有必需的socket(),bind()和connect())。这个   连接由新的fd -fd2-标识,应用程序未   知道,并在API中对其进行跟踪。

     

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

请注意,即使您按照此答案的第一部分所述交换文件描述符,也不会影响FD在任何write(fd1)中的任一个成员身份,因为此类成员身份是逻辑,不是物理上的。如果呼叫者依赖,则必须手动管理fd_set成员身份。

我不清楚fd_set是否打算像api_select()一样同时为多个(调用者指定的)文件描述符提供服务,但是我认为簿记这样做必不可少。另一方面,如果实际上该函数一次仅处理一个调用者提供的FD,则模仿select()的接口是...很奇怪。

在这种情况下,我强烈建议您设计一个更合适的界面。除其他事项外,这种接口应解决交换FD的问题。取而代之的是,它可以通过返回它或通过将其写入指向调用者指定的变量的指针来直接告诉调用者准备使用的FD(如果有)。

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