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

C 如何在可以在另一个线程上关闭的套接字上优雅地选择()?,c,sockets,C,Sockets,我遇到了这样一种情况,线程正在选择由单独线程管理的套接字 当一个套接字关闭时,select()可能会将该套接字返回为“可用”,直到我尝试读取它时,我才意识到它已关闭 但我看到了一个悖论:当套接字从另一个线程关闭时,系统可以自由地重新分配其文件描述符用于其他目的。(我想。) 如何保证在我从套接字(只是一个数字描述符)读取时,系统还没有回收该描述符并将其用于新的套接字?换句话说,据我所知,我可能正在读取最近打开的其他套接字(可能是我甚至不应该在我的select()!)中包含的套接字),而不是刚刚关闭

我遇到了这样一种情况,线程正在选择由单独线程管理的套接字

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

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

如何保证在我从套接字(只是一个数字描述符)读取时,系统还没有回收该描述符并将其用于新的套接字?换句话说,据我所知,我可能正在读取最近打开的其他套接字(可能是我甚至不应该在我的
select()
!)中包含的套接字),而不是刚刚关闭的套接字


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

简短的回答:不要从另一个线程关闭套接字,这是您正在阅读的线程


FD可能会被重新分配。但是,如果您在多个线程中读取一个FD,而没有在它们之间进行通信的方案,那么您将遇到问题。现在,如果共享内存中有一个“Socket description”结构,它有一个控制信号量和一些FD指示以及其他状态信息,那么这可能是可以管理的,但我想您会发现,最简单的解决方案几乎总是让FD特定于单个线程…

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

如果您关闭一个套接字,然后在任何fd集中使用关闭的套接字fd集调用
select
,select将立即返回EBADF,因此不要这样做

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


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

有很多方法可以做到这一点,这里有一些其他的想法供您参考。不过,两者都是针对套接字的所有者(即执行关闭的线程,而不是另一个线程)

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

e、 g

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

您还可以调用信号,这通常会中断阻塞IO调用。除非它们自动重启。调用将返回-1,errno将设置为EINTR。您可以检查一个标志,看看是否应该关闭并中断呼叫

e、 g


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

使用非阻塞插座(O_非阻塞)

使API能够为select()调用中添加的每个FD添加回调函数

现在,每当套接字准备好进行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 */
}
即使您被卡住了,您也必须从其他线程关闭插座,然后您可以创建一个
管道
,并将其添加到
选择()


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

如果正在使用阻塞套接字,从另一个线程关闭套接字通常是解除阻塞操作的唯一方法,特别是如果连接的远程端点未正常关闭,则阻塞操作不会立即检测到关闭,并在本地端点关闭之前处于死锁状态。我(可能是盲目地)假设OP使用非阻塞I/O,因为引用了
select
,但是;在这种情况下,必须使用信号量或类似的(可能是线程之间的共享变量)来表示闭包;在这种情况下,“读卡器”线程应立即检查,在每次操作后,如果设置了“关闭”标志/信号量,则“退出”…@brpoock,我使用非阻塞I/O是正确的。我认为我必须统一这些套接字操作,以便它们总是从单个线程发生。我正在考虑可能选择套接字以外的其他对象作为控制消息。对-这是一个典型的“医生,我做这个/然后不做那个”情况。在我选择的FDs中添加一个
管道()
,并将其用于控制消息
if (n == -1 && errno == EINTR)
    goto cleanup;
MultiplexAdd(fd, callbackFn);
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 */
}