闲着想做一个服务器之间的高速文件传输工具
突然发现了一些关于NIO的新坑,顺带把NIO这块会遇到的问题都整理一下
本次心得
1. NIO 其实不适合做 长连接通信,BIO更适合
因为NIO 的原理:由调度员(Selector) 把来客户端上送的数据缓冲到 SocketChannel通道中, 等待用户处理,处理好后,关闭来客连接,等待其他人上送数据
而BIO 更容易为每个客户保持一个长连接通道. 虽然NIO也可以做到,但是非常不方便.
2. 现在我非要用NIO做长连接,并上传大数据,就会遇到一系列问题
-
避免重复实例化ByteBuffer
NIO的数据是放在HeapByteBuffer 堆缓存中然后过渡给你自己定义的ByteBuffer ,这类对象不要重复实例化,因为没有必要 ,只要两个足矣,一个是读,一个是写, 我为了研究 故意用队列 插入了一堆接收到的Buf缓存,结果反而因为实例化更慢. -
ByteBuffer 会被覆盖吗?
如果读缓存只有一个,那接收数据时会不会被后续的新数据覆盖呢?
答: 如果你没有及时处理堆缓存, 堆缓存是会被新数据覆盖, 我称为数据洪峰的影响,就像城市下暴雨,下水道来不及排水, 发生水涝一样. -
如何解决数据洪峰问题?
在高速且连续发送大数据块的情况下,客户端是无法不丢失数据
只有改进通信机制才可以解决, 有两种简单方案可以解决:
1 每发送一批次 就 让对方应答,对方应答后,再发送下一批次
2 每发送一批次 等待一段时间再发送第二批次 -
通过延时发送的时间解决数据洪峰时,应该延时多久?
其实我不建议用这种方法,
因为如果你的缓冲区大,延时时间就要久一些,
如果cpu处理的慢,那么延时也要久一些,
这就像显微镜的细准焦螺旋,要调的适合才行,如果你不想调,
可以粗暴的设置间隔时间在30ms以上 -
应答方式解决数据洪峰 在nio架构中是比较难设计的,所以建议用bio
-
ByteBuffer 发送缓存的大小是有限定的,不能随意
太小 传输次数多, 传输时长变长
太大 连续发送大数据时会导致数据丢失(这点是我多次实验才发现的)
不同计算机因为网卡和内存,磁盘,cpu等不同,会导致ByteBuffer 最佳尺寸有不同, -
为了适应 网络卡,或者信号不好的环境, 发送 ByteBuffer的 尺寸建议 512~2k
-
局域网 1千兆带宽的环境, 发送 ByteBuffer的 尺寸建议 4~8k
-
一定要用while {}来取缓存数据
SocketChannel通道切换后, 会用read取数据到缓存( ByteBuffer),这个时候一定要用while, 因为通道的数据用read()一次,是无法取完的,只能while才能取干净. -
网络上经常说NIO是同步且非阻塞的,这一点不能盲信.
同步是有条件的! 前提是接收方要来得及处理.
非阻塞的 前提也是接收方要来得及处理. NIO割裂了发送和反馈. 从发送到反馈这个过程如果要花较长时间,那也会造成数据洪涝现象.
总结: NIO也好BIO也好,只要接收方处理来不及都会出问题
网络编程常见问题
粘包
粘包就是两个数据包收到时连在一起,怎么分割?
答: 传统解决方法有两种:
例如单片机领域喜欢用时序脉冲, 间隔一段时间收取;
而在高级编程领域喜欢用通信协议, 比如协议在包头定义每个包的大小,这样按照包头信息指示,读取指定长度的包体就可以了.
丢包
很早以前只知道UDP会丢包,其实TCP也会丢包,只是TCP丢包是困难的
TCP丢包的常见原因有5种:
- 发送缓存大于接收缓存时会产生丢包
- 就像之前说的 数据洪峰来临 ,导致客户端无法及时处理丢包3.
- 网络差, 网络断断续续,导致数据离奇丢失(非常少见)
- 伪代理基站或路由器被植入木马,篡改TCP包数据
- 程序有bug ,发送与接收的处理不严谨,导致
丢包处理需要针对性设计:
至少保证 : 接收缓存>发送缓存*4 并且 发送缓存不要大于10k(稳定性要求越高 发送缓存就要越小) ;
数据洪峰的解决方案前面写过了;
数据被篡改,需要记录日志,具体问题具体分析,甚至要改动程序安全架构;
非黑客原因直接断开客户端,等待客户重连即可;
半包
半包就是你当前只收到一半的数据,
另一半的数据在上一次已经收到,或在下一次接收时才可以收到.
这种问题,只能程序处理,解决方案:
就是实例化一个半包管理器,对半包进行处理
坏包
坏包,来源不明,包格式非协议约定,导致无法解读,遇到这种情况,因为不知道坏包长度多少,所以最好办法断开客户端,等待重新连接后,再按照双方约定的通信协议 发送有效的数据包.
拆包
就是把一个大的数据包,分成若干固定大小的数据子包,最后一个子包的大小可以小于等于固定大小.
双通
数据从客户端发送到服务端, 看上去有一条单通道, 实际上这个通道是双向的, 服务端可以利用该通道把数据发回给客户端.
数据重发
就是数据包坏了,如何重新发送.
如果数据非常大,几个T 数据重发显然不现实
那么大数据如何数据重发,就要涉及到拆包,先把打包拆开,分开发,哪个错了就重新发哪个.
最后把所有收到的子包按照顺序重新组装成完整的包,这也需要算法支持
数据包并发
如果我们正在发送一个很大的包 有好几个G
那么期间能不能 穿插着发送其他数据呢?
就比如说我正在发送文件给对方,能不能在发送消息呢?
如果不拆包,肯定是不行的,只有拆包了,才可以在中途穿插发送其他的数据包.
交错发送引发数据混乱
有一次我设计了多个发送函数,结果不小心被多个线程调用, 可想而知如果同时调用两个发送函数
必然会导致通道交错write 数据, 那么接收方接收到的数据就是混乱的
要保证每个包发送完成才能发送另外一个包,不能在发送一个包的过程中 发送另外的数据包
总结:
拆包,应答,半包,粘包, 是网络编程必须解决的底层问题