1倔韭、java.lang.NegativeArraySizeException
發(fā)生在用負數(shù)長度創(chuàng)建數(shù)組時,原因是應用層報文設計數(shù)據(jù)最大長度為short绣版,當數(shù)據(jù)超過short強制轉換回丟失數(shù)據(jù)導致數(shù)值變負數(shù)篷牌。
所以在設計應用層報文時一定要注意數(shù)據(jù)的長度小于上限。本例中可以用int代替short讓數(shù)據(jù)有2^32-1的長度涩金。
2、濫用ExecutorFilter
Mina是基于REACTOR模型的暇仲,當連接建立對方發(fā)來消息首先由Processor線程池派出一個線程執(zhí)行讀取事件步做,經過粘包/缺包處理器處理,然后就完整的字節(jié)報文轉換成上層應用報文奈附。
假設解碼器的代碼為:
connector.getFilterChain().addLast("BaseFilter", new ProtocolCodecFilter(new BaseCodecFactory()));
如果在FilterChain之前加入代碼:
connector.getFilterChain().addLast("threadpool",
new ExecutorFilter(Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1)));
意思是解碼器將不會在Processor線程池執(zhí)行全度,而是由新創(chuàng)建的ExecutorFilter中的線程池執(zhí)行。
這就導致了一個問題斥滤,假設數(shù)據(jù)長度8K将鸵,網絡速度16K/S, 本地寫入速度4K/S,明顯網絡速度大于本地IO速度佑颇,所以可能ExecutorFilter中的線程還沒處理完一個完整應用層報文顶掉,Processor已經開始了下一個網絡流的讀取,從而導致數(shù)據(jù)錯位挑胸。
本例中如果網絡速度小于IO速度不會出現(xiàn)此問題一喘,但是因為本地IO速度是動態(tài)的,當連接數(shù)增大IO遇到瓶頸很可能觸發(fā)此問題嗜暴。所以在FilterChain層加入線程池至少應該放在解碼層后面凸克,避免不同步處理數(shù)據(jù)的異常。
3闷沥、不同步發(fā)送文件分段導致文件寫入不同步
雖然Reactor模型的SelectionKey.OP_READ 事件是按順序讀取SocketChannel的萎战,但是如果在mina解碼后配置:
connector.getFilterChain().addLast("threadpool",
new ExecutorFilter(Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1)));
會導致處理上層報文的無序。
如果傳輸文件使用無序寫入舆逃,就不能用FileOutputStream的追加模式:
FileOutputStream fos = new FileOutputStream(fileTask.zippedFilePath, true);
fos.write(filePart.data);
fos.flush();
fos.close();
因為寫入是并發(fā)的蚂维,并不能保證其有序。
在并發(fā)環(huán)境下寫入文件可以使用RandomAccessFile,支持在文件的指定位置寫入:
RandomAccessFile randomAccessFile = new RandomAccessFile(fileTask.zippedFilePath, "rw");
long beginIndex = fileTask.fileSegmentSize*filePart.partId;
randomAccessFile.seek(beginIndex);
System.out.println("file length = "+randomAccessFile.length()+" , beginIndex = "+beginIndex);
randomAccessFile.write(filePart.data);
randomAccessFile.close();
因為文件處理是并行的路狮,最后一段數(shù)據(jù)可能并不是最后處理完成的虫啥,所以怎么判斷寫入文件執(zhí)行完畢?
發(fā)送文件分段時會傳入當前文件分段的partId奄妨,在接收文件處理中使用AtomicInteger為partId計數(shù)涂籽,算出總計有多少個分段,再判斷當前完成的分段數(shù)是否達到總數(shù)砸抛,就能夠判斷文件是否完全寫入:
//每個線程執(zhí)行完分段的寫入將partId加1
int partId = fileTask.partId.incrementAndGet();
//算出總計有多少分段评雌,用文件總大小除以分段的大小,如果有小數(shù)則加1取整
//最大傳輸4G的文件(2^32-1字節(jié))直焙,超過則程序異常
int totalPart = (int) Math.ceil((double)fileTask.zippedFileSize/fileTask.fileSegmentSize);
if (partId==totalPart) {
//文件接收完畢
//md5驗證
//通知對方
}
4景东、Processor線程池內的線程是固定的,要保證每個線程在運行期間不發(fā)生異常終止奔誓,否則Processor線程池不會重新創(chuàng)建線程斤吐。