Netty处理器的小技巧和Netty大文件传输方法思路

本文介绍如何使用Netty处理大文件上传请求,通过ChunkedWriteHandler进行分包,并实现按序号分发小文件下载,提高并发和安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. Netty处理器的小技巧

使用一个解析处理器,对上传的请求进行解析,对特定的请求进行解析,再在pipeline后面加上指定的handler理器

  1. ((FullHttpRequest) msg).release();
    
    ctx.fireChannelRead(msg);
    				
    ctx.channel().pipeline().remove(this);
    
    pipeline.addAfter(new Process());
    				
    @Sharable
    public class ParseRequestHandler extends ChannelInboundHandlerAdapter {
  2. 大文件传输方法的思路

大文件传输我这里在原来的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;
	}
	
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值