shutdown 与 close 的区别

来源:互联网 发布:历史最贵域名 编辑:程序博客网 时间:2024/05/17 07:45

最近弄一个ssl的客户端工具,客户端每条报文总是以RST结束断开连接,不是想象中的四路断开,百思不得其解,最后添加shutdown得以解决。

转了三篇文章,需要一一看下来,才会对这两个函数有个彻底的认识,特别是第三篇,得拿出源码分析才是王道!当然,结合实际验证也很重要。

转一:http://blog.csdn.net/jnu_simba/article/details/9068059

假设server和client 已经建立了连接,server调用了close, 发送FIN 段给client(其实不一定会发送FIN段,后面再说),此时server不能再通过socket发送和接收数据,此时client调用read,如果接收到FIN 段会返回0,但client此时还是可以write 给server的,write调用只负责把数据交给TCP发送缓冲区就可以成功返回了,所以不会出错,而server收到数据后应答一个RST段,表示服务器已经不能接收数据,连接重置,client收到RST段后无法立刻通知应用层,只把这个状态保存在TCP协议层。如果client再次调用write发数据给server,由于TCP协议层已经处于RST状态了,因此不会将数据发出,而是发一个SIGPIPE信号给应用层,SIGPIPE信号的缺省处理动作是终止程序。

有时候代码中需要连续多次调用write,可能还来不及调用read得知对方已关闭了连接就被SIGPIPE信号终止掉了,这就需要在初始化时调用sigaction处理SIGPIPE信号,对于这个信号的处理我们通常忽略即可,signal(SIGPIPE, SIG_IGN); 如果SIGPIPE信号没有导致进程异常退出(捕捉信号/忽略信号),write返回-1并且errno为EPIPE(Broken pipe)。

 #include <unistd.h>
 int close(int fd);

close 关闭了自身数据传输的两个方向。

 #include <sys/socket.h>
 int shutdown(int sockfd, int how);

shutdown 可以选择关闭某个方向或者同时关闭两个方向,shutdown how = 0 or how = 1 or how = 2 (SHUT_RD or SHUT_WR or SHUT_RDWR),后两者可以保证对等方接收到一个EOF字符(即发送了一个FIN段),而不管其他进程是否已经打开了这个套接字。而close不能保证,只有当某个sockfd的引用计数为0,close 才会发送FIN段,否则只是将引用计数减1而已。也就是说只有当所有进程(可能fork多个子进程都打开了这个套接字)都关闭了这个套接字,close 才会发送FIN 段。

所以说,如果是调用shutdown how = 1 ,则意味着往一个已经发送出FIN的套接字中写是允许的,接收到FIN段仅代表对方不再发送数据,但对方还是可以读取数据的,可以让对方可以继续读取缓冲区剩余的数据。


转载二:http://blog.chinaunix.net/uid-24532607-id-3014406.html

1,只要TCP栈的读缓冲里还有未读取(read)数据,则调用close时会直接向对端发送RST。 

  2,shutdown与socket描述符没有关系,即使调用shutdown(fd, SHUT_RDWR)也不会关闭fd,最终还需close(fd)。 

  3,可以认为shutdown(fd, SHUT_RD)是空操作,因为shutdown后还可以继续从该socket读取数据,这点也许还需要进一步证实。 

  4,在已发送FIN包后write该socket描述符会引发EPIPE/SIGPIPE。 

  5,当有多个socket描述符指向同一socket对象时,调用close时首先会递减该对象的引用计数,计数为0时才会发送FIN包结束TCP连接。shutdown不同,只要以SHUT_WR/SHUT_RDWR方式调用即发送FIN包。 

  6,SO_LINGER与close,当SO_LINGER选项开启但超时值为0时,调用close直接发送RST(这样可以避免进入TIME_WAIT状态,但破坏了TCP协议的正常工作方式),SO_LINGER对shutdown无影响。 

  7,TCP连接上出现RST与随后可能的TIME_WAIT状态没有直接关系,主动发FIN包方必然会进入TIME_WAIT状态,除非不发送FIN而直接以发送RST结束连接。

转载三:http://dev.riaos.com/?p=9006329

浅谈shutdown()和close()的区别

 

  shutdown()函数可以选择关闭全双工连接的读通道或者写通道,如果两个通道同时关闭,则这个连接不能再继续通信。close()函数会同时关闭全双工连接的读写通道,除了关闭连接外,还会释放套接字占用的文件描述符。而shutdown()只会关闭连接,但是不会释放占用的文件描述符。所以即使使用了SHUT_RDWR类型调用shutdown()关闭连接,也仍然要调用close()来释放连接占用的文件描述符。
1. close()
    close()函数对应的系统调用是sys_close(),在fs/open.c中定义。在sys_close()中,会首先根据文件描述符在进程的打开文件表中查找对应的file结构实例,然后调用filp_close()来关闭文件。关闭操作是在fput()(由filp_close()调用)中进行的,引用数减1后为零,才会调用__fput()来释放文件占用的内存。对套接字来说,__fput()中我们主要关心以下代码:

void __fput(struct file
*file)
{
    ……
    if (file->f_op
&& file->f_op->release)
        file->f_op->release(inode, file);
    …..
    dput(dentry);
    ……
}
  file->f_op指向的是文件操作实例,套接字的文件操作由socket_file_ops提供。socket_file_ops属于socket层,socket层是vfs和底层协议栈连接的桥梁,真正的操作还是由协议栈来提供。在这里,file->f_op->release指向sock_close()函数。在socket层下面,接着是协议族,在这个层,不同的传输层协议都会提供自己的操作接口。在协议族层,TCP和UDP协议提供的接口都是inet_release(),这个函数最终会调用到不同的传输层协议提供的close接口。TCP协议提供的是tcp_close()函数,UDP协议提供的是udp_lib_close()。
  tcp_close()中会首先将套接字的sk_shutdown标志设置为SHUTDOWN_MASK,表示双向关闭。然后检查接收缓冲区是否有数据未读(不包括FIN包),如果有数据未读,协议栈会发送RST包,而不是FIN包。如果套接字设置了SO_LINGER选项,并且lingertime设置为0,这种情况下也会发送RST包来终止连接。其他情况下,会检查套接字的状态,只有在套接字的状态是TCP_ESTABLISHED、TCP_SYN_RECV和TCP_CLOSE_WAIT的状态下,才会发送FIN包。在决定了是否发包以及发送什么类型的包之后,协议栈会进行套接字占用的资源的清理,包括sock结构、缓冲区和错误队列占用的内存等,并进行状态的变更。如果是发送FIN包进行正常关闭,后续会进行四次关闭操作,这个过程是在协议栈中完成的,和用户进程没有关系,用户进程也不能再操作这个套接字。
  udp_lib_close()中只是简单地调用了sk_common_release()函数,sk_common_release()中会调用udp_destroy_sock()来释放发送队列中占用的内存。如果UDP套接字已绑定本地端口,会添加到udp_table哈希表中,所以套接字如果已经被添加到哈希表中,udp_lib_unhash()中会将套接字从哈希表中移除。接下来会调用sock_orphan()解除进程和套接字的关系,然后释放sock结构占用的资源。
  socket结构实例占用的内存,是在dput()调用到的sock_destroy_inode()函数来释放的,sock_destroy_inode()中只是简单地调用kmem_cache_free()释放占用的内存。
2. shutdown()
  shutdown()函数对应的系统调用是sys_shutdown(),在net/socket.c中定义。由于close()不仅可以用于关闭套接字,也可以关闭普通文件、字符设备文件等类型,为了处理不同类型文件的关闭,操作比较复杂。而shutdown()只能用于套接字类型的文件,处理也比较简单。
  sys_shutdown()中首先调用sockfd_lookup_light()来查找描述符对应的socket结构,然后调用套接字对应的协议族层中提供的shutdown接口。UDP和TCP协议提供的接口都是inet_shutdown()函数,主要处理如下所示:

int inet_shutdown(struct socket
*sock, int how)
{
    ……
    switch (sk->sk_state) {
    case TCP_CLOSE:
        err = -ENOTCONN;
        /* Hack to wake up other listeners, who can poll for
           POLLHUP, even on eg. unconnected UDP sockets — RR */

    default:
        sk->sk_shutdown
|= how;
        if (sk->sk_prot->shutdown)
            sk->sk_prot->shutdown(sk, how);
        break;

    /* Remaining two branches are temporary solution for missing
     * close() in multithreaded environment. It is _not_ a good idea,
     * but we have no choice until close() is repaired at VFS level.
     */

    case TCP_LISTEN:
        if (!(how
& RCV_SHUTDOWN))
            break;
        /* Fall through */
    case TCP_SYN_SENT:
        err = sk->sk_prot->disconnect(sk, O_NONBLOCK);
        sock->state
= err ? SS_DISCONNECTING
: SS_UNCONNECTED;
        break;
    }

    /* Wake up anyone sleeping in poll. */
    sk->sk_state_change(sk);
    ……
}

  在说明代码的处理之前,先来了解一下UDP套接字的状态。UDP的传输是没有状态的,内核中在描述UDP套接字的状态时,借用了TCP的状态。UDP套接字只有两种状态,TCP_CLOSE和TCP_ESTABLISHED。在套接字刚创建时,不管是UDP还是TCP,状态都是TCP_CLOSE。UDP在调用connect()后,状态改变为TCP_ESTABLISHED。
  如果套接字的状态TCP_CLOSE,套接字要么是刚创建的,要么连接已经关闭,所以调用shutdown()是不合适的,此时要返回ENOTCONN错误。

  接下来的代码会处理TCP_LISTEN和TCP_SYN_SENT状态以外的情况。将用户设置的关闭选项设置到套接字的sk_shutdown标志,然后调用传输层协议提供的shutdown接口。TCP协议提供的是tcp_shutdown()函数,而UDP并没有提供任何函数。
  在tcp_shutdown()中,首先检查是否是否关闭了写通道,如果不是,则直接返回。如果关闭了写通道,并且状态是TCP_ESTABLISHED、TCP_SYN_SENT、TCP_SYN_RECV或TCP_CLOSE_WAIT,会调用tcp_close_state()来进行状态的变更。如果变更状态后需要发送FIN包,则调用tcp_send_fin()来发送。
  由于UDP没有TCP_LISTEN和TCP_SYN_SENT状态,所以sk->sk_prot->disconnect只会调用调用tcp_disconnect()函数。如果是套接字状态是TCP_LISTEN状态,并且是关闭读通道,内核会停止套接字的监听状态,释放sock结构占用的资源。如果是TCP_SYN_SENT状态,会发送RST包来终止连接的创建过程,释放sock结构占用的资源。
  最后会调用套接字的sk_state_change接口(通常是sock_def_wakeup()),通知用户进程状态已经发生改变。
3. 总结
  现在总结一下shutdown()和close()的主要区别:
    1)对应的系统调用不同
    2)shutdown()只能用于套接字文件,close()可以用于所有文件类型
    3)shutdown()只是关闭连接,并没有释放文件描述符,close()可以
    4)shutdown()不能用于TCP_CLOSE状态的套接字,否则会返回ENOTCONN错误
    5)shutdown()可以选择关闭读通道或写通道,close()不能。

 

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 婴儿连体衣长了怎么办 冰丝面料变长了怎么办 t恤袖口大了怎么办 长袖t恤袖子长了怎么办 机打的扣子掉了怎么办 四个月宝宝头扁怎么办 鞋子前面穿翘了怎么办 休完产假没人带怎么办 休完产假孩子吃奶怎么办 巴布豆童鞋里面臭了到底怎么办 连体裤有点卡档怎么办 宝宝连体衣扣子掉了怎么办 买衣服被骂了怎么办? 把人车刮了跑了怎么办 打工打不下去了怎么办 白色衣服变粉了怎么办 一量血压就紧张怎么办 一紧张就血压高怎么办 不在上班时间在单位受伤怎么办 生活过得太压抑怎么办 高低床踏板断了怎么办 高低床 孩子摔下来怎么办 小孩子太小高低床爬梯怎么办 辞职后单位不发工资怎么办 买了个上下床搬家怎么办 爸妈不让学日语怎么办 把共享单车弄坏了怎么办 裙子沾到单车油怎么办? 外穿的短裤卷边怎么办 蹬完单车膝盖疼怎么办 夏天骑摩托太热怎么办 半框眼镜线断了怎么办 镜片从镜框脱了怎么办 干活累的肩膀痛怎么办 干活累的肩膀疼怎么办 骑山地车手腕和脖子疼怎么办? 布艺沙发坐软了怎么办 篮球护臂太大了怎么办 豪爵摩托车没电怎么办 西装裤裤脚太宽怎么办 全棉衣服有异味怎么办