原文地址:https://www.cnblogs.com/xiaol…
前言
整顿了对于 TCP 三次握手和四次挥手的面试题型,跟大家一起探讨探讨。
TCP 根本意识
TCP 连贯建设
TCP 连贯断开
Socket 编程
PS:本次文章不波及 TCP 流量管制、拥塞管制、可靠性传输等方面常识,这些留在下篇哈!
注释
01 TCP 根本意识
瞧瞧 TCP 头格局
咱们先来看看 TCP 头的格局,标注色彩的示意与本文关联比拟大的字段,其余字段不做具体论述。
TCP 头格局 TCP 头格局
序列号:在建设连贯时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。
确认应答号:指下一次「冀望」收到的数据的序列号,发送端收到这个确认应答当前能够认为在这个序号以前的数据都曾经被失常接管。用来解决不丢包的问题。
管制位:
ACK:该位为 1 时,「确认应答」的字段变为无效,TCP 规定除了最后建设连贯时的 SYN 包之外该位必须设置为 1。
RST:该位为 1 时,示意 TCP 连贯中出现异常必须强制断开连接。
SYN:该位为 1 时,示意心愿建设连贯,并在其「序列号」的字段进行序列号初始值的设定。
FIN:该位为 1 时,示意今后不会再有数据发送,心愿断开连接。当通信完结心愿断开连接时,通信单方的主机之间就能够相互交换 FIN 位为 1 的 TCP 段。
为什么须要 TCP 协定?TCP 工作在哪一层?
IP 层是「不牢靠」的,它不保障网络包的交付、不保障网络包的按序交付、也不保障网络包中的数据的完整性。
OSI 参考模型与 TCP/IP 的关系 OSI 参考模型与 TCP/IP 的关系
如果须要保障网络数据包的可靠性,那么就须要由下层(传输层)的 TCP 协定来负责。
因为 TCP 是一个工作在传输层的牢靠数据传输的服务,它能确保接收端接管的网络包是无损坏、无距离、非冗余和按序的。
什么是 TCP?
TCP 是面向连贯的、牢靠的、基于字节流的传输层通信协议。
面向连贯:肯定是「一对一」能力连贯,不能像 UDP 协定能够一个主机同时向多个主机发送音讯,也就是一对多是无奈做到的;
牢靠的:无论的网络链路中呈现了怎么的链路变动,TCP 都能够保障一个报文肯定可能达到接收端;
字节流:音讯是「没有边界」的,所以无论咱们音讯有多大都能够进行传输。并且音讯是「有序的」,当「前一个」音讯没有收到的时候,即便它先收到了前面的字节,那么也不能扔给应用层去解决,同时对「反复」的报文会主动抛弃。
什么是 TCP 连贯?
咱们来看看 RFC 793 是如何定义「连贯」的:
Connections: The reliability and flow control mechanisms described above require that TCPs initialize and maintain certain status information for each data stream. The combination of this information, including sockets, sequence numbers, and window sizes, is called a connection.
简略来说就是,用于保障可靠性和流量管制保护的某些状态信息,这些信息的组合,包含 Socket、序列号和窗口大小称为连贯。
所以咱们能够晓得,建设一个 TCP 连贯是须要客户端与服务器端达成上述三个信息的共识。
Socket:由 IP 地址和端口号组成
序列号:用来解决乱序问题等
窗口大小:用来做流量管制
如何惟一确定一个 TCP 连贯呢?
TCP 四元组能够惟一的确定一个连贯,四元组包含如下:
源地址
源端口
目标地址
目标端口
TCP 四元组 TCP 四元组
源地址和目标地址的字段(32 位)是在 IP 头部中,作用是通过 IP 协定发送报文给对方主机。
源端口和目标端口的字段(16 位)是在 TCP 头部中,作用是通知 TCP 协定应该把报文发给哪个过程。
有一个 IP 的服务器监听了一个端口,它的 TCP 的最大连接数是多少?
服务器通常固定在某个本地端口上监听,期待客户端的连贯申请。
因而,客户端 IP 和 端口是可变的,其理论值计算公式如下:
对 IPv4,客户端的 IP 数最多为 2 的 32 次方,客户端的端口数最多为 2 的 16 次方,也就是服务端单机最大 TCP 连接数,约为 2 的 48 次方。
当然,服务端最大并发 TCP 连接数远不能达到实践下限。
首先次要是文件描述符限度,Socket 都是文件,所以首先要通过 ulimit 配置文件描述符的数目;
另一个是内存限度,每个 TCP 连贯都要占用肯定内存,操作系统的内存是无限的。
UDP 和 TCP 有什么区别呢?别离的利用场景是?
UDP 不提供简单的管制机制,利用 IP 提供面向「无连贯」的通信服务。
UDP 协定真的十分简,头部只有 8 个字节(64 位),UDP 的头部格局如下:
UDP 头部格局 UDP 头部格局
指标和源端口:次要是通知 UDP 协定应该把报文发给哪个过程。
包长度:该字段保留了 UDP 首部的长度跟数据的长度之和。
校验和:校验和是为了提供牢靠的 UDP 首部和数据而设计。
TCP 和 UDP 区别:
- 连贯
TCP 是面向连贯的传输层协定,传输数据前先要建设连贯。
UDP 是不须要连贯,即刻传输数据。
- 服务对象
TCP 是一对一的两点服务,即一条连贯只有两个端点。
UDP 反对一对一、一对多、多对多的交互通信
- 可靠性
TCP 是牢靠交付数据的,数据能够无差错、不失落、不反复、按需达到。
UDP 是尽最大致力交付,不保障牢靠交付数据。
- 拥塞管制、流量管制
TCP 有拥塞管制和流量管制机制,保障数据传输的安全性。
UDP 则没有,即便网络十分拥挤了,也不会影响 UDP 的发送速率。
- 首部开销
TCP 首部长度较长,会有肯定的开销,首部在没有应用「选项」字段时是 20 个字节,如果应用了「选项」字段则会变长的。
UDP 首部只有 8 个字节,并且是固定不变的,开销较小。
- 传输方式
TCP 是流式传输,没有边界,但保障程序和牢靠。
UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序。
- 分片不同
TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,指标主机收到后,也同样在传输层组装 TCP 数据包,如果中途失落了一个分片,只须要传输失落的这个分片。
UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,指标主机收到后,在 IP 层组装完数据,接着再传给传输层,然而如果中途丢了一个分片,则就须要重传所有的数据包,这样传输效率十分差,所以通常 UDP 的报文应该小于 MTU。
TCP 和 UDP 利用场景:
因为 TCP 是面向连贯,能保证数据的可靠性交付,因而常常用于:
FTP 文件传输
HTTP / HTTPS
因为 UDP 面向无连贯,它能够随时发送数据,再加上 UDP 自身的解决既简略又高效,因而常常用于:
包总量较少的通信,如 DNS、SNMP 等
视频、音频等多媒体通信
播送通信
为什么 UDP 头部没有「首部长度」字段,而 TCP 头部有「首部长度」字段呢?
起因是 TCP 有可变长的「选项」字段,而 UDP 头部长度则是不会变动的,无需多一个字段去记录 UDP 的首部长度。
为什么 UDP 头部有「包长度」字段,而 TCP 头部则没有「包长度」字段呢?
先说说 TCP 是如何计算负载数据长度:
其中 IP 总长度 和 IP 首部长度,在 IP 首部格局是已知的。TCP 首部长度,则是在 TCP 首部格局已知的,所以就能够求得 TCP 数据的长度。
大家这时就奇怪了问:“UDP 也是基于 IP 层的呀,那 UDP 的数据长度也能够通过这个公式计算呀?为何还要有「包长度」呢?”
这么一问,的确感觉 UDP「包长度」是冗余的。
因为为了网络设备硬件设计和解决不便,首部长度须要是 4 字节的整数倍。
如果去掉 UDP「包长度」字段,那 UDP 首部长度就不是 4 字节的整数倍了,所以小林感觉这可能是为了补全 UDP 首部长度是 4 字节的整数倍,才补充了「包长度」字段。
02 TCP 连贯建设
TCP 三次握手过程和状态变迁
TCP 是面向连贯的协定,所以应用 TCP 前必须先建设连贯,而建设连贯是通过三次握手来进行的。
TCP 三次握手 TCP 三次握手
一开始,客户端和服务端都处于 CLOSED 状态。先是服务端被动监听某个端口,处于 LISTEN 状态
第一个报文—— SYN 报文第一个报文—— SYN 报文
客户端会随机初始化序号(client_isn),将此序号置于 TCP 首部的「序号」字段中,同时把 SYN 标记地位为 1,示意 SYN 报文。接着把第一个 SYN 报文发送给服务端,示意向服务端发动连贯,该报文不蕴含应用层数据,之后客户端处于 SYN-SENT 状态。
第二个报文 —— SYN + ACK 报文第二个报文 —— SYN + ACK 报文
服务端收到客户端的 SYN 报文后,首先服务端也随机初始化本人的序号(server_isn),将此序号填入 TCP 首部的「序号」字段中,其次把 TCP 首部的「确认应答号」字段填入 client_isn + 1, 接着把 SYN 和 ACK 标记地位为 1。最初把该报文发给客户端,该报文也不蕴含应用层数据,之后服务端处于 SYN-RCVD 状态。
第三个报文 —— ACK 报文第三个报文 —— ACK 报文
客户端收到服务端报文后,还要向服务端回应最初一个应答报文,首先该应答报文 TCP 首部 ACK 标记地位为 1,其次「确认应答号」字段填入 server_isn + 1,最初把报文发送给服务端,这次报文能够携带客户到服务器的数据,之后客户端处于 ESTABLISHED 状态。
服务器收到客户端的应答报文后,也进入 ESTABLISHED 状态。
从下面的过程能够发现第三次握手是能够携带数据的,前两次握手是不能够携带数据的,这也是面试常问的题。
一旦实现三次握手,单方都处于 ESTABLISHED 状态,此时连贯就已建设实现,客户端和服务端就能够互相发送数据了。
如何在 Linux 零碎中查看 TCP 状态?
TCP 的连贯状态查看,在 Linux 能够通过 netstat -napt 命令查看。
TCP 连贯状态查看 TCP 连贯状态查看
为什么是三次握手?不是两次、四次?
置信大家比拟常答复的是:“因为三次握手能力保障单方具备接管和发送的能力。”
这答复是没问题,但这答复是全面的,并没有说出次要的起因。
在后面咱们晓得了什么是 TCP 连贯:
用于保障可靠性和流量管制保护的某些状态信息,这些信息的组合,包含 Socket、序列号和窗口大小称为连贯。
所以,重要的是为什么三次握手才能够初始化 Socket、序列号和窗口大小并建设 TCP 连贯。
接下来以三个方面剖析三次握手的起因:
三次握手才能够阻止反复历史连贯的初始化(次要起因)
三次握手才能够同步单方的初始序列号
三次握手才能够防止资源节约
起因一:防止历史连贯
咱们来看看 RFC 793 指出的 TCP 连贯应用三次握手的首要起因:
The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.
简略来说,三次握手的首要起因是为了避免旧的反复连贯初始化造成凌乱。
网络环境是盘根错节的,往往并不是如咱们冀望的一样,先发送的数据包,就先达到指标主机,反而它很骚,可能会因为网络拥挤等乌七八糟的起因,会使得旧的数据包,先达到指标主机,那么这种状况下 TCP 三次握手是如何防止的呢?
三次握手防止历史连贯三次握手防止历史连贯
客户端间断发送屡次 SYN 建设连贯的报文,在网络拥挤状况下:
一个「旧 SYN 报文」比「最新的 SYN」报文早达到了服务端;
那么此时服务端就会回一个 SYN + ACK 报文给客户端;
客户端收到后能够依据本身的上下文,判断这是一个历史连贯(序列号过期或超时),那么客户端就会发送 RST 报文给服务端,示意停止这一次连贯。
如果是两次握手连贯,就不能判断以后连贯是否是历史连贯,三次握手则能够在客户端(发送方)筹备发送第三次报文时,客户端因有足够的上下文来判断以后连贯是否是历史连贯:
如果是历史连贯(序列号过期或超时),则第三次握手发送的报文是 RST 报文,以此停止历史连贯;
如果不是历史连贯,则第三次发送的报文是 ACK 报文,通信单方就会胜利建设连贯;
所以,TCP 应用三次握手建设连贯的最次要起因是避免历史连贯初始化了连贯。
起因二:同步单方初始序列号
TCP 协定的通信单方,都必须保护一个「序列号」,序列号是牢靠传输的一个关键因素,它的作用:
接管方能够去除反复的数据;
接管方能够依据数据包的序列号按序接管;
能够标识发送进来的数据包中,哪些是曾经被对方收到的;
可见,序列号在 TCP 连贯中占据着十分重要的作用,所以当客户端发送携带「初始序列号」的 SYN 报文的时候,须要服务端回一个 ACK 应答报文,示意客户端的 SYN 报文已被服务端胜利接管,那当服务端发送「初始序列号」给客户端的时候,仍然也要失去客户端的应答回应,这样一来一回,能力确保单方的初始序列号能被牢靠的同步。
四次握手与三次握手四次握手与三次握手
四次握手其实也可能牢靠的同步单方的初始化序号,但因为第二步和第三步能够优化成一步,所以就成了「三次握手」。
而两次握手只保障了一方的初始序列号能被对方胜利接管,没方法保障单方的初始序列号都能被确认接管。
起因三:防止资源节约
如果只有「两次握手」,当客户端的 SYN 申请连贯在网络中阻塞,客户端没有接管到 ACK 报文,就会从新发送 SYN,因为没有第三次握手,服务器不分明客户端是否收到了本人发送的建设连贯的 ACK 确认信号,所以每收到一个 SYN 就只能先被动建设一个连贯,这会造成什么状况呢?
如果客户端的 SYN 阻塞了,反复发送屡次 SYN 报文,那么服务器在收到申请后就会建设多个冗余的有效链接,造成不必要的资源节约。
两次握手会造成资源节约两次握手会造成资源节约
即两次握手会造成音讯滞留状况下,服务器反复承受无用的连贯申请 SYN 报文,而造成反复分配资源。
小结
TCP 建设连贯时,通过三次握手能避免历史连贯的建设,能缩小单方不必要的资源开销,能帮忙单方同步初始化序列号。序列号可能保障数据包不反复、不抛弃和按序传输。
不应用「两次握手」和「四次握手」的起因:
「两次握手」:无奈避免历史连贯的建设,会造成单方资源的节约,也无奈牢靠的同步单方序列号;
「四次握手」:三次握手就曾经实践上起码牢靠连贯建设,所以不须要应用更多的通信次数。
为什么客户端和服务端的初始序列号 ISN 是不雷同的?
如果一个曾经生效的连贯被重用了,然而该旧连贯的历史报文还残留在网络中,如果序列号雷同,那么就无奈分辨出该报文是不是历史报文,如果历史报文被新的连贯接管了,则会产生数据错乱。
所以,每次建设连贯前从新初始化一个序列号次要是为了通信单方可能依据序号将不属于本连贯的报文段抛弃。
另一方面是为了安全性,避免黑客伪造的雷同序列号的 TCP 报文被对方接管。
初始序列号 ISN 是如何随机产生的?
起始 ISN 是基于时钟的,每 4 毫秒 + 1,转一圈要 4.55 个小时。
RFC1948 中提出了一个较好的初始化序列号 ISN 随机生成算法。
ISN = M + F (localhost, localport, remotehost, remoteport)
M 是一个计时器,这个计时器每隔 4 毫秒加 1。
F 是一个 Hash 算法,依据源 IP、目标 IP、源端口、目标端口生成一个随机数值。要保障 Hash 算法不能被内部轻易推算得出,用 MD5 算法是一个比拟好的抉择。
既然 IP 层会分片,为什么 TCP 层还须要 MSS 呢?
咱们先来意识下 MTU 和 MSS
MTU 与 MSSMTU 与 MSS
MTU:一个网络包的最大长度,以太网中个别为 1500 字节;
MSS:除去 IP 和 TCP 头部之后,一个网络包所能包容的 TCP 数据的最大长度;
如果在 TCP 的整个报文(头部 + 数据)交给 IP 层进行分片,会有什么异样呢?
当 IP 层有一个超过 MTU 大小的数据(TCP 头部 + TCP 数据)要发送,那么 IP 层就要进行分片,把数据分片成若干片,保障每一个分片都小于 MTU。把一份 IP 数据报进行分片当前,由指标主机的 IP 层来进行从新组装后,再交给上一层 TCP 传输层。
这看起来颠三倒四,但这存在隐患的,那么当如果一个 IP 分片失落,整个 IP 报文的所有分片都得重传。
因为 IP 层自身没有超时重传机制,它由传输层的 TCP 来负责超时和重传。
当接管方发现 TCP 报文(头部 + 数据)的某一片失落后,则不会响应 ACK 给对方,那么发送方的 TCP 在超时后,就会重发「整个 TCP 报文(头部 + 数据)」。
因而,能够得悉由 IP 层进行分片传输,是十分没有效率的。
所以,为了达到最佳的传输效力 TCP 协定在建设连贯的时候通常要协商单方的 MSS 值,当 TCP 层发现数据超过 MSS 时,则就先会进行分片,当然由它造成的 IP 包的长度也就不会大于 MTU,天然也就不必 IP 分片了。
握手阶段协商 MSS 握手阶段协商 MSS
通过 TCP 层分片后,如果一个 TCP 分片失落后,进行重发时也是以 MSS 为单位,而不必重传所有的分片,大大增加了重传的效率。
什么是 SYN 攻打?如何防止 SYN 攻打?
SYN 攻打
咱们都晓得 TCP 连贯建设是须要三次握手,假如攻击者短时间伪造不同 IP 地址的 SYN 报文,服务端每接管到一个 SYN 报文,就进入 SYN_RCVD 状态,但服务端发送进来的 ACK + SYN 报文,无奈失去未知 IP 主机的 ACK 应答,长此以往就会占满服务端的 SYN 接管队列(未连贯队列),使得服务器不能为失常用户服务。
SYN 攻打 SYN 攻打
防止 SYN 攻击方式一
其中一种解决形式是通过批改 Linux 内核参数,管制队列大小和当队列满时应做什么解决。
当网卡接管数据包的速度大于内核解决的速度时,会有一个队列保留这些数据包。管制该队列的最大值如下参数:
net.core.netdev_max_backlog
SYN_RCVD 状态连贯的最大个数:
net.ipv4.tcp_max_syn_backlog
超出解决能时,对新的 SYN 间接回报 RST,抛弃连贯:
net.ipv4.tcp_abort_on_overflow
防止 SYN 攻击方式二
咱们先来看下 Linux 内核的 SYN(未实现连贯建设)队列与 Accpet(已实现连贯建设)队列是如何工作的?
失常流程失常流程
失常流程:
当服务端接管到客户端的 SYN 报文时,会将其退出到内核的「SYN 队列」;
接着发送 SYN + ACK 给客户端,期待客户端回应 ACK 报文;
服务端接管到 ACK 报文后,从「SYN 队列」移除放入到「Accept 队列」;
利用通过调用 accpet() socket 接口,从「Accept 队列」取出连贯。
应用程序过慢应用程序过慢
应用程序过慢:
如果应用程序过慢时,就会导致「Accept 队列」被占满。
受到 SYN 攻打受到 SYN 攻打
受到 SYN 攻打:
如果一直受到 SYN 攻打,就会导致「SYN 队列」被占满。
tcp_syncookies 的形式能够应答 SYN 攻打的办法:
net.ipv4.tcp_syncookies = 1
tcp_syncookies 应答 SYN 攻打 tcp_syncookies 应答 SYN 攻打
当「SYN 队列」满之后,后续服务器收到 SYN 包,不进入「SYN 队列」;
计算出一个 cookie 值,再以 SYN + ACK 中的「序列号」返回客户端,
服务端接管到客户端的应答报文时,服务器会查看这个 ACK 包的合法性。如果非法,间接放入到「Accept 队列」。
最初利用通过调用 accpet() socket 接口,从「Accept 队列」取出的连贯。
03 TCP 连贯断开
TCP 四次挥手过程和状态变迁
天下没有不散的宴席,对于 TCP 连贯也是这样,TCP 断开连接是通过四次挥手形式。
单方都能够被动断开连接,断开连接后主机中的「资源」将被开释。
客户端被动敞开连贯 —— TCP 四次挥手客户端被动敞开连贯 —— TCP 四次挥手
客户端打算敞开连贯,此时会发送一个 TCP 首部 FIN 标记位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。
服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSED_WAIT 状态。
客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。
期待服务端解决完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
服务器收到了 ACK 应答报文后,就进入了 CLOSED 状态,至此服务端曾经实现连贯的敞开。
客户端在通过 2MSL 一段时间后,主动进入 CLOSED 状态,至此客户端也实现连贯的敞开。
你能够看到,每个方向都须要一个 FIN 和一个 ACK,因而通常被称为四次挥手。
这里一点须要留神是:被动敞开连贯的,才有 TIME_WAIT 状态。
为什么挥手须要四次?
再来回顾下四次挥手单方发 FIN 包的过程,就能了解为什么须要四次了。
敞开连贯时,客户端向服务端发送 FIN 时,仅仅示意客户端不再发送数据了然而还能接收数据。
服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据须要解决和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意当初敞开连贯。
从下面过程可知,服务端通常须要期待实现数据的发送和解决,所以服务端的 ACK 和 FIN 个别都会离开发送,从而比三次握手导致多了一次。
为什么 TIME_WAIT 期待的工夫是 2MSL?
MSL 是 Maximum Segment Lifetime,报文最大生存工夫,它是任何报文在网络上存在的最长工夫,超过这个工夫报文将被抛弃。因为 TCP 报文基于是 IP 协定的,而 IP 头中有一个 TTL 字段,是 IP 数据报能够通过的最大路由数,每通过一个解决他的路由器此值就减 1,当此值为 0 则数据报将被抛弃,同时发送 ICMP 报文告诉源主机。
MSL 与 TTL 的区别:MSL 的单位是工夫,而 TTL 是通过路由跳数。所以 MSL 应该要大于等于 TTL 耗费为 0 的工夫,以确保报文已被天然沦亡。
TIME_WAIT 期待 2 倍的 MSL,比拟正当的解释是:网络中可能存在来自发送方的数据包,当这些发送方的数据包被接管方解决后又会向对方发送响应,所以一来一回须要期待 2 倍的工夫。
比方如果被动敞开方没有收到断开连接的最初的 ACK 报文,就会触发超时重发 Fin 报文,另一方接管到 FIN 后,会重发 ACK 给被动敞开方,一来一去正好 2 个 MSL。
2MSL 的工夫是从客户端接管到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 工夫内,因为客户端的 ACK 没有传输到服务端,客户端又接管到了服务端重发的 FIN 报文,那么 2MSL 工夫将从新计时。
在 Linux 零碎里 2MSL 默认是 60 秒,那么一个 MSL 也就是 30 秒。Linux 零碎停留在 TIME_WAIT 的工夫为固定的 60 秒。
其定义在 Linux 内核代码里的名称为 TCP_TIMEWAIT_LEN:
define TCP_TIMEWAIT_LEN (60HZ) / how long to wait to destroy TIME-WAIT
state, about 60 seconds */
如果要批改 TIME_WAIT 的工夫长度,只能批改 Linux 内核代码里 TCP_TIMEWAIT_LEN 的值,并从新编译 Linux 内核。
为什么须要 TIME_WAIT 状态?
被动发动敞开连贯的一方,才会有 TIME-WAIT 状态。
须要 TIME-WAIT 状态,次要是两个起因:
避免具备雷同「四元组」的「旧」数据包被收到;
保障「被动敞开连贯」的一方能被正确的敞开,即保障最初的 ACK 能让被动敞开方接管,从而帮忙其失常敞开;
起因一:避免旧连贯的数据包
假如 TIME-WAIT 没有等待时间或工夫过短,被提早的数据包到达后会产生什么呢?
接管到历史数据的异样接管到历史数据的异样
如上图黄色框框服务端在敞开连贯之前发送的 SEQ = 301 报文,被网络提早了。
这时有雷同端口的 TCP 连贯被复用后,被提早的 SEQ = 301 到达了客户端,那么客户端是有可能失常接管这个过期的报文,这就会产生数据错乱等重大的问题。
所以,TCP 就设计出了这么一个机制,通过 2MSL 这个工夫,足以让两个方向上的数据包都被抛弃,使得原来连贯的数据包在网络中都天然隐没,再呈现的数据包肯定都是新建设连贯所产生的。
起因二:保障连贯正确敞开
在 RFC 793 指出 TIME-WAIT 另一个重要的作用是:
TIME-WAIT – represents waiting for enough time to pass to be sure the remote TCP received the acknowledgment of its connection termination request.
也就是说,TIME-WAIT 作用是期待足够的工夫以确保最初的 ACK 能让被动敞开方接管,从而帮忙其失常敞开。
假如 TIME-WAIT 没有等待时间或工夫过短,断开连接会造成什么问题呢?
没有确保失常断开的异样没有确保失常断开的异样
如上图红色框框客户端四次挥手的最初一个 ACK 报文如果在网络中被失落了,此时如果客户端 TIME-WAIT 过短或没有,则就间接进入了 CLOSED 状态了,那么服务端则会始终处在 LASE_ACK 状态。
当客户端发动建设连贯的 SYN 申请报文后,服务端会发送 RST 报文给客户端,连贯建设的过程就会被终止。
如果 TIME-WAIT 期待足够长的状况就会遇到两种状况:
服务端失常收到四次挥手的最初一个 ACK 报文,则服务端失常敞开连贯。
服务端没有收到四次挥手的最初一个 ACK 报文时,则会重发 FIN 敞开连贯报文并期待新的 ACK 报文。
所以客户端在 TIME-WAIT 状态期待 2MSL 工夫后,就能够保障单方的连贯都能够失常的敞开。
TIME_WAIT 过多有什么危害?
如果服务器有处于 TIME-WAIT 状态的 TCP,则阐明是由服务器方被动发动的断开申请。
过多的 TIME-WAIT 状态次要的危害有两种:
第一是内存资源占用;
第二是对端口资源的占用,一个 TCP 连贯至多耗费一个本地端口;
第二个危害是会造成重大的结果的,要晓得,端口资源也是无限的,个别能够开启的端口为 32768~61000,也能够通过如下参数设置指定
net.ipv4.ip_local_port_range
如果发动连贯一方的 TIME_WAIT 状态过多,占满了所有端口资源,则会导致无奈创立新连贯。
客户端受端口资源限度:
客户端 TIME_WAIT 过多,就会导致端口资源被占用,因为端口就 65536 个,被占满就会导致无奈创立新的连贯。
服务端受系统资源限度:
因为一个四元组示意 TCP 连贯,实践上服务端能够建设很多连贯,服务端的确只监听一个端口 然而会把连贯扔给解决线程,所以实践上监听的端口能够持续监听。然而线程池解决不了那么多始终一直的连贯了。所以当服务端呈现大量 TIME_WAIT 时,系统资源被占满时,会导致解决不过去新的连贯。
如何优化 TIME_WAIT?
这里给出优化 TIME-WAIT 的几个形式,都是有利有弊:
关上 net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_timestamps 选项;
net.ipv4.tcp_max_tw_buckets
程序中应用 SO_LINGER,利用强制应用 RST 敞开。
形式一:net.ipv4.tcp_tw_reuse 和 tcp_timestamps
如下的 Linux 内核参数开启后,则能够复用处于 TIME_WAIT 的 socket 为新的连贯所用。
有一点须要留神的是,tcp_tw_reuse 性能只能用客户端(连贯发起方),因为开启了该性能,在调用 connect() 函数时,内核会随机找一个 time_wait 状态超过 1 秒的连贯给新的连贯复用。
net.ipv4.tcp_tw_reuse = 1
应用这个选项,还有一个前提,须要关上对 TCP 工夫戳的反对,即
net.ipv4.tcp_timestamps=1(默认即为 1)
这个工夫戳的字段是在 TCP 头部的「选项」里,用于记录 TCP 发送方的以后工夫戳和从对端接管到的最新工夫戳。
因为引入了工夫戳,咱们在后面提到的 2MSL 问题就不复存在了,因为反复的数据包会因为工夫戳过期被天然抛弃。
形式二:net.ipv4.tcp_max_tw_buckets
这个值默认为 18000,当零碎中处于 TIME_WAIT 的连贯一旦超过这个值时,零碎就会将前面的 TIME_WAIT 连贯状态重置。
这个办法过于暴力,而且治标不治本,带来的问题远比解决的问题多,不举荐应用。
形式三:程序中应用 SO_LINGER
咱们能够通过设置 socket 选项,来设置调用 close 敞开连贯行为。
struct linger so_linger;
so_linger.l_onoff = 1;
so_linger.l_linger = 0;
setsockopt(s, SOL_SOCKET, SO_LINGER, &so_linger,sizeof(so_linger));
如果 l_onoff 为非 0,且 l_linger 值为 0,那么调用 close 后,会立该发送一个 RST 标记给对端,该 TCP 连贯将跳过四次挥手,也就跳过了 TIME_WAIT 状态,间接敞开。
但这为逾越 TIME_WAIT 状态提供了一个可能,不过是一个十分危险的行为,不值得提倡。
如果曾经建设了连贯,然而客户端忽然呈现故障了怎么办?
TCP 有一个机制是保活机制。这个机制的原理是这样的:
定义一个时间段,在这个时间段内,如果没有任何连贯相干的流动,TCP 保活机制会开始作用,每隔一个工夫距离,发送一个探测报文,该探测报文蕴含的数据非常少,如果间断几个探测报文都没有失去响应,则认为以后的 TCP 连贯曾经死亡,零碎内核将错误信息告诉给下层应用程序。
在 Linux 内核能够有对应的参数能够设置保活工夫、保活探测的次数、保活探测的工夫距离,以下都为默认值:
net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75
net.ipv4.tcp_keepalive_probes=9
tcp_keepalive_time=7200:示意保活工夫是 7200 秒(2 小时),也就 2 小时内如果没有任何连贯相干的流动,则会启动保活机制
tcp_keepalive_intvl=75:示意每次检测距离 75 秒;
tcp_keepalive_probes=9:示意检测 9 次无响应,认为对方是不可达的,从而中断本次的连贯。
也就是说在 Linux 零碎中,起码须要通过 2 小时 11 分 15 秒才能够发现一个「死亡」连贯。
这个工夫是有点长的,咱们也能够依据理论的需要,对以上的保活相干的参数进行设置。
如果开启了 TCP 保活,须要思考以下几种状况:
第一种,对端程序是失常工作的。当 TCP 保活的探测报文发送给对端, 对端会失常响应,这样 TCP 保活工夫会被重置,期待下一个 TCP 保活工夫的到来。
第二种,对端程序解体并重启。当 TCP 保活的探测报文发送给对端后,对端是能够响应的,但因为没有该连贯的无效信息,会产生一个 RST 报文,这样很快就会发现 TCP 连贯曾经被重置。
第三种,是对端程序解体,或对端因为其余起因导致报文不可达。当 TCP 保活的探测报文发送给对端后,杳无音信,没有响应,间断几次,达到保活探测次数后,TCP 会报告该 TCP 连贯曾经死亡。
04 Socket 编程
针对 TCP 应该如何 Socket 编程?
基于 TCP 协定的客户端和服务器工作基于 TCP 协定的客户端和服务器工作
服务端和客户端初始化 socket,失去文件描述符;
服务端调用 bind,将绑定在 IP 地址和端口;
服务端调用 listen,进行监听;
服务端调用 accept,期待客户端连贯;
客户端调用 connect,向服务器端的地址和端口发动连贯申请;
服务端 accept 返回用于传输的 socket 的文件描述符;
客户端调用 write 写入数据;服务端调用 read 读取数据;
客户端断开连接时,会调用 close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用 close,示意连贯敞开。
这里须要留神的是,服务端调用 accept 时,连贯胜利了会返回一个已实现连贯的 socket,后续用来传输数据。
所以,监听的 socket 和真正用来传送数据的 socket,是「两个」socket,一个叫作监听 socket,一个叫作已实现连贯 socket。
胜利连贯建设之后,单方开始通过 read 和 write 函数来读写数据,就像往一个文件流外面写货色一样。
listen 时候参数 backlog 的意义?
Linux 内核中会保护两个队列:
未实现连贯队列(SYN 队列):接管到一个 SYN 建设连贯申请,处于 SYN_RCVD 状态;
已实现连贯队列(Accpet 队列):已实现 TCP 三次握手过程,处于 ESTABLISHED 状态;
SYN 队列 与 Accpet 队列 SYN 队列 与 Accpet 队列
int listen (int socketfd, int backlog)
参数一 socketfd 为 socketfd 文件描述符
参数二 backlog,这参数在历史版本有肯定的变动
在晚期 Linux 内核 backlog 是 SYN 队列大小,也就是未实现的队列大小。
在 Linux 内核 2.2 之后,backlog 变成 accept 队列,也就是已实现连贯建设的队列长度,所以当初通常认为 backlog 是 accept 队列。
然而上限值是内核参数 somaxconn 的大小,也就说 accpet 队列长度 = min(backlog, somaxconn)。
accept 产生在三次握手的哪一步?
咱们先看看客户端连贯服务端时,发送了什么?
客户端连贯服务端客户端连贯服务端
客户端的协定栈向服务器端发送了 SYN 包,并通知服务器端以后发送序列号 client_isn,客户端进入 SYN_SENT 状态;
服务器端的协定栈收到这个包之后,和客户端进行 ACK 应答,应答的值为 client_isn+1,示意对 SYN 包 client_isn 的确认,同时服务器也发送一个 SYN 包,通知客户端以后我的发送序列号为 server_isn,服务器端进入 SYN_RCVD 状态;
客户端协定栈收到 ACK 之后,使得应用程序从 connect 调用返回,示意客户端到服务器端的单向连贯建设胜利,客户端的状态为 ESTABLISHED,同时客户端协定栈也会对服务器端的 SYN 包进行应答,应答数据为 server_isn+1;
应答包达到服务器端后,服务器端协定栈使得 accept 阻塞调用返回,这个时候服务器端到客户端的单向连贯也建设胜利,服务器端也进入 ESTABLISHED 状态。
从下面的形容过程,咱们能够得悉客户端 connect 胜利返回是在第二次握手,服务端 accept 胜利返回是在三次握手胜利之后。
客户端调用 close 了,连贯是断开的流程是什么?
咱们看看客户端被动调用了 close,会产生什么?
客户端调用 close 过程客户端调用 close 过程
客户端调用 close,表明客户端没有数据须要发送了,则此时会向服务端发送 FIN 报文,进入 FIN_WAIT_1 状态;
服务端接管到了 FIN 报文,TCP 协定栈会为 FIN 包插入一个文件结束符 EOF 到接收缓冲区中,应用程序能够通过 read 调用来感知这个 FIN 包。这个 EOF 会被放在已排队等待的其余已接管的数据之后,这就意味着服务端须要解决这种异常情况,因为 EOF 示意在该连贯上再无额定数据达到。此时,服务端进入 CLOSE_WAIT 状态;
接着,当解决完数据后,天然就会读到 EOF,于是也调用 close 敞开它的套接字,这会使得客户端会收回一个 FIN 包,之后处于 LAST_ACK 状态;
客户端接管到服务端的 FIN 包,并发送 ACK 确认包给服务端,此时客户端将进入 TIME_WAIT 状态;
服务端收到 ACK 确认包后,就进入了最初的 CLOSE 状态;
客户端通过 2MSL 工夫之后,也进入 CLOSE 状态;
伟人的肩膀
[1] 趣谈网络协议专栏. 刘超. 极客工夫.
[2] 网络编程实战专栏. 盛延敏. 极客工夫.
[3] 计算机网络 - 自顶向下办法. 陈鸣 译. 机械工业出版社
[4] TCP/IP 详解 卷 1:协定. 范建华 译. 机械工业出版社
[5] 图解 TCP/IP. 竹下隆史. 人民邮电出版社
[6] https://www.rfc-editor.org/rf…
[7] https://draveness.me/whys-the…
[8] https://draveness.me/whys-the…
唠叨唠叨
小林为写此文重学了一篇 TCP,深感 TCP 真的是一个非常复杂的协定,要想轻易拿下,也不是一天两天的事,所以小林破费了一个星期多才写完此文章。
正所谓晓得的越多,不晓得的也越多。
下篇给大家带来 TCP 滑动窗口、流量管制、拥塞管制的图解文章!
本文只是抛砖引玉,若你有更好的想法或文章有误的中央,欢送留言探讨!
小林是专为大家图解的工具人,Goodbye,咱们下次见!
读者问答
读者问:“对于文中三次握手最次要的起因,有一个疑难:为什么新包和旧包的 seq 会不一样?查阅了 tcp/ip 详解 卷 1,以及一些其余网络书籍和 RFC793 局部,都没有明确阐明对于第一个 SYN 如果产生重传会扭转 seq。”
文章的例子不是超时重发的 SYN 报文,而是新产生的一个 SYN 报文,所以 seq 是不一样的。
我文章的例子是 RFC 793:https://www.rfc-editor.org/rf… , 33 页的 Figure 9。
读者问:“求教个问题,为了不便调试服务器程序,个别会在服务端设置 SO_REUSEADDR 选项,这样服务器程序在重启后,能够立即应用。这里设置 SO_REUSEADDR 是不是就等价于对这个 socket 设置了内核中的 net.ipv4.tcp_tw_reuse=1 这个选项?”
这两个货色没有关系的哦。
tcp_tw_reuse 是内核选项,次要用在连贯的发起方(客户端)。TIME_WAIT 状态的连贯创立工夫超过 1 秒后,新的连贯才能够被复用,留神,这里是「连贯的发起方」;
SO_REUSEADDR 是用户态的选项,用于「连贯的服务方」,用来通知操作系统内核,如果端口已被占用,然而 TCP 连贯状态位于 TIME_WAIT,能够重用端口。如果端口忙,而 TCP 处于其余状态,重用会有“Address already in use”的错误信息。
tcp_tw_reuse 是为了缩短 time_wait 的工夫,避免出现大量的 time_wait 连贯而占用系统资源,解决的是 accept 后的问题。
SO_REUSEADDR 是为了解决 time_wait 状态带来的端口占用问题,以及反对同一个 port 对应多个 ip,解决的是 bind 时的问题。
读者问:“求教一下,如果客户端第四次挥手 ack 失落,服务端超时重发的 fin 报文也失落,客户端 timewait 工夫超过了 2msl,这个时候会产生什么?认为连贯曾经敞开吗?”
当客户端 timewait 工夫超过了 2MSL,则客户端就间接进入敞开状态。
服务端超时重发 fin 报文的次数如果超过 tcp_orphan_retries 大小后,服务端也会敞开 TCP 连贯。
读者问:“求教两个小问题:文章在解释 IP 分片和 TCP MSS 分片时说,如果用 IP 分片会有两个问题:(1)IP 按 MTU 分片,如果某一片失落则须要所有分片都重传;(2)IP 没有重传机制,所以须要等 TCP 发送方超时能力重传;问题一:MSS 跟 IP 的 MTU 分片相比,只是多了一步协商 MSS 值的过程,而 IP 的 MTU 能够看作是默认协商好就是 1500 字节,所以为什么协商后的 MSS 能够做到失落后只发失落的这一片来提高效率,而默认协商好 1500 字节的 IP 分片就须要所有片都重传呢?问题二:TCP MSS 分片如果失落了一片,是不是也须要发送方期待超时再重传?如果不是,MSS 的协商如何能在超时前就直到丢了分片从而提高效率的呢?谢谢老师。”
问题一:
如果一个大的 TCP 报文是被 MTU 分片,那么只有「第一个分片」才具备 TCP 头部,前面的分片则没有 TCP 头部,接管方 IP 层只有重组了这些分片,才会认为是一个 TCP 报文,那么失落了其中一个分片,接管方 IP 层就不会把 TCP 报文丢给 TCP 层,那么就会期待对方超时重传这一整个 TCP 报文。
如果一个大的 TCP 报文被 MSS 分片,那么所有「分片都具备 TCP 头部」,因为每个 MSS 分片的是具备 TCP 头部的 TCP 报文,那么其中一个 MSS 分片失落,就只须要重传这一个分片就能够。
问题二:
TCP MSS 分片如果失落了一片,发送方没收到对方 ACK 应答,也是会触发超时重传的,因为 TCP 层是会保证数据的牢靠交付。
读者问:“大佬,求教个问题,如果是服务提供方发动的 close,而后引起过多的 time_wait 状态的 tcp 链接,time_wait 会影响服务端的端口吗?谢谢。”
不会。
如果发动连贯一方(客户端)的 TIME_WAIT 状态过多,占满了所有端口资源,则会导致无奈创立新连贯。
客户端受端口资源限度:
客户端 TIME_WAIT 过多,就会导致端口资源被占用,因为端口就 65536 个,被占满就会导致无奈创立新连贯。
服务端受系统资源限度:
因为一个 TCP 四元组示意 TCP 连贯,实践上服务端能够建设很多连贯,服务端只监听一个端口,然而会把连贯扔给解决线程,所以实践上监听的端口能够持续监听。然而线程池解决不了那么多始终一直的连贯了。所以当服务端呈现大量 TIMEWAIT 时,系统资源容易被耗尽。