协议的概念

从应用的角度出发,协议可理解为『规则』,是数据传输和数据解释的规则。

协议如同人与人的对话

OSI 参考模型和 TCP/IP 协议

网络分层架构目的:为了减少协议设计的复杂性,大多数网络模型均采用分层的方式来组织。每一层都有自己的功能,就想建筑物一样,每一层都靠下一层支持。每一层利用下一层提供的服务来为上一层提供服务,本层服务的实现细节对上层屏蔽。好处:分工明确。

OSI 参考模型作为一种概念模型,由国际标准化组织(ISO)提出,一个试图使各种计算机在世界范围内互连为网络的标准框架。我们熟悉的 HTTP、FTP 等协议都工作在最顶端的应用层(Application Layer)。

而 TCP/IP 协议族(Protocol Suite)将软件通信过程抽象化为四个抽象层,常被视为是简化的七层OSI模型。当多个层次的协议共同工作时,类似数据结构中的堆栈,因此又被称为 TCP/IP 协议栈(Protocol Stack)。『TCP/IP 协议栈包含 IP, TCP, UDP, HTTP, SMTP等协议』

越下面的层,越靠近硬件;越上面的层,越靠近用户。

OSI模型和TCP/IP协议

OSI 参考模型

在这一模型中,每个分层都接受由它下一层所提供的特定服务,并且负责为自己的上一层提供特定的服务。上下层之间进行交互时所遵循的约定叫做『接口』;同一层之间的交互所遵循的约定叫做『协议』。

通过对话理解分层

OSI协议与OSI参考模型

TCP/IP协议

应用层:主要是根据协议处理传输层的收发数据,提取有用信息(应用层的作用:就是规定应用程序的数据格式)

传输层:主要是处理端口号 port【端口号:网络程序的一个编号/标识】(包含TCP/UDP协议)

网络层:主要是处理 ip 信息

链路层:主要是处理 mac 地址(即物理地址)

互联网程序通讯的流程:一开始利用网络层进行『广播』寻找目标 IP 组,然后再利用链路层的 mac 地址寻找目标设备(如电脑),然后再利用传输层的端口号寻找目标应用程序,最后利用应用层协议解析数据并提供有用信息。

四层协议

每一层都定义了一些协议

网络通信条件

  1. 网卡,mac 地址(不需要用户处理,ARP => 通过 ip 可以找到 mac 地址)

  2. 逻辑地址,即 ip 地址(需要用户指定) => 为了确定哪台电脑接收

  3. 端口 => 为了确定哪个程序接收

    • 同一系统,一个端口只能绑定一个程序
    • 不同系统,同一端口对应的程序可能不一样

通信过程如何组包和拆包

飞Q通讯过程的组包和拆包流程

粘包问题

严格意义上来说,TCP协议本身是没有粘包问题的,粘包问题是因为程序员有特殊的需求:需要对数据包进行拆分解析,才导致需要程序员自己解决粘包问题

TCP 的粘包问题,这种叫法是不太对的,本身 TCP 就是面向连接的流式传输协议,特性如此,我们却说是 TCP 这个协议出了问题,这只能说是使用者的无知。多个数据包粘连到一起无法拆分是我们的需求过于复杂造成的,是程序员的问题而不是协议的问题,TCP 协议表示这锅它不想背。

粘包出现的根本原因

是不确定消息的边界。接收端在面对"无边无际"的二进制流的时候,根本不知道收了多少 01 才算一个消息。一不小心拿多了就说是粘包。其实粘包根本不是 TCP 的问题,是使用者对于 TCP 的理解有误导致的一个问题。『简单理解:就是发消息太快了,没带标点,导致多拿了数据,这就是粘包』

只要在发送端每次发送消息的时候给消息带上识别消息边界的信息,接收端就可以根据这些信息识别出消息的边界,从而区分出每个消息。

常见解决方案:

  • 在发送数据块之前,在数据块最前边添加一个固定大小的数据头,这时候数据有两部分组成:数据头+数据块

    • 数据头:存储当前数据包的总节数,接收端先接收数据头,然后再根据数据头接受对应大小的字节
    • 数据块:当前数据包的内容
  • 在头尾加入特殊标志

加数据头

发送方产生粘包

采用 TCP 协议传输数据的客户端与服务器经常是保持一个长连接的状态(一次连接发一次数据不存在粘包),双方在连接不断开的情况下,可以一直传输数据。但当发送的数据包过于的小时,那么 TCP 协议默认的会启用 Nagle 算法,将这些较小的数据包进行合并发送(缓冲区数据发送是一个堆压的过程);这个合并过程就是在发送缓冲区中进行的,也就是说数据发送出来它已经是粘包的状态了。『就算关闭 Nagle 算法也无法解决粘包问题』

接收方产生粘包

接收方采用 TCP 协议接收数据时的过程是这样的:数据到接收方,从网络模型的下方传递至传输层,传输层的 TCP 协议处理是将其放置接收缓冲区,然后由应用层来主动获取(C 语言用 recv、read 等函数);这时会出现一个问题,就是我们在程序中调用的读取数据函数不能及时的把缓冲区中的数据拿出来,而下一个数据又到来并有一部分放入的缓冲区末尾,等我们读取数据时就是一个粘包。(放数据的速度 > 应用层拿数据速度)

TCP通信-三次握手与四次挥手

图片

下图是一次 TCP 通讯的时序图。TCP 连接建立断开。包含大家熟知的三次握手和四次挥手。

图片

在这个例子中,首先客户端主动发起连接、发送请求,然后服务器端响应请求,然后客户端主动关闭连接。两条竖线表示通讯的两端,从上到下表示时间的先后顺序。注意,数据从一端传到网络的另一端也需要时间,所以图中的箭头都是斜的。

TCP 状态

在TCP层,有个FLAGS字段,这个字段有以下几个标识:SYN, FIN, ACK, PSH, RST, URG.

其中,对于我们日常的分析有用的就是前面的五个字段。它们的含义是:

  • SYN 表示建立连接,
  • FIN 表示关闭连接,
  • ACK 表示响应,
  • PSH 表示有 DATA 数据传输,
  • RST 表示连接重置。

三次握手

三次挥手

所谓三次握手(Three-Way Handshake)即建立 TCP 连接,就是指建立一个 TCP 连接时,需要客户端和服务端总共发送 3 个包以确认连接的建立。好比两个人在打电话:

Client:“喂,你听得到吗?”

Server:“我听得到,你听得到我吗?”

Client:“我能听到你,今天 balabala…”

建立连接(三次握手)的过程:

  1. 客户端发送一个带 SYN 标志的 TCP 报文到服务器。这是上图中三次握手过程中的段 1。客户端发出 SYN 位表示连接请求。序号是 1000,这个序号在网络通讯中用作临时的地址,每发一个数据字节,这个序号要加 1,这样在接收端可以根据序号排出数据包的正确顺序,也可以发现丢包的情况。

    另外,规定 SYN 位和 FIN 位也要占一个序号,这次虽然没发数据,但是由于发了 SYN 位,因此下次再发送应该用序号 1001。

    mss 表示最大段尺寸,如果一个段太大,封装成帧后超过了链路层的最大长度,就必须在 IP 层分片,为了避免这种情况,客户端声明自己的最大段尺寸,建议服务器端发来的段不要超过这个长度。

  2. 服务器端回应客户端,是三次握手中的第 2 个报文段,同时带 ACK 标志和 SYN 标志。表示对刚才客户端 SYN 的回应;同时又发送 SYN 给客户端,询问客户端是否准备好进行数据通讯。

    服务器发出段 2,也带有 SYN 位,同时置 ACK 位表示确认,确认序号是 1001,表示 “我接收到序号 1000 及其以前所有的段,请你下次发送序号为 1001 的段”,也就是应答了客户端的连接请求,同时也给客户端发出一个连接请求,同时声明最大尺寸为 1024。

  3. 客户必须再次回应服务器端一个 ACK 报文,这是报文段 3。

    客户端发出段 3,对服务器的连接请求进行应答,确认序号是 8001。在这个过程中,客户端和服务器分别给对方发了连接请求,也应答了对方的连接请求,其中服务器的请求和应答在一个段中发出。

    因此一共有三个段用于建立连接,称为 “三方握手”。在建立连接的同时,双方协商了一些信息,例如,双方发送序号的初始值、最大段尺寸等。

    数据传输的过程:

    • 客户端发出段 4,包含从序号 1001 开始的 20 个字节数据。
    • 服务器发出段 5,确认序号为 1021,对序号为 1001-1020 的数据表示确认收到,同时请求发送序号 1021 开始的数据,服务器在应答的同时也向客户端发送从序号 8001 开始的 10 个字节数据。
    • 客户端发出段 6,对服务器发来的序号为 8001-8010 的数据表示确认收到,请求发送序号 8011 开始的数据。 在数据传输过程中,ACK 和确认序号是非常重要的,应用程序交给 TCP 协议发送的数据会暂存在 TCP 层的发送缓冲区中,发出数据包给对方之后,只有收到对方应答的 ACK 段才知道该数据包确实发到了对方,可以从发送缓冲区中释放掉了,如果因为网络故障丢失了数据包或者丢失了对方发回的 ACK 段,经过等待超时后 TCP 协议自动将发送缓冲区中的数据包重发。

四次挥手

四次挥手

所谓四次挥手(Four-Way-Wavehand)即终止 TCP 连接,就是指断开一个 TCP 连接时,需要客户端和服务端总共发送 4 个包以确认连接的断开。在 socket 编程中,这一过程由客户端或服务器任一方执行 close 来触发。好比两个人打完电话要挂断:

Client:“我要说的事情都说完了,我没事了。挂啦?”

Server:“等下,我还有一个事儿。Balabala…”

Server:“好了,我没事儿了。挂了啊。”

Client:“ok!拜拜”

关闭连接(四次握手)的过程:

由于 TCP 连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个 FIN 来终止这个方向的连接。收到一个 FIN 只意味着这一方向上没有数据流动,一个 TCP 连接在收到一个 FIN 后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

  1. 客户端发出段 7,FIN 位表示关闭连接的请求。
  2. 服务器发出段 8,应答客户端的关闭连接请求。
  3. 服务器发出段 9,其中也包含 FIN 位,向客户端发送关闭连接请求。
  4. 客户端发出段 10,应答服务器的关闭连接请求。

建立连接的过程是三次握手,而关闭连接通常需要 4 个段,服务器的应答和关闭连接请求通常不合并在一个段中,因为有连接半关闭的情况,这种情况下客户端关闭连接之后就不能再发送数据给服务器了,但是服务器还可以发送数据给客户端,直到服务器也关闭连接为止。

参考文章

TCP通信过程-三次握手与四次挥手以及TCP状态转换

UDP通信-与TCP的差异-服务器与客户端-并发

GO的网络编程分享

硬核图解|tcp为什么会粘包?背后的原因让人暖心