【整理】close 和 shutdown 的原理

http://stackoverflow.com/questions/14740852/linux-socket-close-vs-shutdown

  1. shutdown(sd, SHUT_WR)  发送FIN给对端,对端会响应一个ACK(协议层-内核所做). 未来任何尝试写这个socket会发生错误。但是对端仍然可以可以读,因为本端关闭写时,可能由于协议层的策略(滑动窗口、拥塞窗口、angle算法等)导致发送延迟,如果有待发送的消息,那么要尽力保证这些消息都发出去的。所以,会在最后一个报文中加入FIN标志,同时,关闭用于减少网络中小报文的angle算法,向连接对端发送消息。如果没有待发送的消息,则构造一个报文,仅含有FIN标志位,发送出去关闭连接;对端可以继续写数据给本端(主动端),因为对端收到FIN(收到FIN之前其实还有主动端缓冲区待发送消息)并响应ACK了(在响应ACK之前可能还有数据发送给本端),响应ACK后就进入CLOSE_WAIT状态,这个状态一般比较短暂,如果存在大量的CLOSE_WAIT,说明程序未关闭socket或者并发量太大来不及,或者你用的网络库有bug
  2. shutdown(sd, SHUT_RD) 不发送任何网络消息:他只是限制本地API对于随后的socket的读取直接返回EOS. 在已经在读操作被shutdown的socket上接收数据的行为(内核的行为, 表明对端要发送数据过来的行为)是依赖于系统的: Unix会确认它并且丢弃它; Linux会确认它并且缓冲它,最终会使得sender停止运行(发送rst给sender); Windows会发出一个RST, 此时sender继续写会报错“Connection reset by peer”
  3. 注:本端(主动端)发出fin进入FIN_WAIT1,收到对端ACK(对端发送ACK之前可能还有数据要发送给本端)进入FIN_WAIT2状态,收到对端FIN,发送ACK则进入TIME_WAIT,在2MSL内可以重发最后的ACK防止最后的ACK丢包;对端收到主动端的fin,如果有数据要发送给主动端,就发送数据,然后发送ACK,如果没有就直接发送ACK给主动端,然后对端进入CLOSE_WAIT状态,此时程序要保证及时关闭socket,不然会close_wait泄漏造成服务挂掉,当关闭socket时发送FIN给主动端,对端进入LASK_ACK状态,主动端收到FIN并ACK给对端,主动端进入TIME_WAIT,对端关闭(TIMEWAIT存在的原因就是防止最后一个ack丢包,在2MSL内可重传ACK)。

close函数:

  参数sockfd为套接字描述符,成功返回0,失败返回-1;该函数的功能是关闭套接字描述符引用计数,当计数大于0时什么都不干,当计数等于0时触发TCP/IP的四次挥手,即主动关闭方发送FIN。

  如果在调用close以前,TCP协议栈的发送队列中有已排队等候发送的数据,则协议栈尝试将数据发送出去,发送完毕后根据套接字描述引用计数来决定是否关闭连接。
      如果在调用close以后,且套接字描述符引用计数为0,则在其上调用write或者read函数则会产生错误码为9即EBADF(bad file descriptor 和Broken Pipe不一样,Broken Pipe表示文件描述符还合法,但是连接状态非法)的错误。

  调用close函数时,TCP协议栈对发送队列中已排队等候发送数据的处理流程受SO_LINGER选项的影响,该选项关联的数据结构如下:
          struct linger{
              int l_onoff;    //0=off,nonzero=on
              int l_linger;    //linger time
          };
      l_onoff是选项开关,如果是0则关闭选项并且忽略参数l_linger,如果是非零则打开选项,默认是0;l_linger是延迟关闭连接时间,只有l_onoff打开时即为非零时,该参数的值才有效。这几个参数与close时TCP协议栈对待发数据的处理流程如下表:

  表1 待发数据处理流程


l_onoff


l_linger


close行为


发送队列


TCP协议栈



忽略


立即返回


保持直至发送完成


接管套接字并保证将数据发送至对端


非零



立即返回


立即放弃


直接发送RST,自身立即复位,不用经过2MSL状态,对端收到复位错误码


非零


非零


阻塞直到l_linger时间超时或数据发送完成(套接字必须设置为阻塞)


在超时时间段内保持尝试发送,若超时则立即放弃


超时则同第二种情况,若发送完成则皆大欢喜

  当应用程序在调用close()函数关闭TCP连接时,Linux内核的默认行为是将套接口发送队列里的原有数据(比如之前残留的数据)以及新加入 的数据(比如函数close()产生的FIN标记,如果发送队列没有残留之前的数据,那么这个FIN标记将单独产生一个新数据包)发送出去并且销毁套接口 (并非把相关资源全部释放,比如只是把内核对象sock标记为dead状态等,这样当函数close()返回后,TCP发送队列的数据包仍然可以继续由内 核协议栈发送,但是一些相关操作就会受到影响和限制,比如对数据包发送失败后的重传次数)后立即返回。这需要知道两点:

  第一,当应用程序获得 close()函数的返回值时,待发送的数据可能还处在Linux内核的TCP发送队列里,因为当我们调用write()函数成功写出数据时,仅表示这些 数据被Linux内核接收放入到发送队列,如果此时立即调用close()函数返回后,那么刚才write()的数据限于TCP本身的拥塞控制机制(比如 发送窗口、接收窗口等等),完全有可能还呆在TCP发送队列里而未被发送出去;当然也有可能发送出去一些,毕竟在调用函数close()时,进入到 Linux内核后有一次数据包的主动发送机会,即:
tcp_close() -> tcp_send_fin() -> __tcp_push_pending_frames() -> tcp_write_xmit()

  第二,所有这些数据的发送,最终却并不一定能全部被对端确认(即数据包到了对端TCP协议栈的接收队列),只能做到TCP协议本身提供的一定程度的 保证,比如TCP协议的重传机制(并且受close()函数影响,重传机制弱化,也就是如果出现类似系统资源不足这样的问题,调用过close()函数进 行关闭的套接口所对应的这些数据会优先丢弃)等,因为如果网络不好可能导致TCP协议放弃继续重传或在意外收到对端发送过来的数据时连接被重置导致未成功 发送的数据全部丢失(后面会看到这种情况)。

时间: 2024-08-07 01:16:58

【整理】close 和 shutdown 的原理的相关文章

框架音频整理513

  框架音频整理: 1 .Strtus工作原理: (1)Strtus本身是一个mvc框架,对底层的servlet进行了封装.Struts的前端是一个核心控制器.叫做StrutsPrepareAndExecuteFilter, (2)这个核心控制器StrutsPrepareAndExecuteFilter是配置在web.xml文件中的,配置的所有请求都会通过web容器进入到strtus框架, (3)从前端发来的请求request,request进来之后会调用ActionProxy(控制器的代理类)

磁盘阵列 RAID 技术原理详解

RAID一页通整理所有RAID技术.原理并配合相应RAID图解,给所有存储新人提供一个迅速学习.理解RAID技术的网上资源库,本文将持续更新,欢迎大家补充及投稿.中国存储网一如既往为广大存储界朋友提供免费.精品资料. 1.什么是Raid;RAID(Redundant Array of Inexpensive Disks)称为廉价磁盘冗余阵列.RAID 的基本原理是把多个便宜的小磁盘组合到一起,成为一个磁盘组,使性能达到或超过一个容量巨大.价格昂贵的磁盘.目前 RAID技术大致分为两种:基于硬件的

大三开学第一天--编译原理和人工智能的初步入门

开学的第一天,学的都是入门,所以知识比较少,但是我还是会整理出来.因为是开学第一天,事情比较忙,知识没有得到很好的消化和了解,所以今天只作初步了解,过几天会找时间重新整理,具体化. 编译原理第一课: 编译器:编译器是一种语言处理器,可以将“一种语言(通常为高级语言)”翻译为“另一种语言(通常为低级语言)”.通俗的讲,就是我们目前所使用的高级语言,如C++,java,都是易于人们理解和编写的.但是对于机器来说,只能看懂机器语言(即汇编语言,属于低级语言).所以,人们用高级语言写出来的代码,首先要经

第18本:《整理的艺术》

第18本:<整理的艺术> 书的作者小山龙介整理出来了90条整理术,根据个人的习惯和流程要有选择性的借鉴,我从豆瓣上抄来了所有标题,但里面的内容并没有照抄原书的内容,是我自己的一些理解和想法.读书是为了行动,这种书的好处就是看到比较好的方法马上可以实践,所以整理出一些[Action]行动放入omniFocus中管理起来,慢慢实践. 第一章 资料整理术 整理术01 打印的纸质资料要毫不留情地废弃 如果有电子版的,就大胆地废弃纸质资料,有意识地克服想保留下来的习惯. 这里有几个例外:带章和签名的合同

git 和 github 的原理和一个常用的工作流程

先学习和整理git 和 github 的原理,再在下一篇介绍git 和github 的使用 首先可以结合 gitbeijing 的内容来学习github的原理和使用 作者:戴嘉华 转载请注明出处,保留原文链接和作者信息:http://segmentfault.com/a/1190000002413519 前言 (本文假设各位已经对基本git 的基本概念.操作有一定的了解俄,入伍相关git的知识,可以参考Pro Git这本书进行相关的学习和练习 很多项目开发都会采用git 这一优秀的分布式管理工具

Java(1)-知识点(面试题)整理

基本概念 操作系统中 heap 和 stack 的区别 什么是基于注解的切面实现 什么是 对象/关系 映射集成模块 什么是 Java 的反射机制 什么是 ACID BS与CS的联系与区别 Cookie 和 Session的区别 fail-fast 与 fail-safe 机制有什么区别 get 和 post请求的区别 Interface 与 abstract 类的区别 IOC的优点是什么 IO 和 NIO的区别,NIO优点 Java 8 / Java 7 为我们提供了什么新功能 什么是竞态条件?

java后端研发经典面试题总结

垃圾回收算法 1.标记-清除算法 标记-清除算法是最基本的算法,和他的名字一样,分为两个步骤,一个步骤是标记需要回收的对象.在标记完成后统一回收被标记的对象.这个算法两个问题.一个是效率问题,标记和清除的效率不高.第二个问题是标记-清除之后会有大量不连续的碎片空间,如果我们需要更大的连续内存就必须GC. 2.复制算法 复制算法,不同于标记-清除,复制算法大多数用于新生代,它需要大小相等的两块内存,每次只使用一块内存,当GC的时候会把这块内存存活的对象复制到另外一块内存上面,解决了时间效率和空间碎

mysql注入篇

博客这个东西真的很考验耐心,每写一篇笔记,都是在艰难的决定中施行的,毕竟谁都有懒惰的一面,就像这个,mysql注入篇,拖拖拖一直拖到现在才开始总结,因为这个实在是太多太杂了,细细的总结一篇太烧脑. 由于我没有找见php的实战本地源码,所以只好用一些漏洞平台的源码来演示了,演示不了的,只能列代码,没有实操图.毕竟找不见源码,,没法... 首先我们都知道mysql数据库和Access数据库的不同,不同在mysql是分多个数据库名的. 就像像我上图贴的这个格式一样,原谅我是在是没有本地源码,连数据库的

java多线程返回处理结果,并终止所有线程

一.概述 同时并发的按照不同的方式处理数据,需要对处理后的结果在处理或用作响应第三方请求. 这时候,有两种常见的需求. 第一种,只要有一个处理有结果,就立刻结束其他还在运行中的处理方式 第二种,等待所有处理有结果后再处理 二.处理方案 1.线程处理返回结果 一般开发中,使用多线程,最常见的就是:1.实现Runnable接口:2.继承Thread类. 但是run方法是没有返回结果,很难满足我们的需求.这时,常用的办法就是实现Callable接口 Callable接口提供了一个call方法入口,我们