大家好,又见面了,我是你们的朋友全栈君。
“ TCP/IP 协议可以说是整个互联网的基石。”
01
—
TCP 是什么?
为了直接认识 TCP 是什么,直接在命令行执行:
代码语言:javascript复制tcpdump是在linux下的一款很好用的抓包工具,(运行此命令需要root权限)。
$ sudo tcpdumpPassword:tcpdump: data link type PKTAPtcpdump: verbose output suppressed, use -v or -vv for full protocol decodelistening on pktap, link-type PKTAP (Apple DLT_PKTAP), capture size 262144 bytes20:33:03.684347 IP 192.168.3.9.57215 > 203.119.218.69.https: Flags [.], ack 2932996508, win 4096, length 020:33:03.690972 IP 192.168.3.9.61166 > 192.168.3.1.domain: 57565 PTR? 69.218.119.203.in-addr.arpa. (45)20:33:03.727782 IP 192.168.3.1.domain > 192.168.3.9.61166: 57565 NXDomain* 0/1/0 (133)20:33:03.728188 IP 203.119.218.69.https > 192.168.3.9.57215: Flags [.], ack 1, win 223, length 020:33:03.730156 IP 192.168.3.9.60580 > 192.168.3.1.domain: 32501 PTR? 1.3.168.192.in-addr.arpa. (42)20:33:03.732748 IP 192.168.3.1.domain > 192.168.3.9.60580: 32501 ServFail- 0/0/0 (42)20:33:03.906599 IP 47.93.14.46.https > 192.168.3.9.50295: Flags [P.], seq 772920791:772920837, ack 172201841, win 243, options [nop,nop,TS val 3817696661 ecr 638826721], length 4620:33:03.906606 IP 47.93.14.46.https > 192.168.3.9.50295: Flags [P.], seq 46:77, ack 1, win 243, options [nop,nop,TS val 3817696661 ecr 638826721], length 3120:33:03.906608 IP 47.93.14.46.https > 192.168.3.9.50295: Flags [F.], seq 77, ack 1, win 243, options [nop,nop,TS val 3817696661 ecr 638826721], length 020:33:03.906683 IP 192.168.3.9.50295 > 47.93.14.46.https: Flags [.], ack 46, win 2047, options [nop,nop,TS val 639061787 ecr 3817696661], length 020:33:03.906715 IP 192.168.3.9.50295 > 47.93.14.46.https: Flags [.], ack 77, win 2046, options [nop,nop,TS val 639061787 ecr 3817696661], length 020:33:03.906735 IP 192.168.3.9.50295 > 47.93.14.46.https: Flags [.], ack 78, win 2046, options [nop,nop,TS val 639061787 ecr 3817696661], length 020:33:03.907378 IP 192.168.3.9.50295 > 47.93.14.46.https: Flags [F.], seq 1, ack 78, win 2048, options [nop,nop,TS val 639061787 ecr 3817696661], length 020:33:04.240165 IP 192.168.3.9.50222 > 17.57.145.84.5223: Flags [R.], seq 934688232, ack 547657281, win 2048, length 020:33:04.366551 IP 192.168.3.9.50295 > 47.93.14.46.https: Flags [F.], seq 1, ack 78, win 2048, options [nop,nop,TS val 639062245 ecr 3817696661], length 020:33:04.405679 IP 192.168.3.9.50430 > host-1.alipay.com.http: Flags [S], seq 1727067475, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 639062284 ecr 0,sackOK,eol], length 020:33:04.720386 STP 802.1d, Config, Flags [none], bridge-id 7000.10:44:00:cd:04:ef.8007, length 35
我们看到了一坨数据。这就是TCP 层传输的数据。
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793 定义。TCP旨在适应支持多网络应用的分层协议层次结构。 连接到不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。TCP假设它可以从较低级别的协议获得简单的,可能不可靠的数据报服务。 原则上,TCP应该能够在从硬线连接到分组交换或电路交换网络的各种通信系统之上操作。
在 OSI 七层模型中,TCP/IP 是传输层的重要协议。如下图:
数据传输过程中,每层都会加上层信息:
After IP adds a header to the TCP segments the resulting IP datagrams are sent one-by-one to the “Ethernet layer”. Note that TCP/IP are part of operating system, while most functionality of Ethernet is implemented on the NIC(Network Interface Card).
https://sandilands.info/sgordon/segmentation-offloading-with-wireshark-and-ethtool
计算机中网络分层模型图:
TCP 是面向连接的传输控制协议,而UDP 提供了无连接的数据报服务;TCP 具有高可靠性,确保传输数据的正确性,不出现丢失或乱序;UDP 在传输数据前不建立连接,不对数据报进行检查与修改,无须等待对方的应答,所以会出现分组丢失、重复、乱序,应用程序需要负责传输可靠性方面的所有工作;UDP 具有较好的实时性,工作效率较 TCP 协议高;UDP 段结构比 TCP 的段结构简单,因此网络开销也小。TCP 协议可以保证接收端毫无差错地接收到发送端发出的字节流,为应用程序提供可靠的通信服务。对可靠性要求高的通信系统往往使用 TCP 传输数据。比如 HTTP 运用 TCP 进行数据的传输。
特征
TCP是一种面向广域网的通信协议,目的是在跨越多个网络通信时,为两个通信端点之间提供一条具有下列特点的通信方式:
(1)基于流的方式;
(2)面向连接;
(3)可靠通信方式;
(4)在网络状况不佳的时候尽量降低系统由于重传带来的带宽开销;
(5)通信连接维护是面向通信的两个端点的,而不考虑中间网段和节点。
为满足TCP协议的这些特点,TCP协议做了如下的规定:
①数据分片:在发送端对用户数据进行分片,在接收端进行重组,由TCP确定分片的大小并控制分片和重组;
②到达确认:接收端接收到分片数据时,根据分片数据序号向发送端发送一个确认;
③超时重发:发送方在发送分片时启动超时定时器,如果在定时器超时之后没有收到相应的确认,重发分片;
④滑动窗口:TCP连接每一方的接收缓冲空间大小都固定,接收端只允许另一端发送接收端缓冲区所能接纳的数据,TCP在滑动窗口的基础上提供流量控制,防止较快主机致使较慢主机的缓冲区溢出;
⑤失序处理:作为IP数据报来传输的TCP分片到达时可能会失序,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层;
⑥重复处理:作为IP数据报来传输的TCP分片会发生重复,TCP的接收端必须丢弃重复的数据;
⑦数据校验:TCP将保持它首部和数据的检验和,这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到分片的检验和有差错,TCP将丢弃这个分片,并不确认收到此报文段导致对端超时并重发。
重传策略
TCP协议用于控制数据段是否需要重传的依据是设立重发定时器。在发送一个数据段的同时启动一个重传,如果在重传超时前收到确认(Acknowlegement)就关闭该重传,如果重传超时前没有收到确认,则重传该数据段。在选择重发时间的过程中,TCP必须具有自适应性。它需要根据互联网当时的通信情况,给出合适的重发时间。
这种重传策略的关键是对定时器初值的设定。采用较多的算法是Jacobson于1988年提出的一种不断调整超时时间间隔的动态算法。其工作原理是:对每条连接TCP都保持一个变量RTT(Round Trip Time),用于存放当前到目的端往返所需要时间最接近的估计值。当发送一个数据段时,同时启动连接的定时器,如果在定时器超时前确认到达,则记录所需要的时间(M),并修正RTT的值,如果定时器超时前没有收到确认,则将RTT的值增加1倍。通过测量一系列的RTT(往返时间)值,TCP协议可以估算数据包重发前需要等待的时间。在估计该连接所需的当前延迟时通常利用一些统计学的原理和算法(如Karn算法),从而得到TCP重发之前需要等待的时间值。
02
—
TCP 报文数据结构(TCP Segment structure)
https://www.geeksforgeeks.org/services-and-segment-structure-in-tcp/
其中,6位标志位:
代码语言:javascript复制URG: 标识紧急指针是否有效 ACK: 标识确认序号是否有效 PSH: 用来提示接收端应用程序立刻将数据从tcp缓冲区读走 RST: 要求重新建立连接. 我们把含有RST标识的报文称为复位报文段 SYN: 请求建立连接. 我们把含有SYN标识的报文称为同步报文段 FIN: 通知对端, 本端即将关闭. 我们把含有FIN标识的报文称为结束报文段
Control flags : These are 6 1-bit control bits that control connection establishment, connection termination, connection abortion, flow control, mode of transfer etc. Their function is:
- URG: Urgent pointer is valid
- ACK: Acknowledgement number is valid( used in case of cumulative acknowledgement)
- PSH: Request for push
- RST: Reset the connection
- SYN: Synchronize sequence numbers
- FIN: Terminate the connection
字段详细说明:
- 源端口和目的端口:各占2个字节,分别写入源端口号和目的端口号。
- 序号:占4个字节。序号使用mod运算。TCP是面向字节流的,在一个TCP连接中传送的字节流中的每一个字节都按顺序编号。故该字段也叫做“报文段序号”。
- 确认序号:占4个字节,是期望收到对方下一个报文段的第一个数据字节的序号。若确认序号=N,则表明:到序号N-1为止的所有数据都已正确收到。
- 数据偏移:占4位,表示TCP报文段的首部长度。注意,“数据偏移”的单位是32位字(即以4字节长的字为计算单位)。故TCP首部的最大长度为60字节。
- 保留:占6位,保留为今后使用,目前置为0;
- 紧急URG:当URG=1,表明紧急指针字段有效。这时发送方TCP就把紧急数据插入到本报文段数据的最前面,而在紧急数据后面的数据仍是普通数据。
- 确认ACK:当ACK=1时,确认字段才有效。当ACK=0时,确认号无效。TCP规定,在连接建立后所有传送的报文段都必须把ACK置1。
- 推送PSH:接收方TCP收到PSH=1的报文段,就尽快地交付给接收应用进程,而不再等到整个缓存都填满了后再向上交付。
- 复位RST:当RST=1时,表明TCP连接中出现严重差错,必须释放连接,然后再重新建立运输连接。
- 同步SYN:在连接建立时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使SYN=1和ACK=1。故SYN置为1,就表示这是一个连接请求和连接接收报文。
- 终止FIN:用来释放连接。当FIN=1时,表明此报文段的发送方的数据已发送完毕,并要求释放运输连接。
- 窗口:占2个字节。窗口值作为接收方让发送方设置其发送窗口的依据。
- 检验和:占2字节。检验和字段检验的范围包括首部和数据这两部分。和UDP数据报一样,在计算检验和时,也要在TCP报文段的前面加上12字节的伪首部。伪首部的格式与UDP用户数据报的伪首部一样,但要将伪首部第四个字段中的17 改为6(协议号),把第5字段中的UDP长度改为TCP长度。
- 紧急指针:占2字节。紧急指针仅在URG=1时才有意义,它指出本报文段中的紧急数据的字节数。
03
—
TCP 建立连接:三次握手过程
在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。
代码语言:javascript复制S: (LISTEN) 一直在线监听中,从未偷懒。。。C: (SYN-SENT)我要连接你!S: (SYN_RCVD) 收到!C: (ESTABLISHED)那我连接了S: (ESTABLISHED)安排!
[图:https://blog.csdn.net/sinat_36629696/article/details/80740678]
刚开始, 客户端和服务器都处于 CLOSE 状态. 此时, 客户端向服务器主动发出连接请求, 服务器被动接受连接请求.步骤详解:
1, TCP服务器进程先创建传输控制块TCB, 时刻准备接受客户端进程的连接请求, 此时服务器就进入了 LISTEN(监听)状态
2, TCP客户端进程也是先创建传输控制块TCB, 然后向服务器发出连接请求报文,此时报文首部中的同步标志位SYN=1, 同时选择一个初始序列号 seq = x, 此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定, SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。
3, TCP服务器收到请求报文后, 如果同意连接, 则发出确认报文。确认报文中的 ACK=1, SYN=1, 确认序号是 x 1, 同时也要为自己初始化一个序列号 seq = y, 此时, TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据, 但是同样要消耗一个序号。
4, TCP客户端进程收到确认后还, 要向服务器给出确认。确认报文的ACK=1,确认序号是 y 1,自己的序列号是 x 1.
5, 此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。当服务器收到客户端的确认后也进入ESTABLISHED状态,此后双方就可以开始通信了。
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j 1),同时自己也发送一个SYN包(syn=k),即SYN ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k 1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手.
完成三次握手,客户端与服务器开始传送数据
SYN:同步序列编号(Synchronize Sequence Numbers)
为什么不用两次?
主要是为了防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送的第一个请求连接并且没有丢失,只是因为在网络中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时之前滞留的那一次请求连接,因为网络通畅了, 到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的费。
如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。
04
—
TCP 关闭连接:四次挥手过程
数据传输完毕后,双方都可以释放连接.
此时客户端和服务器都是处于ESTABLISHED状态,然后客户端主动断开连接,服务器被动断开连接.
1, 客户端进程发出连接释放报文,并且停止发送数据。
释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时客户端进入FIN-WAIT-1(终止等待1)状态。TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
2, 服务器收到连接释放报文,发出确认报文,ACK=1,确认序号为 u 1,并且带上自己的序列号seq=v,此时服务端就进入了CLOSE-WAIT(关闭等待)状态。
TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
3, 客户端收到服务器的确认请求后,此时客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最终数据)
4, 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,确认序号为v 1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
5, 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,确认序号为w 1,而自己的序列号是u 1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
6, 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
再来看一张图.
为什么最后客户端还要等待 2*MSL的时间呢?
MSL(Maximum Segment Lifetime),TCP允许不同的实现可以设置不同的MSL值。
第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。
为什么建立连接是三次握手,关闭连接确是四次挥手呢?
建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。
而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。
如果已经建立了连接, 但是客户端突发故障了怎么办?
TCP设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75分钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
05
—
TCP 状态机
Kotlin 开发者社区
国内第一Kotlin 开发者社区公众号,主要分享、交流 Kotlin 编程语言、Spring Boot、Android、React.js/Node.js、函数式编程、编程思想等相关主题。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/186508.html原文链接:https://javaforall.cn