本篇主要讲解TCP协议报头的各个字段,在讲解字段的过程中引出TCP的各种机制。
TCP协议的报文格式
1. 源端口号和目的端口号
源端口号:表示报文的发送端口,占16位。源端口和源IP地址组合起来,可以标识报文的发送地址。可以理解表示为数据从哪个进程来的。
目的端口号:表示报文的接收端口,占16位。目的端口和目的IP地址相结合,可以标识报文的接收地址。可以理解为表示数据要去到哪个进程。
2. 4位首部长度
4位首部长度是用来表示TCP报头长度的。TCP报头是可变的,一般由选项决定;标准的TCP报头(没有选项)是20字节。
4位首部长度的范围为[0,15],但是这只是数值,不是报头的实际大小,4位首部长度是由单位的,单位是int或者4字节;实际范围为[0,60];
4位首部长度是多少取决与选项。例如,没有选项,报头的实际大小是20字节,根据4位首部长度单位,推出数值应该为5, 4位首部长度就是0101。
3.序号和确认序号
确认应答(ACK)机制:
首先要理解确认应答,客户端给服务端发送报文,只有服务端发送一个回应(报文),才能确认收到上一条报文;又要让客户端确认是否收到,又要再发一个回应(报文),要确认就会一直这样循环下去。
所以,发送回应报文,只能确认上一步,进一步说,只能确认历史报文的接受,下一步或者现在我们不能确定!
实际上的通信,是客户端发送了几个请求,服务端一起回应,这个回应有先后顺序,而且怎么确认哪个请求被回应?TCP是通过序号的,每个请求都有一个序号,回应的报文需要把对应的序号+1。这样就解决了请求是否被回应,以及回应多个请求时的辨别问题。
捎带应答机制的初识:
实际的应答不仅仅是确认上一个报文以及收到,也可能会需要发送数据,与这次应答一起发送给对方。因此,既需要一个序号用来确认对方的报文,自己也要有序号来发送自己的报文。这就是TCP的序号与确认序号。
总结:序号保证报文能够按序到达,确认应答;确认序号和序号保证同时进行应答和发送数据。
3.TCP面向字节流:
TCP协议下的发送和接受缓冲区,存储单位是字节;于是把字节的下标当作序号,这样每个字节就有序号了。
4.控制标志
控制标志是用来区分报文类型的。报文类型有建立连接的报文、发送数据的报文和断开连接的报文等。
常见的标志位SYN:表示该报文请求建立连接或者请求接受报文。
ACK:表示接受报文成功,并且该报文包含确认序号。
FIN:表示该报文请求断开连接
其他:
超时重传机制:
发送报文后,可能遇到的情况:报文没送到丢失,甚至报文送的太慢;收到报文,应答丢失,或者应答太慢甚至对方不应答。
为了解决上述情况,TCP定义了 超时重传机制。发送了报文,在特定时间内没有收到应答,一律视为丢包,并进行重新发送。
如果出现对方只是应答有问题,报文收到了,由于报头有序号的原因,可以识别重复的数据进行去重。
超时时间的确定:
超时时间是动态的,取决于当前网络状态。例如:Linux的超时时间机制
连接管理机制:
第二次握手:服务端收到SYN报文后,进入SYN_RCVD状态;同时发送包含SYN和ACK的报文给客户端,通知客户端已经接受报文,需要进行确认。 第三次握手:收到SYN和ACK的报文后,客户端先把状态变为ESTABLISHED,认为建立连接成功;然后给服务端发送ACK报文,服务端收到该报文,会进入ESTABLISHED,认为建立连接成功。
至此,双方连接建立成功,可以发送数据了。
四次挥手的目的是断开连接:
第一次挥手:主动断开方发送包含FIN(请求结束)的报文,然后进入FIN_WAIT_1状态;
第二次挥手:被动断开方收到FIN报文,然后发送一个ACK报文(应答同意断开连接);然后进入CLOSE_WAIT状态;主动方接受到该报文,进入FIN_WAIT_2状态,此时主动方到被动方的连接已经断开,不能发数据了(应答还可以)。
第三次挥手:在发送完成ACK报文后,被动断开方还可以继续完成业务数据的发送,待剩余数据发送完成后,或者CLOSE-WAIT(关闭等待)截止后,被动断开方发送FIN报文,进入LAST_ACK状态。
第四次挥手:主动方收到FIN报文,进入TIME_WAIT状态,等待超时后最终关闭连接;然后给被动方发送ACK报文。被动方接受到该报文,断开连接。
至此,双方连接断开。
注:在发送报文的过程中,如果连接异常或者收到的报文有问题;就会给对方发一个含有RST的报文。
RST:用于重置一个已经混乱的连接,也可用于拒绝一个无效的数据段或者拒绝一个连接请求。如果数据段被设置了RST位,说明报文发送方有问题发生。
在内核中的过程:
在三次握手中,客户端首先要有套接字,服务端要处于监听状态;客户端通过connect()接口发起三次握手,如果建立连接成功,服务端通过accept()返回新的套接字和客户端通信。
在四次握手中,主动方通过关闭套接字close(),发起四次挥手,期间被动发的read()接口阻塞,等待可能还要发送的数据,read()返回0,被动方也通过关闭套接字close(),继续四次挥手让连接断开。
为什么是三次挥手?
1.保证信道(网络)是健康的。三次握手,双方都进行了一次发送和接受,保证双方都是全双工。
其次,1,2次握手,有安全隐患;服务器要维护连接,需要成本;选择1,2次握手,服务器可能被故意大量访问,使得服务器无法正常工作。
2.确保双方是健康且愿意通信的。三次握手,双方都有接受和请求,表示双方都愿意进行通信,避免不愿意的连接。
四次挥手中的CLOSE_WAIT和TIME_WAIT:
1.CLOSE_WAIT:等待被动方内核调用close()关闭套接字;
对于服务器上出现大量的 CLOSE_WAIT 状态, 原因就是服务器没有正确的关闭 socket, 导致四次挥手没有正确完成. 这是一个 BUG. 只需要加上对应的 close 即可解决问题。
2.TIME_WAIT:让客户到服务端的连接等待,不用调用close();主要是保证第四次挥手,如果ACK报文没发到,对方会触发超时重传再来第四次挥手;其次为了等到历史陈旧报文丢弃以免对下次连接产生影响。
TIME_WAIT期间,没有调用close(),响应的套接字是被占用的,下次绑定这个套接字会失败!
通常使用socketopt函数解决这个问题。
5.16位窗口大小
窗口大小指的是自己 接受缓冲区 的剩余空间大小;此字段用来进行流量控制。
6.流量控制
注:
如果接受端将窗口大小设置为0,发送方的探测报文会包含PSH控制标志,“催"对方赶紧把数据交给上层。
PSH:接收方在收到数据后立即将数据交给上层,而不是直到整个缓冲区满。
7.滑动窗口
滑动窗口,意思是暂时不需要应答,可以一直发送数据。数据量也就是窗口大小是对方接受缓冲区的剩余大小(暂时,不考虑网络的状态)。
1.滑动窗口的动作
滑动窗口内的数据发送后,只有收到应答,窗口才会滑动。可以支持超时重传!
滑动窗口只能向右滑动,窗口左边的数据是已经被接受的。
滑动窗口的大小可以变大变小。窗口本质是一个限制区域,通过两个下标就可以维护。
2.滑动窗口的策略
只有收到应答的数据,才会被滑出窗口,来到窗口的左边;
如果窗口内发送的数据丢包,由于确认序号:只能确认序号之前的数据被接受。所有回应滑动窗口的应答都会只表示未收到的数据之前的序号(比如发送数据1000~5000,其中1001到2001的数据丢失,所有应答的确认序号都是1001)。
如果连续收到三个重复的确认序号,就会触发快重传机制:补发重复确认序号的报文。
如果数据发送的次数少(数据是一批一批发的),ACK也少,导致有丢失但是收到的重复确认序号少于三个,就触发超时重传机制!。
8.拥塞控制
此处引入一个概念称为拥塞窗口
发送开始的时候, 定义拥塞窗口大小为 1;
每次收到一个 ACK 应答, 拥塞窗口加 1;
每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口;
像上面这样的拥塞窗口增长速度, 是指数级别的. "慢启动" 只是指初使时慢, 但是增长速
度非常快.为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍.此处引入一个叫做慢启动的阈值
当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长