1、TCP 协议
TCP 是工作中最常用到的协议,也是面试中最常考的协议,具有面向连接,可靠传输,面向字节流,全双工的特点,其中可靠传输是 TCP 安身立命的本钱,TCP 设计的初心就是为了解决“可靠传输”这个问题。
2、确认应答
确保 TCP 可靠性最核心的机制,称为“确认应答”。发送方给接收方发送消息成功后,接收方会返回一个应答报文(ack 报文,acknowledge)。
ack(acknowledge,应答报文)
2.1、确认序号
确认序号是返回的应答报文中的一个值,确认序号是按照发送过去的最后一个字节的序号再加上 1 来进行设定的。
如上图所示,主机 B 收到 1 - 1000 这些字节数据之后,反馈了一个应答报文。应答报文中的确认序号的值就是 1001。 此处发送回去的 1001 有两个含义:
- 告诉发送方主机 A ,小于 1001 的数据已经收到了
- 发送方主机 A接下来要发送从 1001 开始的数据
3、超时重传
超时重传,是确认应答的补充,用于处理丢包情况。
如果一切顺利,通过应答报文就可以告诉发送方,当前的数据是否成功收到,但是网络上可能存在“丢包”情况。如果数据包丢了,没有到达对方,对方自然也就不会发送 ack 应答报文。为了针对这种情况,提出了“超时重传”。
超时重传简单来说就是:发送方发了一个数据后,会进行等待,如果超过了预设的等待时间依然没有 ack 应答报文返回(超时),此时发送方就会认为数据的传输出现了丢包,就会将刚才发送的数据包再传输一次(重传)。
重传并不会无限的重传,当重传次数到达一定上限时,会尝试重置连接,如果重置也失败,则会直接放弃连接。并且重传的超时时间阈值也不是固定不变的,会随着重传次数的增加而增大(重传频率越来越低)。
当然,发送方没有接收到 ack 应答报文,有两种情况,一种是数据丢了,一种是接收方收到了数据,再回传 ack 时 ack 丢了。
当 ack 丢失时,会导致发送多次相同数据到接收方,此时接收方可以通过“接收缓冲区”将重复的数据过滤掉,防止因为重复数据出现问题。
接收缓冲区除了能够帮助我们进行去重之外,还能进行排序,对收到的数据按照序号进行排序,确保应用程序读到的数据和发送的数据顺序是一致的,可以简单认为接收缓冲区是一个“优先级队列”,以序号作为优先级的参考依据。
注意:上述谈到的 ack、超时重传、保证顺序、自动去重,都是 TCP 内置的,只需要调用 outputStream.write() 上述功能即可自动生效。
反之如果使用 UDP,上述问题都需要好好考虑并处理。
4、连接管理
4.1、建立连接(三次握手)
操作系统内核建立连接的过程称为“三次握手”,目的是让通信双方都能保存对方的相关信息。
socket = new Scoket(serverIp,serverPort);
这个操作就是在进行“三次握手”,而服务器是在“三次握手”之后通过 accept 从队列中获取最新连接
syn(synchronize,同步报文段)不携带载荷,只携带报头。
第一次交互,一定是客户端主动发起的,此时客户端会发送一个 syn 。服务器收到 syn 之后,会返回 ack (应答报文)和 syn ,其中 ack 表示收到了连接请求,syn 表示接收连接。此时客户端接收到 syn 后再给服务器发送一个 ack。
所谓的建立连接的过程,本质上就是通信双方各自给对方发送一个 syn,各自给对方回一个 ack。
由于服务器返回 ack 和 syn 的过程和应用程序代码无关,由内核完成,中间两步的触发时机几乎完全一致,因此可以合并为一步。
“三次握手”的意义:
- 可以先针对通信路径,进行投石问路,初步的确认以下通信链路是否畅通。【关注点在中间过程】
- 验证通信双方的“发送能力”和“接受能力”是否正常。【关注点在两端】
- 三次握手的过程中协商一些必要的参数。例如序号
4.2、断开连接(四次挥手)
通过 socket.close() 开始执行断开连接操作。
FIN(finish,结束报文段)
在实际过程中,也可能是“三次挥手”,三次和四次取决于第一个 ack 和第二个 fin 的时间间隔,由于这两者之间的间隔与应用程序代码有关,如果时间间隔比较长,此时就无法进行合并。但是通常情况下还是认为是四次挥手。
所谓的断开连接的过程,本质上就是通信双方各自给对方发送一个 fin,各自给对方回一个 ack。