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