套接字关闭时,UDP套接字上的select不会结束 - 我做错了什么?

时间:2009-01-19 18:07:56

标签: c unix sockets network-programming

我正在使用Linux系统(带有2.6.20内核的Ubuntu 7.04服务器)。

我有一个程序,它有一个线程(thread1)等待选择一个UDP套接字变得可读。 我正在使用select(使用我的套接字作为单个readfd和单个exceptfd)而不是仅仅调用recvfrom,因为我想要超时。

从另一个线程,我关闭并关闭套接字。 如果我在recvfrom中阻塞thread1时执行此操作,则recvfrom将立即终止。 如果我执行此操作而thread1在超时的select中被阻止,那么select将不会立即终止,但最终会正常超时。

任何人都可以告诉我为什么在套接字关闭后select不会退出?这不是一个例外吗?我可以看到它不可读的地方(很明显),但它已经关闭,这似乎是例外的。

这是套接字的开放(为了简单起见,删除了所有错误处理):

m_sockfd = socket(PF_INET, SOCK_DGRAM, 0);
struct sockaddr_in si_me;
memset((char *) &si_me, 0, sizeof(si_me));
si_me.sin_family = AF_INET;
si_me.sin_port = htons(port);
si_me.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(m_sockfd, (struct sockaddr *)(&si_me), sizeof(si_me)) < 0)
{
// deal with error
}

这是thread1执行的select语句:

struct timeval to;
to.tv_sec = timeout_ms/1000;// just the seconds portion
to.tv_usec = (timeout_ms%1000)*1000;// just the milliseconds 
                                    // converted to microseconds

// watch our one fd for readability or
// exceptions.
fd_set  readfds, exceptfds;
FD_ZERO(&readfds);
FD_SET(m_sockfd, &readfds);
FD_ZERO(&exceptfds);
FD_SET(m_sockfd, &exceptfds);

int nsel = select(m_sockfd+1, &readfds, NULL, &exceptfds, &to);

更新:显然(如下所述),关闭套接字不是一个例外情况(从select的角度来看)。我想我需要知道的是:为什么?而且,这是故意的吗?

我真的想要了解这种选择行为背后的想法,因为这似乎与我的期望背道而驰。因此,我显然需要调整我对TCP堆栈如何工作的思考。请解释一下。

6 个答案:

答案 0 :(得分:4)

UDP是一种无连接协议。由于没有连接,所以没有任何连接,因此消费者不知道生产者永远不会再发送。

您可以让生产者发送“流结束”消息,让消费者在收到消息后终止。

答案 1 :(得分:4)

也许你应该用别的东西来唤醒选择。也许是管子之类的东西。

答案 2 :(得分:3)

您是否可以向线程发送信号(例如USR2),这会导致select()返回EINTR? 然后在信号处理程序中设置一个标志,告诉它不要重新启动select()?

这将消除等待多个文件描述符的需要,并且似乎比使用管道杀死它更清洁。

答案 3 :(得分:2)

我认为最明显的解决方案是关闭不被视为特殊情况。我认为问题的根源在于,你并没有真正接受select的哲学。为什么你在另一个线程中摆弄套接字,这听起来像是灾难的秘诀。

答案 4 :(得分:2)

我会说区别在于recvfrom正在积极尝试从单个套接字读取消息,其中select正在等待消息到达,可能在多个句柄上,而不一定是套接字句柄。

答案 5 :(得分:0)

您的代码从根本上被打破了。这种错误的变化很常见,并且在过去引起了严重的安全隐患。这就是你所缺少的:

当您关闭套接字时,您无法知道另一个线程是否在select中被阻止或即将在select中阻止。例如,请考虑以下事项:

  1. 该主题会调用select,但不会安排。
  2. 你关闭了套接字。
  3. 在您的代码不知道的线程中(可能是平台内部内存管理或日志记录内部的一部分)库会打开一个套接字并获得与您关闭的套接字相同的标识符。
  4. 线程现在进入select,但它是select在库打开的套接字上。
  5. 灾难来袭。
  6. 当另一个线程正在或可能正在使用它时,您不得尝试释放资源。