之前使用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。睡眠時間和緩衝區大小存在一定的關係,緩衝區設定得大,就可以選擇一個較大的睡眠時間,根本目的是保證發送緩衝區一直有資料,不存在空的情況。