0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

基于linux 2.6.24内核版本浅谈socket的close

Linux爱好者 来源:未知 作者:工程师曾玲 2018-08-18 11:22 次阅读

笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情。上篇博客讲了socket的阻塞和非阻塞,这篇就开始谈一谈socket的close(以tcp为例且基于linux-2.6.24内核版本)

TCP关闭状态转移图

众所周知,TCP的close过程是四次挥手,状态机的变迁也逃不出TCP状态转移图,如下图所示:

基于linux 2.6.24内核版本浅谈socket的close

tcp的关闭主要分主动关闭、被动关闭以及同时关闭(特殊情况,不做描述)

主动关闭

close(fd)的过程

C语言为例,在我们关闭socket的时候,会使用close(fd)函数:

intsocket_fd;

socket_fd = socket(AF_INET,SOCK_STREAM,0);

...

// 此处通过文件描述符关闭对应的socket

close(socket_fd)

而close(int fd)又是通过系统调用sys_close来执行的:

asmlinkage longsys_close(unsignedintfd)

{

// 清除(close_on_exec即退出进程时)的位图标记

FD_CLR(fd,fdt->close_on_exec);

// 释放文件描述符

// 将fdt->open_fds即打开的fd位图中对应的位清除

// 再将fd挂入下一个可使用的fd以便复用

__put_unused_fd(files,fd);

// 调用file_pointer的close方法真正清除

retval = filp_close(filp,files);

}

我们看到最终是调用的filp_close方法:

基于linux 2.6.24内核版本浅谈socket的close

紧接着我们进入fput:

基于linux 2.6.24内核版本浅谈socket的close

同一个file(socket)有多个引用的情况很常见,例如下面的例子:

基于linux 2.6.24内核版本浅谈socket的close

所以在多进程的socket服务器编写过程中,父进程也需要close(fd)一次,以免socket无法最终关闭

然后就是_fput函数了:

基于linux 2.6.24内核版本浅谈socket的close

由于我们讨论的是socket的close,所以,我们现在探查下file->f_op->release在socket情况下的实现:

f_op->release的赋值

我们跟踪创建socket的代码,即

基于linux 2.6.24内核版本浅谈socket的close

socket_file_ops的实现为:

staticconststructfile_operations socket_file_ops = {

.owner = THIS_MODULE,

......

// 我们在这里只考虑sock_close

.release = sock_close,

......

};

继续跟踪:

基于linux 2.6.24内核版本浅谈socket的close

在上一篇博客中,我们知道sock->ops为下图所示:

基于linux 2.6.24内核版本浅谈socket的close

即(在这里我们仅考虑tcp,即sk_prot=tcp_prot):

基于linux 2.6.24内核版本浅谈socket的close

关于fd与socket的关系如下图所示:

基于linux 2.6.24内核版本浅谈socket的close

上图中红色线标注的是close(fd)的调用链

tcp_close

基于linux 2.6.24内核版本浅谈socket的close

四次挥手

现在就是我们的四次挥手环节了,其中上半段的两次挥手下图所示:

基于linux 2.6.24内核版本浅谈socket的close

首先,在tcp_close_state(sk)中已经将状态设置为fin_wait1,并调用tcp_send_fin

voidtcp_send_fin(structsock *sk)

{

......

// 这边设置flags为ack和fin

TCP_SKB_CB(skb)->flags = (TCPCB_FLAG_ACK | TCPCB_FLAG_FIN);

......

// 发送fin包,同时关闭nagle

__tcp_push_pending_frames(sk,mss_now,TCP_NAGLE_OFF);

}

如上图Step1所示。 接着,主动关闭的这一端等待对端的ACK,如果ACK回来了,就设置TCP状态为FIN_WAIT2,如上图Step2所示,具体代码如下:

基于linux 2.6.24内核版本浅谈socket的close

值的注意的是,从TCP_FIN_WAIT1变迁到TCP_FIN_WAIT2之后,还调用tcp_time_wait设置一个TCP_FIN_WAIT2定时器,在tmo+(2MSL或者基于RTO计算超时)超时后会直接变迁到closed状态(不过此时已经是inet_timewait_sock了)。这个超时时间可以配置,如果是ipv4的话,则可以按照下列配置:

net.ipv4.tcp_fin_timeout

/sbin/sysctl -wnet.ipv4.tcp_fin_timeout=30

如下图所示:

基于linux 2.6.24内核版本浅谈socket的close

有这样一步的原因是防止对端由于种种原因始终没有发送fin,防止一直处于FIN_WAIT2状态。

接着在FIN_WAIT2状态等待对端的FIN,完成后面两次挥手:

基于linux 2.6.24内核版本浅谈socket的close

由Step1和Step2将状态置为了FIN_WAIT_2,然后接收到对端发送的FIN之后,将会将状态设置为time_wait,如下代码所示:

基于linux 2.6.24内核版本浅谈socket的close

time_wait状态时,原socket会被destroy,然后新创建一个inet_timewait_sock,这样就能及时的将原socket使用的资源回收。而inet_timewait_sock被挂入一个bucket中,由 inet_twdr_twcal_tick定时从bucket中将超过(2MSL或者基于RTO计算的时间)的time_wait的实例删除。 我们来看下tcp_time_wait函数

voidtcp_time_wait(structsock *sk,intstate,inttimeo)

{

// 建立inet_timewait_sock

tw = inet_twsk_alloc(sk,state);

// 放到bucket的具体位置等待定时器删除

inet_twsk_schedule(tw, &tcp_death_row,time,TCP_TIMEWAIT_LEN);

// 设置sk状态为TCP_CLOSE,然后回收sk资源

tcp_done(sk);

}

具体的定时器操作函数为inet_twdr_twcal_tick,这边就不做描述了

被动关闭

close_wait

在tcp的socket时候,如果是established状态,接收到了对端的FIN,则是被动关闭状态,会进入close_wait状态,如下图Step1所示:

基于linux 2.6.24内核版本浅谈socket的close

具体代码如下所示:

基于linux 2.6.24内核版本浅谈socket的close

我们再看下tcp_fin

基于linux 2.6.24内核版本浅谈socket的close

这边有意思的点是,收到对端的fin之后并不会立即发送ack告知对端收到了,而是等有数据携带一块发送,或者等携带重传定时器到期后发送ack。

如果对端关闭了,应用端在read的时候得到的返回值是0,此时就应该手动调用close去关闭连接

if(recv(sockfd,buf,MAXLINE,0) == 0){

close(sockfd)

}

我们看下recv是怎么处理fin包,从而返回0的,上一篇博客可知,recv最后调用tcp_rcvmsg,由于比较复杂,我们分两段来看:

tcp_recvmsg第一段

基于linux 2.6.24内核版本浅谈socket的close

上面代码的处理过程如下图所示:

基于linux 2.6.24内核版本浅谈socket的close

我们看下tcp_recmsg的第二段:

基于linux 2.6.24内核版本浅谈socket的close

由上面代码可知,一旦当前skb读完了而且携带有fin标识,则不管有没有读到用户期望的字节数量都会返回已读到的字节数。下一次再读取的时候则在刚才描述的tcp_rcvmsg上半段直接不读取任何数据再跳转到found_fin_ok并返回0。这样应用就能感知到对端已经关闭了。 如下图所示:

基于linux 2.6.24内核版本浅谈socket的close

last_ack

应用层在发现对端关闭之后已经是close_wait状态,这时候再调用close的话,会将状态改为last_ack状态,并发送本端的fin,如下代码所示:

基于linux 2.6.24内核版本浅谈socket的close

在接收到主动关闭端的last_ack之后,则调用tcp_done(sk)设置sk为tcp_closed状态,并回收sk的资源,如下代码所示:

基于linux 2.6.24内核版本浅谈socket的close

上述代码就是被动关闭端的后两次挥手了,如下图所示:

基于linux 2.6.24内核版本浅谈socket的close

出现大量close_wait的情况

linux中出现大量close_wait的情况一般是应用在检测到对端fin时没有及时close当前连接。有一种可能如下图所示:

基于linux 2.6.24内核版本浅谈socket的close

当出现这种情况,通常是minIdle之类参数的配置不对(如果连接池有定时收缩连接功能的话)。给连接池加上心跳也可以解决这种问题。

如果应用close的时间过晚,对端已经将连接给销毁。则应用发送给fin给对端,对端会由于找不到对应的连接而发送一个RST(Reset)报文。

操作系统何时回收close_wait

如果应用迟迟没有调用close_wait,那么操作系统有没有一个回收机制呢,答案是有的。 tcp本身有一个包活(keep alive)定时器,在(keep alive)定时器超时之后,会强行将此连接关闭。可以设置tcp keep alive的时间

/etc/sysctl.conf

net.ipv4.tcp_keepalive_intvl = 75

net.ipv4.tcp_keepalive_probes = 9

net.ipv4.tcp_keepalive_time = 7200

默认值如上面所示,设置的很大,7200s后超时,如果想快速回收close_wait可以设置小一点。但最终解决方案还是得从应用程序着手。

关于tcp keepalive包活定时器可见笔者另一篇博客:

https://my.oschina.net/alchemystar/blog/833981

进程关闭时清理socket资源

进程在退出时候(无论kill,kill -9 或是正常退出)都会关闭当前进程中所有的fd(文件描述符)

基于linux 2.6.24内核版本浅谈socket的close

这样我们又回到了博客伊始的filp_close函数,对每一个是socket的fd发送send_fin

Java GC时清理socket资源

Java的socket最终关联到AbstractPlainSocketImpl,且其重写了object的finalize方法

基于linux 2.6.24内核版本浅谈socket的close

所以Java会在GC时刻会关闭没有被引用的socket,但是切记不要寄希望于Java的GC,因为GC时刻并不是以未引用的socket数量来判断的,所以有可能泄露了一堆socket,但仍旧没有触发GC。

总结

linux内核源代码博大精深,阅读其代码很费周折。之前读《TCP/IP详解卷二》的时候由于有先辈引导和梳理,所以看书中所使用的BSD源码并不觉得十分费劲。直到现在自己带着问题独立看linux源码的时候,尽管有之前的基础,仍旧被其中的各种细节所迷惑。希望笔者这篇文章能帮助到阅读linux网络协议栈代码的人。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    11233

    浏览量

    208991
  • Socket
    +关注

    关注

    0

    文章

    211

    浏览量

    34639

原文标题:从 Linux 源码看 socket 的 close

文章出处:【微信号:LinuxHub,微信公众号:Linux爱好者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    如何编译Linux内核rpm包

    进入github官网,搜索linux,使用git下载最新版本,或者其它版本内核代码。
    发表于 06-07 16:24 1870次阅读
    如何编译<b class='flag-5'>Linux</b><b class='flag-5'>内核</b>rpm包

    linux socket 问题

    初学linux socket ,想用socket写一个ftp,刚开始就遇到了问题,在windows下架设ftp服务器,并创建sail用户,密码111111,代码如下,已经能连接上ftp,也可以
    发表于 10-07 20:52

    开发板的内核版本和源码的内核版本都是linux3.8,安装模块失败???

    基于源码生成的内核版本(未下载进开发板,开发板本身运行的内核没有动过,我是为了编译驱动,编译了源码生成了内核)如下:开发板运行的linux3
    发表于 09-30 11:47

    WEB CLOSE_WAIT socket不释放如何解决呢

    问题一:RTT版本3.1.5,webnet-v2.0.3,PC浏览器连了装置之后,出现了socketCLOSE_WAIT状态,而且过了很久不释放,后面就再也连不上WEB了,请教一下大家。问题二
    发表于 09-27 10:06

    Linux内核教程

    本章学习目标掌握LINUX内核版本的含义理解并掌握进程的概念掌握管道的概念及实现了解内核的数据结构了解LINUX
    发表于 04-10 16:59 0次下载

    Linux+Socket编程

    本内容详细讲述了Linux+Socket编程技巧指南,适合所有学习编程的广大用户使用
    发表于 06-10 11:19 0次下载
    <b class='flag-5'>Linux+Socket</b>编程

    Linux-socket网络编程

    linux开发编程教程资料——Linux-socket网络编程,感兴趣的小伙伴们可以看一看。
    发表于 08-23 16:23 0次下载

    Linux 0.01版本内核的源码和注释的详细资料免费下载

    对于学习linux内核很有帮助,能学到很多基础性的知识。本文档的主要内容详细介绍的是linux 0.01版本内核的源码和注释的详细资料免费下
    发表于 07-30 08:00 0次下载

    Linux内核与Android的关系

    Android虽然建立在Linux内核之上,但是他对内核进行了一些扩展,增加了一些驱动。比如Binder,loger等等驱动。可以拿Android内核代码和其Baseline
    发表于 09-09 09:10 4577次阅读

    Linux这么多的内核版本你是怎么选的?内核版本使用建议

    Linux Kernel 的稳定分支维护者 Greg Kroah-Hartman 近日在其个人博客上谈及了关于稳定内核版本的选择。Kroah-Hartman 表示经常会有人咨询他们的产品/设备
    的头像 发表于 10-03 12:34 4888次阅读

    socket程序从linux移植到windows上

    文件描述符#define close closesocket// windows上需要额外加载和关闭socket库#define LOAD_WIN_SOCK_LIB \WSAData wsaData
    发表于 04-02 14:41 389次阅读

    谷歌Android设备内核引入主线Linux内核难吗?

    Android是基于Linux内核的操作系统,但是,运行在Android设备上的内核其实与Google选择的LTS版本Linux
    的头像 发表于 11-22 10:41 3031次阅读
    谷歌Android设备<b class='flag-5'>内核</b>引入主线<b class='flag-5'>Linux</b><b class='flag-5'>内核</b>难吗?

    Linux 5.4内核正式版本有哪些新功能

    今天,Linus Torvalds正式签署了Linux 5.4内核的正式版本,带来了大量新功能,强化了安全,更新了硬件驱动,你值得拥有。
    的头像 发表于 11-25 16:31 1w次阅读

    如何查看Linux系统版本信息

    这里所谓的Linux版本信息,包括Linux内核版本信息和Linux系统
    发表于 05-19 09:11 3297次阅读
    如何查看<b class='flag-5'>Linux</b>系统<b class='flag-5'>版本</b>信息

    Linux 6.1发布,微软贡献Linux内核代码

    此外,公告中并没有提及 Linux 6.1 是否是 LTS 版本。按照 Linux 内核维护者 Greg Kroah-Hartman 的说法,Lin
    的头像 发表于 12-14 09:54 1188次阅读