Non-blocking socket accept without spinlock in C [

2019-07-28 03:00发布

问题:

Possible Duplicate:
Wake up thread blocked on accept() call

I am writing a small server which listening for connections (accepting them and passing them to worker threads) until a custom stop signal is sent.

If I use blocking sockets, then my main accept loop cannot break on the custom stop signal being sent. However, I wish to avoid having a busy-wait/spinlock loop with a non-blocking socket.

What I want is for my main accept loop to block until either a connection is received or the stop signal is sent.

Is this possible in C on Linux?

Many Thanks.

回答1:

If I understand correctly then you are open to using any kind of "signal", not necessarily a POSIX signal. Actually, POSIX signals are a poor choice because checking if you have received one in a loop has unavoidable race conditions.

What you need to use is anything that can be monitored through a file descriptor. It could be:

  • A pipe. Your accept loop monitors the read end of the pipe. To wake up the loop, another thread writes something into (doesn't matter what) into the write end.
  • Another socket. Similarily, another thread wakes up your accept loop by writing into the other end of the socket.
  • A file in the filesystem which you monitory using inotify.
  • A device which receives some data when the loop should be interrupted.
  • etc...

The later entries in the list of examples aren't generally practical. They're just to illustrate that it can be any type of object as long as it is has monitorable file descriptor. The simplest, cheapest, and most popular way is a pipe.

If you are already using nonblocking sockets, then you certainly already have some kind of polling loop to check when they're ready to accept connections. I'm going to assume you're using poll() to do this.

Before you start your loop, set up a pipe like this:

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

The fcntl is to set the read end of the pipe to non blocking mode. You're going to be using that end of the pipe in your poll call together with your socket, so it needs to be nonblocking.

Then just add the read end of the pipe to the list of tile descriptors that you monitor in your accept loop:

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 */
    }
}

The example uses poll but you might be using select (or even epoll) instead. select has a different interface but the idea is the same.

This technique is known as the self-pipe trick.



回答2:

If you do not to want pass an unblocking socket to accpet(), then sending a signal (as described by man signal to the accept()ing thread is the only way to interrupt a blocking accept() (waiting for connections).

As already pointed out by some commenters most system calls (including accept()) will be interupted when their thread received a signal.

If accept() returns -1and errno is set to EINTR the call to accept() was interupted by a signal.

To send a signal to a process use kill(<pid>, <signal-number>).

If you are in a multi-threaded enviroment, block the signal you'll be using (typical SIGUSR1 or SIGUSR2) for all other threads, but the thread that calls accept(), asi t is nondeterministic which thread of a process will handle a signal sent to a process ("addressed" via the process' pid.)

Alternativly you can use pthread_kill(<pthread>, <signal-number>) to send a signal to a specific thread only.