1. 程式人生 > >關於close和shutdown

關於close和shutdown

-- 父子進程 connect wal 發現 tin font 成了 app

我們知道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\n", 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\n", 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\n", 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\n");
                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\n", 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\n");
                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\n");
                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\n", 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\n", 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發來的數據。

關於close和shutdown