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