OSI七层模型
OSI模型各层的基本作用
TCP在网络OSI的七层模型中的第四层 --- Transport 层,IP在第三层 --- Network层,ARP在第二层 --- Data Link层(TCP/IP模型中属于网络层)
第二层的数据我们叫 Frame,在第三层的数据叫 Packet,第四层的数据叫 Segment
我们程序的数据 👉🏻 打到 TCP 的 Segment 👉🏻 打到 IP 的 Packet 👉🏻 打到以太网 Ethernet 的 Frame 中,传到对端后,各个层解析自己的协议,然后把数据交给更高层的协议处理。
注意:
src_ip
,src_port
,dst_ip
,dst_port
)准确说是五元组,还有一个是协议。但因为这里只是说 TCP 协议,所以,这里我们只说四元组。Sequence Number
是包的序列号:用来解决网络包乱序(reordering)问题。Acknowledgement Number
就是ACK:用于确认收到,用来解决不丢包的问题关于其它的东西,可以参看下面的图示
其实,网络上的传输是没有连接的,包括TCP也是一样的。而 TCP 所谓的“连接”,其实只不过是在通讯双方维护一个“连接状态”,让它看上去好像有连接一样。所以,TCP状态变换是非常重要的。
下面是:TCP协议的状态机和TCP建连接、TCP断连接、传数据的对照图。
常见面试题?
- 什么是 TCP 的三次握手和四次挥手?
- TCP的三次握手(为什么是三次?)
- TCP的四次挥手(为什么是四次?)
TCP是面向连接的单播协议,在发送数据前,通信双方必须在彼此建立一条连接。所谓的“连接”,其实是客户端和服务器的内存里保存的一份关于对方的信息,如IP地址、端口号等。
TCP可以看成是一种字节流,它会处理 IP 层或以下的层的丢包、重复以及错误问题。在连接的建立过程中,双方需要交换一些连接参数。这些参数可以放在 TCP 头部。
TCP 提供了一个可靠、面向连接、字节流、传输层的服务,采用三次握手建立一个连接;采用四次挥手来关闭一个连接。
一个TCP连接由一个4元组构成,分别是两个IP地址和两个端口号。一个TCP连接通常分为三个阶段:启动、数据传输、退出(关闭)
当TCP接收到另一端的数据时,它会发送一个确认,但这个确认不会立即发送,一般会延迟一会。
ACK是累积的,一个确认字节号N的ACK 表示所有直到 N 的字节(不包括N)已经成功被接收了。这样的好处是如果一个ACK丢失,很可能后续的 ACK 就足以确认前面的报文段了。
一个完整的 TCP 连接是双向和对称的,数据可以在两个方向上平等地流动。给上层应用程序提供一种双工服务。一旦建立了一个连接,这个连接的一个方向上的每个TCP报文段都包含了相反方向上的每个TCP报文段都包含了相反方向上的报文段的一个ACK
序列号的作用是使得一个 TCP 接收端可丢弃重复的报文段,记录以杂乱次序到达的报文段。因为 TCP 使用 IP 来传输报文,而 IP 不提供重复消除或者保证次序正确的功能。
另一个方面,TCP是一个字节流协议,绝不会以杂乱的次序给上层程序发送数据。因此 TCP 接收端会被迫先保持大序列号的数据不交给应用程序,直到缺失的小序列号的报文段被填满。
三次握手的过程
假设 A 为客户端,B为服务端。
首先 B 处于 LISTEN(监听)状态,等待客户端的连接请求。
SYN = 1
, ACK = 0
, 选择一个初始的序号 x.SYN=1
,ACK=1
,确认号为 x+1
,同时也选择一个初始的序号yB收到A的确认后,建立连接。
为什么要三次呢?
从上面的过程可以看到,最少是需要三次握手过程的。两次达不到让双方都得出自己、对方的接收、发送能力都正常的结论。
四次挥手的过程
为什么要四次挥手呢?
TCP 连接是双向传输的对等的模式,就是说双方都可以同时向对方发送或接收数据。当有一方要关闭连接时,会发送指令告知对方,我要关闭连接了。
这时对方会回一个 ACK,此时一个方向的连接关闭。但是另一个方向仍然可以继续传输数据。也就是说,服务端收到客户端的 FIN 标志,知道客户端想要断开这次连接了,但是,我服务端,我还想发数据呢?我等到发完所有的数据后,会发送一个 FIN 段来关闭此方向上的连接。接收方发送 ACK 确认关闭连接。
客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。
因为服务端在 LISTEN 状态下,收到建立连接请求的 SYN 报文后,把 ACK 和 SYN 放在一个报文里发送给客户端。而关闭连接时,当收到对方的 FIN 报文时,仅仅表示对方不再发送数据了,但是还能接收数据,己方是否现在关闭发送数据通道,需要上层应用来决定,因此,己方 ACK 和 FIN 一般都会分开发。
TIME_WAIT
为什么 TCP 要有重传机制?
TCP 要保证所有的数据包都可以到达,所以,必须要有重传机制。
重传机制包括四种
重传的场景:
Note: 接收端给发送端的 Ack 确认只会确认最后一个连续的包。
比如,发送端发了1,2,3,4,5一共五份数据,接收端收到了1,2,于是回ack3,然后收到了4(注意此时3没收到),此时的TCP会怎么办?我们要知道,因为正如前面所说的,SeqNum和Ack是以字节数为单位,所以ack的时候,不能跳着确认,只能确认最大的连续收到的包,不然,发送端就以为之前的都收到了。
一种是不回ack,死等3,当发送方发现收不到3的ack超时后,会重传3。一旦接收方收到3后,会ack回4 --- 意味着3和4都收到了。而发送方也完全不知道发生了什么事情,应为没有收到Ack,所以,发送方可能会悲观地认为也丢了,所以有可能也会导致4和5的重传。
对此有两种选择:
这两种方式有好也有不好。第一种会节省带宽,但是慢,第二种会快一点,但是会浪费带宽,也可能会有无用功。但总体来说都不好。因为都在等timeout,timeout可能会很长。
TCP引入了一种叫 Fast Retransmit 的算法,不以时间驱动,而以数据驱动重传。也就是说,如果,包没有连续到达,就ack最后那个可能被丢了的包,如果发送方连续收到3次相同的ack,就重传。Fast Retransmit 的好处是不用等 timeout 了再重传。
比如:如果发送方发出了1,2,3,4,5份数据,第一份先到送了,于是就ack回2,结果2因为某些原因没收到,3到达了,于是还是ack回2,后面的4和5都到了,但是还是ack回2,因为2还是没有收到,于是发送端收到了三个ack=2的确认,知道了2还没有到,于是就马上重转2。然后,接收端收到了2,此时因为3,4,5都收到了,于是ack回6。示意图如下:
Fast Retransmit只解决了一个问题,就是timeout的问题,它依然面临一个艰难的选择,就是,是重传之前的一个还是重传所有的问题。对于上面的示例来说,是重传#2呢还是重传#2,#3,#4,#5呢?因为发送端并不清楚这连续的3个ack(2)是谁传回来的?也许发送端发了20份数据,是#6,#10,#20传来的呢。这样,发送端很有可能要重传从2到20的这堆数据(这就是某些TCP的实际的实现)。可见,这是一把双刃剑。
另外一种更好的方式叫:Selective Acknowledgment(SACK)(参看RFC 2018),这种方式需要在 TCP 头里加一个 SACK 的东西,ACK还是Fast Retransmit的ACK,SACK则是汇报收到的数据碎版。
这样,在发送端就可以根据回传的SACK知道哪些数据到了,哪些没有到。于是就优化了Fast Retransmit的算法。
这里还需要注意一个问题——接收方Reneging,所谓Reneging的意思就是接收方有权把已经报给发送端SACK里的数据给丢了。这样干是不被鼓励的,因为这个事会把问题复杂化了,但是,接收方这么做可能会有些极端情况,比如要把内存给别的更重要的东西。所以,发送方也不能完全依赖SACK,还是要依赖ACK,并维护Time-Out,如果后续的ACK没有增长,那么还是要把SACK的东西重传,另外,接收端这边永远不能把SACK的包标记为Ack。
注意:SACK会消费发送方的资源,试想,如果一个攻击者给数据发送方发一堆SACK的选项,这会导致发送方开始要重传甚至遍历已经发出的数据,这会消耗很多发送端的资源。详细的东西请参看《TCP SACK的性能权衡》
Duplicate SACK 又称 D-SACK,其主要使用了SACK来告诉发送方有哪些数据被重复接收了。RFC-2883里有详细描述和示例。
TCP必须要解决的可靠传输以及包乱序(reordering)的问题,所以,TCP必需要知道网络实际的数据处理带宽或是数据处理速度,这样才不会引起网络拥塞,导致丢包。
TCP引入了一些技术和设计来做网络流控,Sliding Window是其中一个技术。前面我们说过,TCP头里有一个字段叫Window
,又叫Advertised-Window
,这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据,于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。