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