概述
TCP 协议是位于传输层的通讯协议,提供了面向连接的,可靠的字节流服务。所有的TCP连接都是全双工、点对点的,所谓全双工,就是可以同时在两个方向上传输数据。
互联网络是非常不可靠的,因为互联网络的不同部分可能有着截然不同的拓扑结构、带宽、延迟等,TCP的设计目标就是能够动态的适应这些特性,保证数据在传输过程中面对各种情况的健壮性。
所以,在学习TCP的时候,不妨从这个角度去思考,如果是你,你会如何满足这一目标?
数据段
发送端和接收端的 TCP 实体使用数据段进行数据输出,TCP段(TCP segment)由一个固定20大小的头以及随后0个或多个数据字节组成。
当应用层交付下来的数据大小过大时,TCP 会将数据分段传输,如何进行分段的,需要了解MTU和MSS。
MTU(最大传输单元)
MTU(最大传输单元)是链路层中的网络对数据帧的一个限制,依然以以太网为例,MTU 为 1500 个字节。一个IP 数据报在以太网中传输,如果它的长度大于该 MTU 值,就要进行分片传输,使得每片数据报的长度小于MTU。分片传输的 IP 数据报不一定按序到达,但 IP 首部中的信息能让这些数据报片按序组装。IP 数据报的分片与重组是在网络层进完成的。
MSS(最大分段大小)
MSS 是 TCP 里的一个概念(首部的选项字段中)。MSS 是 TCP 数据包每次能够传输的最大数据分段,TCP 报文段的长度大于 MSS 时,要进行分段传输。TCP 协议在建立连接的时候通常要协商双方的 MSS 值,每一方都有用于通告它期望接收的 MSS 选项(MSS 选项只出现在 SYN 报文段中,即 TCP 三次握手的前两次)。MSS 的值一般为 MTU 值减去两个首部大小(需要减去 IP 数据包包头的大小 20Bytes 和 TCP 数据段的包头 20Bytes)所以如果用链路层以太网,MSS 的值往往为 1460。而 Internet 上标准的 MTU 为 576,那么如果不设置,则MSS的默认值就为 536 个字节。TCP报文段的分段与重组是在运输层完成的。
TCP段的头格式
先上一张网图(图片来源):
下面描述几个比较重要的点:
- Source Port、Destination Port(源端口和目标端口): TCP包中是没有IP地址的,IP地址可以在IP层找到。一个TCP连接需要 5 个元组来表示是同一个连接,即:源IP、源端口、目标IP、目标端口、协议(这里就指TCP协议)
- Sequence Number: 包的序号,用来解决网络包乱序问题。
- Acknowledgement Number: 就是ACK,用于确认收到,用来解决不丢包的问题。
- Window: 滑动窗口的窗口大小。
- TCP Flag: 包的类型,比如三次握手中的SYN、ACK,挥手中的FIN等。主要是用于操控TCP的状态机的。
连接与断开
TCP的三次握手建立连接,和四次挥手断开连接,讲烂了的概念,直接贴一个讲比较详细的链接吧:
hit-alibaba.github.io/interview/b…
补充:
- 关于建立连接时SYN超时, 如果在建立连接时,client端在发送完SYN后掉线了,server端没有收到client回来的ACK,那么,这个连接处于一个中间状态,即没成功,也没失败。于是,server端如果在一定时间内没有收到的TCP会重发SYN-ACK。在Linux下,默认重试次数为5次,重试的间隔时间从1s开始每次都翻售,5次的重试时间间隔为1s, 2s, 4s, 8s, 16s,总共31s,第5次发出后还要等32s都知道第5次也超时了,所以,总共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 2^6 -1 = 63s,TCP才会把断开这个连接。
- 关于SYN Flood攻击,如果有些人恶意制造大量请求,给服务器发完SYN后掉线,那么服务端就会造成连接队列耗尽,正常请求无法处理。针对SYN Flood攻击的防范:
- 第一种是缩短SYN Timeout时间,由于SYN Flood攻击的效果取决于服务器上保持的SYN半连接数,这个值=SYN攻击的频度 x SYN Timeout,所以通过缩短从接收到SYN报文到确定这个报文无效并丢弃改连接的时间,例如设置为20秒以下,可以成倍的降低服务器的负荷。但过低的SYN Timeout设置可能会影响客户的正常访问。
- 第二种方法是设置SYN Cookie,就是给每一个请求连接的IP地址分配一个Cookie,如果短时间内连续受到某个IP的重复SYN报文,就认定是受到了攻击,并记录地址信息,以后从这个IP地址来的包会被一概丢弃。这样做的结果也可能会影响到正常用户的访问。
流量控制
IP协议是不可靠的传输协议,并不关心数据是否已经发送到,也不关心接收端对于数据的处理速度。所以这一项工作就由传输层的TCP协议来实现了,在比较早的时候 TCP 使用的是send--wait--send的模式,发送数据方在发送数据之后会启动定时器,但是如果数据或者ACK丢失,那么定时器到期之后,收不到ACK就认为发送出现状况,要进行重传。无疑这种方式的通讯效率是极低的。
滑动窗口
滑动窗口是实现TCP流量控制的一项技术,关于滑动窗口,可以参考:blog.csdn.net/wdscq1234/a…
补充:
- 滑动窗口大小和接收端的Sokect缓冲区的大小一致,由于TCP头中窗口大小用16bit表示,因此缓冲区最大为65536。
另外,这里还想再补充一点关于linux 内核缓冲区的一些概念:
整个数据的流程中,首先网卡接收到的数据存放到内核缓冲区内,然后内核缓冲区存放的数据根据TCP信息将数据移动到具体的某一个TCP连接上的接收缓冲区内,也就是接收滑动窗口内。
如果网卡处理数据的速度比内核处理数据的速度慢,那么内核会有一个队列来保存这些数据,这个队列的大小就是由参数 netdev_max_backlog 决定的。
对于发送数据来说,应用程序将数据拷贝到各自TCP发送缓冲区内(也就是发送滑动窗口),然后系统将TCP缓冲区(也就是发送滑动窗口)内的数据拷贝到内核发送缓冲区内,最后内核将内核缓冲区的数据经过网卡发送出去。
TCP的发送/接受缓冲区(也就是发送/接受滑动窗口),是针对某一个具体的TCP连接来说的,每一个TCP连接都会有相应的滑动窗口,但是内核的发送/接受缓冲区是针对整个系统的,里面存放着整个系统的所有TCP连接的接收/发送的数据。
最后贴一张网图(图片来源):
- 关于零窗口(Zero Window),当接收方回复ack时滑动窗口为0时,发送方就不会正常发送数据了。但是为了在接收方窗口有空闲时及时接收到,发送方会间歇性的发ZWP包给接收方(重复次数通常为3,间隔30~60秒)。如果几次后窗口还是0的话,发送方会断开链接。
拥塞控制
关于拥塞控制参考:zhuanlan.zhihu.com/p/76023663,这篇文章写的很好,就不赘述了