Skip to main content

Gatech CS3210 学习笔记 - lab2 part1

· 5 min read

这里继续上一部分的内容,实现剩下的两个库。

1C xmodem#

这个模块实现的是一个十分古老的文件传输协议 xmodem,它只有五种控制字符,没有身份验证功能,抗干扰能力和纠错能力也很弱。优点是协议简单,用 rust 实现的发送端和接收端的代码量都在一百行左右。 实现这个模块需要我自己打的代码大约只有一百行。由于协议文档并不长,也预先给好了一部分定义和函数,所以在前期花了一些时间梳理协议的逻辑和提供的函数定义,课程也建议可以参考测试数据 debug,后面发现测试函数里面的一些代码不容易理解,自己对于协议的纠错能力过于自信,所以花了不少时间 debug。实际上这个协议一旦出现非预期的结果就抛出异常,不会尝试纠错。 在这个过程中我更加理解了 rust 的 match 语句、解构、问号操作符的优势,我最初没有利用好这些特性的代码有最终版本的两倍长,而且有很多嵌套分支结构,在修改之后就清晰了许多。以接收端的核心逻辑为例,仅两个 match 语句就实现了分支选择,还有函数调用后面的问号操作符简化了返回值的判断。

match self.read_byte(true) {    Ok(byte) if byte == SOH => {        self.expect_byte_or_cancel(self.packet, "packet number")?;        self.expect_byte_or_cancel(255 - self.packet, "packet number 1s complete")?;
        for i in 0..128 {            buf[i] = self.read_byte(false)?;        }        match self.expect_byte(get_checksum(buf), "checksum") {            Ok(_) => {                self.write_byte(ACK)?;                self.packet += 1;                return Ok(128);            },            Err(_) => {                self.write_byte(NAK)?;                return ioerr!(Interrupted, "checksum failed");            }        }    },    Ok(byte) if byte == EOT => {        self.write_byte(NAK)?;        self.expect_byte_or_cancel(EOT, "second EOT");        self.write_byte(ACK)?;        return Ok(0);    },    Ok(_) => {        self.write_byte(CAN);        return ioerr!(InvalidData, "")    },    Err(e)  => {        self.write_byte(CAN)?;        return Err(e)    }}

1D ttywrite#

这个模块是一个串口发送端 cli 程序,使用的依赖包相比前面的模块要多一些,参数解析使用了structopt,串口配置与通信使用了serial,通信协议用到了上一个模块 xmodem,串口相关参数的检查代码也预先提供了,实际需要编写的内容非常少。串口参数没有什么可说的,这个模块支持两种数据源和两种发送方式,如何用更少的代码写出判断逻辑花了我一些时间,后来参考别人的代码看到装箱茅塞顿开。调试好代码后又去学习了一遍错误处理和智能指针的内容。

let mut _file:Box<dyn io::Read> = if let Some(path) = opt.input {    Box::new(File::open(path).unwrap())} else {    Box::new(io::stdin())};
if opt.raw {    io::copy(&mut _file, &mut port).unwrap();} else {    Xmodem::transmit_with_progress(_file, port, progress_fn).unwrap();}

在处理输入的部分,两种输入方式分别为std::fs::Filestd::io::Stdin,这两种结构体都实现了后面需要用到的std::io::Read,满足多态的条件。Box<T>会将变量移动到堆上并维护一个指针,通过装箱实现了多态。 在处理输出的部分,由于io::copy接受两个相同类型的参数,所以只能将两个参数都设为可变类型。在上面装箱的输入流在这里并没有显式拆箱,我理解是由于 Rust 的解引用强制多态(deref coercions)特性实现了隐式的拆箱和类型匹配。 代码中的unwrap()函数是异常处理代码,一旦前面的函数抛出错误就恐慌(panic)。