为什么需要ReplayingDecoder,它和FrameDecoder有什么不同呢?ReplayingDecoder是一种特殊FrameDecoder,它能实现在IO阻塞的情况下实现无阻塞的解码。听起来比较拗口而且不好理解,举个例子来说,假设你在用netty设计一个文件传输的模块,采用的Header-Content的协议,在Header中说明文件的长度,我们用一个int来容纳长度,Content则是该int长度的字节数组。该如何设计这个Decoder呢?代码有可能是下面这样的。可能你会觉得比你想象的要复杂,多了markReadIndex(),多了是否可读的判断,假如读一个int,我们得首先判断可读的字节数是否大于等于4个。为什么需要这样呢?这就回到了开头的问题,一种原因是和FrameDecoder有关,假设每一帧都是100bytes,其中4bytes用来标示Header中的int,其余96字节为content。假设NioWorker读取了1003bytes交给FrameDecoder去处理,那么前10帧都能完美地处理,当处理第11帧的时候,要读取int的时候,但是只有3bytes了。为了防范这类问题我们需要在每次解码之前mark一下当前的index,发现没有足够的bytes时resetindex为之前mark的值。并且返回null在下一个loop中处理。
i++;
if(buffer.readableBytes() < 4){
//buffer.resetReaderIndex();
return null;
}
int nameLen = buffer.readInt();
if(buffer.readableBytes() < nameLen){
buffer.resetReaderIndex();
return null;
}
System.out.println("文件名长" + nameLen);
byte[] fileNameBytes = new byte[nameLen];
buffer.readBytes(fileNameBytes);
String fileName = new String(fileNameBytes);
int contentLen = buffer.readInt();
byte[] fileContent = new byte[contentLen];
if(buffer.readableBytes() < contentLen){
System.out.println("只读到了" + buffer.readableBytes());
System.out.println("待下次接收完成再处理");
// byte[] read = buffer.readBytes(buffer.readableBytes()).array();
// System.out.println(new String(read));
buffer.resetReaderIndex();
return null;
}
switch (state) {
case READ_FILENAME_LENGTH: {
int fileNameLength = buffer.readInt();
currentTransfer = new TransferFile();
currentTransfer.setFileNameLength(fileNameLength);
checkpoint(State.READ_FILENAME);
}
case READ_FILENAME:{
byte[] fileNameBytes = new byte[currentTransfer.getFileNameLength()];
buffer.readBytes(fileNameBytes);
String fileName = new String(fileNameBytes);
try{
currentTransfer.setFileName(fileName);
FileOutputStream fos = new FileOutputStream(fileName);
currentTransfer.setFos(fos);
}catch(Exception e){
currentTransfer.setSuccess(false);
return reset();
}
}
case READ_FILECONTENT_LENGTH:{
currentTransfer.setFileContentLength(buffer.readInt());
checkpoint(State.READ_FILECONTENT);
}
case READ_FILECONTENT:{
int currentChunkSize = this.chunkSize;
int maxCanRead = buffer.readableBytes();
if(maxCanRead < currentTransfer.getFileContentLength()){
currentChunkSize = Math.min(maxCanRead, chunkSize);
}else{
currentChunkSize = currentTransfer.getFileContentLength();
}
try{
currentTransfer.getFos().write(buffer.readBytes(currentChunkSize).array());
currentTransfer.decrement(currentChunkSize);
}catch(Exception e){
currentTransfer.setSuccess(false);
return reset();
}
if(currentTransfer.isLast()){
currentTransfer.setSuccess(true);
return reset();//读取完一个完整的帧
}
}
}
return null;