之前使用Java IO实现了一个文件传送的小demo,今天打算采用java nio重写一遍。
首先,用nio写好文件接收端后,采用原先的IO程序测试,发现并不存在问题。
接着,写文件发送端,ByteBuffer大小设置为1024,发送端分多次发送文件片段,在接受端组合形成文件,近而写入文件系统。
程序写好后:
1,用一个2K的文件测试,发现并无问题
2,用一个44k的文件测试,发现接收端只接受到23k的内容;
调试发现发送端中 SocketChannel.write()循环中在发送23k左右的内容后就一直返回0
问题分析:在socket 编程中,对于write(),内容受带宽限制不可能瞬间发送出去,应该有缓冲区。在普通阻塞IO中,当缓冲区满了就阻塞了,而对于非阻塞的socket,缓冲区满了就立即返回0。
解决方案:Java nio的默认缓冲区为8k。将其用setSendBufferSize设置为32*1024后,稍大点的文件就能正常发送了。但对于大小为十几兆的文件估计还是有困难。经查找可以在write返回0的时候,将OP_WRITE注册进selector,然后对可写性进行判断,当selector检测到可写时,继续发送文件片段。发送完毕后注销OP_WRITE。
但我觉得与其用这种方法还不如用Java普通的IO。阻塞IO CPU耗费还比NIO小很多。
对于大文件的传输将缓冲区的容量增大,只能是杯水车薪。原因在于这个缓冲区的最大容量是有限制的。
造成缓冲区满的根本原因,是接收端的问题,假设将文件从客户端发给服务器,客户端很可能是在一个while循环发送读取到的文件内容。
这存在很多问题:
1,带宽,数据不可能秒发,在带宽很小的情况下,write()的写数据速度要比缓冲区的发送速度快的多。可以将缓冲区比作一个水桶,这个水桶底部有一个漏水孔,而write就是一台抽水机,不停的往水桶送水,水桶里的水肯定溢出。
2,服务器接收端的处理速度。一般的服务器都存在多个线程,而且对于传进来的数据还需要进行很多的处理,如解码,重排等等。而客户端就只执行一个任务或者是线程:不停的发送数据。这是个典型的生产者消费者的问题,在普通阻塞IO中由于存在阻塞,并不存在问题。而在NIO中,由于读写非阻塞,write()在缓冲区已满的情况下无法写入,返回0,造成数据丢失
这里还有一个解决的办法就是侦测ByteBuffer.remanning(),在还有数据的时候重新发送,直到发送出去。另为保证CPU,循环内加Sleep。睡眠时间和缓冲区大小存在一定的关系,缓冲区设置得大,就可以选择一个较大的睡眠时间,根本目的是保证发送缓冲区一直有数据,不存在空的情况。