-
Netty处理器的小技巧
使用一个解析处理器,对上传的请求进行解析,对特定的请求进行解析,再在pipeline后面加上指定的handler理器
-
((FullHttpRequest) msg).release();
ctx.fireChannelRead(msg);
ctx.channel().pipeline().remove(this);
pipeline.addAfter(new Process());
@Sharable
public class ParseRequestHandler extends ChannelInboundHandlerAdapter {
-
大文件传输方法的思路
大文件传输我这里在原来的Netty文件传输之上进行了修改,本质原因是文件太多,不能一次传完,需要分多次进行传输。
-
使用了ChunkedWriteHandler,对文件信息进行了分包处理,使得文件传输压力减小,其次保证了文件的安全性
-
对大文件进行分片读取,读取生成一个指定的小文件,再发送小文件。(实现了大文件向小文件的过渡)最后再删除小文件即可
-
客户端使用了序号进行区分信息,被一个小文件都带有一个序号,用于区分文件地址。客户端每次发送一个序号,服务器返回一个指定位置的指定长度的小文件,客户端再对序号加一,请求服务器,直到服务器完全传输完毕。
package ProcessRequest;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URLDecoder;
import java.net.URLEncoder;
import org.apache.log4j.Logger;
import Utility.ExecutorGroup;
import Utility.FileHandlerFunction;
import Utility.HTTPDefineConstant;
import Utility.HTTPResponse;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelProgressiveFuture;
import io.netty.channel.ChannelProgressiveFutureListener;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpChunkedInput;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.stream.ChunkedFile;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.concurrent.EventExecutorGroup;
public class DownloadOneHugeFileProcess extends ChannelInboundHandlerAdapter{
// 日志
private static final Logger logger = Logger.getLogger(DownloadOneHugeFileProcess.class);
private String userID = "";
private ChannelHandlerContext ctx = null;
// 执行耗时操作的线程池
private EventExecutorGroup group = ExecutorGroup.getInstance().getExecutorGroup();
private String fileName;
//客户端发送的需要返回的文件里面序号段,这里序号段从0开始传输文件
private long sequenceNumber = 0;
private int dataPackageNumber = 0;
// 设置为每次传输5m大小的数据,实现单个文件速度的快递提高,让客户端能马上获得文件
// 让客户端马上能播放文件,实现一边播放一边下载
private final int chunkSize = 5*1024*1024;
private RandomAccessFile downloadFile;
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
this.ctx = ctx;
this.userID = "" + ctx.channel().remoteAddress();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
this.ctx = ctx;
this.userID = "" + ctx.channel().remoteAddress();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof FullHttpRequest) {
FullHttpRequest request = (FullHttpRequest) msg;
// 获取请求方法名
String method = request.method().toString();
// 下载文件(2022-03-25)
if ("DownloadOneFileRequest".equals(method)) {
System.out.println("DownloadOneHugeFileRequest Process");
/*
* 服务器根据客户端发送的数据序号,从文件中截取指定长度的文件发送回客户端
* */
fileName =URLDecoder.decode(request.headers().get("oneFileName"), "UTF-8");
System.out.println("下载的文件名"+fileName);
sequenceNumber = Long.parseLong(request.headers().get("sequenceNumber"));
startSend(System.getProperty("user.dir") + "/AudioAndVideo/" + fileName, "668");
((FullHttpRequest) msg).release();
} else {
// 激活下一个Handler
ctx.fireChannelRead(msg);
ctx.channel().pipeline().remove(this);
}
} else {
ctx.fireChannelRead(msg);
}
}
public void startSend(String path, String statusCode) {
// 提交耗时任务给线程池
group.submit(new Runnable() {
@Override
public void run() {
try {
/*
*downloadFile的内置开始位置在getContent()中设置
*这个文件在等全部分包完成再关闭吧
* */
downloadFile = new RandomAccessFile(path, "r");
long fileLen = downloadFile.length();
// 文件不为空且文件长度小于0
if (fileLen < 0 && downloadFile != null) {
logger.error("读取待批改文件遇到异常!");
downloadFile.close();
}
else {
dataPackageNumber = (int) (fileLen/(chunkSize));
int tem = (int) (fileLen%(chunkSize) );
if(tem != 0) {
//有余数
dataPackageNumber++;
}
}
//生成一个临时文件
sendHugeFile(statusCode,sequenceNumber,createOnePartSmallFile());
} catch (IOException e) {
logger.error(e);
e.printStackTrace();
}
}
});
}
private void sendHugeFile( String statusCode ,long number, RandomAccessFile temFile) throws IOException {
HttpResponseStatus status = null;
switch (statusCode) {
case "668":
status = HTTPDefineConstant.DOWNLOAD_ONEFILE_SUCCESS;
break;
case "669":
status = HTTPDefineConstant.DOWNLOAD_ONEFILE_FAIL;
break;
default:
break;
}
if (null == ctx.pipeline().get(ChunkedWriteHandler.class)) {
ctx.pipeline().addBefore("downloadOneHugeFile", "chunked writer", new ChunkedWriteHandler());
}
ChannelFuture sendFileFuture;
HttpResponse response;
if(number != dataPackageNumber-1)
{
response = HTTPResponse.downloadOneHugeFileResponse(status, temFile.length() , URLEncoder.encode(fileName, "UTF-8") , String.valueOf(number) , "NO");
ctx.write(response);
}else {
logger.info("sequenceNumber 文件包序号为:"+ number + " 为最后一个文件包");
response = HTTPResponse.downloadOneHugeFileResponse(status, temFile.length() , URLEncoder.encode(fileName, "UTF-8") , String.valueOf(number) , "YES");
ctx.write(response);
}
sendFileFuture = ctx.writeAndFlush(new HttpChunkedInput(new ChunkedFile(temFile,0, (long)temFile.length(),(int)temFile.length()) ), ctx.newProgressivePromise());
sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
@Override
public void operationComplete(ChannelProgressiveFuture future) throws Exception {
if (future.isSuccess()) {
try {
temFile.close();
logger.info("downloadFile1文件关闭成功");
} catch (IOException e) {
logger.error("downloadFile文件关闭出现异常:" + e);
}
// try {
// temFile.write(new byte[] {1, 2, 3});
//
// } catch (IOException e) {
// e.printStackTrace(); //如果流已经关闭,这里会报
// }
//进行删除文件的操作
String temFilePath = System.getProperty("user.dir") + "/AudioAndVideo/" + number + fileName;
if(FileHandlerFunction.delete(temFilePath))
{
System.out.println("成功删除:"+temFilePath);
}else {
System.out.println("删除失败:"+temFilePath);
}
if(number == dataPackageNumber-1)
{
try {
downloadFile.close();
logger.info("最后一个downloadFile文件关闭成功");
} catch (IOException e) {
logger.error("downloadFile文件关闭出现异常:" + e);
}
logger.info("下载文件 :" + temFile.getFD() + " 传输完毕\n接收用户:" + userID);
ctx.pipeline().remove("downloadOneHugeFile");
ctx.pipeline().remove("chunked writer");
}
}
else {//发送失败的情况,重新发送
sendHugeFile(statusCode,number,temFile);
}
}
@Override
public void operationProgressed(ChannelProgressiveFuture future, long progress, long total)
throws Exception {
}
});
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.error(cause);
}
private RandomAccessFile createOnePartSmallFile() throws IOException
{
long fileLen = downloadFile.length();
// logger.info("客户端请求传输大文件 fileLen:"+ fileLen);
// logger.info("客户端请求传输大文件 dataPackageNumber:"+ dataPackageNumber);
String temFilePath = System.getProperty("user.dir") + "/AudioAndVideo/" + sequenceNumber + fileName;
RandomAccessFile temFile = null;
try {
temFile = new RandomAccessFile(temFilePath, "rw");
} catch (IOException e) {
logger.error("文件打开出现异常:" + e);
}
temFile.seek(0);
if(dataPackageNumber-1 != sequenceNumber )
{
logger.info("生成下载文件,文件序号为:"+ sequenceNumber);
temFile.write(getContent(sequenceNumber*chunkSize,chunkSize, downloadFile));
}
else {
int tem1 = (int) (fileLen%(chunkSize) );
if(tem1 != 0) {
//有余数
logger.info("生成最后一个文件,有余数,文件序号为:"+sequenceNumber);
long temChunkSize = fileLen-(sequenceNumber*chunkSize);
temFile.write(getContent(sequenceNumber*temChunkSize,temChunkSize, downloadFile));
}else {
//无余数
logger.info("生成最后一个文件,无余数,文件序号为:"+sequenceNumber);
temFile.write(getContent(sequenceNumber*chunkSize,chunkSize, downloadFile));
}
}
return temFile;
}
private byte[] getContent(long off , long len, RandomAccessFile downloadFile) throws IOException {
downloadFile.seek(off);
byte[] bytes = new byte[(int) len];
downloadFile.read(bytes,(int)0, (int)len);
return bytes;
}
}