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!\n";
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>\n",
      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\n",
                    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选项会不会传递给子脚本?

上一篇:UNP --- 第一章 介绍


下一篇:FreeSWITCH 架构