netty文件上传断点续传的演示

2023-11-16

Netty文件上传断点续传的演示

一、理论和协议规范和工具类等

1、实现原理:

    netty文件上传采用自定义的协议方式实现,断点续传主要是依据RandomAccessFile类的随机读写能力,主要流程是客户端发起请求,将需要上传文件名称、路径、读取文件的数据、以及读取文件的起始位置等等信息,并且缓存在服务端中(以文件路径为key,自定义协议对象为value),服务端拿到客户端发送的上述数据,就会写文件,并且写完文件,也会记录写过数据位置等信息,再次发送信息给客户端下一次需要读取的数据。

    假如这个过程中,客户端断开了链接,此时由于服务端缓存了已经写过文件的位置,那么只会从写过文件的位置进行读文件,再传文件给服务端写等循环操作,从而达到了断点续茶传的效果。

 2、RandomAccessFile的基本使用

     *  RandomAccessFile的基本api
     * .getFilePointer : 获取当前操作的位置
     * .seek(index): 将操作位置设置到index
     * .read(byte):读文件到byte数组中,返回读取文件的长度
     * 
     * 构造函数
     *  rf = new RandomAccessFile(new File(filePath),"r"); // 参数2是模式
     *  
       *    模式详解:
     *    “r” 以只读方式来打开指定文件夹。如果试图对该RandomAccessFile执行写入方法,都将抛出IOException异常。
     *    “rw” 以读,写方式打开指定文件。如果该文件尚不存在,则试图创建该文件。
     *    “rws” 以读,写方式打开指定文件。相对于”rw” 模式,还要求对文件内容或元数据的每个更新都同步写入到底层设备。
     *    “rwd” 以读,写方式打开指定文件。相对于”rw” 模式,还要求对文件内容每个更新都同步写入到底层设备。

	 * 测试1:下面演示基本的读文件操作。
	 */
	@Test
	public void test1() throws IOException {
		String filePath = "src/d00_file/a.txt";
		RandomAccessFile rf = null;
		try {
			rf = new RandomAccessFile(new File(filePath), "r");
			System.out.println("输入内容:" + rf.getFilePointer());
			// 从10位置操作
			rf.seek(10);
			byte[] b = new byte[1024];
			int len = 0;
			// 循环读写 (len是读取的长度)
			while ((len = rf.read(b)) > 0) {
				System.out.print(new String(b, 0, len));
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			rf.close();
		}
	}
	
	/**
	 * 测试2: 向文件追加内容
	 */
	@Test
	public void test2() throws IOException{
        String filePath="src/d00_file/a.txt";
        RandomAccessFile rf=null;
        try {
			rf = new RandomAccessFile(new File(filePath), "rw");
			// 将操作指针移动到文件的最后
			rf.seek(rf.length());
			// 向文件写出数据
			rf.write("这是追加的内容。。".getBytes());
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            rf.close();
        }
    }
	
	/**
	 * 测试3:修改文件内容
	 */
	@Test
	public void test3() {
		RandomAccessFile rf = null;
		String oldStr = "www.www.www";
		String newStr = "hahahaha";
		try {
			rf = new RandomAccessFile("src/d00_file/a.txt", "rw");
			String line = null;
			// 记录上次操作点
			long lastpoint = 0;
			while ((line = rf.readLine()) != null) {
				long ponit = rf.getFilePointer();
				// 如果包含替换字符串
				if (line.contains(oldStr)) {
					// 替换字符串
					String str = line.replace(oldStr, newStr);
					// 恢复到上次读取位置 (readLine前的位置)
					rf.seek(lastpoint);
					// 重写行数据
					if (oldStr.length() != newStr.length()) {
						byte[] newb = Arrays.copyOf(str.getBytes(), line.getBytes().length);
						rf.write(newb);
						// lastpoint = lastpoint + newb.length;
						lastpoint = rf.getFilePointer();
						continue;
					} else {
						rf.write(str.getBytes());
					}
				}
				lastpoint = ponit;
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				rf.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

 3、传输对象和协议对象设计

传输对象分为三种,一种是客户端发送给服务端的起始传输信息类、一种是分片文件数据类、一种是服务端返回给客户端分片文件指令类。

协议对象就是包含二个字段,一个是类型(上面三个对象的类型),一个是obejct字段,表示传输对象。

常量:

/**
 * 文件传输常量
 */
public class Constants {

    /**
     * 文件传输状态的标示
     */
    public static class FileStatus{
        public static int BEGIN = 0;    //开始
        public static int CENTER = 1;   //中间
        public static int END = 2;      //结尾
        public static int COMPLETE = 3; //完成
    }

    /**
     * 协议对象的传输对象类型
     */
    public static class TransferType{
        public static int REQUEST = 0;    //文件信息类型
        public static int INSTRUCT = 1;   //文件指令类型
        public static int DATA = 2;       //文件分片类型
    }

}

协议对象:

public class FileTransferProtocol {

	private Integer transferType; // 类型
	private Object transferObj; // 数据对象;(0)FileDescInfo、(1)FileBurstInstruct、(2)FileBurstData

	public Integer getTransferType() {
		return transferType;
	}

	public void setTransferType(Integer transferType) {
		this.transferType = transferType;
	}

	public Object getTransferObj() {
		return transferObj;
	}

	public void setTransferObj(Object transferObj) {
		this.transferObj = transferObj;
	}

}

文件传输相关对象:

/**
 * 文件描述信息
 */
public class FileDescInfo {

    private String fileUrl; // 文件url
    private String fileName; // 文件名称
    private Long fileSize; // 文件size

    public String getFileUrl() {
        return fileUrl;
    }

    public void setFileUrl(String fileUrl) {
        this.fileUrl = fileUrl;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public Long getFileSize() {
        return fileSize;
    }

    public void setFileSize(Long fileSize) {
        this.fileSize = fileSize;
    }
}


/**
 * 文件分片指令
 */
public class FileBurstInstruct {

	private Integer status; // Constants.FileStatus {0开始、1中间、2结尾、3完成}
	
	private String clientFileUrl; // 客户端文件URL
	
	private Integer readPosition; // 读取位置

	public FileBurstInstruct() {
	}

	public FileBurstInstruct(Integer status) {
		this.status = status;
	}

	public Integer getStatus() {
		return status;
	}

	public void setStatus(Integer status) {
		this.status = status;
	}

	public String getClientFileUrl() {
		return clientFileUrl;
	}

	public void setClientFileUrl(String clientFileUrl) {
		this.clientFileUrl = clientFileUrl;
	}

	public Integer getReadPosition() {
		return readPosition;
	}

	public void setReadPosition(Integer readPosition) {
		this.readPosition = readPosition;
	}
}
/**
 * 文件分片数据
 */
public class FileBurstData {

    private String fileUrl;     //客户端文件地址
    private String fileName;    //文件名称
    private Integer beginPos;   //开始位置
    private Integer endPos;     //结束位置
    private byte[] bytes;       //文件字节;再实际应用中可以使用非对称加密,以保证传输信息安全
    private Integer status;     //Constants.FileStatus {0开始、1中间、2结尾、3完成}

    public FileBurstData(){

    }

    public String getFileUrl() {
        return fileUrl;
    }

    public void setFileUrl(String fileUrl) {
        this.fileUrl = fileUrl;
    }

    public FileBurstData(Integer status){
       this.status = status;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public Integer getBeginPos() {
        return beginPos;
    }

    public void setBeginPos(Integer beginPos) {
        this.beginPos = beginPos;
    }

    public Integer getEndPos() {
        return endPos;
    }

    public void setEndPos(Integer endPos) {
        this.endPos = endPos;
    }

    public byte[] getBytes() {
        return bytes;
    }

    public void setBytes(byte[] bytes) {
        this.bytes = bytes;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }
}

4、编码解码器

从协议对象的设计我们知道,本次文件上传将采用对象传输的方式,那么就要自定义编码解码器,自定义编码解码器采用的是以protostuff为基础实现,编码时,通过加一个int类型的长度字段,以及序列化的协议对象,解码时,通过解码长度字段后,读取数据,然后反序列化协议对象。

1) 序列化工具 

/**
 * protostuff序列化工具类
 */
public class SerializingUtil {

    /**
     * 将目标类序列化为byte数组
     */
    public static <T> byte[] serialize(T source) {
        Schema<T> schema = RuntimeSchema.getSchema((Class<T>) source.getClass());
        LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
        final byte[] result;
        try {
            result = ProtobufIOUtil.toByteArray(source, schema, buffer);
        } finally {
            buffer.clear();
        }
        return result;
    }

    /**
     * 将byte数组序列化为目标类
     */
    public static <T> T deserialize(byte[] source, Class<T> clazz) {
        Schema<T> schema = RuntimeSchema.getSchema(clazz);
        T t = schema.newMessage();
        ProtobufIOUtil.mergeFrom(source, t, schema);
        return t;
    }

}

2) 对象解码器 (输入向处理--放置在处理器链第一个--后续处理器可直接获得Clazz类型对象)

/**
 * 对象解码器
 */
public class ObjectDecode extends ByteToMessageDecoder{

	private Class<?> clazz ;
	
	public ObjectDecode(Class<?> clazz) {
		super();
		this.clazz = clazz;
	}

	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
		if (in.readableBytes() < 4) {
			return;
		}
		// 标记包头位置
		in.markReaderIndex();
		// 读取长度
		int len = in.readInt();
		// 可读数少于长度
		if(in.readableBytes() < len) {
			// 重置到包头位置
			in.resetReaderIndex();
			return;
		}
		// 读取数据并序列化
		byte[] bytes = new byte[len];
		in.readBytes(bytes);
		Object object = SerializingUtil.deserialize(bytes, clazz);
		out.add(object);
	}

}

3) 对象编码器 (输出编码器---编码协议对象---放置在处理器链倒数第二个)

public class ObjectEncode extends MessageToByteEncoder<Object> {

	private Class<?> clazz;

	public ObjectEncode(Class<?> clazz) {
		super();
		this.clazz = clazz;
	}

	@Override
	protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
		if (clazz.isInstance(msg)) {
			byte[] data = SerializingUtil.serialize(msg);
			out.writeInt(data.length);
			out.writeBytes(data);
		}
	}

}

5、底层文件读写工具

public class FileUtil {
	
	// 默认一次只能读取10k数据
	private static final int DEF_BUFF_SIZE = 1024*10;
	
	private static int buff_size = DEF_BUFF_SIZE;

	/**
	 * 客户端根据文件路径和position读取文件,返回文件分片数据FileBurstData对象。
	 */
	public static FileBurstData readFile(String fileUrl, Integer readPosition) throws IOException {
		File file = new File(fileUrl);
		RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
		randomAccessFile.seek(readPosition);
		byte[] bytes = new byte[buff_size];
		int readSize = randomAccessFile.read(bytes);
		if (readSize <= 0) {
			randomAccessFile.close();
			return new FileBurstData(Constants.FileStatus.COMPLETE);
		}
		FileBurstData fileBurstData = new FileBurstData();
		fileBurstData.setFileUrl(fileUrl);
		fileBurstData.setFileName(file.getName());
		fileBurstData.setBeginPos(readPosition);
		fileBurstData.setEndPos(readPosition + readSize);
		// 不足buff尺寸需要拷贝去掉空字节
		if (readSize < buff_size) {
			byte[] copy = new byte[readSize];
			System.arraycopy(bytes, 0, copy, 0, readSize);
			fileBurstData.setBytes(copy);
			fileBurstData.setStatus(Constants.FileStatus.END);
		} else {
			fileBurstData.setBytes(bytes);
			fileBurstData.setStatus(Constants.FileStatus.CENTER);
		}
		randomAccessFile.close();
		return fileBurstData;
	}

	/**
	 * 服务端根据url和客户端的文件分片数据对象,进行写文件,并且返回文件分片指令(由客户端使用)
	 */
	public static FileBurstInstruct writeFile(String baseUrl, FileBurstData fileBurstData) throws IOException {

		if (Constants.FileStatus.COMPLETE == fileBurstData.getStatus()) {
			return new FileBurstInstruct(Constants.FileStatus.COMPLETE); // Constants.FileStatus {0开始、1中间、2结尾、3完成}
		}
		
		// 服务端写文件
		File file = new File(baseUrl + "/" + fileBurstData.getFileName());
		RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
		randomAccessFile.seek(fileBurstData.getBeginPos()); 
		randomAccessFile.write(fileBurstData.getBytes()); 
		randomAccessFile.close();

		if (Constants.FileStatus.END == fileBurstData.getStatus()) {
			return new FileBurstInstruct(Constants.FileStatus.COMPLETE); 
		}

		// 构建文件分片指令
		FileBurstInstruct fileBurstInstruct = new FileBurstInstruct();
		fileBurstInstruct.setStatus(Constants.FileStatus.CENTER); // 字段:读取状态
		fileBurstInstruct.setClientFileUrl(fileBurstData.getFileUrl());
		fileBurstInstruct.setReadPosition(fileBurstData.getEndPos() + 1); // 字段(核心):下次读取位置
		return fileBurstInstruct;
	}

另外补充文件协议对象的一个构建工具类

/**
 * 消息构建对象
 */
public class MsgUtil {

    /**
     * 构建对象;文件传输info(客户端)
     */
    public static FileTransferProtocol buildRequestTransferFile(String fileUrl, String fileName, Long fileSize) {
        FileDescInfo fileDescInfo = new FileDescInfo();
        fileDescInfo.setFileUrl(fileUrl);
        fileDescInfo.setFileName(fileName);
        fileDescInfo.setFileSize(fileSize);

        FileTransferProtocol fileTransferProtocol = new FileTransferProtocol();
        fileTransferProtocol.setTransferType(0);//0请求传输文件、1文件传输指令、2文件传输数据
        fileTransferProtocol.setTransferObj(fileDescInfo);

        return fileTransferProtocol;

    }

    /**
     * 构建对象;文件传输指令(服务端)
     * @param status         文件读取状态
     * @param clientFileUrl   客户端文件地址
     * @param readPosition    读取位置
     */
    public static FileTransferProtocol buildTransferInstruct(Integer status, String clientFileUrl, Integer readPosition) {

        FileBurstInstruct fileBurstInstruct = new FileBurstInstruct();
        fileBurstInstruct.setStatus(status);
        fileBurstInstruct.setClientFileUrl(clientFileUrl);
        fileBurstInstruct.setReadPosition(readPosition);

        FileTransferProtocol fileTransferProtocol = new FileTransferProtocol();
        fileTransferProtocol.setTransferType(Constants.TransferType.INSTRUCT); // 0 (info) 1 (INSTRUCT) 2 (data)
        fileTransferProtocol.setTransferObj(fileBurstInstruct);

        return fileTransferProtocol;
    }

    /**
     * 构建对象;文件传输指令(服务端)
     */
    public static FileTransferProtocol buildTransferInstruct(FileBurstInstruct fileBurstInstruct) {
        FileTransferProtocol fileTransferProtocol = new FileTransferProtocol();
        fileTransferProtocol.setTransferType(Constants.TransferType.INSTRUCT);  // 0 (info) 1 (INSTRUCT) 2 (data)
        fileTransferProtocol.setTransferObj(fileBurstInstruct);
        return fileTransferProtocol;
    }

    /**
     * 构建对象;文件传输数据(客户端)
     */
    public static FileTransferProtocol buildTransferData(FileBurstData fileBurstData) {
        FileTransferProtocol fileTransferProtocol = new FileTransferProtocol();
        fileTransferProtocol.setTransferType(Constants.TransferType.DATA); // 0 (info) 1 (INSTRUCT) 2 (data)
        fileTransferProtocol.setTransferObj(fileBurstData);
        return fileTransferProtocol;
    }

}

二、客户端、服务端实现代码

上面已经完成的代码包和类结构如下

1、客户端编写

public class FileTransferClient {
	
	private EventLoopGroup workerGroup = new NioEventLoopGroup();
	private Channel channel;

	public ChannelFuture connect(String inetHost, int inetPort) {
		ChannelFuture channelFuture = null;
		try {
			Bootstrap b = new Bootstrap();
			b.group(workerGroup);
			b.channel(NioSocketChannel.class);
			b.option(ChannelOption.AUTO_READ, true);
			b.handler(new ChannelInitializer<SocketChannel>() {
				@Override
				protected void initChannel(SocketChannel ch) throws Exception {
					ch.pipeline().addLast(new ObjectDecode(FileTransferProtocol.class));
					ch.pipeline().addLast(new ObjectEncode(FileTransferProtocol.class));
					ch.pipeline().addLast(new FileTransferHandler());
				}
			});
			channelFuture = b.connect(inetHost, inetPort).syncUninterruptibly();
			this.channel = channelFuture.channel();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (null != channelFuture && channelFuture.isSuccess()) {
				System.out.println("client start done");
			} else {
				System.out.println("client start error");
			}
		}
		return channelFuture;
	}

	public void destroy() {
		if (null == channel)
			return;
		channel.close();
		workerGroup.shutdownGracefully();
	}
	
}

我们将客户端的channel返回,通过channel进行初始发起请求给服务端,自定义处理器只负责接收数据,经过解码器的处理,我们知道接收的数据都是协议对象FileTransferProtocol,而这个对象有三个类型,我们客户端接收的都是服务端的指令对象,服务端缓存的也是指令对象,即客户端读取数据是经过初始请求后,由接收到服务端返回的指令对象后才会进行读数据,发送读数据等等循环操作。

客户端发送数据的演示:

public class ClientMain {
	
	public static void main(String[] args) {
		FileTransferClient client = new FileTransferClient();
		ChannelFuture connect = client.connect("127.0.0.1", 7000);
		File file = new File("C:\\test\\src\\测试传输文件.rar");
		// 构建传输协议对象 (是包装info对象)
		FileTransferProtocol fileTransferProtocol = MsgUtil.buildRequestTransferFile(file.getAbsolutePath(),
				file.getName(), file.length());
		connect.channel().writeAndFlush(fileTransferProtocol);
	}
	
}

客户端自定义处理器:

/**
 * 客户端自定义处理器
 */
public class FileTransferHandler extends ChannelInboundHandlerAdapter{

	
	/**
	 * 主要逻辑:
	 * 	判断指令是否完成,完成退出,没完成,读数据,构建传输对象,并且写传输对象到服务端。 
	 **/
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		// 客户端接收FileTransferProtocol对象
		if (!(msg instanceof FileTransferProtocol)) return;
	    FileTransferProtocol fileTransferProtocol = (FileTransferProtocol) msg;
		switch (fileTransferProtocol.getTransferType()) {
			// 客户端只会处理类型为1,即传输对象是FileBurstInstruct(指令)
			case 1:
                FileBurstInstruct fileBurstInstruct = (FileBurstInstruct) fileTransferProtocol.getTransferObj();
                // 服务端返回的instruct的状态已经完成,客户端退出操作
                if (Constants.FileStatus.COMPLETE == fileBurstInstruct.getStatus()) {
                    ctx.flush();
                    ctx.close();
                    System.exit(-1);
                    return;
                }
                // 客户端读文件数据返回fileBurstData
                FileBurstData fileBurstData = FileUtil.readFile(fileBurstInstruct.getClientFileUrl(), fileBurstInstruct.getReadPosition());
                
                System.out.println("客户端读取一次文件,结尾是:" + fileBurstData.getEndPos());
                // 构建协议对象传输
                ctx.writeAndFlush(MsgUtil.buildTransferData(fileBurstData));
				break;
			default:
				break;
		}
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		ctx.close();
	    System.out.println("异常信息:\r\n" + cause.getMessage());
	}
	
	
}

2、服务端编写

/**
 * 服务端
 */
public class FileTransferServer {

	/**
	 * Main函数
	 */
	public static void main(String[] args) {
		String path = "";
		FileTransferServer server = new FileTransferServer(path);
		server.bind(7000);
	}

	private EventLoopGroup parentGroup = new NioEventLoopGroup(1);
	private EventLoopGroup childGroup = new NioEventLoopGroup();
	private Channel channel;

	// 上传文件的服务端存储目标路径
	private String dest_path;

	public FileTransferServer(String dest_path) {
		super();
		this.dest_path = dest_path;
	}

	public ChannelFuture bind(int port) {
		ChannelFuture channelFuture = null;
		try {
			ServerBootstrap b = new ServerBootstrap();
			b.group(parentGroup, childGroup).channel(NioServerSocketChannel.class) // 非阻塞模式
					.option(ChannelOption.SO_BACKLOG, 128).childHandler(new ChannelInitializer<SocketChannel>() {
						@Override
						protected void initChannel(SocketChannel ch) throws Exception {
							ch.pipeline().addLast(new ObjectDecode(FileTransferProtocol.class));
							ch.pipeline().addLast(new ObjectEncode(FileTransferProtocol.class));
							ch.pipeline().addLast(new FileTransferServerHandler(dest_path));
						}
					});
			channelFuture = b.bind(port).sync();
			this.channel = channelFuture.channel();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (null != channelFuture && channelFuture.isSuccess()) {
				System.out.println("server start done");
			} else {
				System.out.println("server start error");
			}
		}
		return channelFuture;
	}

	public void close() {
		if (channel != null) {
			channel.close();
		}
		parentGroup.shutdownGracefully();
		childGroup.shutdownGracefully();
	}

	public Channel getChannel() {
		return channel;
	}

	public void setChannel(Channel channel) {
		this.channel = channel;
	}

}

我们从客户端的发送请求的方法以及read方法可以知晓,服务端的处理器需要接收二种类型的传输对象,一个是info对象,一个是data对象,info对象是请求打开时处理用途,data对象是在已经传输过程中接收data对象,并且将data数据写入服务端。

在上面这个过程中,我们不管是接收info对象还是data对象,发送给客户端的都是instruct指令对象。

如果接收的是info对象,就要判断是否有instruct缓存?如果有,就是断点续传的,那么直接拿到此缓存对象发送给客户端,并且叫客户端读文件传数据。如果没有,那么就要初始构建instruct指令对象,即指定position位置为0起始。

如果接收的是data对象,就要将data对象的数据写入服务端存储地址,并且构建insturct指令存入缓存(指定position为上次读文件的结尾+1),然后发送此insturct对象给客户端,客户端根据是否完成来决定是否继续读取文件。

服务端自定义处理器实现:


public class FileTransferServerHandler extends ChannelInboundHandlerAdapter{
	
	private String dest_path;

	public FileTransferServerHandler(String dest_path) {
		this.dest_path = dest_path;
	}

	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		 System.out.println("客户端链接" + ctx.channel().localAddress().toString());
	}

	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		 System.out.println("客户端断开链接" + ctx.channel().localAddress().toString());
	}

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		if (!(msg instanceof FileTransferProtocol))return;
        FileTransferProtocol fileTransferProtocol = (FileTransferProtocol) msg;
        switch (fileTransferProtocol.getTransferType()) {
		case 0:
			FileDescInfo info = (FileDescInfo) fileTransferProtocol.getTransferObj();
			// 有缓存说明是断点续传,instruct记录的是下次需要读的postion
			FileBurstInstruct old = CacheUtil.get(info.getFileName());
			if(old!=null) {
			    if (old.getStatus() == Constants.FileStatus.COMPLETE) {
                    CacheUtil.remove(info.getFileName());
                }
				ctx.writeAndFlush(MsgUtil.buildTransferInstruct(old));
				return ;
			}
			// 没缓存就是初始传输,主要是指定instruct的position为0
            FileTransferProtocol sendFileTransferProtocol = MsgUtil.buildTransferInstruct(Constants.FileStatus.BEGIN, info.getFileUrl(), 0);
            ctx.writeAndFlush(sendFileTransferProtocol);
			break;
		case 2:
			FileBurstData fileBurstData = (FileBurstData) fileTransferProtocol.getTransferObj();
			FileBurstInstruct fileBurstInstruct = FileUtil.writeFile(dest_path, fileBurstData);

			// 保存断点续传信息
			CacheUtil.put(fileBurstData.getFileName(), fileBurstInstruct);

			ctx.writeAndFlush(MsgUtil.buildTransferInstruct(fileBurstInstruct));

			// 传输完成删除断点信息
			if (fileBurstInstruct.getStatus() == Constants.FileStatus.COMPLETE) {
				CacheUtil.remove(fileBurstData.getFileName());
			}
			break;
		default:
			break;
		}
		  
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		System.out.println("异常信息:\r\n" + cause.getMessage());
	}

}

 

 

end !!!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

netty文件上传断点续传的演示 的相关文章

  • Netty SSL 主机名验证支持

    据我所知 没有可以用来在 Netty 中启用 SSL 主机名验证的 标志 或配置设置 我见过使用 SslHandler handshake 返回的 ChannelFuture 添加自定义实现的示例 ChannelFuture handsha
  • Java Netty负载测试问题

    我使用文本协议编写了 接受连接和轰炸消息 100 字节 的服务器 并且我的实现能够使用第 3 方客户端发送大约 400K 秒的环回消息 我为这项任务选择了 Netty SUSE 11 RealTime JRockit RTS 但是当我开始基
  • 使用 vertx Web 客户端版本 4.0.0 时出现 java.lang.IllegalAccessError

    将 vertx 4 0 0 与 jdk 14 结合使用 尝试使用 vertx Web 客户端发出 REST 请求时 出现以下异常 15 56 48 294 vert x eventloop thread 0 ERROR io vertx c
  • netty-daxin-4(http&websocket)

    文章目录 http 服务端 NettyHttpServer HelloWorldServerHandler 客户端 ApiPost
  • netty源码:(28)ChannelPromise

    ChannelPromise是ChannelFuture的子接口 它是可写入的 其父接口Promise定义如下 ChannelPromise有个默认的实现类 DefaultChannelPromise 它的setSuccess方法用来调用所
  • netty源码:(28)ChannelPromise

    ChannelPromise是ChannelFuture的子接口 它是可写入的 其父接口Promise定义如下 ChannelPromise有个默认的实现类 DefaultChannelPromise 它的setSuccess方法用来调用所
  • 从 netty ByteBuf 获取字符串

    如何从netty中获取字符串ByteBuf 到目前为止 我能够逐个字符地获取它 有没有办法直接获取字符串对象 message is of type ByteBuf for int i 0 i lt message capacity i by
  • 在 netty 通道上设置套接字超时

    我有一个 netty 通道 我想在底层套接字上设置超时 默认设置为 0 超时的目的是 如果 15 分钟内没有发生任何事情 则未使用的通道将被关闭 虽然我没有看到任何配置可以这样做 而且套接字本身也对我隐藏 Thanks 如果使用ReadTi
  • 使用 Netty 的多线程 UDP 服务器

    我正在尝试使用 Netty 实现 UDP 服务器 这个想法是只绑定一次 因此只创建一个Channel This Channel仅使用一个处理程序进行初始化 该处理程序通过一个线程在多个线程之间分派传入数据报的处理ExecutorServic
  • 如何使用 netty 通过 HTTP 传输响应

    我正在使用 Netty 3 6 6 我想向调用者发送一个大的响应 我无法将响应正文复制到 ChannelBuffer 中 因为在某些情况下它会非常大 我正在将服务器从CXF迁移到Netty 以前 我只能使用CXF提供的OutputStrea
  • 出站 ChannelHandler 的捕获所有异常处理

    在 Netty 中 您有入站和出站处理程序的概念 只需在管道的末尾 尾部 添加一个通道处理程序并实现一个捕获所有入站异常处理程序即可实现exceptionCaught覆盖 如果未沿途处理 沿入站管道发生的异常将沿着处理程序传播 直到遇到最后
  • 为什么JDK NIO使用这么多anon_inode文件描述符?

    我正在使用 Sun 的 JDK 1 6 0 26 和 NIO 带有 Netty 在 lsof 中我看到数百个文件描述符anon inode lsof np 11225 fgrep w anon inode java 11225 nobody
  • netty ChannelInboundHandlerAdapter 将帧裁剪为 ~1500 字节

    我已经实现了一个服务器应用程序 它使用 netty 框架通过 ChannelInblundHandlerAdapter 读取传入的字节 如标题所示 我的问题是 我不定期地从客户端获取内容 我认为这些内容在 1 500 字节后被剪切 例如 在
  • Spring WebClient:SSLEngine 已关闭

    我们使用 Spring boot 版本 2 3 1 也使用 WebClient 我的网络客户端配置 private val client WebClient init val sslCtx SslContextBuilder forClie
  • Netty连接限制

    我正在开发一个使用 netty 3 6 5 的应用程序服务器 我想先了解一下期权积压的完整含义 另外 为什么没有关于 serverbootstrap 选项的文档来帮助我们开发人员 我的另一个问题是如何最好地限制服务器的并发连接数以获得更好的
  • 内蒂不写

    当尝试使用 netty 写入时 写入的数据永远不会在远程端结束 这已通过 Wireshark 确认 我努力了 Directly using writeAndFlush channel writeAndFlush new Packet Man
  • 如何使用 Netty 发送对象?

    如何通过Netty从服务器端发送bean并在客户端接收该bean 当我发送简单的整数消息 inputstream 时 它工作成功 但我需要发送 bean 如果您在客户端和服务器端使用 Netty 那么您可以使用 Netty对象解码器 htt
  • netty 4.x.x 中的 UDP 广播

    我们需要使用 Netty 4 0 0 二进制文件通过 UDP 通道广播对象 Pojo 在 Netty 4 0 0 中 它允许我们仅使用 DatagramPacket 类来发送 UDP 数据包 此类仅接受 ByteBuf 作为参数 还有其他方
  • 如何用Java处理来自客户端的Websocket消息?

    我正在使用 Websocket 用 Ja va 开发客户端 服务器应用程序 目前 所有客户端消息均使用 switch case 进行处理 如下所示 OnMessage public String onMessage String unscr
  • 为什么我的 Camel Netty 路由会在 JMS 消息的开头添加换行符?

    我有一个 Camel Netty 路由 它将 XML 发送到服务器端口并将其放入 JMS 消息中 在第一条消息之后 所有其他消息的顶部都有一个换行符 导致当 GUI 收到它时 我的 XML 无法解组 我的路线是这样的

随机推荐

  • 逻辑设计基础_第1章_初识数字逻辑

    这一个月打算学习数字逻辑设计 刚听完课 现在做一下笔记 第1章 初始数字逻辑 本节知识总结为三个方面 分别是数字逻辑的知识脉络 数字逻辑设计的用途和三种二进制编码 下面分别说明 1 1 数字逻辑的知识脉络 首先 学习逻辑代数 其次 根据逻辑
  • docker容器拷贝

    背景 当前jenkins服务器部署在内网环境 需要迁移到云服务器 版本和配置以及之前安装过的jenkins插件都需要同步迁移 方案1 使用docker commit将当前容器打包成镜像 docker commit contain id co
  • CSR867x — 蓝牙音频发射器方案(支持USB、模拟和SPDIF)

    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XX 作 者 文化人 XX 联系方式 或进群 471144274 XX 版权声明 原创文章 欢迎评论和转载 转载时能告诉我一声就
  • 傅里叶变换公式整理

    1 一维傅里叶变换 1 1 一维连续傅里叶变换 正变换 F
  • RS485(Modbus RTU)协议

    Modbus是啥 Modbus 是一种串行通信协议 是施耐德电气为使用可编程逻辑控制器 PLC 通信而发布 是工业领域通信协议的业界标准 并且是工业电子设备之间常用的连接方式 读完好像还是不知道是啥 没关系 你只要记住一点 Modbus是与
  • LeetCode初级算法-,买卖股票数组算法

    题目 给定一个数组 prices 其中 prices i 是一支给定股票第 i 天的价格 设计一个算法来计算你所能获取的最大利润 你可以尽可能地完成更多的交易 多次买卖一支股票 JAVA class Solution public int
  • DNS的解析流程,DNS主从配置,使用httpd服务演示安全上下文值的设定(selinux),使用web服务端口的改变来演示端口的设定(selinux)

    DNS的解析流程 一 DNS的解析方式 1 正向解析 正向解析文件中存储的记录称为A记录 A记录记录着域名和IP的映射关系 2 反向解析 反向解析文件中存储的记录称为PTR指针 PTR记录着IP和域名的映射关系 二 DNS域名的分层结构 国
  • OpenCV 二维码定位与识别

    因为二维码本身含有信息 因此可以作为产品的信息载体 如 产品特征 在工业领域常用在产品入库 分拣和包装上 但常常会因为二维码图像污点 光照不均匀以及二维码图像倾斜等原因 使得二维码的识别正确率低 针对这些问题 通过学习贾老师OpenCV课程
  • 数据库查询,返回前5、10行数据

    1 SQLServer sqlserver 支持top关键字 返回前若干条数据 select top 5 from table 返回前5行数据 2 MySQL mysql 支持 limit 只能适用于mysql limit 子句用于强制 s
  • 数据分析学习

    前言 数据分析已经是我们工作离不开的一个东西 其本质上还是基于数据算法对于数据的多维度计算 数据分析概念 数据分析方法
  • Android--- UI组件AdapterView and 适配器Adapter

    Android AdapterView and Adapter 适配器 Adapter UI控件 AdapterView ListView 简单的ListView实现 图文ListView实现 ListView的监听函数 GridView
  • socket的fd是什么?fd是啥的缩写?

    socket的fd是什么 fd是啥的缩写 fd 是 file descriptor 这种一般是BSD Socket的用法 用在Unix Linux系统上 在Unix Linux系统下 一个socket句柄 可以看做是一个文件 在socket
  • 7-7

    思路 在整个二维数组上下各加上一行0 然后从左到右 一列一列进行判断 判断的是数字改变次数 这里一列一列的找是因为可以把1 6 8分解成一片一片的元素 而且1 lt 8 lt 6 不知道能看懂不 代码里去掉 输入例子 可以得到 0 0 0
  • C#Tcp服务端主动断开,客户端无法感知问题

    服务端使用tcplistener接收连接请求 客户端使用tcpclient connect主动连接 在一对一的情况下 1个服务端只连接1个客户端时 服务端调用client Close 主动关闭连接后 客户端接收函数 revString br
  • Helm & Kubernetes Offline Deploy Rancher v2.7.5 Demo (helm 离线部署 rancher 实践)

    文章目录 1 简介 2 预备条件 3 选择 SSL 配置 4 离线安装的 Helm Chart 选项 5 下载介质 6 生成证书 7 镜像入库 8 安装 rancher 9 配置 nodeport 10 配置 ingress 11 界面访问
  • [1168]OSS ossutil64安装及使用

    文章目录 下载和安装 Linux系统安装 Windows系统安装 使用 oss下载到指定文件夹 从指定文件夹上传到oss相应的bucket下 设置ossutil的语言 clearOssData sh 下载和安装 下载地址 https hel
  • 一道有趣的面试:Trie 树及其改进

    0x00 导言 Trie 树是一种常见的数据结构 用以解决在给定单词在字典中是否存在的问题 而且支持动态的增删词典内容 常见的实现结构如下 struct node bool is word struct node 26 对于任意词典 查找给
  • 木马制作——图片木马制作

    木马制作 图片木马制作 一 简单说明 图片木马指的是图片格式的文件木马 图片格式包括jpg png等 原理是将木马程序和图片捆绑在一起 达到伪装的目的 程序可以是脚本语言或者编译语言 在web渗透中 通常将脚本编写的webshell和图片合
  • janus以及coturncentos8的配置_排错填坑完结篇

    前言 查阅了不少资料 网上对janus以及coturn的配置比比皆是 不过 我发现了一个问题 那就是 janus要直接对接coturn作为turn服务器吗 为什么 janus的demo例子 譬如 video room 能不能在手机4g网络上
  • netty文件上传断点续传的演示

    Netty文件上传断点续传的演示 一 理论和协议规范和工具类等 1 实现原理 netty文件上传采用自定义的协议方式实现 断点续传主要是依据RandomAccessFile类的随机读写能力 主要流程是客户端发起请求 将需要上传文件名称 路径