• 关于close和shutdown


    我们知道TCP是全双工的,可以在接收数据的同时发送数据。
    假设有主机A在和主机B通信,可以认为是在两者之间存在两个管道。就像这样:
    A ---------> B
    A <--------- B


    1.close
      close可以用来关闭一个文件描述符。也就可以用来关闭一个套接字。
      当关闭一个套接字时,该套接字不能再由调用进程使用。如果调用进程再去read、write就会出错。

      我们知道关闭一个socket描述符时,会给对方发送一个FIN数据段。比如在主机A中close了与主机B通信的sockA。相当于终止了全双工的那两个管道。而从传输层来看,TCP会尝试将目前发送缓冲区中积压的数据发到链路层上,然后才会发起TCP的4次挥手以彻底关闭TCP连接。
      之后在主机A中就不能用sockA来接收数据和发送数据了,同时由于是面向连接的。之前与sockA连接的sockB也收不到数据了。
      如果依然通过sockB往主机A上写数据,开始会触发一个RST重连包,然后会收到一个SIGPIPE信号。

      但是如果存在父子进程共用socket描述符的时候(比如fork了一个子进程),父子进程都有相同数值的文件描述符,且都是打开的。这时候去关闭父进程中的描述符并不会发送FIN包给对方。只有子进程也关闭了才会发送FIN。


      原因在于,fork时,父子进程共享着套接字,套接字描述符的引用计数记录着共享着的进程个数。fork一次时相当于引用计数为2了。这时候去关闭一个,只会让引用计数减一。只有当引用计数为0时(也就是子进程也close了),才会发送FIN给连接方。
      (就有点像windows下的句柄handle,是一个内核对象,当每被打开一次时,引用计数就会加一,CloseHandle时引用计数减一,若引用计数为0时,操作系统会回收这个内核对象)

    2.shutdown
    也可以用来关闭TCP数据传输的一个或两个方向。
    原型:

    SYNOPSIS
           #include <sys/socket.h>

           int shutdown(int sockfd, int how);

    DESCRIPTION
           The  shutdown()  call causes all or part of a full-duplex connection on
           the socket associated with sockfd to be shut down.  If how is  SHUT_RD,
           further  receptions  will  be  disallowed.   If how is SHUT_WR, further
           transmissions will be disallowed.  If how is SHUT_RDWR, further  recep‐
           tions and transmissions will be disallowed.

    RETURN VALUE
           On  success,  zero is returned.  On error, -1 is returned, and errno is
           set appropriately.

    参数:第一个表示socket描述符
    第二个表示关闭读还是写。具体有三个值:
    1)SHUT_WR:关闭读,表示不能用第一个参数对应的描述符往管道里面写数据了。(但是依然可以写数据)
    2)SHUT_RD:关闭写,不能写数据了。(依然可以接收数据)
    3)SHUT_RDWR:同时关闭读和写

    3.close和shutdown的区别
    1)close只会让引用计数减一,只有在引用计数减为零的时候才会给对方发送FIN段来断开连接。而shutdown会直接关闭连接,不受引用计数的限制,这就意味着在多进程中,只有调用了这个关闭了写端,那么其他进程也都不能写了。
    2)close会关闭两端,shutdown可以选择关闭某个端。(这点非常有用处,比如主机A和B正在通信,A觉得没数据发送了,想要断开连接。然后A调用了close,那么B的数据也将发不过来,但是可以选择用shutdown关闭写端,这时候可以接收完B发的数据)

    4.实例,用于更好的分析理解shutdown的机制:
    client从标准输入中接收数据发送给server。server用来接收client的数据,并且回射回去。
    这里做一个处理,client发送一次数据之后马上按下Ctrl+D(会导致fgets返回NULL),然后shutdown写端(相当于往server发送了FIN段)。server收到数据后,sleep10s再回射回去。
    具体关于下面代码的理解可以参考:http://www.cnblogs.com/xcywt/p/8087677.html

    #include<sys/types.h>
    #include<sys/socket.h>
    #include<sys/select.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    
    #include<stdlib.h>
    #include<stdio.h>
    #include<string.h>
    #include<errno.h>
    #include<signal.h>
    
    #define CLIENTCOUNT 100
    
    void sig_recvpipe(int sig)
    {
        printf("recv pipe = %d
    ", sig);
    }
    
    int main(int argc, char **argv)
    {
        signal(SIGPIPE, sig_recvpipe);
        int listenfd = socket(AF_INET, SOCK_STREAM, 0);
        if(listenfd < 0)
        {
            perror("socket");
            return -1;
        }
        
        unsigned short sport = 8080;
        if(argc == 2)
        {
            sport = atoi(argv[1]);
        }
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        printf("port = %d
    ", sport);
        addr.sin_port = htons(sport);
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        
        if(bind(listenfd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
        {
            perror("bind");
            return -2;
        }
    
        if(listen(listenfd, 20) < 0)
        {
            perror("listen");
            return -3;
        }
            
        struct sockaddr_in connaddr;
        int len = sizeof(connaddr);
        
        int i = 0, ret = 0;
        int client[CLIENTCOUNT];
        for(i = 0; i<CLIENTCOUNT; i++)
            client[i] = -1;
    
        fd_set rset;
        fd_set allset;
        FD_ZERO(&rset);    
        FD_ZERO(&allset);    
        FD_SET(listenfd, &allset);
        int maxfd = listenfd;
        int nready = 0;
        char buf[1024] = {0};
        while(1)
        {
            rset = allset;
            nready = select(maxfd+1, &rset, NULL, NULL, NULL);
            if(nready == -1)
            {
                perror("select");
                            return -3;
    
            }
            if(nready == 0)
            {
                continue;
            }
    
            if(FD_ISSET(listenfd, &rset))
            {
                int conn = accept(listenfd, (struct sockaddr*)&connaddr, &len);
                if(conn < 0)
                {
                    perror("accept");
                    return -4;
                }
            
                char strip[64] = {0};
                char *ip = inet_ntoa(connaddr.sin_addr);
                strcpy(strip, ip);
                printf("new client connect, conn:%d,ip:%s, port:%d
    ", conn, strip,ntohs(connaddr.sin_port));
                
                FD_SET(conn, &allset);
                if(maxfd < conn) // update maxfd
                    maxfd = conn;    
    
                int i = 0;
                for(i = 0; i<CLIENTCOUNT; i++)
                {
                    if(client[i] == -1)
                    {
                        client[i] = conn;
                        break;
                    }
                }
                if(i == CLIENTCOUNT)
                {
                    printf("to many client connect
    ");
                    exit(0);
                }
                
                if(--nready <= 0)
                    continue;
            }
            for(i = 0; i < CLIENTCOUNT; i++)
            {
                if(client[i] == -1)
                    continue;
                if(FD_ISSET(client[i], &rset))
                {
                    ret = read(client[i], buf, sizeof(buf));
                    if(ret == -1)
                    {
                        perror("read");
                        return -4;
                    }
                    else if(ret == 0)
                    {
                        printf("client close remove:%d
    ", client[i]);
                        FD_CLR(client[i], &allset);
                        close(client[i]);
                        client[i] = -1;  // 要在这里移除
                    }
                    
                    // fputs(buf, stdout);
                    printf("Recv client%d:%s", client[i], buf);
                    sleep(10);
                    write(client[i], buf, sizeof(buf));
                    memset(buf, 0, sizeof(buf));
    
                    if(--nready <= 0)
                        continue;
                }
            }        
        }
    
        close(listenfd);
        return 0;
    }

    client端:

    #include<sys/types.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<sys/select.h>
    
    #include<stdlib.h>
    #include<stdio.h>
    #include<string.h>
    
    void select_test(int conn)
    {
        int ret = 0;
        fd_set rset;
        FD_ZERO(&rset);
    
        int nready;
        int maxfd = conn;
        int fd_stdin = fileno(stdin);
        if(fd_stdin > maxfd)
        {
            maxfd = fd_stdin;
        }
        
        int stdinoff = 0;
        int len = 0;
        char readbuf[1024] = {0};
        char writebuf[1024] = {0};
        while(1)
        {
            FD_ZERO(&rset);
            if(!stdinoff)
                FD_SET(fd_stdin, &rset);
            FD_SET(conn, &rset);
            nready = select(maxfd+1, &rset, NULL, NULL, NULL);
            if(nready == -1)
            {
                perror("select");
                exit(0);
            }
            else if(nready == 0)
            {
                continue;    
            }
    
            if(FD_ISSET(conn, &rset))
            {
                ret = read(conn, readbuf, sizeof(readbuf));
                if(ret == 0)
                {
                    printf("server close1
    ");
                    break;
                }
                else if(-1 == ret)
                {
                    perror("read1");
                    break;
                }    
    
                fputs(readbuf, stdout);
                memset(readbuf, 0, sizeof(readbuf));
            }    
            
            if(FD_ISSET(fd_stdin, &rset))
            {
                if(fgets(writebuf, sizeof(writebuf), stdin) == NULL)
                {
                    #if 0
                    printf("After 5s client exit
    ");
                    close(conn);
                    sleep(5);
                    exit(EXIT_FAILURE);
                    #else
                    shutdown(conn, SHUT_WR);
                    stdinoff = 1;
                    #endif
                }
                else
                {
                    write(conn, writebuf, sizeof(writebuf));
                    memset(writebuf, 0, sizeof(writebuf));    
                }
            }
        }
        close(conn);
    }
    
    int sockfd = 0;
    int main(int argc, char **argv)
    {
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if(sockfd < 0)
        {
            perror("socket");
            return -1;
        }
            
        unsigned short sport = 8080;
        if(argc == 2)
        {
            sport = atoi(argv[1]);
        }
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        printf("port = %d
    ", sport);
        addr.sin_port = htons(sport);
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
        if(connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
        {
            perror("connect");
            return -2;
        }
    
        struct sockaddr_in addr2;
        socklen_t len = sizeof(addr2);
        if(getpeername(sockfd, (struct sockaddr*)&addr2, &len) < 0)
        {
            perror("getsockname");
            return -3;
        }
    
        printf("Server: port:%d, ip:%s
    ", ntohs(addr2.sin_port), inet_ntoa(addr2.sin_addr));
    
        select_test(sockfd);
    
        close(sockfd);
        return 0;
    }

    编译运行:
    makefile:

    CC=gcc
    CFLAGS=-Wall -g
    LIBS=-lpthread
    all:echoser echocli
    echoser:server.c
        $(CC) $< $(CFLAGS) $(LIBS) -o $@
    echocli:client.c
        $(CC) $< $(CFLAGS) $(LIBS) -o $@
    .PHONY:clean
    clean:
        rm -f *.o echoser echocli *~

    client端运行:
    在发送完1111111时马上按下Ctrl+d,将读端关闭。然后会发现10s以后还是可以收到server的数据。

    xcy@xcy-virtual-machine:~/test/sock8_shutdown$ ./echocli 
    port = 8080
    Server: port:8080, ip:127.0.0.1
    11111111
    11111111
    server close1
    xcy@xcy-virtual-machine:~/test/sock8_shutdown$

    server端运行:
    server收到数据,10s后发送给client。之后还会read返回0,会认为是client关闭了,然后就把套接字关闭了。最后client也能收到read返回0。

    xcy@xcy-virtual-machine:~/test/sock8_shutdown$ ./echoser 
    port = 8080
    new client connect, conn:4,ip:127.0.0.1, port:55852
    Recv client4:11111111
    client close remove:4
    ^C
    xcy@xcy-virtual-machine:~/test/sock8_shutdown$

    查看TCP状态:
    当按下Ctrl+d时去查看状态,下面第2行可以看出来client已经变成CLOSE_WAIT状态了,server变成了FIN_WAIT2.
    等10s到了,再去看client变成了TIME_WAIT状态(要保持2MSL)
    具体可以参考:十一种状态 http://www.cnblogs.com/xcywt/p/8082428.html

    xcy@xcy-virtual-machine:~$ netstat -an | grep 8080
    tcp        0      0 127.0.0.1:8080          0.0.0.0:*               LISTEN     
    tcp        1      0 127.0.0.1:8080          127.0.0.1:55852         CLOSE_WAIT 
    tcp        0      0 127.0.0.1:55852         127.0.0.1:8080          FIN_WAIT2  
    xcy@xcy-virtual-machine:~$ netstat -an | grep 8080
    tcp        0      0 127.0.0.1:8080          0.0.0.0:*               LISTEN     
    tcp        0      0 127.0.0.1:55852         127.0.0.1:8080          TIME_WAIT  
    xcy@xcy-virtual-machine:~$ netstat -an | grep 8080
    xcy@xcy-virtual-machine:~$

    我们可以看出来,client可以关闭写端,但是还是可以接收到到server发来的数据。

  • 相关阅读:
    儿子和女儿——解释器和编译器的区别与联系
    求eclipse中的java build path 详解
    求eclipse中的java build path 详解
    System.Activator类
    htmlagilitypack解析html
    KindleEditor insertfile初始化多个
    按住ALT键复制
    隐藏行错误排查
    列类型: 202错误
    C#中的&运算
  • 原文地址:https://www.cnblogs.com/xcywt/p/8127773.html
Copyright © 2020-2023  润新知