C socket blocking call

纵然是瞬间 提交于 2019-12-20 12:41:10

问题


I wonder about the behaviour of socket on blocking and nonblocking actions. What happens to threads blocking on socket when the socket blocking mode changes ? Here is the scenario; thread1(T1) creates a UDP socket and

fd = socket(AF_INET , SOCK_DGRAM, 0);

T1 waiting(sleeping) for receive

recv(fd, buf , sizeof(buf) , 0);

and thread2(T2) changes socket mode to non-blocking before socket receive any data

fcntl(fd, F_SETFL, O_NONBLOCK);

what happens to T1 ? Is it signalled/waked because the socket is no more blocking ?


回答1:


The behavior is literally unspecified: fcntl is not required to unblock any threads.

Linux just sets the flag in the file description struct file and returns without unblocking any blocked threads.

A thread already blocked in recv can be scheduled to run only if:

  • data to read becomes available;
  • or an error condition on the file descriptor is detected (FIN/RST, socket read timeout, TCP keep-alive failure, the file descriptor is closed by another thread);
  • or a signal is received and the signal disposition does not include SA_RESTART;
  • or it is pthread_cancelled.

The fact that you are trying to change flags of a file descriptor of another thread suggests that your design requires a review. Ideally, threads must not share any data and not poke at each other's state, rather they should use message passing to communicate with each other.




回答2:


You made me curious. First, it's quite obvious that since there is no standard that specifies that the socket should wake up, it will not be awoken because it would be quite a pain to implement this (since the non-blocking flag is in a different layer than where the socket is blocking). So we can say quite confidently that the socket will not wake up until we receive a packet. Or will it?

#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <err.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>

int sock;

static void
sighand(int s)
{
        write(1, "sig\n", 4);
}

static void *
rcv(void *v)
{
        struct sigaction sa;

        memset(&sa, 0, sizeof(sa));
        sa.sa_handler = sighand;
        sa.sa_flags = SA_RESTART;
        if (sigaction(SIGUSR1, &sa, NULL) == -1)
                err(1, "sigaction");

        char buf[64];
        ssize_t sz = recv(sock, buf, sizeof(buf), 0);
        printf("recv %d\n", (int)sz);
        return NULL;
}

pthread_t t1, t2;

static void *
beeper(void *v)
{
        for (;;) {
                nanosleep(&((struct timespec){.tv_sec = 1}), NULL);
                if (pthread_kill(t1, SIGUSR1))
                        errx(1, "pthread_kill");
                printf("beep\n");
        }
}

int
main(int argc, char **argv)
{
        if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
                err(1, "socket");

        struct sockaddr_in sin;
        memset(&sin, 0, sizeof(sin));
        sin.sin_family = AF_INET;
        sin.sin_port = htons(4711);
        sin.sin_addr.s_addr = htonl(INADDR_ANY);
        if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) == -1)
                err(1, "bind");

        if (pthread_create(&t1, NULL, rcv, NULL))
                errx(1, "pthread_create");

        if (pthread_create(&t2, NULL, beeper, NULL))
                errx(1, "pthread_create");

        /* pretend that this is correct synchronization. */
        nanosleep(&((struct timespec){.tv_sec = 3}), NULL);

        printf("setting non-block\n");
        if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1)
                err(1, "fcntl");
        printf("set\n");

        nanosleep(&((struct timespec){.tv_sec = 3}), NULL);

        return 0;
}

The code above (sorry, couldn't make it shorter). Blocks the thread in recv, waits a bit and then sets non-blocking on the file descriptor. As expected nothing happens. But then I added a twist. The receiving thread has a signal handler that wakes up once a second with SA_RESTART. Of course, since we have SA_RESTART recv should not wake up.

On OSX this will "misbehave" if we consider any behavior correct. I'm pretty sure this will behave the same way on all BSDs. Same on Linux. In fact, the way SA_RESTART is usually implemented I'm pretty sure this will "misbehave" pretty much everywhere.

Funny. This of course doesn't mean that the code above is useful for anything, but it's an interesting curiosity. To answer your question, this is unspecified, but in most cases it won't wake up, unless it will. Please don't do weird things like this.




回答3:


I think POSIX specification is pretty clear about this:

If no messages are available at the socket and O_NONBLOCK is not set on the socket's file descriptor, recv() shall block until a message arrives. If no messages are available at the socket and O_NONBLOCK is set on the socket's file descriptor, recv() shall fail and set errno to [EAGAIN] or [EWOULDBLOCK].

You call recv when O_NONBLOCK is not yet set, to it should block until message arrives (not until mode changes).




回答4:


Unblocked thread (didn't block yet due to reading when it was in blocking mode) will behave as if it was always a non-blocking mode.

Reading recv(2) manual page

If no messages are available at the socket, and if the socket is nonblocking, the value -1 is returned and the external variable errno is set to EAGAIN or EWOULDBLOCK.


Blocked threads (blocked while reading when in blocking mode) before changing to non-blocking. As pointed by @Maxim sharing the code of the function which doesn't awaken threads, The blocked threads will only be awaken after a write is done (data is available).



来源:https://stackoverflow.com/questions/45635871/c-socket-blocking-call

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!