目录
- 1.TCP头部格式
- 2.TCP协议的特点
- 3.TCP如何封装与分用
- 4.通过序列号和确认应答号提高可靠性
- 1.32位序列号
- 2.32位确认应答号
- 3.保证可靠性
- 4.为什么序列号和确认应答号是单独的字段
- 5.窗口大小
- 1.TCP的发送和接收缓冲区
- 2.窗口大小
- 6.连接管理机制
1.TCP头部格式
-
TCP全称为"传输控制协议"(Transmission Control Protocol),人如其名, 要对数据的传输进行一个详细的控制
-
源/目标端口号:表示数据是从哪个进程来,到哪个进程去
-
序列号:在建立连接时由计算机生成的随机数作为初始值,通过SYN包传给接收端主机,后续每发一次数据,就累加一次数据字节数大小
- 用来解决网络包乱序问题
-
确认应答号:指下一次期望收到的序列号,发生端收到这个确认应答号后可以认为这个序列号之前的数据都已经被正常接收
- 用来解决丢包问题
-
序列号和确认应答号:
- 将请求和应答进行一一对应
- 允许部分确认丢失,或者不给应答
-
4位TCP报头长度:表示该TCP头部有多少个32位bit(有多少个4字节),所以TCP头部最大长度是15 * 4 = 60
-
6位标志位:
- URG:该位为1时,表明紧急指针字段有效
- 它告诉系统此报文段中有紧急数据,应当尽快传输(相当于高优先级的数据),而不要按原来的排队顺序来传送
- ACK:该位为1时,表明确认应答字段有效
- TCP规定除了最初建立连接时的SYN包之外,该位必须置为1
- PSH:该位为1时,提示接收端应用程序立刻从TCP缓冲区把数据读取走
- 督促对方尽快将数据进行向上交付
- RST:该位为1时,表示要求重新建立连接,以下四种情况会发送RST包
- 端口未打开
- 请求超时
- 提前关闭
- 在一个已关闭的socket上收到数据
- SYN:该位为1时,表示希望建立连接,并在序列号的字段进行初识序列号值的设定
- FIN:该位为1时,表示今后不会再有数据发送,希望断开连接
- URG:该位为1时,表明紧急指针字段有效
-
16位窗口大小
-
16位校验和:发送端填充,CRC校验。接收端校验不通过,则认为数据有问题。此处的检验和不仅包含TCP首部,也包含TCP数据部分
-
16位紧急指针:标识哪部分数据是紧急数据
-
40字节头部选项
2.TCP协议的特点
- 面向连接:一定是一对一才能连接,不能像UDP协议那样可以一个主机同时向多个主机发生消息,也就是一对多是无法做到的
- 可靠的:无论网络链路层出现了什么变化,TCP都可以保证一个报文一定能够到达接收端
- 字节流:
- 消息是没有边界的,所以无论消息有多大都可以进行传输
- 并且消息是有序的,当前一个消息还没收到的时候,即便收到了后面的字节,那么也不能交付给应用层去处理,同时对重复的报文会自动丢弃
3.TCP如何封装与分用
- 4位首部长度:
- 它表示的是TCP的首部长度,基本单位是4字节 ,能够表示的范围 [0000 , 1111]
- 当这个字段填入的是1111时**(即15)**,那么TCP首部长度为 15 * 4 = 60 字节
- TCP首部长度 - TCP标准长度 = 60 - 20 = 40字节,这个就表示选项字段的长度
- 一般4位首部长度用0101表示(即5),那么TCP首部长度就等于20字节,也就是TCP的标准长度
- 如何封装:将应用层的有效载荷加上TCP的首部长度,交付给下一层
- 如何分用:
- 根据4位首部长度,计算出TCP的标准长度与选项长度
- 如果TCP的标准长度刚好是20字节,那么剩下的就是有效载荷
- 如果大于20字节,则减去20字节后,得到选项字段的长度,再读取完选项的长度后,便可得到有效载荷
4.通过序列号和确认应答号提高可靠性
1.32位序列号
- 对于32位序列号,TCP将每个字节的数据都进行了编号,即为序列号
- 从下图可以看出,当想要发送比较大的数据时,TCP会将这些数据按字节进行编号,然后按照编号的顺序发送给服务器
- 这样一来,32位序列号的作用就是能够保证报文的按序到达
- **注意:**客户端向服务端发送报文时,序列号初始值并非为0,而是随机初始化这个序列号
2.32位确认应答号
- 32位确认应答号主要是用来对历史报文数据的一个响应,以客户端发送报文给服务端为例:
- 当客户端发送报文给服务端时,本质上发送的是序列号,如果发生的序列号为100,当服务端收到客户端发来的序列号后,服务端就会给客户端响应一个报文,报文内部附带着确认应答号,这个确认应答号就是对客户端发来的序列号+1
- 当客户端发送报文给服务端时,本质上发送的是序列号,如果发生的序列号为100,当服务端收到客户端发来的序列号后,服务端就会给客户端响应一个报文,报文内部附带着确认应答号,这个确认应答号就是对客户端发来的序列号+1
3.保证可靠性
-
最核心的机制就是基于序号的确认应答机制 ,TCP不能做到100%可靠,但是通过应答机制来确保上一条信息是不是100%被对方收到
- 只要有一条信息应答了,就可以肯定上一条消息是可靠的被对方收到了
- 只要有一条信息应答了,就可以肯定上一条消息是可靠的被对方收到了
-
当客户端向服务端发送了0~100之间的数据时,本质上就是发送了一个报文,如果服务端没有响应,那么客户端就不知道自己发出的数据是否被服务端收到,所以只有当服务端给客户端一个响应后,客户端就知道自己刚刚发出的数据已经被服务端收到了,这个确认应答号就保证了历史报文的可靠性
4.为什么序列号和确认应答号是单独的字段
-
如果TCP的报头将这两个字段设计成了一个字段(这里假设为序列号),那么新的TCP头部格式如下
-
如下图所示:当客户端向服务端发送数据时,服务端给出响应;当服务端给客户端发送数据时,客户端再给出响应
-
这样的效率明显很低,并且TCP是全双工通信协议,双方通信时,一个报文,既可以携带要发送的数据,也可以携带对历史报文的确认
- 即:TCP是全双工的,任何一方,既可以收,又可以发
- 即:TCP是全双工的,任何一方,既可以收,又可以发
5.窗口大小
1.TCP的发送和接收缓冲区
-
TCP本身是具有接收缓冲区和发送缓冲区的
- 接收缓冲区用来暂时保存接收到的数据
- 发送缓冲区用来暂时保存还未发送的数据
- 这两个缓冲区都是在TCP传输层内部实现的
-
可以理解为在TCP内部malloc了2段空间
-
当客户端的应用层调用write/send时,并不是将数据发送到网络上,而是将应用层的数据拷贝到TCP自带的发送缓冲区中,在已经连接好的客户服务器中,经由网络传输到服务端的接收缓冲取中,服务端的接收缓冲区有数据后,调用read/recv时,是将服务端的接收缓冲区的数据拷贝给应用层
-
当数据写入到TCP的发送缓冲区后,对应的write/send函数就可以返回了,至于发送缓冲区当中的数据具体什么时候发,怎么发等问题实际都是由TCP决定的
-
之所以称TCP为传输层控制协议,就是因为最终数据的发送和接收方式,以及传输数据时遇到的各种问题应该如何解决,都是由TCP自己决定的,用户只需要将数据拷贝到TCP的发送缓冲区,以及从TCP的接收缓冲区当中读取数据即可
-
TCP的发送缓冲区和接收缓冲区存在的意义?
- 数据在网络中传输时可能会出现某些错误,此时就可能要求发送端进行数据重传,因此TCP必须提供一个发送缓冲区来暂时保存发送出去的数据,以免需要进行数据重传。只有当发出去的数据被对端可靠的收到后,发送缓冲区中的这部分数据才可以被覆盖掉
- 接收端处理数据的速度是有限的,为了保证没来得及处理的数据不会被迫丢弃,因此TCP必须提供一个接收缓冲区来暂时保存未被处理的数据,因为数据传输是需要耗费资源的,不能随意丢弃正确的报文
- 此外,TCP的数据重排也是在接收缓冲区当中进行的
- 因为缓冲区的存在,所以可以做到应用层和TCP进行解耦
2.窗口大小
-
窗口大小反映的就是客户端或服务端发送和接收缓冲区剩余空间的大小
-
如下图所示,假设客户端和服务端各自的缓冲区只能容纳100字节的数据,当客户端向服务端发送20个字节的数据后,服务端的接收缓冲区的大小就变成了80字节;反过来对于客户端也是一样
-
当发送端要将数据发送给对端时,本质是把自己发送缓冲区当中的数据发送到对端的接收缓冲区当中。但缓冲区是有大小的,如果接收端处理数据的速度小于发送端发送数据的速度,那么总有一个时刻接收端的接收缓冲区会被打满,这时发送端再发送数据过来就会造成数据丢包,进而引起丢包重传等一系列的连锁反应
-
在TCP报文中有了这个字段后, 接收端在对发送端发来的数据进行响应时,就可以通过16位窗口大小这个字段告知发送端自己当前接收缓冲区剩余空间的大小,此时发送端就可以根据这个窗口大小字段来调整自己发送数据的速度
- 窗口大小字段越大,说明接收端接收数据的能力越强,此时发送端可以提高发送数据的速度
- 窗口大小字段越小,说明接收端接收数据的能力越弱,此时发送端可以减小发送数据的速度
- 如果窗口大小的值为0,说明接收端接收缓冲区已经被打满了,此时发送端就不应该再发送数据了
6.连接管理机制
- 服务端状态转化:
- [CLOSED -> LISTEN]:服务器端调用listen后进入LISTEN状态,等待客户端连接
- [LISTEN -> SYN_RCVD]:一旦监听到连接请求(同步报文段),就将该连接放入内核等待队列中,并向客户端发送SYN确认报文
- [SYN_RCVD -> ESTABLISHED]:服务端一旦收到客户端的确认报文,就进入ESTABLISHED状态,可以进行读写数据了
- [ESTABLISHED -> CLOSE_WAIT]:当客户端主动关闭连接(调用close),服务器会收到结束报文段,服务器返回确认报文段并进入CLOSE_WAIT
- [CLOSE_WAIT -> LAST_ACK]:进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据),当服务器真正调用close关闭连接时,会向客户端发送FIN,此时服务器进入LAST_ACK状态,等待最后一个ACK到来(这个ACK是客户端确认收到了FIN)
- [LAST_ACK -> CLOSED]:服务器收到了对FIN的ACK, 彻底关闭连接
- 客户端状态转化:
- [CLOSED -> SYN_SENT]:客户端调用connect,发送同步报文段
- [SYN_SENT -> ESTABLISHED]:connect调用成功,则进入ESTABLISHED状态,开始读写数据
- [ESTABLISHED -> FIN_WAIT_1]:客户端主动调用close时,向服务器发送结束报文段,同时进入FIN_WAIT_1
- [FIN_WAIT_1 -> FIN_WAIT_2]:客户端收到服务器对结束报文段的确认,则进入FIN_WAIT_2,开始等待服务器的结束报文段
- [FIN_WAIT_2 -> TIME_WAIT]:客户端收到服务器发来的结束报文段,进入TIME_WAIT,并发出LAST_ACK
- [TIME_WAIT -> CLOSED]:客户端要等待一个2MSL(Max Segment Life,报文最大生存时间)的时间,才会进入CLOSED状态