XMODEM是一个简单的文件传输协议。该协议在1977年被设计出来,具有包校验码,连接取消和自动重传等功能。它被广泛使用在串行接口上用于文件传输。
1. XMODEM的性质
XMODEM是二进制协议: 字节以二进制形式(01序列)被发送和接受。
XMODEM是半双工通信: 同一时间,发送方和接收方最多一方在工作。
XMODEM是packet-based:数据被划分为128字节的包进行传输。
2. 文件传输
预定于如下常量
1 | |
2.1. 包结构
| Byte 1 | Byte 2 | Byte 3 | Byte 4-131 | Byte 132 |
|---|---|---|---|---|
| start of header | packet number | complement of packet number | packet data | 16-bit CRC |
- 字节1仅可以为上述定义的常量中
SOH,EOT其它情况为error - 字节2为packet number
- 字节3为packet number的补码
- 构成数据包的字节4-131可以为任意数据
- 字节132为校验码(后文并没有使用crc作为校验码,crc算法的实现见末尾)
2.2. 传输建立
为启动文件传输,接收者发送NAK字节,发送者等待该字节。当发送者接收到NAK后,开始数据包(packet)的传输。
一旦文件传输开始,每一个数据包的传输和接收是相同的。数据包从1开始按序标号,并在255之后重新从0开始计数。
每个数据包128字节,如果不够128字节使用0padding。
取消传输(canncel the transfer):某一方若取消传输则要向对方发送CAN.
2.3. 发送方
- 发送
SOH字节 - 发送数据包号
- 发送数据包号的补码
- 发送数据包
- 发送数据包校验和
- 校验和为数据包所有字节之和模256.
- 从接收方读取一个字节
- 若字节是
NAK,则重传相同的数据包(重传上限为10次) - 若字节是
ACK,则发送下一个数据包
- 若字节是
2.4. 接受方
- 等待发送方的
SOH或EOT字节- 如果收到一个不同的字节,取消此次传输
- 若收到
EOT字节,接收者主动终止传输(end of transmission)
- 读取下一个字节并和当前数据包号比较
- 如果收到错误的数据包号,接收者取消传输
- 读取下一个字节并和数据包号的补码比较
- 如果收到的数据错误,取消传输
- 从发送者处读取数据包(128字节)
- 为数据包计算校验和
- 读取下一个字节同时比较和计算得到的校验和比较
- 若不同,发送
NAK同时重新尝试接收相同的数据包 - 若相同,发送
ACK同时接收下一个数据包
- 若不同,发送
为了取消传输,CAN字节被接收方或发送方使用。当任意一方收到CAN,报错,并终止连接。
2.5. 终止传输
发送方
- 发送
EOT字节 - 等待
NAK字节,如果收到一个不同的字节,报错 - 发送第二个
EOT字节 - 等待
ACK字节,如果收到一个不同的字节,报错
接收方
- 发送
NAK字节 - 等待第二个
EOT字节,如果收到一个不同的字节,取消传输 - 发送
ACK字节
3. rust实现
考虑如下设计模式:
为单次连接设置数据结构记录该次连接的状态。i) 通过静态方法向外导出数据发送抽象和数据接收抽象。ii) 对符合T: io::Read + io::Write的类型,实现底层的字节、数据包传输抽象。
静态方法仅使用底层提供的i) 构造器 ii) read/write_packet()抽象,并处理底层返回的error和重传机制。
1 | |
4. 测试
4.1. 一些边界情况
- packet中的传输字符可以是任意的,因而可以存在
CAN - 根据1,若packet中只存在1个
CAN计算得到checksum(求和对256取模)应当也为CAN。因此,若checksum若收到CAN不应当认为是接收方发的cancel。
4.2. 测试的实现
- 一端的测试: 使用
std::io::cursor套在内存缓冲区上 - 两端通信的模拟:使用
pipe模拟连接通路,两端分别使用线程调用transmit和receive方法。
pipe结构体的实现如下:
1 | |
5. 附录(crc算法的c实现)
1 | |
6. 参考
- https://tc.gts3.org/cs3210/2020/spring/lab/lab2.html
- Xmodem Protocol with CRC