Netty 大文件传输
使用netty进行大文件传输,对文件大小没有限制
实际上,传输的是文件分片,分片大小自定义
整体思路
- 客户端一连上服务器,即发送指定文件的文件分片
- 服务器收到分片后,写入指定路径,并向客户端返回所收到字节数(即通知客户端下次从文件的此字节开始传输)
- 客户端收到服务器的回应后,从指定字节开始创建下一个文件分片 继续发送给服务器,若已到文件末尾,则发送-1表示文件传输结束
- 服务器循环步骤2,直至收到客户端发来的-1,也回应给客户端-1表示收到传输结束的通知
- 客户端在收到-1后,断开与服务器的连接
代码实现
-
完整代码
github:https://github.com/StanAugust/NettyFileTransfer/tree/master
-
主要代码
- 客户端
/** * @ClassName: ClientHandler * @Description: 客户端的处理器,在在client.ClientInitializer initChannel中被调用 * @author Stan * @date: 2020年3月24日 */
public class ClientHandler extends ChannelInboundHandlerAdapter{
private static final Logger logger = Logger.getLogger(ClientHandler.class.getName());
private int byteRead; //一次读取的字节
private volatile int start = 0; //文件当前读取位置
private volatile int lastLength = 0; //单次文件传输剩余长度
private RandomAccessFile file;
private FileConfig fc;
public ClientHandler(FileConfig fc) {
if(fc.getFile().exists()) {
if(!fc.getFile().isFile()) {
logger.info("error:" + fc.getFile() + "is not a file!");
return;
}
}
this.fc = fc;
}
/** * @Description: * 连接一激活就向服务器发送文件,发送文件的函数在这里修改 * 若文件可一次传输完,则只调用本方法,否则调用一次本方法后,其余在channelRead中传输 * * @param ctx * @throws Exception * @see io.netty.channel.ChannelInboundHandlerAdapter#channelActive(io.netty.channel.ChannelHandlerContext) */
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception{
file = new RandomAccessFile(fc.getFile(), "r");
send0(ctx, start);
}
/** * @Description: 服务器通知已接受到的字节数,客户端收到通知并传输剩余文件 * @param ctx * @param msg * @throws Exception * @see io.netty.channel.ChannelInboundHandlerAdapter#channelRead(io.netty.channel.ChannelHandlerContext, java.lang.Object) */
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//收到服务器发来的字节数
if(msg instanceof Integer) {
start = (Integer)msg;
if(start != -1) {
//文件没有传完
logger.info("服务器已收到字节数:" + start);
send0(ctx, start);
}else {
file.close();
logger.info("服务器已接收全部文件");
// 服务器接收完文件就与客户端断开连接
ctx.close();
}
}
}
/** * @throws Exception * @Description: 具体处理发送 */
private void send0(ChannelHandlerContext ctx, int start) throws Exception {
// 文件没有传完
if (start != -1) {
file.seek(start); // 把文件的记录指针定位到start字节的位置。也就是说本次将从start字节开始读数据
int nowLength = (int) (file.length() - start); // 文件当前总剩余长度
int transferLength = FileConfig.getTransferLength(); // 自定义的单次传输长度
lastLength = nowLength<transferLength ? nowLength:transferLength; // 选取较短一方作为单次文件传输剩余长度
transfer(ctx, lastLength);
}
}
/** * @Description: 完成单次传输 * @param ctx * @param length 单次传输长度 * @throws Exception */
private void transfer(ChannelHandlerContext ctx, int length) throws Exception {
byte[] buf = new byte[length];
if ((byteRead = file.read(buf)) != -1 && length > 0) {
fc.setEndPos(byteRead);
fc.setFileBuf(buf);
} else {
fc.setEndPos(-1); //结束位置-1,表示文件传输结束
fc.setFileBuf(null);
logger.info("文件已上传完毕");
}
ctx.writeAndFlush(fc);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
- 服务器
/** * @ClassName: ServerHandler * @Description: 服务器端的处理器,在server.ServerInitializer initChannel中调用 * @author Stan * @date: 2020年3月24日 */
public class ServerHandler extends ChannelInboundHandlerAdapter{
private static final Logger logger = Logger.getLogger(ServerHandler.class.getName());
private int byteRead;
private int start = 0;
/** * @Description: 服务器接收到消息后进入这个方法,接收文件的函数在这里修改 * @param ctx * @param msg * @throws Exception * @see io.netty.channel.ChannelInboundHandlerAdapter#channelRead(io.netty.channel.ChannelHandlerContext, java.lang.Object) */
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//如果传递过来的是文件或文件分片
if(msg instanceof FileConfig) {
FileConfig fc = (FileConfig)msg;
byte[] fileBuf = fc.getFileBuf(); //接收到的文件字节数组
byteRead = fc.getEndPos(); //记录当前文件传输结束的位置
if(byteRead == -1) {
// 约定的结束的标志
fileEnd(ctx);
}else {
if(byteRead > 0) {
// TODO 文件接收路径需要指定
RandomAccessFile file = new RandomAccessFile(new File("test2.txt"), "rw");
file.seek(start); //把文件的记录指针定位到start字节的位置。也就是说程序本次将从start字节开始写数据
file.write(fileBuf);//把传输过来的数据写进文件里
start += byteRead; //确保文件下次能从当前结束的地方继续读取
ctx.writeAndFlush(start); //向客户端通知下次从第start字节开始传输
file.close();
logger.info("服务器已接收字节数:" + start + ",客户端地址:" + ctx.channel().remoteAddress());
}else {
exceptionCaught(ctx, new Throwable("可读字节小于0"));
}
}
}
}
/** * @Description: 文件接收完毕 * @param ctx */
private void fileEnd(ChannelHandlerContext ctx) {
ctx.writeAndFlush(-1);
logger.info("服务器接收文件完毕"
+ "\n文件来源:"+ ctx.channel().remoteAddress()
+ "\n文件大小:" + start + " 字节");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
今天的文章Netty 大文件传输分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/6335.html