• SO_LINGER选项的作用和意义


    一、选项在内核中的使用
    搜索一下内核中对于SO_LINGER的使用,主要集中在socket的关闭、两个必不可少的set/get sockopt函数中,所以真正使用这个选项的地方并不多,所以分析起来可能并不复杂,也没什么影响,但是正如之前所说的,问题的严重性和重要性往往不是问题本身决定的,而是它可能引起的后果决定的,所以还是简单总结一下这个选项的意义。
    两个读取和设置该选项的内容就直接跳过了,现在直接看一下这个参数真正起作用的位置。
    二、socket文件的关闭
    sock_close--->>>sock_release
        if (sock->ops) {
            struct module *owner = sock->ops->owner;

            sock->ops->release(sock);
            sock->ops = NULL;
            module_put(owner);
        }
    对于TCP连接来说,这个注册的位置在static int __init inet_init(void)函数中初始化
        for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
            inet_register_protosw(q);
    注册的TCP结构为
        {
            .type =       SOCK_STREAM,
            .protocol =   IPPROTO_TCP,
            .prot =       &tcp_prot,
            .ops =        &inet_stream_ops,
            .capability = -1,
            .no_check =   0,
            .flags =      INET_PROTOSW_PERMANENT |
                      INET_PROTOSW_ICSK,
        },
    注册之后TCP socket创建的时候通过inet_create中找到对应的注册协议,然后初始化sock的操作
    sock->ops = answer->ops;
    所以这里的sock的ops就被初始化为下面的结构
    const struct proto_ops inet_stream_ops = {
        .family           = PF_INET,
        .owner           = THIS_MODULE,
        .release       = inet_release,
        .bind           = inet_bind,
        .connect       = inet_stream_connect,
        .socketpair       = sock_no_socketpair,
        .accept           = inet_accept,
        .getname       = inet_getname,
        .poll           = tcp_poll,
        .ioctl           = inet_ioctl,
        .listen           = inet_listen,
        .shutdown       = inet_shutdown,
        .setsockopt       = sock_common_setsockopt,
        .getsockopt       = sock_common_getsockopt,
        .sendmsg       = inet_sendmsg,
        .recvmsg       = sock_common_recvmsg,
        .mmap           = sock_no_mmap,
        .sendpage       = tcp_sendpage,
    #ifdef CONFIG_COMPAT
        .compat_setsockopt = compat_sock_common_setsockopt,
        .compat_getsockopt = compat_sock_common_getsockopt,
    #endif
    }
    接下来的事情就水到渠成了。
    三、socket关闭时SO_LINGER的作用
    1、网络层关闭
    inet_release中
        if (sk) {
            long timeout;

            /* Applications forget to leave groups before exiting */
            ip_mc_drop_socket(sk);

            /* If linger is set, we don't return until the close
             * is complete.  Otherwise we return immediately. The
             * actually closing is done the same either way.
             *
             * If the close is due to the process exiting, we never
             * linger..
             */
            timeout = 0;
            if (sock_flag(sk, SOCK_LINGER) &&
                !(current->flags & PF_EXITING)
    此时LINGER第一次出场,此时如果设置了SOCK_LINGER选项,此时的超时时间为linger中设置的超时时间,如果timeout为零表示无限等待
                timeout = sk->sk_lingertime;
            sock->sk = NULL;
            sk->sk_prot->close(sk, timeout);
        }
    2、tcp层关闭
    tcp_close中对于SO_LINGER的处理
    if (data_was_unread) {
            /* Unread data was tossed, zap the connection. */
            NET_INC_STATS_USER(LINUX_MIB_TCPABORTONCLOSE);
            tcp_set_state(sk, TCP_CLOSE);
            tcp_send_active_reset(sk, GFP_KERNEL);
        } else if (sock_flag(sk, SOCK_LINGER) && !sk->sk_lingertime) {如果设置了SO_LINGER选项,并且LINGER时间为0,则直接丢掉缓冲区中未发送数据,否则在在下面的if分支中判断进入FIN_WAIT1状态,并且发送tcp_fin包,开始断开连接,在tcp_disconnect中通过tcp_need_reset判断,会向对方发送RESET命令,导致对方不优雅的结束
            /* Check zero linger _after_ checking for unread data. */
            sk->sk_prot->disconnect(sk, 0);
            NET_INC_STATS_USER(LINUX_MIB_TCPABORTONDATA);
        } else if (tcp_close_state(sk)) {
            /* We FIN if the application ate all the data before
             * zapping the connection.
             */

            /* RED-PEN. Formally speaking, we have broken TCP state
             * machine. State transitions:
             *
             * TCP_ESTABLISHED -> TCP_FIN_WAIT1
             * TCP_SYN_RECV    -> TCP_FIN_WAIT1 (forget it, it's impossible)
             * TCP_CLOSE_WAIT -> TCP_LAST_ACK
             *
             * are legal only when FIN has been sent (i.e. in window),
             * rather than queued out of window. Purists blame.
             *
             * F.e. "RFC state" is ESTABLISHED,
             * if Linux state is FIN-WAIT-1, but FIN is still not sent.
             *
             * The visible declinations are that sometimes
             * we enter time-wait state, when it is not required really
             * (harmless), do not send active resets, when they are
             * required by specs (TCP_ESTABLISHED, TCP_CLOSE_WAIT, when
             * they look as CLOSING or LAST_ACK for Linux)
             * Probably, I missed some more holelets.
             *                         --ANK
             */
            tcp_send_fin(sk);
        }
    3、FIN_WAIT1何时结束
    由于发送方发送了FIN包,所以当FIN包被确认之后就可以认为FIN1时间结束了。如果对方一直不发送FIN的回应包,那么此时只有等待正常的TCP写操作超时来关闭套接口。
    如果设置了LINGER1表示,在inet_release中,这个lingertime将会传递给tcp_close函数,从而在lingertime之后超时,超时之后套接口被关闭,这也就是lingertime非零时的意义。
    四、如果listen套接口设置了该选项,accept的套接口是否会继承该选项
    tcp_check_req--->>tcp_v4_syn_recv_sock--->>>tcp_create_openreq_child--->>>inet_csk_clone--->>sk_clone
    大家看看在这个过程中有没有修改SO_LINGER选项所在的sk_flags字段和超时时间所在的sk_lingertime,答案是否定的,也就是说这些字段都是直接clone了父套接口,也就是listen的标志状态。
    下面是验证代码,测试代码由下面程序简单修改之后得到例子代码原版
    修改之后代码
    /*
     * Listing 1:
     * Simple "Hello, World!" server
     * Ivan Griffin (ivan.griffin@ul.ie)
     */

    #include <stdio.h>   /* */
    #include <stdlib.h>  /* exit() */
    #include <string.h>  /* memset(), memcpy() */
    #include <sys/utsname.h>   /* uname() */
    #include <sys/types.h>
    #include <sys/socket.h>   /* socket(), bind(),
                                 listen(), accept() */
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    #include <unistd.h>  /* fork(), write(), close() */

    /*
     * prototypes
     */
    int _GetHostName(char *buffer, int length);

    /*
     * constants
     */
    const char MESSAGE[] = "Hello, World! ";
    const int BACK_LOG = 5;

    int main(int argc, char *argv[])
    {
        int serverSocket = 0,
            on = 0,
            port = 0,
            status = 0,
            childPid = 0;
        struct hostent *hostPtr = NULL;
        char hostname[80] = "";
        struct sockaddr_in serverName = { 0 };

        if (2 != argc)
        {
            fprintf(stderr, "Usage: %s <port> ",
          argv[0]);
            exit(1);
        }
        port = atoi(argv[1]);

        serverSocket = socket(PF_INET, SOCK_STREAM,
          IPPROTO_TCP);
        if (-1 == serverSocket)
        {
            perror("socket()");
            exit(1);
        }

        /*
         * turn off bind address checking, and allow
         * port numbers to be reused - otherwise
         * the TIME_WAIT phenomenon will prevent
         * binding to these address.port combinations
         * for (2 * MSL) seconds.
         */

        on = 1;
    /*
        status = setsockopt(serverSocket, SOL_SOCKET,
          SO_REUSEADDR,
            (const char *) &on, sizeof(on));

        if (-1 == status)
        {
            perror("setsockopt(...,SO_REUSEADDR,...)");
        }
    */
        /*
         * when connection is closed, there is a need
         * to linger to ensure all data is
         * transmitted, so turn this on also
         */
        {
            struct linger linger = { 0 };

            linger.l_onoff = 1;
            linger.l_linger = 0;
            status = setsockopt(serverSocket,
          SOL_SOCKET, SO_LINGER,
          (const char *) &linger,
          sizeof(linger));

            if (-1 == status)
            {
                perror("setsockopt(...,SO_LINGER,...)");
            }
        }

        /*
         * find out who I am
         */

        status = _GetHostName(hostname,
          sizeof(hostname));
        if (-1 == status)
        {
            perror("_GetHostName()");
            exit(1);
        }

        hostPtr = gethostbyname(hostname);
        if (NULL == hostPtr)
        {
            perror("gethostbyname()");
            exit(1);
        }

        (void) memset(&serverName, 0,
          sizeof(serverName));
        (void) memcpy(&serverName.sin_addr,
          hostPtr->h_addr,
          hostPtr->h_length);

    /*
     * to allow server be contactable on any of
     * its IP addresses, uncomment the following
     * line of code:
     * serverName.sin_addr.s_addr=htonl(INADDR_ANY);
     */

        serverName.sin_family = AF_INET;
        /* network-order */
        serverName.sin_port = htons(port);

        status = bind(serverSocket,
       (struct sockaddr *) &serverName,
            sizeof(serverName));
        if (-1 == status)
        {
            perror("bind()");
            exit(1);
        }

        status = listen(serverSocket, BACK_LOG);
        if (-1 == status)
        {
            perror("listen()");
            exit(1);
        }

        for (;;)
        {
            struct sockaddr_in clientName = { 0 };
            int slaveSocket, clientLength =
          sizeof(clientName);

            (void) memset(&clientName, 0,
          sizeof(clientName));

            slaveSocket = accept(serverSocket,
          (struct sockaddr *) &clientName,
          &clientLength);
            if (-1 == slaveSocket)
            {
                perror("accept()");
                exit(1);
            }

            childPid = fork();

            switch (childPid)
            {
            case -1: /* ERROR */
                perror("fork()");
                exit(1);

            case 0: /* child process */

                close(serverSocket);

                if (-1 == getpeername(slaveSocket,
          (struct sockaddr *) &clientName,
          &clientLength))
                {
                    perror("getpeername()");
                }
                else
                {
                printf("Connection request from %s ",
                        inet_ntoa(clientName.sin_addr));
                }

                /*
                 * Server application specific code
                 * goes here, e.g. perform some
                 * action, respond to client etc.
                 */
                write(slaveSocket, MESSAGE,
             strlen(MESSAGE));
        struct linger linger = { 0 };
        socklen_t socklen;
            status = getsockopt(serverSocket,
          SOL_SOCKET, SO_LINGER,
           &linger,
          (socklen_t*)&socklen);
        printf("status %d linger.",status, linger.l_onoff ,linger.l_linger );
                close(slaveSocket);
                exit(0);

            default: /* parent process */
                close(slaveSocket);
            }
        }

        return 0;
    }

    /*
     * Local replacement of gethostname() to aid
     * portability */
    int _GetHostName(char *buffer, int length)
    {
        struct utsname sysname = { 0 };
        int status = 0;

        status = uname(&sysname);
        if (-1 != status)
        {
            strncpy(buffer, sysname.nodename, length);
        }

        return (status);
    }
    2、测试例子
    编译服务器代码
    [root@Harry socklinger]# gcc socklinger.c 
    [root@Harry socklinger]# ./a.out 2222

    通过Telnet连接该端口
    [root@Harry ~]# telnet 127.0.0.1 2222
    Trying 127.0.0.1...
    Connected to 127.0.0.1.
    Escape character is '^]'.
    Hello, World!
    Connection closed by foreign host.
    [root@Harry ~]# 
    服务器端输出代码
    Connection request from 127.0.0.1
    status 0 linger.onoff 1 linger.linger 0
    3、其它相关问题
    make中命令行设置的-i选项会不会传递给子make,bash的 -e选项会不会传递给子脚本?

  • 相关阅读:
    第二周总结
    2019春总结作业
    第二次编程总结
    第四周作业
    第十二周作业
    第十一周作业
    第十周作业
    第九周作业
    第八周作业
    第六周作业
  • 原文地址:https://www.cnblogs.com/tsecer/p/10487522.html
Copyright © 2020-2023  润新知