你如何在可以在另一个线程上关闭的套接字上优雅地选择()?

时间:2011-12-12 20:29:06

标签: c sockets

我遇到一个线程在由单独线程管理的套接字上选择的情况。

当套接字关闭时,select()可能会将该套接字返回为“available”,直到我尝试从中读取它才意识到它已关闭。

但我看到一个悖论:当套接字从另一个线程关闭时,系统可以自由地为其他目的重新分配其文件描述符。 (我想。)

当我从套接字读取时(只是一个数字描述符),我怎么能保证系统还没有回收那个描述符并将它用于新的套接字?换句话说,就我所知,我可能正在读取最近打开的其他套接字(也许是一个套接字,我甚至不应该包括在我的select()中!)而不是刚刚关闭的套接字。< / p>

我可以保留最近关闭的描述符列表,但我想知道是否有更好的方法。

5 个答案:

答案 0 :(得分:4)

简短的回答:不要关闭另一个线程的套接字,你正在读这个套接字!

可能会重新分配FD。但是如果你从多个线程中的一个FD读取而没有某种方案来在它们之间进行通信,那么会出现问题。现在,如果你在共享内存中有一个“套接字描述”结构,它有一个控制信号量和FD的一些指示,以及其他状态信息,也许这是可以管理的,但我想你会发现最简单的解决方案几乎是总是让FD专用于单个线程...

答案 1 :(得分:3)

有很多方法可以做到这一点,这里有几个其他的想法。但两者的想法是针对套接字的所有者(即线程做关闭,而不是另一个线程)。

在选择调用中为您的读取集添加一个控制套接字,例如unix socket。写一些控制数据以从其select调用中中断该线程。然后线程可以检查套接字是否应该关闭。可以将其作为套接字结构的一部分,甚至可以作为控制包的实际数据。

e.g。

fd_set readSet;
FD_ZERO(&readSet);
FD_SET(sock, &readSet);
FD_SET(controlSock, &readSet);

int n = select(maxfd, &readSet, NULL, NULL);
...
if (FD_ISSET(controlSock, &readSet)) {
    /* check if sock should be closed or not, also drain controlSock */
}

然后只需写入控制插座即可发出关闭信号。

您也可以调用信号,这通常会破坏阻塞的IO调用。除非他们自动重启。调用将返回-1并且errno将设置为EINTR。你可以检查一个标志,看看你是否应该关闭并退出通话。

e.g。

if (n == -1 && errno == EINTR)
    goto cleanup;

答案 2 :(得分:0)

如果您知道哪个线程正在访问哪个套接字,则可以在从另一个线程关闭套接字之前终止或至少在该线程中设置一个标志。然后,当您仍在使用旧连接时,文件描述符不能重新用于新连接。

答案 3 :(得分:0)

如果你关闭一个套接字然后用任何fdset中设置的闭合套接字fd调用select,select将立即返回EBADF,所以不要这样做。

如果你想让多个线程管理一个公共的套接字池并干净地处理它们,你需要使用某种锁来确保一个线程在另一个线程调用select时不关闭套接字。如果您有全局fd_sets来跟踪哪些套接字是“实时”,您可以使用读取器/写入器锁来保护对该组的访问。在复制集合并调用select之前获取读锁定;选择返回后释放锁定。在关闭套接字之前获取写锁定,然后在从fd_set移除现在关闭的套接字后释放它。

另一种可能性是使用原子读 - 修改 - 写指令来操作全局fd_set,然后当你想要关闭套接字时,首先将它从全局fd_set中删除,然后等待足够长的时间让所有线程在之前取得进展实际上关闭它。这会给你一个竞争条件(另一个线程可能会在你删除关闭fd之前复制全局fd_set,而不是在之后再调用select)所以你需要知道等待多长时间,这取决于你的系统。

答案 4 :(得分:0)

在这里工作的更好方式是:

使用非阻塞套接字(O_NONBLOCK)。

制作API,以便为select()调用中添加的每个FD添加回调函数。

MultiplexAdd(fd, callbackFn);

现在每当套接字准备好I / O时,你的回调都会被调用,你可以做任何你想做的事。(甚至关闭套接字)。

因此,您可以在一个线程中系统地处理套接字。

示例代码:

int f1()
{
    MultiplexAdd(fd1, callbackFn1);
    MultiplexAdd(fd2, callbackFn2);

    MultiplexMainLoop(); /* This is where you will be blocked on select() */
}


void callbackFn1(int fd)
{
     /* Processing as desired */
     close(fd);
     MultiplexRemove(fd); /* Remove fd from select, else select will return -1 
                                                                    with EABDF */
}

即使您遇到困难,您也必须从其他线程关闭套接字,然后您可以创建pipe并将其添加到select()

您的write()正在关闭套接字。在管道pipeReadCb()的回调中,您可以关闭套接字。