非阻塞套接字在C中没有自旋锁接受

时间:2012-11-10 15:19:51

标签: c sockets signals nonblocking spinlock

  

可能重复:
  Wake up thread blocked on accept() call

我正在编写一个小型服务器,它监听连接(接受它们并将它们传递给工作线程),直到发送自定义停止信号。

如果我使用阻塞套接字,那么我的主接受循环不能在发送的自定义停止信号上中断。但是,我希望避免使用带有非阻塞套接字的busy-wait / spinlock循环。

我想要的是我的主接受循环阻止,直到收到连接或发送停止信号。

这可能在Linux上用C吗?

非常感谢。

2 个答案:

答案 0 :(得分:2)

如果我理解正确,那么您可以使用任何类型的“信号”,而不一定是POSIX信号。实际上,POSIX信号是一个糟糕的选择,因为检查你是否已经在一个循环中收到一个是不可避免的竞争条件。

您需要使用的是可以通过文件描述符监视的任何内容。它可能是:

  • 管道。您的接受循环监视管道的读取端。为了唤醒循环,另一个线程在写入端写入(无关紧要)。
  • 另一个插座。类似地,另一个线程通过写入套接字的另一端来唤醒你的接受循环。
  • 使用inotify监视的文件系统中的文件。
  • 当循环应该被中断时接收一些数据的设备。
  • 等...

示例列表中的后续条目通常不实用。它们只是为了说明它可以是任何类型的对象,只要它具有可监视的文件描述符即可。最简单,最便宜,最受欢迎的方式是管道。

如果您已经在使用非阻塞套接字,那么您肯定已经有某种轮询循环来检查它们何时准备好接受连接。我假设您正在使用poll()来执行此操作。

在开始循环之前,请设置如下管道:

pipe(&pipefd[0]);
read_end = pipefd[0];
write_end = pipefd[1];
fcntl(read_end, F_SETFL, O_NONBLOCK);

fcntl是将管道的读取结束设置为非阻塞模式。您将在poll调用中与套接字一起使用管道的那一端,因此它需要是非阻塞的。

然后只需将管道的读取末尾添加到您在接受循环中监视的磁贴描述符列表中:

for (;;) { /* accept loop, loop forever */
    /* Monitor your socket. You should already have this in your code */
    pollfd[0].fd = your_socket;
    pollfd[1].events = POLLIN;

    /* Add a new entry to monitor the read end of the pipe */
    pollfd[1].fd = read_end;
    pollfd[1].events = POLLIN;

    /* Now call poll, as before, but monitor 2 file descriptors instead of just 1 */
    n = poll(&pollfd[0], 2, -1);

    /* Check if your socket is ready to accept.
       This part, you are already doing. */
    if (pollfd[0].revents) {
        newsocket = accept(your_socket, etc....)
        do_somehting_with(new_socket);
    }

    /* New part: check if anyone has written something for you into your pipe */
    if (pollfd[1].revents) {
        break; /* stop the loop */
    }
}

该示例使用poll,但您可能正在使用select(甚至是epoll)。 select有不同的界面,但想法是一样的。

此技术称为self-pipe trick

答案 1 :(得分:0)

如果你不想将解除阻塞的套接字传递给accpet(),那么发送一个信号(由man signal描述到accept()线程是中断阻塞的唯一方法{{ 1}}(等待连接)。

正如一些评论者所指出的,大多数系统调用(包括accept())将在其线程收到信号时被中断。

如果accept()返回accept()-1设置为errno,则EINTR的呼叫会被信号中断。

要向流程发送信号,请使用accept()

如果您处于多线程环境中,请阻止您将使用的信号(典型kill(<pid>, <signal-number>)SIGUSR1)用于所有其他线程,但调用SIGUSR2的线程,asi t是不确定的,一个进程的线程将处理发送到进程的信号(通过进程'accept()“寻址”。)

另外,您可以使用pid仅向特定线程发送信号。