【问题标题】:Stopping a receiver thread that blocks on recv()停止在 recv() 上阻塞的接收器线程
【发布时间】:2011-12-24 09:09:21
【问题描述】:

我有一个聊天应用程序,它有一个单独的线程来监听传入的消息。

while (main thread not calling for receiver to quit) {
  string message = tcpCon.tcpReceive();    // Relies on the recv() function
  processIncomingMessage(message);
}

这种工作方式有一个大问题。大多数情况下,循环会在recv() 上阻塞,因此接收线程不会退出。在几秒钟后不强制线程终止的情况下解决这个问题的正确方法是什么?

【问题讨论】:

  • 投票重新打开它,因为链接的副本是关于 winsock(Windows 套接字)的,这里的答案是关于 Linux 下的套接字

标签: c++ c sockets networking


【解决方案1】:

使用shutdown() 关闭套接字以对所有接收者关闭它。

这会在我的系统上打印出“recv returned 0”,表明接收器看到了有序关闭。注释掉 shutdown() 并看着它永远挂起。

从长远来看,OP 应该修复设计,使用select 或在协议中包含明确的退出消息。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>

/* Free on my system. YMMV */
int port = 7777;
int cd;

void *f(void *arg)
{
    /* Hack: proper code would synchronize here */
    sleep(1);

    /* This works: */
    shutdown(cd, SHUT_RDWR);

    close(cd);
    return 0;
}

int main(void)
{
    /* Create a fake server which sends nothing */
    int sd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in sa = { 0 };
    const int on = 1;
    char buf;
    pthread_t thread;
    sa.sin_family = AF_INET;
    sa.sin_addr.s_addr = htonl(INADDR_ANY);
    sa.sin_port = htons(port);
    setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on);

    /* Other error reporting omitted for clarity */
    if (bind(sd, (const struct sockaddr*)&sa, sizeof sa) < 0) {
        perror("bind");
        return EXIT_FAILURE;
    }

    /* Create a client */
    listen(sd, 1);
    cd = socket(AF_INET, SOCK_STREAM, 0);
    connect(cd, (const struct sockaddr*)&sa, sizeof sa);
    accept(sd, 0, 0);

    /* Try to close socket on another thread */
    pthread_create(&thread, 0, f, 0);
    printf("recv returned %d\n", recv(cd, &buf, 1, 0));
    pthread_join(thread, 0);

    return 0;
}

【讨论】:

  • 这和close(socketFD)有区别吗?
  • @Pieter - 是的。 close() 不会中断在读取中阻塞的线程。 shutdown() 对其他读者关闭,即使在其他进程中也是如此。
  • @EJP - 查看示例代码。 recv 返回 0 表示有序关闭。
  • RE:包括明确的退出消息。我不能,因为我正在实施已经存在的 DCC 子协议。我的假设是否正确,尽管 shutdown() 方法会以更字面的方式回答我的问题,但如果我想正确地使用 select() 并有一定的超时时间?
  • 嗯,“正确地做”是一个设计问题,这实际上只是另一种说法,这是一个意见问题。当然,阅读器端的 shutdown() 将解决您的问题,并且可以通过评论接受。感觉有点脏,因为从道德上讲,您不应该在不知道它会完成的情况下永远阻止读取。那么有哪些替代方案呢?如果所有套接字所做的都是向/从 GUI 携带聊天,我会在一个包含监听套接字的单个选择循环中在主线程上为它们提供服务。
【解决方案2】:

您可以使用 select() 来等待传入数据并避免在 recv() 中阻塞。 select() 也会阻塞,但您可以在设置的时间间隔后让它超时,以便 while 循环可以继续并检查是否有信号从主线程退出:

while (main thread not calling for receiver to quit) {
  if (tcpCon.hasData(500)) {              // Relies on select() to determine that data is
                                          // available; times out after 500 milliseconds
    string message = tcpCon.tcpReceive(); // Relies on the recv() function
    processIncomingMessage(message);
  }
}

【讨论】:

  • 更简单地说,您可以使用读取超时来获得相同的效果。
  • @EJP:如果没有 select() 这样的东西,你会怎么做?
  • @Void:他指的是可以使用 setsockopt() 设置的 SO_RECVTIMEO 选项。它将为 recv() 分配一个超时,如果超时期限过去并且没有收到任何数据,则将返回 -1,并将 errno 设置为 EAGAIN。
  • @dgraves:啊,当然!我已经有一段时间没有看到那个了。好叫EJP。感谢您清除它,dgraves。
  • 请注意,并非所有平台都支持SO_RCVTIMEO。
【解决方案3】:

从任何其他线程调用套接字上的close 将使recv 调用立即失败。

【讨论】:

  • 在 Windows 中是,在 Linux 中不是。
【解决方案4】:

如果你在另一个线程中关闭了套接字,那么 recv() 将退出。

【讨论】:

  • 当一个单独的线程在 recv() 上阻塞同一个套接字时,在一个套接字上调用 close() 是否是线程安全的?
  • 简短回答:是的。长答案:通常是的,但线程安全取决于操作系统和套接字堆栈。某些操作可能会导致问题,但通常您无论如何都不想这样做(例如,在同一个套接字上并行读取)
猜你喜欢
  • 1970-01-01
  • 2015-07-20
  • 2013-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-05
相关资源
最近更新 更多