搞懂Socket通信(二)

732 阅读6分钟

这是我参与8月更文挑战的第15天,活动详情查看:8月更文挑战

搞懂Socket通信(二)

上篇文章讲到,在长连接通信中可以使用多种三方框架,当第三方框架满足不了设计场景的时候,我们又该怎么办,该篇文章讲解Socket TCP的特性和原理。

一、应用场景

1.1 场景一

大家都知道,在浏览器输入一个网址,在有网络的情况下,网页上就能渲染出来页面。

这种模式是大概是这样,这段连接在请求完毕之后结束了。


graph LR

A[输入网址]-->B[请求服务器]

B-->C[服务器返回数据]

C-->D[页面渲染]

D-->E[结束]

为了满足该场景,有了Http协议,请求时开启TCP连接,请求完毕时关闭TCP连接。

1.2 场景二

A同学 发送消息 给B同学,B同学收到消息。


graph LR

A[A同学发送]-->B[服务器接收到]

B-->C[服务器把消息转给B同学]

C-->D[B同学收到消息]

D-->E[结束]

为了满足该场景,就要有双向连接,客户端可以和服务端发送消息,服务端也可以向客户端发送连接;为了能即时接到消息,就必须要实时性,就用到了长连接。

所以确立这种场景的通信是双向长连接

二、TCP特性

TCP位于传输层,相应的还有UDP传输协议,TCP为什么可靠,因为它有连接时的三次握手和断开时的四次挥手,保障发送数据的可靠性,相应的它会增加一点时间,牺牲一点效率。

为了保证数据的可靠性,我们都是用TCP传输,当我们使用Socket操作TCP时,也无需关心TCP复杂的底层逻辑。

那么TCP有什么特性

  1. TCP是全双工通信,将占用两个计算机之间的通信线路,直到它被一方或双方关闭为止

  2. TCP是可靠的,三次握手和四次挥手 + 包序号可以证明

  3. TCP是流,不是包,像水流一样,需要分割才能取出正确数据

三、使用Socket TCP该注意什么

3.1 连接状态

也可以把这个章节的标题改为其它标题,比如

为什么需要心跳?

为什么服务端断网时,客户端依旧显示连接状态?

正常情况

在正常情况下,无论是客户端还是服务端,在正常执行 socket.close()的情况下,对方是可以收到回调、可以监听到对方离线的状态。

强制断网情况

在强制断网的情况下,对方是无法监听到断开状态的

这也就是“Socket TCP 服务端断网时,客户端依旧显示连接状态”的现象。

使用socket进行测试:

  1. 客户端断网时,客户端接到断开回调

  2. 服务端手动关闭时,客户端接到断网回调

  3. 服务端运行突然断网时,客户端接收不到断网的回调(分很多场景,参考:

www.cnblogs.com/549294286/p…

为什么在服务端断网时接不到回调呢?

这是因为TCP建立的通道被破坏,无法进行断开的四次挥手(因为网络断了),造成了所谓的死链接。

这个时候是就需要心跳机制,来互相确认是否在线。

3.2 发送状态

同样,标题也可以改为

为什么我发送了消息,Socket tcp回调成功了,对方却没有收到?

长连接时靠什么保障消息的可靠性?

为什么有些Socket框架提供了发送结果的回调却并不可靠?

由于在断网情况下,TCP已经断开了,但是另一方没有及时进行断开。

但这个时候,如果发送消息给对象,得到的发送回调却是发送成功。

这是因为,当通道破坏时,另一方没有及时收到,而发送成功的连接仅仅代表数据被放进了缓冲区,并不代表数据已经发送给了对方

所以这个回调结果只是代表 我发出去了,至于我发出去之后的路上遇到了什么,就不得而知了,所以只能有参考作用。

那么如果需要把消息做的可靠,还是应该,在接收到消息时,告诉对方这条消息接收成功。

3.3 粘包分包

概念

粘包:

当发送的字节数据包比较小且频繁发送时,Socket内部会将字节数据进行粘包处理,既将频繁发送的小字节数据打包成 一个整包进行发送,降低内存的消耗。

分包:

当发送的字节数据包比较大时,Socket内部会将发送的字节数据进行分包处理,降低内存和性能的消耗。

为什么会出现粘包

发生粘包分包实际上是在TCP传输协议对数据的优化,TCP是“流”协议,传输的过程就像流水一样没有边界,没有界限,而实际上我们只需要取出我们发送的部分即可;这里说明下UDP是"数据包"协议,所以在UDP中不存在粘包分包的情况。

为什么会出现分包

在传输过程中,TCP为了保护网络(也称为流量控制),并不是接收到什么就传递什么,而是根据一系列的限制决定发送多少数据出去,这些限制有:用户缓冲区(接收发送缓冲区)、TCP底层缓冲区(各系统下表现会不同)、MTU(最大传输单元,属于数据链路层)等等

例子解释


当前发送方发送了两个包,两个包的内容如下:

123456789

ABCDEFGH

我们希望接收方的情况是:收到两个包,第一个包为:123456789,第二个包为:ABCDEFGH。但是在粘包和分包出现的情况就达不到预期情况。

粘包情况

两个包在很短的时间间隔内发送,比如在0.1秒内发送了这两个包,如果包长度足够的话,那么接收方只会接收到一个包,如下:


123456789ABCDEFGH

分包情况

假设包的长度最长设置为5字节(较极端的假设,一般长度设置为1000到1500之间),那么在没有粘包的情况下,接收方就会收到4个包,如下:


12345

6789

ABCDE

FGH

如何处理粘包分包?

在数据层面处理:

  1. 约定结束符,遇到结束符时分割数据。

  2. 发送数据时约定数据长度,获取数据时先接到长度,然后根据长度分割数据。

  3. 发送数据末尾拼接换行符,使用BufferedReaderrd.readLine(),原理同上。

技术选型:

  1. 使用成熟的socket框架,很多框架会帮忙处理合包的操作。

  2. 使用WebSocket,WebSocket是一个message-based的协议,它可以自动将数据分片,并且自动将分片的数据组装。