socket中shutdown和closesocket的区别

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了socket中shutdown和closesocket的区别相关的知识,希望对你有一定的参考价值。

一、从函数调用上来分析(msdn):

一旦完成了套接字的连接,应当将套接字关闭,并且释放其套接字句柄所占用的所有资源。真正释放一个已经打开的套接字句柄的资源直接调用closesocket即可,但要明白closesocket的调用可能会带来负面影响,具体的影响和如何调用有关,最明显的影响是数据丢失,因此一般都要在closesocket之前调用shutdown来关闭套接字。
shutdown:为了保证通信双方都能够收到应用程序发出的所有数据,一个合格的应用程序的做法是通知接受双发都不在发送数据!这就是所谓的“正常关闭”套接字的方法,而这个方法就是由shutdown函数,传递给它的参数有SD_RECEIVE,SD_SEND,SD_BOTH三种,如果是SD_RECEIVE就表示不允许再对此套接字调用接受函数。这对于协议层没有影响,另外对于tcp套接字来说,无论数据是在等候接受还是即将抵达,都要重置连接(注意对于udp协议来说,仍然接受并排列传入的数据,因此udp套接字而言shutdown毫无意义)。如果选择SE_SEND,则表示不允许再调用发送函数。对于tcp套接字来说,这意味着会在所有数据发送出并得到接受端确认后产生一个FIN包。如果指定SD_BOTH,答案不言而喻。
closesocket:对此函数的调用会释放套接字的描述,这个道理众所周知(凡是经常翻阅msdn的程序员),因此,调用此函数后,再是用此套接字就会发生调用失败,通常返回的错误是WSAENOTSOCK。此时与被closesocket的套接字描述符相关联的资源都会被释放,包括丢弃传输队列中的数据!!!!对于当前进程中的线程来讲,所有被关起的操作,或者是被挂起的重叠操作以及与其关联的任何事件,完成例程或完成端口的执行都将调用失败!另外SO_LINGER标志还影响着closesocket的行为,但对于传统的socket程序,这里不加解释
因此可以可以看出shutdown对切断连接有着合理的完整性。 

二、从tcp协议上来分析shutdown和closesocket的行为(behavior):

closesocket或shutdown(使用SD_SEND当作参数时),会向通信对方发出一个fin包,而此时套接字的状态会由ESTABLISHED变成FIN_WAIT_1,然后对方发送一个ACK包作为回应,套接字又变成FIN_WAIT_2,如果对方也关闭了连接则对方会发出FIN,我方会回应一个ACK并将套接字置为TIME_WAIT。因此可以看出closesocket,shutdown所进行的TCP行为是一样的,所不同的是函数部分,shutdown会确保windows建立的数据传输队列中的数据不被丢失,而closesocket会冒然的抛弃所有的数据,因此如果你愿意closesocket完全可以取代shutdown,然而在数据交互十分复杂的网络协议程序中,最好还是shutdown稳妥一些!

参考技术A close是关闭socket,彻底释放socket资源。 shutdown只是关闭掉socket中的某种行为(read/write),并不关闭socket。

socket 编程 : shutdown vs close

shutdown vs close

在linux c++ 网络编程中 socket的关闭有两个常用的函数 close 和 shutdown两个函数。作者今天讨论一下在tcp/ip 协议中这两个函数有什么不同。

功能上

linux有一个特点:file、 socket、 dev 都会通过一个 file description (文件描述符)标识,都抽象成IO操作。 对于close 函数来讲,socket 的 fd 与其他fd 描述符没啥区别。下面给出 close 函数的描述

close() closes a file descriptor, so that it no longer refers to any
file and may be reused.  Any record locks (see fcntl(2)) held on the
file it was associated with, and owned by the process, are removed
(regardless of the file descriptor that was used to obtain the lock).

If fd is the last file descriptor referring to the underlying open
file description (see open(2)), the resources associated with the
open file description are freed; if the file descriptor was the last
reference to a file which has been removed using unlink(2), the file
is deleted.

主要注意的有两点:一、一个进程中调用 close 函数会减少 fd的内核引用计数, 如果是最后一个引用 fd 的进程调用了close, 就会将fd 对应的资源彻底释放; 二、在进程中调用close 后 该fd不可以再使用。

对应于 tcp/ip socket 编程来讲,如果一个 socket 在 n 个进程中使用,只有一个进程 close( socket fd) 是不会触发 tcp/ip 的四次挥手过程。但是 在调用 close函数后, 该socket fd不可以在该进程中被函数调用来与其他进程通信。

tcp/ip 是一个全双工的面向链接的通信协议,一个tcp/ip socket可以同时用于收取和发送信息, 那么就可能存在如下的场景: 进程不再需要读取数据 但仍然需要接受数据 或者 相反的情况。shutdown() 函数就具有这种能力,shutdown()函数描述如下:

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 receptions and transmissions will be disallowed.

在调用函数的时候可以设置关闭的模式:SHUT_RD 关闭读取、 SHUT_WR 关闭写入、 SHUT_RDWR 完全关闭。

实际情况

从函数的介绍上,我们可以很清楚的看出两者的区别,那实际上两者实际上在tcp/ip 协议中会触发怎样操作?? 作者做了一个简单的实验:
通过 tcpdump 抓取 close函数、以及shutdown的三种关闭模式的网络包,分析其在底层网络上的行为。

下面贴出测试代码的主函数(可以忽略代码部分,直接看实际的实验结果):

    
int main(int argc, char const *argv[])
{
    /* code */
    if (argc < 2) {
        printf("must select: 1:server or  2: client.
");
        return -1;
    }

    int type = atoi(argv[1]);
    CTcp* s = new CTcp();
    if(type == 1 ){
        s->registryCallBackMethod((void*)recv_cb_ch, NULL, CBase::READ); 
        s->registryCallBackMethod((void*)close_cb, NULL, CBase::CLOSE);    
   
        s->bindAddress("127.0.0.1", 9906);
        s->startServer();
        std::cin.get();
    }else{
        s->connect("127.0.0.1", 9906, 5);
        s->sendMessage("hello", sizeof("hello"));
        printf("must input the test type:
 1: close 2: shutdown: 
");
        scanf("%d", &type);
        int client = s->socketClient();
        switch(type){
        case 1:{
            close(client); 
            std::cin.get();
        }
        break;
        case 2:{
            printf("please input shutdown type:
 1: read, 2: write, 3: all
");
            scanf("%d", &type);
            if (type == 1){
                shutdown(client, SHUT_RD);
            }else if(type == 2){
                shutdown(client, SHUT_WR);
            }else{
                shutdown(client, SHUT_RDWR);
            }
            
            std::cin.get();
            std::cin.get();

        }
        break;
        default:
            printf("the type is not support %d
", type);
        }
    }

    delete s;
    s = NULL;
    return 0;
}

作者的实验环境是在 centos 系统的云主机中,调用的socket函数为标准库函数,下面贴出实验过程和结果:

setup1. 启动服务端

    $ ./shutdown 1
        the max is 3
        the server time is out

setup2. 启动tcpdump监听

    $sudo tcpdump -i lo -vv 
        tcpdump: listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes

因为服务端绑定的是 127.0.0.1地址,所以在tcpdump 中指定了 lo (本地回环网卡)。

setup3. 启动客户端

    $./shutdown 2
        connect .....
        connect is success!
        must input the test type:
        1: close 2: shutdown: 

客户端在链接后会自动发送一个‘hello‘ 消息给服务端, 此时tcpdump抓取到如下的数据包:

09:17:41.773070 IP (tos 0x0, ttl 64, id 17657, offset 0, flags [DF], proto TCP (6), length 60)
localhost.41894 > localhost.9906: Flags [S], cksum 0xfe30 (incorrect -> 0x3df3), seq 967462950, win 43690, options [mss 65495,sackOK,TS val 2188873883 ecr 0,nop,wscale 7], length 0
09:17:41.773098 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
localhost.9906 > localhost.41894: Flags [S.], cksum 0xfe30 (incorrect -> 0xc0f0), seq 989081322, ack 967462951, win 43690, options [mss 65495,sackOK,TS val 2188873883 ecr 2188873883,nop,wscale 7], length 0
09:17:41.773124 IP (tos 0x0, ttl 64, id 17658, offset 0, flags [DF], proto TCP (6), length 52)
localhost.41894 > localhost.9906: Flags [.], cksum 0xfe28 (incorrect -> 0x9335), seq 1, ack 1, win 342, options [nop,nop,TS val 2188873883 ecr 2188873883], length 0
09:17:41.773168 IP (tos 0x0, ttl 64, id 17659, offset 0, flags [DF], proto TCP (6), length 58)
localhost.41894 > localhost.9906: Flags [P.], cksum 0xfe2e (incorrect -> 0x4f55), seq 1:7, ack 1, win 342, options [nop,nop,TS val 2188873883 ecr 2188873883], length 6
09:17:41.773177 IP (tos 0x0, ttl 64, id 14859, offset 0, flags [DF], proto TCP (6), length 52)
localhost.9906 > localhost.41894: Flags [.], cksum 0xfe28 (incorrect -> 0x932f), seq 1, ack 7, win 342, options [nop,nop,TS val 2188873883 ecr 2188873883], length 0

其中 9906 端口是服务端端口, 41894 端口是客户端端口。前三个报文中双方完成了三次握手,同步了报文首地址偏移量(seq)、窗口大小(win)、报文最大存活时间(mss)等等。4、5报文 完成了‘hello‘ 消息的发送和应答,客户端的报文偏移量 seq = sizeof(‘hello‘) + 1 = 7。

setup4. 开始测试

  • close 测试:

        $ ./shutdown 2
            connect .....
            connect is success!
            must input the test type:
            1: close 2: shutdown: 
            1

    tcpdump 抓取报文显示:

      09:32:59.182336 IP (tos 0x0, ttl 64, id 1672, offset 0, flags [DF], proto TCP (6), length 52)
      localhost.41896 > localhost.9906: Flags [F.], cksum 0xfe28 (incorrect -> 0x5aca), seq 7, ack 1, win 342, options [nop,nop,TS val 2189791308 ecr 2189785847], length 0
      09:32:59.223130 IP (tos 0x0, ttl 64, id 48256, offset 0, flags [DF], proto TCP (6), length 52)
      localhost.9906 > localhost.41896: Flags [.], cksum 0xfe28 (incorrect -> 0x454c), seq 1, ack 8, win 342, options [nop,nop,TS val 2189791349 ecr 2189791308], length 0
      09:33:02.183021 IP (tos 0x0, ttl 64, id 48257, offset 0, flags [DF], proto TCP (6), length 52)
      localhost.9906 > localhost.41896: Flags [F.], cksum 0xfe28 (incorrect -> 0x39bc), seq 1, ack 8, win 342, options [nop,nop,TS val 2189794308 ecr 2189791308], length 0
      09:33:02.183053 IP (tos 0x0, ttl 64, id 24386, offset 0, flags [DF], proto TCP (6), length 52)
      localhost.41896 > localhost.9906: Flags [.], cksum 0x2e03 (correct), seq 8, ack 2, win 342, options [nop,nop,TS val 2189794309 ecr 2189794308], length 0

    可见close触发了tcp/ip的四次挥手, 在双方互相发送FIN 消息并确认后结束了socket链接。

  • shutdown + SHUT_RD 测试:

        ./shutdown 2
            connect .....
            connect is success!
            must input the test type:
            1: close 2: shutdown: 
            2
            please input shutdown type:
            1: read, 2: write, 3: all
            1

    此时查看tcpdump的抓取记录会发现没有任何新增的数据包,这说明在此种情况下客户端并未发送任何报文给服务端。

  • shutdown + SHUT_WR 测试:

        ./shutdown 2
            connect .....
            connect is success!
            must input the test type:
            1: close 2: shutdown: 
            2
            please input shutdown type:
            1: read, 2: write, 3: all
            2

    tcpdump 抓取报文显示:

      localhost.41900 > localhost.9906: Flags [F.], cksum 0xfe28 (incorrect -> 0x173a), seq 7, ack 1, win 342, options [nop,nop,TS val 2190212694 ecr 2190205129], length 0
      09:40:00.602136 IP (tos 0x0, ttl 64, id 5571, offset 0, flags [DF], proto TCP (6), length 52)
      localhost.9906 > localhost.41900: Flags [.], cksum 0xfe28 (incorrect -> 0xf983), seq 1, ack 8, win 342, options [nop,nop,TS val 2190212735 ecr 2190212694], length 0
      09:40:03.561641 IP (tos 0x0, ttl 64, id 5572, offset 0, flags [DF], proto TCP (6), length 52)
      localhost.9906 > localhost.41900: Flags [F.], cksum 0xfe28 (incorrect -> 0xedf3), seq 1, ack 8, win 342, options [nop,nop,TS val 2190215694 ecr 2190212694], length 0
      09:40:03.561661 IP (tos 0x0, ttl 64, id 30067, offset 0, flags [DF], proto TCP (6), length 52)
      localhost.41900 > localhost.9906: Flags [.], cksum 0xfe28 (incorrect -> 0xe23b), seq 8, ack 2, win 342, options [nop,nop,TS val 2190215694 ecr 2190215694], length 0

    可以看出它触发了tcp/ip四次挥手的操作。

  • shutdown + SHUT_RDWR 测试:

    它也会触发四次挥手操作。

总结

简单的总结一下如上的测试:

operator send FIN
close yes
shutdown SHUTRD no
shutdown SHUTWR yes
shutdown SHUTRDWR yes



以上是关于socket中shutdown和closesocket的区别的主要内容,如果未能解决你的问题,请参考以下文章

socket 编程 : shutdown vs close

socket中close()和shutdown()区别

Socket之shutdown()用法

socket.shutdown 与 socket.close

linger

Linux socket shutdown()和close()