Demostración del currículum del punto de interrupción de carga de archivos netty

Demostración del currículum del punto de interrupción de carga de archivos de Netty

1. Especificaciones y herramientas de teoría y protocolo, etc.

1. Principio de implementación:

    La carga de archivos de Netty se implementa mediante un protocolo personalizado. La carga reanudable se basa principalmente en las capacidades de lectura y escritura aleatorias de la clase RandomAccessFile. El proceso principal es que el cliente inicia una solicitud. Deberá cargar el nombre del archivo, la ruta, leer los datos del archivo y leer Tomar información, como la posición inicial del archivo, y almacenarlo en caché en el servidor (utilizando la ruta del archivo como clave y el objeto de protocolo personalizado como valor). El servidor escribirá el archivo después de recibir el datos anteriores enviados por el cliente, y terminar de escribir El archivo también registrará información como la ubicación de los datos que se han escrito, y enviará la información nuevamente a los datos que el cliente necesita leer la próxima vez.

    Si el cliente desconecta el enlace durante este proceso, en este momento, porque el servidor almacena en caché la ubicación del archivo que se ha escrito, solo leerá el archivo desde la ubicación donde se escribió el archivo y luego lo transferirá a el servidor para escribir y otras operaciones cíclicas, para lograr el efecto de reanudar la transmisión del té.

 2. Uso básico de RandomAccessFile

     *
     Api básica de RandomAccessFile * .getFilePointer: Obtiene la posición de la operación actual
     * .seek (index): Establece la posición de la operación en index
     * .read (byte): Lee el archivo en la matriz de bytes, devuelve la longitud de la lectura file
     * 
     * Estructura Función
     * rf = new RandomAccessFile (new File (filePath), "r"); // El parámetro 2 es el modo
     *  
       * Detalles del modo:
     * "r" abre la carpeta especificada en modo de solo lectura. Si intenta ejecutar el método de escritura en este RandomAccessFile, se lanzará una IOException.
     * "Rw" abre el archivo especificado en modo lectura o escritura. Si el archivo aún no existe, intente crearlo.
     * "Rws" abre el archivo especificado en modo de lectura y escritura. En comparación con el modo "rw", cada actualización del contenido del archivo o de los metadatos también debe escribirse sincrónicamente en el dispositivo subyacente.
     * "Rwd" abre el archivo especificado en modo de lectura y escritura. En comparación con el modo "rw", también requiere que cada actualización del contenido del archivo se escriba sincrónicamente en el dispositivo subyacente.

	 * 测试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. Diseño de objeto de transmisión y objeto de protocolo

Hay tres tipos de objetos de transmisión, uno es el tipo de información de transmisión inicial enviado por el cliente al servidor, el otro es el tipo de datos de archivo de fragmentos y el otro es el tipo de instrucción de archivo de fragmentos devuelto por el servidor al cliente.

El objeto de protocolo contiene dos campos, uno es el tipo (el tipo de los tres objetos anteriores) y el otro es el campo del objeto, que representa el objeto de transmisión.

constante:

/**
 * 文件传输常量
 */
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;       //文件分片类型
    }

}

Objeto del acuerdo:

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;
	}

}

Objetos relacionados con la transferencia de archivos:

/**
 * 文件描述信息
 */
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. Códec

Por el diseño del objeto de protocolo, sabemos que esta carga de archivo utilizará el método de transmisión del objeto, por lo que debe personalizar el códec. El códec personalizado se implementa en base a protostuff. Al codificar, agregue un tipo int. Al decodificar el campo de longitud y el objeto de protocolo serializado, después de decodificar el campo de longitud, lea los datos y luego deserialice el objeto de protocolo.

1) Herramienta de serialización 

/**
 * 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) Decodificador de objetos (entrada al procesamiento, colocada primero en la cadena de procesadores, los procesadores posteriores pueden obtener directamente objetos de tipo 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) Codificador de objeto (codificador de salida --- objeto de protocolo de codificación --- colocado en la penúltima cadena de procesadores)

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. Herramientas de lectura y escritura de archivos de bajo nivel

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;
	}

Además, una clase de herramienta de construcción para objetos de protocolo de archivo suplementarios

/**
 * 消息构建对象
 */
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;
    }

}

En segundo lugar, el código de implementación del cliente y el servidor.

El paquete de código y la estructura de clases que se han completado anteriormente son los siguientes

1. Escritura del lado del cliente

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();
	}
	
}

Devolvemos el canal del cliente e iniciamos una solicitud inicial al servidor a través del canal. El procesador personalizado solo es responsable de recibir datos. Después de que se procesa el decodificador, sabemos que los datos recibidos son el objeto de protocolo FileTransferProtocol, y este objeto tiene tres tipos, lo que nuestro cliente recibe es el objeto de instrucción del servidor, y lo que el servidor almacena en caché también es el objeto de instrucción. Es decir, después de que el cliente lee los datos después de la solicitud inicial, los datos se leerán y enviarán después del objeto de instrucción devuelto por el servidor, se reciben los datos y así sucesivamente las operaciones del ciclo.

Demostración de los datos enviados por el cliente:

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);
	}
	
}

Procesador personalizado del cliente:

/**
 * 客户端自定义处理器
 */
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. Escritura del lado del servidor

/**
 * 服务端
 */
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;
	}

}

Podemos saber por el método de envío de solicitudes del cliente y el método de lectura que el procesador del servidor necesita recibir dos tipos de objetos de transmisión, uno es el objeto de información, el otro es el objeto de datos, el objeto de información se usa para procesar cuando la solicitud es abierto y el objeto de datos está en El objeto de datos se recibió durante el proceso de transmisión y los datos se escriben en el servidor.

En el proceso anterior, ya sea que recibamos el objeto de información o el objeto de datos, lo que enviamos al cliente es un objeto de instrucción de instrucción.

Si se recibe el objeto de información, ¿es necesario determinar si hay un caché de instrucciones? Si lo hay, es una carga reanudable, luego obtenga directamente el objeto en caché y envíelo al cliente, y pídale al cliente que lea el archivo y transfiera los datos. De lo contrario, el objeto de instrucción instruir debe construirse inicialmente, es decir, la posición de posición especificada es 0 comenzando.

Si se recibe el objeto de datos, los datos del objeto de datos deben escribirse en la dirección de almacenamiento del servidor y la instrucción insturct se construye para almacenar en la caché (especifique la posición como el final del último archivo leído + 1), y luego envía el objeto insturct al cliente. El cliente decide si continuar leyendo el archivo según esté completo.

Implementación del procesador personalizado del lado del servidor:


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());
	}

}

 

 

fin !!!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Supongo que te gusta

Origin blog.csdn.net/shuixiou1/article/details/114947474
Recomendado
Clasificación