socket和TCP拆包粘包 Day10


套接字socket

socket

socket通常称为“套接字”,用于描述IP地址和端口,是一个通信链的句柄。应用程序通过套接字向网络发出请求或应答网络请求。

服务器和客户端通过socket进行交互。服务器需要绑定在本机的某个端口号上,客户端需要声明自己连接哪个地址的哪个端口,这样服务器和客户端就能连接了。

根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
(1)服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
(2)客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
(3)连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
socket是对TCP/IP协议的封装和应用。在TCP/IP协议中,TCP协议通过三次握手建立一个可靠的连接。
第一次握手:客户端尝试连接服务器,向服务器发送syn包(同步序列编号Synchronize Sequence Numbers),syn=j,客户端进入SYN_SEND状态等待服务器确认。
第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

服务器socket与客户端socket建立连接的部分其实就是“三次握手”。

socket和http区别

socket连接:
socket不属于协议范畴,而是一个调用接口(API),是对TCP/IP协议的封装。实现服务器与客户端之间的物理连接,并进行数据传输。Socket处于网络协议的传输层,主要有TCP/UDP两个协议。
socket连接是长连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉;但是由于各种环境因素可能会是连接断开,比如:服务器端或客户端主机宕机了、网络故障,或者两者之间长时间没有数据传输,网络防火墙可能会断开该连接以释放网络资源。所以当一个socket连接中没有数据的传输,那么为了维持连接需要发送心跳消息。
socket传输的数据可自定义,为字节级,数据量小,可以加密,数据安全性高,适合Client/Server之间信息实时交互。

http连接:
HTTP是基于TCP/IP协议的应用层协议,定义的是传输数据的内容的规范。
HTTP是基于请求-响应形式并且是短连接,即客户端向服务器端发送一次请求,服务器端响应后连接即会断掉。
HTTP是无状态的协议,针对其无状态特性,在实际应用中又需要有状态的形式,因此一般会通过session/cookie技术来解决此问题。
HTTP的传输速度慢,数据包大,数据传输安全性差,如实现实时交互,服务器性能压力大。

socket应用场景

socket一般用于比较即时的通信和实时性较高的情况,比如推送,聊天,保持心跳长连接等;
http一般用于实时性要求不那么高的情况,比如信息反馈,图片上传,获取新闻信息等。

某些场合使用http不使用socket的原因是socket一旦连接上了,就一直保持连接,就会造成阻塞IO,与此相对的的,还有非阻塞IO。
阻塞IO是指客户端请求服务器端,服务器端进行处理后,返回值给客户端。但当客户端太多、同时并发请求的时候,服务器端处理不过来,就会卡,因此提出了非阻塞IO,客户端请求服务器端,服务器端有一些监听器负责接受请求,接受好了,就告诉客户端已经接受好了,处理完后把数据发给客户端。

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

WebSocket

我们知道 TCP 连接的两端,同一时间里,双方都可以主动向对方发送数据。这就是所谓的全双工。

而现在使用最广泛的HTTP/1.1,也是基于TCP协议的,同一时间里,客户端和服务器只能有一方主动发数据,这就是所谓的半双工。

也就是说,好好的全双工 TCP,被 HTTP/1.1 用成了半双工。

为什么?

这是由于 HTTP 协议设计之初,考虑的是看看网页文本的场景,能做到客户端发起请求再由服务器响应,就够了,根本就没考虑网页游戏这种,客户端和服务器之间都要互相主动发大量数据的场景。

所以,为了更好的支持这样的场景,我们需要另外一个基于TCP的新协议。

于是新的应用层协议WebSocket就被设计出来了。

大家别被这个名字给带偏了。虽然名字带了个socket,但其实 socket 和 WebSocket 之间,就跟雷峰和雷峰塔一样,二者接近毫无关系。

TCP拆包粘包

粘包

在进行 Java NIO 学习时,可能会发现:如果客户端连续不断的向服务端发送数据包时,服务端接收的数据会出现两个数据包粘在一起的情况。

1.TCP 是基于字节流的,虽然应用层和 TCP 传输层之间的数据交互是大小不等的数据块,但是 TCP 把这些数据块仅仅看成一连串无结构的字节流,没有边界;
从 TCP 的帧结构也可以看出,在 TCP 的首部没有表示数据长度的字段。

2.基于上面两点,在使用 TCP 传输数据时,才有粘包或者拆包现象发生的可能。一个数据包中包含了发送端发送的两个数据包的信息,这种现象即为粘包。

接收端收到了两个数据包,但是这两个数据包要么是不完整的,要么就是多出来一块,这种情况即发生了拆包和粘包。拆包和粘包的问题导致接收端在处理的时候会非常困难,因为无法区分一个完整的数据包。

产生原因

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

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

解决方法

分包机制一般有两个通用的解决方法:

1.特殊字符控制;
2.在包头首都添加数据包的长度。

如果使用 netty 的话,就有专门的编码器和解码器解决拆包和粘包问题了。

tips:UDP 没有粘包问题,但是有丢包和乱序。不完整的包是不会有的,收到的都是完全正确的包。传送的数据单位协议是 UDP 报文或用户数据报,发送的时候既不合并,也不拆分。

总结

TCP是面向流,没有界限的一串数据。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。

为什么会产生粘包和拆包呢?

1.要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包;
2.接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包;
3.要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包;
4.待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。即TCP报文长度-TCP头部长度>MSS。

解决方案:

1.发送端将每个数据包封装为固定长度
2.在数据尾部增加特殊字符进行分割
3.将数据分为两部分,一部分是头部,一部分是内容体;其中头部结构大小固定,且有一个字段声明内容体的大小。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值