package Ymodem; // 定义包名为Ymodem import java.io.*; // 导入Java输入输出包 import java.nio.file.Files; // 导入NIO文件操作类 import java.nio.file.Path; // 导入NIO路径类 import java.nio.file.attribute.BasicFileAttributes; // 导入文件属性类 import java.util.Arrays; // 导入数组工具类 public class YModem { // 定义YModem类 private Modem modem; // 声明Modem对象 static int size; // 静态大小变量 /** * 构造函数 * @param inputStream 流,用于从另一端读取接收到的数据 * @param outputStream 用于将数据写入另一端的流 */ public YModem(InputStream inputStream, OutputStream outputStream) { // 构造函数 this.modem = new Modem(inputStream, outputStream); // 初始化Modem对象 } // 进度回调接口 public interface ProgressCallback { // 定义进度回调接口 void onProgress(int currentBlock, int totalBlocks); // 进度回调方法 } /** * 发送文件。 * 此方法支持正确的线程中断,当线程中断时将发送"取消传输"。 * 因此,您可以将长传输移动到其他线程并根据您的算法中断它。 * @param file 文件路径 * @throws IOException */ public void send(Path file) throws IOException { // 发送文件方法 send(file, null); // 调用带进度回调的发送方法 } /** * 发送文件(带进度回调)。 * 此方法支持正确的线程中断,当线程中断时将发送"取消传输"。 * 因此,您可以将长传输移动到其他线程并根据您的算法中断它。 * @param file 文件路径 * @param progressCallback 进度回调 * @throws IOException */ public void send(Path file, ProgressCallback progressCallback) throws IOException { // 带进度回调的发送方法 try (DataInputStream dataStream = new DataInputStream(Files.newInputStream(file))) { // 创建文件输入流 Timer timer = new Timer(Modem.WAIT_FOR_RECEIVER_TIMEOUT).start(); // 创建并启动计时器 boolean useCRC16 = modem.waitReceiverRequest(timer); // 等待接收方请求并确定CRC类型 CRC crc; // 声明CRC对象 if (useCRC16) // 如果使用CRC16 crc = new CRC16(); // 创建CRC16对象 else // 否则 crc = new CRC8(); // 创建CRC8对象 // 计算总块数 long fileSize = Files.size(file); // 获取文件大小 int totalDataBlocks = (int) Math.ceil(fileSize / 128.0); // 计算数据块总数 int totalBlocks = 2 + totalDataBlocks; // 文件头块 + 数据块 + 结束块 //发送块0(文件头) BasicFileAttributes readAttributes = Files.readAttributes(file, BasicFileAttributes.class); // 读取文件属性 String name=file.getFileName().toString(); // 获取文件名 String fileNameString = name + (char)0 + fileSize + " "+ Long.toOctalString(readAttributes.lastModifiedTime().toMillis() / 1000); // 构建文件名字符串 byte[] fileNameBytes = Arrays.copyOf(fileNameString.getBytes(), 128); // 将文件名转换为字节数组 modem.sendBlock(0, Arrays.copyOf(fileNameBytes, 128), 128, crc); // 发送文件头块 // 更新进度(文件头块) if (progressCallback != null) { // 如果进度回调不为空 progressCallback.onProgress(1, totalBlocks); // 调用进度回调 } modem.waitReceiverRequest(timer); // 等待接收方请求 byte[] block = new byte[128]; // 创建数据块数组 // 发送数据块 int blockNumber = 1; // 初始化块编号 int bytesRead; // 声明读取字节数变量 while ((bytesRead = dataStream.read(block)) != -1) { // 读取文件数据 if (bytesRead < 128) { // 如果读取字节数小于128 // 填充不足部分 Arrays.fill(block, bytesRead, 128, Modem.CPMEOF); // 用CPMEOF填充剩余部分 } modem.sendBlock(blockNumber, block, 128, crc); // 发送数据块 // 更新进度(数据块) if (progressCallback != null) { // 如果进度回调不为空 progressCallback.onProgress(1 + blockNumber, totalBlocks); // 调用进度回调 } blockNumber++; // 块编号递增 } modem.sendEOT(); // 发送传输结束信号 modem.waitReceiverRequest(timer); // 等待接收方请求 byte[] bytes = new byte[128]; // 创建空字节数组 modem.sendBlock(0, bytes, 128, crc); // 发送空块表示传输结束 // 更新进度(结束块) if (progressCallback != null) { // 如果进度回调不为空 progressCallback.onProgress(totalBlocks, totalBlocks); // 调用进度回调 } } } /** * 批量发送文件。
*

* 此方法支持正确的线程中断,当线程中断时将发送"取消传输"。 * 因此,您可以将长传输移动到其他线程并根据您的算法中断它。 * * @param files * @throws IOException */ public void batchSend(Path... files) throws IOException { // 批量发送文件方法 for (Path file : files) { // 遍历文件数组 send(file); // 发送每个文件 } sendBatchStop(); // 发送批量传输结束信号 } private void sendBatchStop() throws IOException { // 发送批量传输结束方法 Timer timer = new Timer(Modem.WAIT_FOR_RECEIVER_TIMEOUT).start(); // 创建并启动计时器 boolean useCRC16 = modem.waitReceiverRequest(timer); // 等待接收方请求并确定CRC类型 CRC crc; // 声明CRC对象 if (useCRC16) // 如果使用CRC16 crc = new CRC16(); // 创建CRC16对象 else // 否则 crc = new CRC8(); // 创建CRC8对象 //发送块0 byte[] bytes = new byte[128]; // 创建空字节数组 modem.sendBlock(0, bytes, bytes.length, crc); // 发送空块表示批量传输结束 } /** * 接收单个文件
*

* 此方法支持正确的线程中断,当线程中断时将发送"取消传输"。 * 因此,您可以将长传输移动到其他线程并根据您的算法中断它。 * * @param directory 文件将保存的目录 * @return 创建的文件路径 * @throws IOException */ public Path receiveSingleFileInDirectory(Path directory) throws IOException { // 接收单个文件方法 return receive(directory, true); // 调用接收方法 } /** * 批量接收文件
*

* 此方法支持正确的线程中断,当线程中断时将发送"取消传输"。 * 因此,您可以将长传输移动到其他线程并根据您的算法中断它。 * * @param directory 文件将保存的目录 * @throws IOException */ public void receiveFilesInDirectory(Path directory) throws IOException { // 批量接收文件方法 while (receive(directory, true) != null) { // 循环接收文件直到返回null } } /** * 接收路径
*

* 此方法支持正确的线程中断,当线程中断时将发送"取消传输"。 * 因此,您可以将长传输移动到其他线程并根据您的算法中断它。 * * @param path 数据将保存的文件路径 * @return 文件路径 * @throws IOException */ public Path receive(Path path) throws IOException { // 接收文件方法 return receive(path, false); // 调用接收方法 } private Path receive(Path path, boolean inDirectory) throws IOException { // 私有接收方法 DataOutputStream dataOutput = null; // 声明数据输出流 Path filePath; // 声明文件路径 try { CRC crc = new CRC16(); // 创建CRC16对象 int errorCount = 0; // 初始化错误计数 // 处理块0 byte[] block; // 声明数据块数组 int character; // 声明字符变量 while (true) { // 循环处理块0 character = modem.requestTransmissionStart(true); // 请求传输开始 try { // 从零块读取文件名 block = modem.readBlock(0, (character == Modem.SOH), crc); // 读取块0 if (inDirectory) { // 如果在目录中 StringBuilder sb = new StringBuilder(); // 创建字符串构建器 if (block[0] == 0) { // 如果块首字节为0 //这是批量文件传输的停止块 modem.sendByte(Modem.ACK); // 发送ACK return null; // 返回null } for (int i = 0; i < block.length; i++) { // 遍历块数据 if (block[i] == 0) { // 如果遇到0字节 break; // 跳出循环 } sb.append((char) block[i]); // 追加字符到字符串构建器 } filePath = path.resolve(sb.toString()); // 解析文件路径 } else { // 否则 filePath = path; // 直接使用路径 } dataOutput = new DataOutputStream(Files.newOutputStream(filePath)); // 创建文件输出流 modem.sendByte(Modem.ACK); // 发送ACK break; // 跳出循环 } catch (TimeoutException | Modem.InvalidBlockException e) { // 捕获超时和无效块异常 errorCount++; // 错误计数增加 if (errorCount == Modem.MAXERRORS) { // 如果错误计数达到最大值 modem.interruptTransmission(); // 中断传输 throw new IOException("Transmission aborted, error count exceeded max"); // 抛出IO异常 } modem.sendByte(Modem.NAK); // 发送NAK } catch (Modem.RepeatedBlockException | Modem.SynchronizationLostException e) { // 捕获重复块和同步丢失异常 //致命传输错误 modem.interruptTransmission(); // 中断传输 throw new IOException("Fatal transmission error", e); // 抛出IO异常 } } //接收数据块 modem.receive(filePath, true); // 接收数据块 } finally { if (dataOutput != null) { // 如果数据输出流不为空 dataOutput.close(); // 关闭数据输出流 } } return filePath; // 返回文件路径 } public static int getSize() { // 获取大小方法 return size; // 返回大小 } }