【Linux】TCP协议简介

TCP协议简介

  • TCP协议格式
  • 面向连接
    • 1.连接管理机制
    • 2.包序管理
  • 可靠传输
    • 1.保证数据可靠到达对端
    • 2.保证数据的传输效率
  • 面向字节流
    • TCP粘包问题

TCP协议格式

【Linux】TCP协议简介_第1张图片

16位源端口号和16位目的端口号:标识数据从哪个进程来,到哪个进程去;
32位序号:在包序管理中,发送消息方和接收消息方各自都维护了包的序号,要用包序来表示哪些数据包是收到的,哪些还未收到。
4位首部长度:表示TCP头部有多少个字节。
6位标志位:
URG:紧急指针是否有效
ACK:确认号是否有效
PSH:提示接收端立刻将数据从TCP缓冲区把数据读走
RST:对方要求重新建立连接
SYN:请求建立连接
FIN:通知对方,本端要关闭了
16位窗口大小:TCP有一个流量控制机制,对端会将自己的接收能力通过16位窗口大小告诉当前端,以便于当前端调整自己发送消息的流量大小
16位紧急指针:标识哪部分数据是紧急数据

面向连接

1.连接管理机制

  1. 三次握手
    下图是三次握手的详细流程图,左端为客户端,也就是发起连接方,右端为服务端
    【Linux】TCP协议简介_第2张图片
    两次握手为什么不行?
    两次握手会导致服务端的状态不能从SYN_RCVD转换到ESTABLISHED状态,从而无法建立连接

  2. 四次挥手
    下图为四次挥手的详细流程图,左端为主动断开连接方,右端为被动断开连接方,双方发送的数据包名称和双方的状态
    【Linux】TCP协议简介_第3张图片
    三次挥手可以吗?五次挥手可以吗?

三次挥手可以,但是很少见,三次挥手存在于被动断开连接时,没有数据要继续发送,此时利用捎带应答机制,将FIN和ACK一起发送给主动断开连接方,会变成三次挥手。但是大部分情况下,被动断开连接方很少在主动断开连接方发起断开时就完全没有消息要发送的了,所以三次挥手可以,但是可能性很小。

五次挥手可以,但是没有必要,因为四次挥手足以将双方完全断开,五次就显得多余了

2.包序管理

对于TCP协议格式中的32位序号和32位确认序号标识的是发送方和接收方各自维护的包序号,对于有数据的TCP包来说,会将每个字节都进行编号,这个编号就是包序号,(除了TCP协议头部之外的数据称为纯有效载荷)有应用层数据的包称为TCP数据包,没有应用层数据的称为TCP协议包,而纯的ACK协议包是不消耗序号的。

可靠传输

1.保证数据可靠到达对端

有两个机制:确认应答机制和超时重传机制

确认应答机制:
当A给B机器发送了1000字节的数据之后,B必须要给A回复1001,表示B已经接收到了A发送的1000个数据,这样就能确保数据是一定到达对端的。

超时重传机制:
当A给B发送了1000个字节,B迟迟不回复给A消息,表明可能A发送的消息已经丢失了,那么A就会重新给B发送消息,而这个超时时间是不固定的,它随着网络的情况和双方通信的情况而变化

2.保证数据的传输效率

保证数据传输效率可以从三个角度分析:
1.自身发送数据量
2.对方的接收能力
3.网络的转发能力

依据自身发送的数据量:滑动窗口机制
也就是说所有需要发送的数据如果为n个窗口大小,当前设置4位窗口,将当前四位窗口中的数据发送到网络中,暂时不用确认,只有收到较先的确认数据之后,窗口才能向后移动,这样就能在同一时刻发送多个数据,而提高发送的数据量,唯一的缺点是需要将窗口内的数据暂时维护起来。

【Linux】TCP协议简介_第4张图片

依据对方的接收能力:有流量控制机制,延时应答机制,捎带应答机制

流量控制机制:TCP协议头部中有16位窗口大小,用于表示对方接收缓冲区的大小,当对方的接收能力强时,发送的数据就多,(收到的0表示0号窗口通告)当值为0表示对方接收缓冲区已满,就停止发送,直到接收方主动给发送发发送“窗口更新通知”或者发送方给接受方发送窗口探测数据包,探测接收方的接收能力。

延时应答机制:为了让接收方给发送方一个更大的窗口,会在向发送方应答时延时200ms,等待接收方将数据从缓冲区中读走,再向发送方应答。

捎带应答:当服务端给客户端发消息时,可以将上次向客户端确认的消息和此次需要发送的消息一起发送给客户端

依据网络的转发能力:拥塞控制

慢启动 机制:先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据;
【Linux】TCP协议简介_第5张图片

面向字节流

创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区;
调用write,数据写入发送缓冲区,太长会被拆分,太短会在缓冲区内等待

TCP粘包问题

如果包长度是固定的,那么在应用层进行读取时,按照固定长度读取即可
如果包长度是不固定的,那么可以在每个数据包前定义一个字段记录当前数据包的长度,或者在每个数据包的尾部加上一个分隔符,用于标识到达了数据的尾部

你可能感兴趣的:(linux,linux,网络)