Socket descriptor not getting released on doing &#

2019-07-20 15:29发布

问题:

I have written below UDP client which basically spawns a separate thread for receiving the data-grams however the data-grams are being sent in the main thread only. Now, having pressed ctrl^D post instantiating ("./udpClient 1") the UDP client on Linux distribution, the implementation comes out of the loop (around the getline () call) and closes the socket descriptor.

What I had observed that despite calling close on the descriptor the same is not released until the client process is terminated and still seeing the associated entry on doing "netstat -an|grep udp". However, if I comment/delete out the code to spawn the receiver thread then the socket descriptor is released immediately having called close ().

It seems socket reference count is getting incremented on spawning a separate thread (which basically receives and blocks on the said socket descriptor) and hence the close () call does nothing.

Is this desired since I was of the understanding that socket reference count is increased if the socket descriptor is shared across processes but not across threads? Is it because of the Linux implementation of the threads as LWP and if so, is this the desired behavior?

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>

void *receiver (void *arg);

int sockfd;

int doConnect = 0;

int
main (int argc, char *argv[])
{
    pthread_t thrid;

    struct sockaddr_in servaddr;

    struct sockaddr_in clntaddr;

    char *line;

    size_t len = 0;

    size_t read;

    if (argc < 4)
    {
        printf
            ("Usage:%s <Server Ipv4 address> <Server Port> <Connect Yes(1)/No(0)> [Client IPv4 address] [Client Port]\n",
             argv[0]);
        return -1;
    }


    /*
     * AF_INET, AF_INET6, AF_UNIX, AF_NETLINK
     * SOCK_STREAM (TCP), SOCK_DGRAM (UDP), SOCK_RAW
     */
    if ((sockfd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
    {
        printf ("Failed to create socket: %s\n", strerror (errno));
        return -1;
    }

    bzero (&clntaddr, sizeof (struct sockaddr_in));

    if (argc == 6)
    {
        printf ("doing bind......\n");
        clntaddr.sin_family = AF_INET;
        clntaddr.sin_port = htons (atoi (argv[5]));
        inet_aton (argv[4], &clntaddr.sin_addr);

        if (bind
            (sockfd, (struct sockaddr *) &clntaddr,
             sizeof (struct sockaddr_in)) == -1)
        {
            printf ("Failed to bind: %s\n", strerror (errno));
            close (sockfd);
            return -1;
        }
    }

    /*
     * fill up the server details
     */
    bzero (&servaddr, sizeof (struct sockaddr_in));

    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons (atoi (argv[2]));
    inet_aton (argv[1], &servaddr.sin_addr);

    doConnect = atoi (argv[3]);

    if (1 == doConnect)
    {
        printf ("doing connect......\n");
        if (-1 ==
            connect (sockfd, (struct sockaddr *) &servaddr,
                     sizeof (struct sockaddr_in)))
        {
            printf ("Failed to connect: %s\n", strerror (errno));
            close (sockfd);
            return -1;
        }
    }

    if (pthread_create (&thrid, NULL, receiver, NULL) < 0)
    {
        printf ("Failed to create thread: %s\n", strerror (errno));
        close (sockfd);
        return -1;
    }

    while ((read = getline (&line, &len, stdin)) != -1)
    {
        if (1 == doConnect)
        {
            if (send (sockfd, line, len, 0) < 0)
            {
                printf ("send () failed: %s\n", strerror (errno));
            }
        }
        else
        {
            if (sendto (sockfd, line, len, 0, (struct sockaddr *) &servaddr,
                        sizeof (struct sockaddr_in)) < 0)
            {
                printf ("sendto () failed: %s\n", strerror (errno));
            }
        }
    }

    if (line)
        free (line);

    close (sockfd);

    printf ("socket is closed.....\n");

    sleep (60);
}

void *
receiver (void *arg)
{
    char buff[512];

    while (1)
    {
        memset (buff, 0, sizeof (buff));

        if (1 == doConnect)
        {
            if (recv (sockfd, buff, sizeof (buff), 0) < 0)
            {
                // printf ("recvfrom () failed: %s\n", strerror (errno));
                printf ("recv () failed\n");
            }
            else
            {
                printf ("recv () returned: %s\n", buff);
            }
        }
        else
        {
            if (recvfrom (sockfd, buff, sizeof (buff), 0, NULL, NULL) < 0)
            {
                // printf ("recvfrom () failed: %s\n", strerror (errno));
                printf ("recvfrom () failed\n");
            }
            else
            {
                printf ("recvfrom () returned: %s\n", buff);
            }
        }
    }
}

回答1:

File descriptors are process global and their reference counts are process global. What's most likely happening from what I'm reading in your code is that the thread is still sitting in recv or recvfrom and the kernel won't close the file until that system call finishes. You can't close a file (even though you might free up the file descriptor slot) until every system call on that file is finished, that would lead to really nasty bugs (and it has in the past).

Shoot the thread before the call to close and you'll see that your file descriptors will close the way you want.