TCP和UDP
TCP:传输控制协议
UDP:用户数据报协议
两者区别
1、TCP 是面向连接的(三次握手建立连接),UDP 是无连接的即发送数据前不需要先建立链接。
2、TCP 提供可靠的服务。也就是说,通过 TCP 连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP 尽最大努力交付,不保证可靠交付。 (并且因为 TCP 可靠,面向连接,不会丢失数据因此适合大数据量的交换)
3、TCP 是面向字节流,UDP 面向报文,并且网络出现拥塞不会使得发送速率降低(因此会出现丢包,对实时的应用比如 IP 电话和视频会议等)。
4、TCP 只能是 1 对 1 的,UDP 支持 1 对 1,1 对多。
5、TCP 的首部较大为 20 字节,而 UDP 只有 8 字节。
6、TCP 是面向连接的可靠性传输,而 UDP 是不可靠的
图表对比:
TCP | UDP | |
---|---|---|
是否连接 | 面向连接 | 无连接 |
是否可靠 | 可靠 | 不可靠 |
连接对象个数 | 只能1对1通信 | 支持一对一、一对多、多对一、多对多交互通信 |
传输方式 | 面向字节流 | 面对报文 |
首部开销 | 首部 开销较大,最小20字节,最大60字节 | 首部开销小,仅8字节 |
适应场景 | 适应于可靠传输的应用,如文件 | 适应于实时应用(IP电话、视频会议) |
TCP如何保证传输可靠性?
TCP建立可靠连接的过程:三次握手、传输确认、四次挥手
三次握手
三次握手是建立连接的过程
当客户端向服务端发起连接请求时,会先发送SYN数据包去询问服务端能否建立连接,如果对端同意连接,则回复SYN+ACK数据包,客户端收到之后回复ACK数据包,则连接建立。
三次握手的原因:防止已失效的请求报文突然又传到服务器引起错误,解决网络信道不可靠的原因。
传输确认
1、TCP协议为每个连接建立了一个发送缓冲区,从建立连接后的第一个字节的序列号为0,后面每个字节的序列号就会增加1;
2、发送数据时,从发送缓冲区取一部分数据组成发送报文,在其TCP协议头中会附带序列和长度;
3、接收端在收到数据后需要回复确认报文确认报文中的ACK等于接受序列号+长度,也就是下一包数据需要发送的起始序列号。这样一问一答的方式能够使发送端确认发送的数据已经被对方收到,发送端也可以一次发送多包数据,接收端只需要回复一次ACK就可以了,这样发送端可以把待发送的数据分割成一系列碎片发送到对端,对端根据序列号和长度在接收后重构出来完整的数据;
4、假设丢失了某些数据包,在接收端可以要求发送端重传,以上连接不区分客户端和服务端,TCP连接是全双工的。
四次挥手
四次挥手用来进行连接关闭
1、当客户端主动发起关闭连接请求,则向服务端发送一包FIN包,表示要关闭连接,然后进入终止等待1状态,即为第一次挥手;
2、服务端收到FIN包,发送一包ACK包,表示自己进入关闭等待状态,客户端进入终止等待2状态,即为第二次挥手,服务端此时还可以发送未发送的数据,而客户端还可以接收数据;
3、待服务端发送完数据之后,发送一包FIN包进入最后确认状态,即为第三次挥手;
4、客户端收到之后回复ACK包,进入超时等待状态,经过超时时间后关闭连接,而服务端收到ACK包后立即关闭连接,即为第四次挥手。
为什么客户端需要等待超时时间?
为了保证对方已收到ACK包,假设客户端发送完最后一包ACK包就释放了连接,一旦ACK包在网络中丢失,服务端将一直停留在最后确认状态,如果客户端发送最后一包ACK包后等待一段时间,这时服务端因为没有收到ACK包会重发FIN包,客户端会响应这个FIN包重发ACK包并刷新超时时间,进行可靠的连接断开确认。
TCP粘包、拆包及解决办法
为什么常说 TCP 有粘包和拆包的问题而不说 UDP ?
由前两节可知,UDP 是基于报文发送的,UDP首部采用了 16bit 来指示 UDP 数据报文的长度,因此在应用层能很好的将不同的数据报文区分开,从而避免粘包和拆包的问题。
而 TCP 是基于字节流的,虽然应用层和 TCP 传输层之间的数据交互是大小不等的数据块,但是 TCP 并没有把这些数据块区分边界,仅仅是一连串没有结构的字节流;另外从 TCP 的帧结构也可以看出,在 TCP 的首部没有表示数据长度的字段,基于上面两点,在使用 TCP 传输数据时,才有粘包或者拆包现象发生的可能。
什么是粘包、拆包?
假设 Client 向 Server 连续发送了两个数据包,用 packet1 和 packet2 来表示,那么服务端收到的数据可以分为三种情况,现列举如下:
第一种情况,接收端正常收到两个数据包,即没有发生拆包和粘包的现象。
第二种情况,接收端只收到一个数据包,但是这一个数据包中包含了发送端发送的两个数据包的信息,这种现象即为粘包。这种情况由于接收端不知道这两个数据包的界限,所以对于接收端来说很难处理。
第三种情况,这种情况有两种表现形式,如下图。接收端收到了两个数据包,但是这两个数据包要么是不完整的,要么就是多出来一块,这种情况即发生了拆包和粘包。这两种情况如果不加特殊处理,对于接收端同样是不好处理的。
为什么会发生 TCP 粘包、拆包?
- 要发送的数据大于 TCP 发送缓冲区剩余空间大小,将会发生拆包。
- 待发送数据大于 MSS(最大报文长度),TCP 在传输前将进行拆包。
- 要发送的数据小于 TCP 发送缓冲区的大小,TCP 将多次写入缓冲区的数据一次发送出去,将会发生粘包。
- 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。
粘包、拆包解决办法
由于 TCP 本身是面向字节流的,无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案,归纳如下:
- 消息定长:发送端将每个数据包封装为固定长度(不够的可以通过补 0 填充),这样接收端每次接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
- 设置消息边界:服务端从网络流中按消息边界分离出消息内容。在包尾增加回车换行符进行分割,例如 FTP 协议。
- 将消息分为消息头和消息体:消息头中包含表示消息总长度(或者消息体长度)的字段。
- 更复杂的应用层协议比如 Netty 中实现的一些协议都对粘包、拆包做了很好的处理。