当前位置: 首页 > 工具软件 > Linger > 使用案例 >

网络编程Socket之TCP之close/shutdown详解 SO_LINGER

曹德明
2023-12-01

close:

当套接字的引用计数为0的时候才会引发TCP的四分组连接终止序列;

 

shutdown:

不用管套接字的引用计数就激发TCP的正常连接终止序列;

这里由一个SO_LINGER套接字选项

struct linger {

     int l_onoff; /* 0 = off, nozero = on */

     int l_linger; /* linger time,POSIX specifies units as seconds */

};

shutdown:SHUT_RD

关闭连接的读这一半,进程不能再对这样的套接字调用任何读操作;

在套接字上不能再发出接收请求,进程仍可往套接字发送数据,套接字接收缓冲区中所有数据被丢弃,再接收到的任何数据由TCP丢弃,对套接字发送缓冲区没有任何影响;

程序模拟:

client:

struct sockaddr_in serverAdd;
    
    bzero(&serverAdd, sizeof(serverAdd));
    serverAdd.sin_family = AF_INET;
    serverAdd.sin_addr.s_addr = inet_addr(SERV_ADDR);
    serverAdd.sin_port = htons(SERV_PORT);
    
    int connfd = socket(AF_INET, SOCK_STREAM, 0);
    
    int connResult = connect(connfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
    if (connResult < 0) {
        printf("连接失败\n");
        close(connfd);
        return;
    }
    else
    {
        printf("连接成功\n");
    }
    
    ssize_t writeLen;
 
    char sendMsg[5000] = {0};
    unsigned long long totalSize = 0;
  
    if (1) {
 
        writeLen = write(connfd, sendMsg, sizeof(sendMsg));
        if (writeLen < 0) {
            printf("发送失败 errno = %d\n",errno);
            return;
        }
        else
        {
            totalSize += writeLen;
            printf("发送成功 totalSize = %zd\n",totalSize);
        }
        sleep(20);
        
    }

server:
#define SERV_PORT 8000
 
int main(int argc, const char * argv[])
{
 
    struct sockaddr_in serverAdd;
    struct sockaddr_in clientAdd;
    
    bzero(&serverAdd, sizeof(serverAdd));
    serverAdd.sin_family = AF_INET;
    serverAdd.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAdd.sin_port = htons(SERV_PORT);
    
    socklen_t clientAddrLen;
    
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    int yes = 1;
    setsockopt(listenfd,
               SOL_SOCKET, SO_REUSEADDR,
               (void *)&yes, sizeof(yes));
    
    if (listenfd < 0) {
        printf("创建socket失败\n");
        return -1;
    }
    
    int bindResult = bind(listenfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
    if (bindResult < 0) {
        printf("绑定端口失败\n");
        close(listenfd);
        return -1;
    }
    
    listen(listenfd, 20);
    
    int connfd;
    unsigned char recvMsg[246988];
    unsigned long long totalSize = 0;
    
    clientAddrLen = sizeof(clientAdd);
    connfd = accept(listenfd,(struct sockaddr *)&clientAdd,&clientAddrLen);
    if (connfd < 0) {
        printf("连接失败\n");
        return -1;
    }
    else{
//        这里我们用于测试,只接收一个连接
        close(listenfd);
    }
    
    shutdown(connfd,SHUT_RD);
 
    while (1) {
        ssize_t readLen = read(connfd, recvMsg, sizeof(recvMsg));
        
        if (readLen < 0) {
            printf("读取失败 errno = %d\n",errno);
            close(connfd);
            return -1;
        }
        else if (readLen == 0) {
            printf("读取完成 totalSize = %llu\n",totalSize);
            close(connfd);
            return -1;
        }
        else
        {
            totalSize += readLen;
            printf("readLen:%ld\n",readLen);
        }
 
    }
    
    return 0;
}


打印信息:
client:

连接成功

发送成功 totalSize = 5000

server:
读取完成 totalSize = 0

是因为读关闭了,套接字接收缓冲区的数据直接被丢弃掉了,如果将服务端的shutdiwn替换成close会如下

读取失败 errno = 9


#define EBADF 9 /* Bad file descriptor */


说明:如果服务端调用shutdown关闭读之后,客户端再往已经收到RST分节的服务端进行write操作,会引发错误导致程序终止,RST详解里面有讲到

抓包信息:


shutdown:SHUT_WR

关闭连接的写这一半,进程不能再对这样的套接字调用任何写操作;

在套接字上不能再发出发送请求,进程仍可从套接字接收数据,套接字发送缓冲区中的内容被发送到对端,后跟正常的TCP连接终止序列(即发送FIN),对套接字接收缓冲区无任何影响;

程序模拟:

client:


struct sockaddr_in serverAdd;
    
    bzero(&serverAdd, sizeof(serverAdd));
    serverAdd.sin_family = AF_INET;
    serverAdd.sin_addr.s_addr = inet_addr(SERV_ADDR);
    serverAdd.sin_port = htons(SERV_PORT);
    
    int connfd = socket(AF_INET, SOCK_STREAM, 0);
    
    int connResult = connect(connfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
    if (connResult < 0) {
        printf("连接失败\n");
        close(connfd);
        return;
    }
    else
    {
        printf("连接成功\n");
    }
    
    ssize_t writeLen;
 
    char sendMsg[200000] = {0};
    unsigned long long totalSize = 0;
 
    if (1) {
 
        writeLen = write(connfd, sendMsg, sizeof(sendMsg));
        if (writeLen < 0) {
            printf("发送失败 errno = %d\n",errno);
            return;
        }
        else
        {
            totalSize += writeLen;
            printf("发送成功 totalSize = %zd\n",totalSize);
        }
        shutdown(connfd,SHUT_WR);
        sleep(20);
 
    }

server:

int main(int argc, const char * argv[])
{
 
    struct sockaddr_in serverAdd;
    struct sockaddr_in clientAdd;
    
    bzero(&serverAdd, sizeof(serverAdd));
    serverAdd.sin_family = AF_INET;
    serverAdd.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAdd.sin_port = htons(SERV_PORT);
    
    socklen_t clientAddrLen;
    
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    int yes = 1;
    setsockopt(listenfd,
               SOL_SOCKET, SO_REUSEADDR,
               (void *)&yes, sizeof(yes));
    
    if (listenfd < 0) {
        printf("创建socket失败\n");
        return -1;
    }
    
    int bindResult = bind(listenfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
    if (bindResult < 0) {
        printf("绑定端口失败\n");
        close(listenfd);
        return -1;
    }
    
    listen(listenfd, 20);
    
    int connfd;
    unsigned char recvMsg[246988];
    unsigned long long totalSize = 0;
    
    clientAddrLen = sizeof(clientAdd);
    connfd = accept(listenfd,(struct sockaddr *)&clientAdd,&clientAddrLen);
    if (connfd < 0) {
        printf("连接失败\n");
        return -1;
    }
    else{
//        这里我们用于测试,只接收一个连接
        close(listenfd);
    }
    
    while (1) {
        ssize_t readLen = read(connfd, recvMsg, sizeof(recvMsg));
        
        if (readLen < 0) {
            printf("读取失败 errno = %d\n",errno);
            close(connfd);
            return -1;
        }
        else if (readLen == 0) {
            printf("读取完成 totalSize = %llu\n",totalSize);
            close(connfd);
            return -1;
        }
        else
        {
            totalSize += readLen;
            printf("readLen:%ld\n",readLen);
        }
 
    }
    
    return 0;
}

打印信息:
client:

连接成功

发送成功 totalSize = 200000


server:

readLen:1448

readLen:1448

readLen:1448

readLen:1448

... ...

readLen:1448

readLen:1448

readLen:3072

读取完成 totalSize = 200000


说明:客户端发送字节200000的大小大于TCP套接字发送缓冲区的大小,数据全部成功送达服务端,但是在调用shutdown关闭写之后不能再调用write方法,会引起错误终止程序,这里和上面的关闭读有差别;

shutdown:SHUT_RDWR

关闭连接的读和写;


close:l_onoff = 0(默认情况):

在套接字上不能再发出发送或接收请求,套接字发送缓冲区中的内容被发送到对端,如果描述符引用计数变为0,在发送完发送缓冲区的数据后,跟以正常的TCP连接终止序列(即发送FIN),套接字接收缓冲区中的内容被丢弃;

程序模拟:

client:

struct sockaddr_in serverAdd;
    
    bzero(&serverAdd, sizeof(serverAdd));
    serverAdd.sin_family = AF_INET;
    serverAdd.sin_addr.s_addr = inet_addr(SERV_ADDR);
    serverAdd.sin_port = htons(SERV_PORT);
    
    int connfd = socket(AF_INET, SOCK_STREAM, 0);
    
    int connResult = connect(connfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
    if (connResult < 0) {
        printf("连接失败\n");
        close(connfd);
        return;
    }
    
    ssize_t writeLen;
    char sendMsg[5000] = {0};
    for (int i = 0 ; i<sizeof(sendMsg); i++) {
        sendMsg[i] = 'a';
    }
    while (1) {
        
        writeLen = write(connfd, sendMsg, sizeof(sendMsg));
        if (writeLen < 0) {
            printf("发送失败\n");
            return;
        }
        else
        {
            printf("发送成功 writeLen = %zd\n",writeLen);
        }
               
        close(connfd);
        
        break;
    }

server:

#define SERV_PORT 8000
 
int main(int argc, const char * argv[])
{
 
    struct sockaddr_in serverAdd;
    struct sockaddr_in clientAdd;
    
    bzero(&serverAdd, sizeof(serverAdd));
    serverAdd.sin_family = AF_INET;
    serverAdd.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAdd.sin_port = htons(SERV_PORT);
    
    socklen_t clientAddrLen;
    
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    int yes = 1;
    setsockopt(listenfd,
               SOL_SOCKET, SO_REUSEADDR,
               (void *)&yes, sizeof(yes));
    
    if (listenfd < 0) {
        printf("创建socket失败\n");
        return -1;
    }
    
    int bindResult = bind(listenfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
    if (bindResult < 0) {
        printf("绑定端口失败\n");
        close(listenfd);
        return -1;
    }
    
    listen(listenfd, 20);
    
    int connfd;
    unsigned char recvMsg[246988];
    unsigned long long totalSize = 0;
    
    clientAddrLen = sizeof(clientAdd);
    connfd = accept(listenfd,(struct sockaddr *)&clientAdd,&clientAddrLen);
    if (connfd < 0) {
        printf("连接失败\n");
        return -1;
    }
    else{
//        这里我们用于测试,只接收一个连接
        close(listenfd);
    }
    
    sleep(1);
    close(connfd);
    while (1)
    {
        ssize_t readLen = read(connfd, recvMsg, sizeof(recvMsg));
        
        if (readLen < 0) {
            printf("读取失败\n");
            break;
        }
        else if (readLen == 0) {
            printf("读取完成 totalSize = %llu\n",totalSize);
            break;
        }
        else
        {
            totalSize += readLen;
            printf("readLen:%ld\n",readLen);
        }
    }
    
    return 0;
}

先运行服务端,再运行客户端,打印信息如下:
client:

发送成功 writeLen = 5000


server:

读取失败

抓包信息:


说明:通过抓包可以看出客户端的5000字节的数据都成功发送到服务端,服务器里面有一秒的休眠才关闭连接就是为了接收完这些数据,关闭连接以后再来通过read读取接收缓冲区的数据却读取失败,说明已经被内核丢弃了,而客户端的数据是已经成功写到发送缓冲区,即使关闭连接,发送缓冲区的数据不会被丢弃,会正常发送到服务器,发送完成以后,接着发送终止连接的第一个FIN分节;

close:l_onoff = 1,l_linger = 0:

在套接字上不能再发出发送或接收请求,如果描述符引用计数变为0,RST被发送到对端,连接的状态被置为CLOSED(没有TIME_WAIT状态),套接字发送缓冲区和接收缓冲区中的内容被丢弃;

模拟程序(套接字发送缓冲区数据被丢弃):

client:


    struct sockaddr_in serverAdd;
    
    bzero(&serverAdd, sizeof(serverAdd));
    serverAdd.sin_family = AF_INET;
    serverAdd.sin_addr.s_addr = inet_addr(SERV_ADDR);
    serverAdd.sin_port = htons(SERV_PORT);
    
    int connfd = socket(AF_INET, SOCK_STREAM, 0);
    
    int connResult = connect(connfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
    if (connResult < 0) {
        printf("连接失败\n");
        close(connfd);
        return;
    }
    else
    {
        printf("连接成功\n");
    }
    
    ssize_t writeLen;
    char sendMsg[200000] = {0};
    unsigned long long totalSize = 0;
 
    struct linger so_linger;
    
    so_linger.l_onoff = 1;
    so_linger.l_linger = 0;
    setsockopt(connfd,
               SOL_SOCKET,
               SO_LINGER,
               &so_linger,
               sizeof so_linger);
 
    
    if (1) {
        
        writeLen = write(connfd, sendMsg, sizeof(sendMsg));
        if (writeLen < 0) {
            printf("发送失败 errno = %d\n",errno);
            return;
        }
        else
        {
            totalSize += writeLen;
            printf("发送成功 totalSize = %zd\n",totalSize);
        }
 
        close(connfd);
        
    }

server:


int main(int argc, const char * argv[])
{
 
    struct sockaddr_in serverAdd;
    struct sockaddr_in clientAdd;
    
    bzero(&serverAdd, sizeof(serverAdd));
    serverAdd.sin_family = AF_INET;
    serverAdd.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAdd.sin_port = htons(SERV_PORT);
    
    socklen_t clientAddrLen;
    
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    int yes = 1;
    setsockopt(listenfd,
               SOL_SOCKET, SO_REUSEADDR,
               (void *)&yes, sizeof(yes));
    
    if (listenfd < 0) {
        printf("创建socket失败\n");
        return -1;
    }
    
    int bindResult = bind(listenfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
    if (bindResult < 0) {
        printf("绑定端口失败\n");
        close(listenfd);
        return -1;
    }
    
    listen(listenfd, 20);
    
    int connfd;
    unsigned char recvMsg[246988];
    unsigned long long totalSize = 0;
    
    clientAddrLen = sizeof(clientAdd);
    connfd = accept(listenfd,(struct sockaddr *)&clientAdd,&clientAddrLen);
    if (connfd < 0) {
        printf("连接失败\n");
        return -1;
    }
    else{
//        这里我们用于测试,只接收一个连接
        close(listenfd);
    }
    
    while (1) {
        sleep(1);
        ssize_t readLen = read(connfd, recvMsg, sizeof(recvMsg));
        
        if (readLen < 0) {
            printf("读取失败 errno = %d\n",errno);
            close(connfd);
            return -1;
        }
        else if (readLen == 0) {
            printf("读取完成 totalSize = %llu\n",totalSize);
            close(connfd);
            return -1;
        }
        else
        {
            totalSize += readLen;
            printf("readLen:%ld\n",readLen);
        }
 
    }
    
    return 0;
}

打印信息:
client:


连接成功

发送成功 totalSize = 200000


server:


readLen:91224

读取失败 errno = 54


抓包信息:

说明:调用close的时候RST分节发送出去,携带89777到91224字节的数据,然后丢弃发送缓冲区中的数据,因此服务器再向已经收到RST分节的套接字read失败,返回错误码:


#define ECONNRESET 54 /* Connection reset by peer */

再看下面的模拟程序(套接字接收缓冲区数据被丢弃):


client:


struct sockaddr_in serverAdd;
    
    bzero(&serverAdd, sizeof(serverAdd));
    serverAdd.sin_family = AF_INET;
    serverAdd.sin_addr.s_addr = inet_addr(SERV_ADDR);
    serverAdd.sin_port = htons(SERV_PORT);
    
    int connfd = socket(AF_INET, SOCK_STREAM, 0);
    
    int connResult = connect(connfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
    if (connResult < 0) {
        printf("连接失败\n");
        close(connfd);
        return;
    }
    else
    {
        printf("连接成功\n");
    }
    
    ssize_t writeLen;
 
    char sendMsg[200000] = {0};
    unsigned long long totalSize = 0;
 
    while (1) {
        
        writeLen = write(connfd, sendMsg, sizeof(sendMsg));
        if (writeLen < 0) {
            printf("发送失败 errno = %d\n",errno);
            return;
        }
        else
        {
            totalSize += writeLen;
            printf("发送成功 totalSize = %zd\n",totalSize);
        }
        
    }

server:

int main(int argc, const char * argv[])
{
 
    struct sockaddr_in serverAdd;
    struct sockaddr_in clientAdd;
    
    bzero(&serverAdd, sizeof(serverAdd));
    serverAdd.sin_family = AF_INET;
    serverAdd.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAdd.sin_port = htons(SERV_PORT);
    
    socklen_t clientAddrLen;
    
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    int yes = 1;
    setsockopt(listenfd,
               SOL_SOCKET, SO_REUSEADDR,
               (void *)&yes, sizeof(yes));
    
    if (listenfd < 0) {
        printf("创建socket失败\n");
        return -1;
    }
    
    int bindResult = bind(listenfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
    if (bindResult < 0) {
        printf("绑定端口失败\n");
        close(listenfd);
        return -1;
    }
    
    listen(listenfd, 20);
    
    int connfd;
    unsigned char recvMsg[246988];
    unsigned long long totalSize = 0;
    
    clientAddrLen = sizeof(clientAdd);
    connfd = accept(listenfd,(struct sockaddr *)&clientAdd,&clientAddrLen);
    if (connfd < 0) {
        printf("连接失败\n");
        return -1;
    }
    else{
//        这里我们用于测试,只接收一个连接
        close(listenfd);
    }
    
    
    struct linger so_linger;
    
    so_linger.l_onoff = 1;
    so_linger.l_linger = 0;
    setsockopt(connfd,
               SOL_SOCKET,
               SO_LINGER,
               &so_linger,
               sizeof so_linger);
    
    while (1) {
        sleep(1);
        ssize_t readLen = read(connfd, recvMsg, sizeof(recvMsg));
        
        if (readLen < 0) {
            printf("读取失败 errno = %d\n",errno);
            close(connfd);
            return -1;
        }
        else if (readLen == 0) {
            printf("读取完成 totalSize = %llu\n",totalSize);
            close(connfd);
            return -1;
        }
        else
        {
            totalSize += readLen;
            printf("readLen:%ld\n",readLen);
        }
        close(connfd);
 
    }
    
    return 0;
}

打印信息:

client:

连接成功

发送成功 totalSize = 200000

发送成功 totalSize = 400000

发送成功 totalSize = 600000

(lldb) 

然后程序返回错误被终止
server:

readLen:246988

读取失败 errno = 9

#define EBADF 9 /* Bad file descriptor */


抓包信息:

说明:服务端调用close以后直接发送RST分节,接收缓冲区数据被丢弃,客户端向已经收到RST分节的套接字write会返回错误终止程序

close:l_onoff = 1,l_linger  != 0:

在套接字上不能再发出发送或接收请求,套接字发送缓冲区中的内容被发送到对端,如果描述符引用计数变为0,在发送完发送缓冲区的数据后,跟以正常的TCP连接终止序列(即发送FIN),套接字接收缓冲区中的内容被丢弃,如果在连接变为CLOSED状态前延滞时间到,那么close返回EWOULDBLOCK错误;

模拟程序:

client:

struct sockaddr_in serverAdd;
    
    bzero(&serverAdd, sizeof(serverAdd));
    serverAdd.sin_family = AF_INET;
    serverAdd.sin_addr.s_addr = inet_addr(SERV_ADDR);
    serverAdd.sin_port = htons(SERV_PORT);
    
    int connfd = socket(AF_INET, SOCK_STREAM, 0);
    
    int connResult = connect(connfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
    if (connResult < 0) {
        printf("连接失败\n");
        close(connfd);
        return;
    }
    else
    {
        printf("连接成功\n");
    }
    
    ssize_t writeLen;
 
    char sendMsg[200000] = {0};
    unsigned long long totalSize = 0;
 
    struct linger so_linger;
    
    so_linger.l_onoff = 1;
    so_linger.l_linger = 3;
    setsockopt(connfd,
               SOL_SOCKET,
               SO_LINGER,
               &so_linger,
               sizeof so_linger);
 
    
    if (1) {
 
        writeLen = write(connfd, sendMsg, sizeof(sendMsg));
        if (writeLen < 0) {
            printf("发送失败 errno = %d\n",errno);
            return;
        }
        else
        {
            totalSize += writeLen;
            printf("发送成功 totalSize = %zd\n",totalSize);
        }
 
        int cr = close(connfd);
        if (cr < 0) {
            printf("失败 errno = %d\n",errno);
        }
 
    }

server:

int main(int argc, const char * argv[])
{
 
    struct sockaddr_in serverAdd;
    struct sockaddr_in clientAdd;
    
    bzero(&serverAdd, sizeof(serverAdd));
    serverAdd.sin_family = AF_INET;
    serverAdd.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAdd.sin_port = htons(SERV_PORT);
    
    socklen_t clientAddrLen;
    
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    int yes = 1;
    setsockopt(listenfd,
               SOL_SOCKET, SO_REUSEADDR,
               (void *)&yes, sizeof(yes));
    
    if (listenfd < 0) {
        printf("创建socket失败\n");
        return -1;
    }
    
    int bindResult = bind(listenfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
    if (bindResult < 0) {
        printf("绑定端口失败\n");
        close(listenfd);
        return -1;
    }
    
    listen(listenfd, 20);
    
    int connfd;
    unsigned char recvMsg[20000];
    unsigned long long totalSize = 0;
    
    clientAddrLen = sizeof(clientAdd);
    connfd = accept(listenfd,(struct sockaddr *)&clientAdd,&clientAddrLen);
    if (connfd < 0) {
        printf("连接失败\n");
        return -1;
    }
    else{
//        这里我们用于测试,只接收一个连接
        close(listenfd);
    }
    
    while (1) {
        sleep(1);
        ssize_t readLen = read(connfd, recvMsg, sizeof(recvMsg));
        
        if (readLen < 0) {
            printf("读取失败 errno = %d\n",errno);
            close(connfd);
            return -1;
        }
        else if (readLen == 0) {
            printf("读取完成 totalSize = %llu\n",totalSize);
            close(connfd);
            return -1;
        }
        else
        {
            totalSize += readLen;
            printf("readLen:%ld\n",readLen);
        }
        
 
    }
    
    return 0;
}

打印信息:
client:

连接成功

发送成功 totalSize = 200000


server:

readLen:20000

... ...

readLen:20000

读取完成 totalSize = 200000


抓包信息:

说明:数据成功发送,且发送正常的FIN分节

参考:

《UNIX Network ProgrammingVolume 1, Third Edition: TheSockets Networking API》
--------------------- 
作者:小T是我 
来源:CSDN 
原文:https://blog.csdn.net/junjun150013652/article/details/37994907 
版权声明:本文为博主原创文章,转载请附上博文链接!

 类似资料: