【Rust】2、实战:文件、网络、时间、进程-线程-容器、内核、信号-中断-异常

2023-11-04

在这里插入图片描述

文章目录

七、文件和存储

7.2 serde 与 bincode 序列化

源码地址为 git clone https://github.com/rust-in-action/code rust-in-action && cd rust-in-action/ch7/ch7-serde-eg。

若想自己创建项目,可设置 Cargo.toml 如下:

[package]
name = "ch7-serde-eg"
version = "0.1.0"
authors = ["Tim McNamara <author@rustinaction.com>"]
edition = "2021"

[dependencies]
bincode = "1"
serde = "1"
serde_cbor = "0.8"
serde_derive = "1"
serde_json = "1"
use bincode::serialize as to_bincode; // <1>
use serde_cbor::to_vec as to_cbor; // <1>
use serde_derive::Serialize;
use serde_json::to_string as to_json; // <1>

#[derive(Serialize)] // 这会让serde_derive软件包来自行编写必要的代码,用来执行在内存中的City和磁盘中的City的转换。
struct City {
  name: String,
  population: usize,
  latitude: f64,
  longitude: f64,
}

fn main() {
  let calabar = City {
    name: String::from("Calabar"),
    population: 470_000,
    latitude: 4.95,
    longitude: 8.33,
  };

  let as_json = to_json(&calabar).unwrap(); // <3>
  let as_cbor = to_cbor(&calabar).unwrap(); // <3>
  let as_bincode = to_bincode(&calabar).unwrap(); // <3>

  println!("json:\n{}\n", &as_json);
  println!("cbor:\n{:?}\n", &as_cbor);
  println!("bincode:\n{:?}\n", &as_bincode);
  println!("json (as UTF-8):\n{}\n", String::from_utf8_lossy(as_json.as_bytes()));
  println!("cbor (as UTF-8):\n{:?}\n", String::from_utf8_lossy(&as_cbor));
  println!("bincode (as UTF-8):\n{:?}\n", String::from_utf8_lossy(&as_bincode));
}

// code result:
json:
{"name":"Calabar","population":470000,"latitude":4.95,"longitude":8.33}

cbor:
[164, 100, 110, 97, 109, 101, 103, 67, 97, 108, 97, 98, 97, 114, 106, 112, 111, 112, 117, 108, 97, 116, 105, 111, 110, 26, 0, 7, 43, 240, 104, 108, 97, 116, 105, 116, 117, 100, 101, 251, 64, 19, 204, 204, 204, 204, 204, 205, 105, 108, 111, 110, 103, 105, 116, 117, 100, 101, 251, 64, 32, 168, 245, 194, 143, 92, 41]

bincode:
[7, 0, 0, 0, 0, 0, 0, 0, 67, 97, 108, 97, 98, 97, 114, 240, 43, 7, 0, 0, 0, 0, 0, 205, 204, 204, 204, 204, 204, 19, 64, 41, 92, 143, 194, 245, 168, 32, 64]

json (as UTF-8):
{"name":"Calabar","population":470000,"latitude":4.95,"longitude":8.33}

cbor (as UTF-8):
"�dnamegCalabarjpopulation\u{1a}\0\u{7}+�hlatitude�@\u{13}������ilongitude�@ ��\u{8f}\\)"

bincode (as UTF-8):
"\u{7}\0\0\0\0\0\0\0Calabar�+\u{7}\0\0\0\0\0������\u{13}@)\\���� @"

7.3 实现一个 hexdump

首先从原始字符串中读取,程序如下:

use std::io::prelude::*;   // prelude导入了在I/O操作中常用的一些trait,例如Read和Write。

const BYTES_PER_LINE: usize = 16;
// 当你使用原始字符串字面量(raw string literal)来构建多行的字符串字面量时,双引号是不需要转义的(注意这里的r前缀和#分隔符)。
// 额外的那个b前缀表示, 应该把这里的字面量数据视为字节数据(&[u8]),而不是UTF-8文本数据(&str)。
const INPUT: &'static [u8] = br#"
fn main() {
    println!("Hello, world!");
}"#;

fn main() -> std::io::Result<()> {
    let mut buffer: Vec<u8> = vec!();       
    INPUT.read_to_end(&mut buffer)?;        

    let mut position_in_input = 0;
    for line in buffer.chunks(BYTES_PER_LINE) {
        print!("[0x{:08x}] ", position_in_input); // 输出当前位置的信息,最多8位,不足8位则在左侧用零填充。如[0x00000000]
        for byte in line {
            print!("{:02x} ", byte); // 如 0a 66 6e 20 6d 61 69
        }
        println!();
        position_in_input += BYTES_PER_LINE;
    }
    Ok(())
}

// code result:
[0x00000000] 0a 66 6e 20 6d 61 69 6e 28 29 20 7b 0a 20 20 20 
[0x00000010] 20 70 72 69 6e 74 6c 6e 21 28 22 48 65 6c 6c 6f 
[0x00000020] 2c 20 77 6f 72 6c 64 21 22 29 3b 0a 7d

其次从文件中读取,程序如下:

use std::env;
use std::fs::File;
use std::io::prelude::*;
const BYTES_PER_LINE: usize = 16; // <1>

fn main() {
  let arg1 = env::args().nth(1);
  let fname = arg1.expect("usage: fview FILENAME");

  let mut f = File::open(&fname).expect("Unable to open file.");
  let mut pos = 0;
  let mut buffer = [0; BYTES_PER_LINE];

  while let Ok(_) = f.read_exact(&mut buffer) {
    print!("[0x{:08x}] ", pos);
    for byte in &buffer {
      match *byte {
        0x00 => print!(".  "),
        0xff => print!("## "),
        _ => print!("{:02x} ", byte),
      }
    }

    println!("");
    pos += BYTES_PER_LINE;
  }
}

// code result:
y% echo abcabcabcabcabcabcabcabcabcabcabcabc > d.txt
y% cargo run d.txt                 
[0x00000000] 61 62 63 61 62 63 61 62 63 61 62 63 61 62 63 61 
[0x00000010] 62 63 61 62 63 61 62 63 61 62 63 61 62 63 61 62 

7.4 操作文件

7.4.1 打开文件

在这里插入图片描述

如果需要更多的控制权限,可以使用std::fs::OpenOptions。它提供了必要的选项,可以根据任何预期的应用情况来调整。清单7.16给出了一个很好的示例,在此代码中使用了 append(追加) 模式。此应用程序需要文件是可读可写的,而且如果文件不存在,它就会创建出该文件。清单7.5摘自清单7.16,展示了使用std::fs::OpenOptions创建一个可写的文件,并且打开文件时不会清空文件内容。

let f = OpenOptions::new() // 建造者模式例子。每个方法都会返回一个OpenOptions结构体的新实例,并且附带相关选项的集合。
        .read(true)   // 为读取而打开文件。
        .write(true)  // 开启写入。这行代码不是必需的,因为后面的append隐含了写入的选项。
        .create(true) // 如果在path处的文件不存在,则创建一个文件出来。
        .append(true) // 不会删除已经写入磁盘中的任何内容。
        .open(path)?; // 打开在path处的文件,然后解包装中间产生的Result。

7.4.2 用 std::fs::Path 交互

处理文件就用专业的 Path 包,而不要用 String 包,防止意想不到的麻烦,例如下文代码中 x 为 Some(“”):

fn main() {
    let hello = String::from("/tmp/ hello.txt");
    let x = hello.split("/").nth(0);
    let y = hello.split("/").nth(1);
    let z = hello.split("/").nth(2);
    println!("{:?}, {:?}, {:?}", x, y, z);
} 

// code result:
Some(""), Some("tmp"), Some(" hello.txt")

7.5 基于 append 模式实现 kv数据库

目标是,通过 append 模式,使 kv 的数据永不丢失或损坏。

7.5.1 kv 模型

在这里插入图片描述

7.5.2 命令行接口

cargo new --lib actionkv
touch src/akv_mem.rs

tree # 输出如下:
├──src
│   ├──akv_mem.rs
│   └──lib.rs
└──Cargo.toml

设置 Cargo.toml 如下:

[package]
name = "actionkv"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
byteorder = "1.2" # 使用额外的trait扩展了许多Rust类型,让它们能够以可重复的、易于使用的方式被写入磁盘和读回到程序中。
crc = "1.7"       # 校验

[lib]                    
name = "libactionkv"  # Cargo.toml中的这个分段,为你将要构建出的库给出一个名字。注意,一个crate中只可以有一个库。
path = "src/lib.rs"

[[bin]]  # [[bin]]分段可以有多个,定义了将从此包中构建出的可执行文件。双方括号语法是必需的,因为它明确地将这个bin描述为一个或多个bin元素的一部分。
name = "akv_mem"
path = "src/akv_mem.rs"

actionkv 项目最后会由多个文件组成。图7.1展示了这些文件之间的关系,以及它们如何协同工作来构建名为akv_mem的可执行文件,这个可执行文件在项目的Cargo. toml文件的分段中进行了描述。

在这里插入图片描述

7.6 前端代码

在这里插入图片描述

use libactionkv::ActionKV; // 尽管src/lib.rs是存在于我们的项目中的,但是在我们项目中的src/bin.rs文件,会把它视为与任何其他的包一样,同等对待。

#[cfg(target_os = "windows")] // 此处的cfg属性注解,可以让Windows用户在此应用的帮助文档中看到正确的文件扩展名。这个属性注解将会在后文中进行讲解。
const USAGE: &str = "
Usage:
    akv_mem.exe FILE get KEY
    akv_mem.exe FILE delete KEY
    akv_mem.exe FILE insert KEY VALUE
    akv_mem.exe FILE update KEY VALUE
";

#[cfg(not(target_os = "windows"))]
const USAGE: &str = "
Usage:
    akv_mem FILE get KEY
    akv_mem FILE delete KEY
    akv_mem FILE insert KEY VALUE
    akv_mem FILE update KEY VALUE
";

fn main() {
  let args: Vec<String> = std::env::args().collect();
  let fname = args.get(1).expect(&USAGE);
  let action = args.get(2).expect(&USAGE).as_ref();
  let key = args.get(3).expect(&USAGE).as_ref();
  let maybe_value = args.get(4);

  let path = std::path::Path::new(&fname);
  let mut store = ActionKV::open(path).expect("unable to open file");
  store.load().expect("unable to load data");

  match action {
    "get" => match store.get(key).unwrap() {
      None => eprintln!("{:?} not found", key),
      Some(value) => println!("{:?}", value),
    },

    "delete" => store.delete(key).unwrap(),

    "insert" => {
      let value = maybe_value.expect(&USAGE).as_ref();
      store.insert(key, value).unwrap()
    }

    "update" => {
      let value = maybe_value.expect(&USAGE).as_ref();
      store.update(key, value).unwrap()
    }

    _ => eprintln!("{}", &USAGE),
  }
}

7.6.1 用条件编译定制要编译的内容

在这里插入图片描述

在这里插入图片描述

7.7 核心:LIBACTIONKV 包

在7.6节中构建的命令行应用程序,把具体的工作分派给了 libactionkv::ActionKV。结构体 ActionkV 负责管理与文件系统的交互,以及编码和解码来自磁盘中的格式数据。图7.2描述了这些关系。

在这里插入图片描述

7.7.1 初始化 ActionKV 结构体

use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::io;
use std::io::prelude::*;
use std::io::{BufReader, BufWriter, SeekFrom};
use std::path::Path;

use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use crc::crc32;
use serde_derive::{Deserialize, Serialize};

type ByteString = Vec<u8>;
type ByteStr = [u8];

#[derive(Debug, Serialize, Deserialize)] // 让编译器自动生成序列化的代码,以便将KeyValuePair(键值对)的数据写入磁盘。
pub struct KeyValuePair {
  pub key: ByteString,
  pub value: ByteString,
}

#[derive(Debug)]
pub struct ActionKV {
  f: File,
  pub index: HashMap<ByteString, u64>,
}

impl ActionKV {
  pub fn open(path: &Path) -> io::Result<Self> {
    let f = OpenOptions::new()
      .read(true)
      .write(true)
      .create(true)
      .append(true)
      .open(path)?;
    let index = HashMap::new();
    Ok(ActionKV { f, index })
  }

  fn process_record<R: Read>(
    // <1>
    f: &mut R,
  ) -> io::Result<KeyValuePair> {
    let saved_checksum = f.read_u32::<LittleEndian>()?;
    let key_len = f.read_u32::<LittleEndian>()?;
    let val_len = f.read_u32::<LittleEndian>()?;
    let data_len = key_len + val_len;

    let mut data = ByteString::with_capacity(data_len as usize);

    {
      f.by_ref() // <2>
        .take(data_len as u64)
        .read_to_end(&mut data)?;
    }
    debug_assert_eq!(data.len(), data_len as usize);

    let checksum = crc32::checksum_ieee(&data);
    if checksum != saved_checksum {
      panic!(
        "data corruption encountered ({:08x} != {:08x})",
        checksum, saved_checksum
      );
    }

    let value = data.split_off(key_len as usize);
    let key = data;

    Ok(KeyValuePair { key, value })
  }

  pub fn seek_to_end(&mut self) -> io::Result<u64> {
    self.f.seek(SeekFrom::End(0))
  }

  pub fn load(&mut self) -> io::Result<()> {
    let mut f = BufReader::new(&mut self.f);

    loop {
      let current_position = f.seek(SeekFrom::Current(0))?;

      let maybe_kv = ActionKV::process_record(&mut f);
      let kv = match maybe_kv {
        Ok(kv) => kv,
        Err(err) => {
          match err.kind() {
            io::ErrorKind::UnexpectedEof => {
              // <3>
              break;
            }
            _ => return Err(err),
          }
        }
      };

      self.index.insert(kv.key, current_position);
    }

    Ok(())
  }

  pub fn get(&mut self, key: &ByteStr) -> io::Result<Option<ByteString>> {
    // <4>
    let position = match self.index.get(key) {
      None => return Ok(None),
      Some(position) => *position,
    };

    let kv = self.get_at(position)?;

    Ok(Some(kv.value))
  }

  pub fn get_at(&mut self, position: u64) -> io::Result<KeyValuePair> {
    let mut f = BufReader::new(&mut self.f);
    f.seek(SeekFrom::Start(position))?;
    let kv = ActionKV::process_record(&mut f)?;

    Ok(kv)
  }

  pub fn find(&mut self, target: &ByteStr) -> io::Result<Option<(u64, ByteString)>> {
    let mut f = BufReader::new(&mut self.f);

    let mut found: Option<(u64, ByteString)> = None;

    loop {
      let position = f.seek(SeekFrom::Current(0))?;

      let maybe_kv = ActionKV::process_record(&mut f);
      let kv = match maybe_kv {
        Ok(kv) => kv,
        Err(err) => {
          match err.kind() {
            io::ErrorKind::UnexpectedEof => {
              // <3>
              break;
            }
            _ => return Err(err),
          }
        }
      };

      if kv.key == target {
        found = Some((position, kv.value));
      }

      // important to keep looping until the end of the file,
      // in case the key has been overwritten
    }

    Ok(found)
  }

  pub fn insert(&mut self, key: &ByteStr, value: &ByteStr) -> io::Result<()> {
    let position = self.insert_but_ignore_index(key, value)?;

    self.index.insert(key.to_vec(), position);
    Ok(())
  }

  pub fn insert_but_ignore_index(&mut self, key: &ByteStr, value: &ByteStr) -> io::Result<u64> {
    let mut f = BufWriter::new(&mut self.f);

    let key_len = key.len();
    let val_len = value.len();
    let mut tmp = ByteString::with_capacity(key_len + val_len);

    for byte in key {
      tmp.push(*byte);
    }

    for byte in value {
      tmp.push(*byte);
    }

    let checksum = crc32::checksum_ieee(&tmp);

    let next_byte = SeekFrom::End(0);
    let current_position = f.seek(SeekFrom::Current(0))?;
    f.seek(next_byte)?;
    f.write_u32::<LittleEndian>(checksum)?;
    f.write_u32::<LittleEndian>(key_len as u32)?;
    f.write_u32::<LittleEndian>(val_len as u32)?;
    f.write_all(&tmp)?;

    Ok(current_position)
  }

  #[inline]
  pub fn update(&mut self, key: &ByteStr, value: &ByteStr) -> io::Result<()> {
    self.insert(key, value)
  }

  #[inline]
  pub fn delete(&mut self, key: &ByteStr) -> io::Result<()> {
    self.insert(key, b"")
  }
}

7.7.2 处理单条记录

在这里插入图片描述

处理代码详见上节的 fn process_record() 函数。

7.7.3 以确定的字节顺序将多字节二进制数据写入磁盘

在这里插入图片描述

use std::io::Cursor; // 因为文件支持seek(),即拥有向前或者向后移动到不同的位置上的能力,要让Vec<T> 能够模拟文件,必须要额外做一些事情。而io::Cursor就是做这个的,它使得位于内存中的Vec<T> 在行为上类似于文件。 
use byteorder::LittleEndian; // 这个类型在此程序中调用多个read_*() 和write_*()方法时,作为这些方法的类型参数来使用。
use byteorder::{ReadBytesExt, WriteBytesExt}; // 这两个trait提供了read_*() 和write_*()方法。

fn write_numbers_to_file() -> (u32, i8, f64) {
  let mut w = vec![]; // 这个变量名w是writer的缩写。

  let one: u32 = 1;
  let two: i8 = 2;
  let three: f64 = 3.0;

  w.write_u32::<LittleEndian>(one).unwrap(); // 把值写入“磁盘”。这些方法会返回io::Result,在这里我们使用简单处理,直接把它给“吞掉了”,因为除非运行该程序的计算机出现严重问题,否则这些方法不会失败。
  println!("{:?}", &w);

  w.write_i8(two).unwrap(); // 单字节的类型i8和u8,显然,因为它们是单字节类型,所以不会接收字节序的参数。
  println!("{:?}", &w);

  w.write_f64::<LittleEndian>(three).unwrap();
  println!("{:?}", &w);

  (one, two, three)
}

fn read_numbers_from_file() -> (u32, i8, f64) {
  let mut r = Cursor::new(vec![1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 8, 64]);
  let one_ = r.read_u32::<LittleEndian>().unwrap();
  let two_ = r.read_i8().unwrap();
  let three_ = r.read_f64::<LittleEndian>().unwrap();

  (one_, two_, three_)
}

fn main() {
  let (one, two, three) = write_numbers_to_file();
  let (one_, two_, three_) = read_numbers_from_file();

  assert_eq!(one, one_);
  assert_eq!(two, two_);
  assert_eq!(three, three_);
}

7.7.4 用校验和验证 I/O 错误

在这里插入图片描述
在这里插入图片描述

fn parity_bit(bytes: &[u8]) -> u8 {
  // 获取一个字节切片作为参数bytes,并返回一个单字节作为输出。此函数可以很容易地返回一个布尔值,但是在这里返回u8,可以让这个返回结果在之后能够移位到某个期望的位置上。
  let mut n_ones: u32 = 0;

  for byte in bytes {
    let ones = byte.count_ones(); // Rust的所有整数类型,都配有count_ones() 方法和count_zeros() 方法。
    n_ones += ones;
    println!("{} (0b{:08b}) has {} one bits", byte, byte, ones);
  }
  (n_ones % 2 == 0) as u8 // 有多种方法可以用来优化这个函数。一种很简单的方法就是,可以硬编码一个类型为const [u8; 256]的数组,数组中的0和1与预期的结果相对应,然后用每个字节对此数组进行索引。
}

fn main() {
  let abc = b"abc";
  println!("input: {:?}", abc);
  println!("output: {:08x}", parity_bit(abc));
  println!();
  let abcd = b"abcd";
  println!("input: {:?}", abcd);
  println!("result: {:08x}", parity_bit(abcd))
}

// code result:
input: [97, 98, 99]
97 (0b01100001) has 3 one bits
98 (0b01100010) has 3 one bits
99 (0b01100011) has 4 one bits
output: 00000001 // 因(3+3+4)%2 == 0 成立, 故返回output=1

input: [97, 98, 99, 100]
97 (0b01100001) has 3 one bits
98 (0b01100010) has 3 one bits
99 (0b01100011) has 4 one bits
100 (0b01100100) has 3 one bits
result: 00000000 // 因(3+3+4+3)%2 == 0 不成立, 故返回output=0

7.7.8 创建 HashMap 和写入

use std::collections::HashMap;

fn main() {
  let mut capitals = HashMap::new();             // <1>

  capitals.insert("Cook Islands", "Avarua");
  capitals.insert("Fiji", "Suva");
  capitals.insert("Kiribati", "South Tarawa");
  capitals.insert("Niue", "Alofi");
  capitals.insert("Tonga", "Nuku'alofa");
  capitals.insert("Tuvalu", "Funafuti");

  let tongan_capital = capitals["Tonga"];        // <2>

  println!("Capital of Tonga is: {}", tongan_capital);
}

// code result:
Capital of Tonga is: Nuku'alofa
#[macro_use]                          // 把serde_json包合并到此包中,并使用它的宏。这个语法会把 json! 宏导入作用域中
extern crate serde_json;              // <1>

fn main() {
  let capitals = json!({              // json! 会接收一个JSON字面量(这个JSON字面量是由字符串组成的Rust表达式),这个宏会把JSON字面量转换成类型为serde_json::Value的Rust值,这个类型是枚举体,能够表示JSON规范中所描述的所有类型。
    "Cook Islands": "Avarua",
    "Fiji": "Suva",
    "Kiribati": "South Tarawa",
    "Niue": "Alofi",
    "Tonga": "Nuku'alofa",
    "Tuvalu": "Funafuti"
  });

  println!("Capital of Tonga is: {}", capitals["Tonga"])
}

7.7.9 查询 HashMap

capitals["Tonga"]     // 返回 "Nuku’alofa"。这种方式会返回该值的一个只读的引用(当处理包含字符串字面量的示例时,这里存在一定的"欺骗性”,因为它们作为引用的状态有些变形)。在Rust文档中,这是指& v,其中&表示只读引用,而v是值的类型。如果键不存在,程序将会引发 panic。

capitals.get("Tonga") // 返回Some( "Nuku’alofa" ), 返回一个 Option<&V>,防止 panic。

7.7.10 HashMap 和 BTreeMap 对比

use std::collections::BTreeMap;

fn main() {
  let mut voc = BTreeMap::new();

  voc.insert(3_697_915, "Amsterdam");
  voc.insert(1_300_405, "Middelburg");
  voc.insert(540_000, "Enkhuizen");
  voc.insert(469_400, "Delft");
  voc.insert(266_868, "Hoorn");
  voc.insert(173_000, "Rotterdam");

  for (guilders, kamer) in &voc {
    println!("{} invested {}", kamer, guilders); // 按照排序顺序输出。
  }

  print!("smaller chambers: ");
  for (_guilders, kamer) in voc.range(0..500_000) {
    // BTreeMap允许你使用范围(range)语法进行迭代,以此来选择操作全部键的一部分。
    print!("{} ", kamer);
  }
  println!("");
}

// code result:
Rotterdam invested 173000
Hoorn invested 266868
Delft invested 469400
Enkhuizen invested 540000
Middelburg invested 1300405
Amsterdam invested 3697915
smaller chambers: Rotterdam Hoorn Delft 

7.7.11 添加数据库索引

在这里插入图片描述
在这里插入图片描述

use libactionkv::ActionKV;
use std::collections::HashMap;

#[cfg(target_os = "windows")]
const USAGE: &str = "
Usage:
    akv_disk.exe FILE get KEY
    akv_disk.exe FILE delete KEY
    akv_disk.exe FILE insert KEY VALUE
    akv_disk.exe FILE update KEY VALUE
";

#[cfg(not(target_os = "windows"))]
const USAGE: &str = "
Usage:
    akv_disk FILE get KEY
    akv_disk FILE delete KEY
    akv_disk FILE insert KEY VALUE
    akv_disk FILE update KEY VALUE
";

type ByteStr = [u8];
type ByteString = Vec<u8>;

fn store_index_on_disk(a: &mut ActionKV, index_key: &ByteStr) {
  a.index.remove(index_key);
  let index_as_bytes = bincode::serialize(&a.index).unwrap();
  a.index = std::collections::HashMap::new();
  a.insert(index_key, &index_as_bytes).unwrap();
}

fn main() {
  const INDEX_KEY: &ByteStr = b"+index";

  let args: Vec<String> = std::env::args().collect();
  let fname = args.get(1).expect(&USAGE);
  let action = args.get(2).expect(&USAGE).as_ref();
  let key = args.get(3).expect(&USAGE).as_ref();
  let maybe_value = args.get(4);

  let path = std::path::Path::new(&fname);
  let mut a = ActionKV::open(path).expect("unable to open file");

  a.load().expect("unable to load data");

  match action {
    "get" => {
      let index_as_bytes = a.get(&INDEX_KEY)
                                    .unwrap()
                                    .unwrap();

      let index_decoded = bincode::deserialize(&index_as_bytes);

      let index: HashMap<ByteString, u64> = index_decoded.unwrap();

      match index.get(key) {
        None => eprintln!("{:?} not found", key),
        Some(&i) => {
          let kv = a.get_at(i).unwrap();
          println!("{:?}", kv.value)                <1>
        }
      }
    }

    "delete" => a.delete(key).unwrap(),

    "insert" => {
      let value = maybe_value.expect(&USAGE).as_ref();
      a.insert(key, value).unwrap();
      store_index_on_disk(&mut a, INDEX_KEY);       <2>
    }

    "update" => {
      let value = maybe_value.expect(&USAGE).as_ref();
      a.update(key, value).unwrap();
      store_index_on_disk(&mut a, INDEX_KEY);       <2>
    }
    _ => eprintln!("{}", &USAGE),
  }
}

八、网络

8.1 网络七层

在这里插入图片描述

8.2 用 reqwest 发起 HTTP 请求

use std::error::Error;

use reqwest;

fn main() -> Result<(), Box<dyn Error>> {        // <1>
  let url = "http://www.rustinaction.com/";
  let mut response = reqwest::get(url)?;

  let content = response.text()?;
  print!("{}", content);

  Ok(())
}
// code result:
Error: Error(Hyper(Error(Connect, Os { code: 22, kind: InvalidInput, message: "Invalid argument" })), "http://www.rustinaction.com/")

8.3 trait object

可将多种 struct 都视为同一种 trait object 类型。 提供了多态性。

在这里插入图片描述

8.3.3 实现 rpg 游戏项目

use rand;
use rand::seq::SliceRandom;
use rand::Rng;

#[derive(Debug)]
struct Dwarf {} // dwarves 矮人族

#[derive(Debug)]
struct Elf {} // elves 精灵族

#[derive(Debug)]
struct Human {} // 人族

#[derive(Debug)]
enum Thing {
  Sword,   // 剑
  Trinket, // 小饰品
}

// 魔法师
trait Enchanter: std::fmt::Debug {
  fn competency(&self) -> f64; // 能力

  // 附魔
  fn enchant(&self, thing: &mut Thing) {
    let probability_of_success = self.competency();
    let spell_is_successful = rand::thread_rng().gen_bool(probability_of_success); // <1>

    print!("{:?} mutters incoherently. ", self); // 语无伦次地嘀咕着
    if spell_is_successful {
      println!("The {:?} glows brightly.", thing); // 发出明亮的光
    } else {
      println!("The {:?} fizzes, then turns into a worthless trinket.", thing); // 发出嘶嘶声,然后变成毫无价值的饰品
      *thing = Thing::Trinket;
    }
  }
}

impl Enchanter for Dwarf {
  fn competency(&self) -> f64 {
    0.5
  }
}
impl Enchanter for Elf {
  fn competency(&self) -> f64 {
    0.95
  }
}
impl Enchanter for Human {
  fn competency(&self) -> f64 {
    0.8
  }
}

fn main() {
  let mut it = Thing::Sword;

  let d = Dwarf {};
  let e = Elf {};
  let h = Human {};

  let party: Vec<&dyn Enchanter> = vec![&d, &h, &e]; // 可把不同类型的成员放到同一个Vec中,因这些成员都实现了这个Enchanter trait
  let spellcaster = party.choose(&mut rand::thread_rng()).unwrap();

  spellcaster.enchant(&mut it);
}

// code result:
Elf mutters incoherently. The Sword glows brightly.

上文中的这两行代码是有区别的,区别如下:

use rand::Rng; // 是一个 trait。&dyn Rng 表示实现了 Rng 的某种东西的一个引用。
use rand::rngs::ThreadRng; // 是一个结构体。&ThreadRng 是一个 ThreadRng 的引用。

trait object 为 rust 提供了类型擦除(type erasure)的形式,当调用 enchant() 时,编译器无法访问这些对象的原始类型。

trait 有如下使用场景:

  • 创建异质对象的集合
  • 作为返回值,使函数可返回多个具体类型
  • 支持动态分派,使在运行时而不是编译时,来确定所要调用的函数

8.4 TCP

use std::io::prelude::*;
use std::net::TcpStream;

fn main() -> std::io::Result<()> {
  let mut connection = TcpStream::connect("www.rustinaction.com:80")?; // 必须显式指定端口号(80),TcpStream并不知道这将成为一个HTTP的请求
  connection.write_all(b"GET / HTTP/1.0")?; // 用HTTP 1.0可以确保在服务器发送响应后关闭此连接。然而,HTTP 1.0并不支持""keep alive”(保持活动状态)的请求。如果使用HTTP 1.1,默认会启用"keep alive",这实际上会使这段代码变得混乱,因为服务器将拒绝关闭此连接,直到它收到另一个请求,可是客户端已经不会再发送一个请求了。
  connection.write_all(b"\r\n")?; // 在许多的网络协议中,都是用\r\n来表示换行符的
  connection.write_all(b"Host: www.rustinaction.com")?; // 我们提供了主机名。我们在第7~8行中建立连接时已经使用了这个确切的主机名,所以你可能会觉得这行代码是多余的。然而,你应该记住的一点是,此连接是通过IP地址建立起来的,其中并没有主机名。当使用TcpStream : connect()连接到服务器的时候,它只使用一个IP地址。通过添加HTTP首部的Host信息,我们把这些信息重新注入上下文。
  connection.write_all(b"\r\n\r\n")?; // 两个换行符表示本次请求结束
  std::io::copy(&mut connection, &mut std::io::stdout())?; // 把字节流从一个Reader写到一个Writer中

  Ok(())
}

// code result:
HTTP/1.0 301 Moved Permanently
content-type: text/html; charset=utf-8
location: https://www.rustinaction.com/
permissions-policy: interest-cohort=()
vary: Origin
date: Fri, 23 Jun 2023 11:13:29 GMT
content-length: 64

<a href="https://www.rustinaction.com/">Moved Permanently</a>.

8.4.2 用 DNS 将 hostname 转换为 IP地址

到目前为止,我们已经为Rust提供了主机名www.ustinaction.com。但是,要通过Internet发送消息,IP(Internet Protocol,互联网协议)需要使用P地址。TCP对域名一无所知,要把域名转换为IP地址,我们需要依赖于域名系统(DNS)以及称为域名解析的这个处理过程。

我们可以通过询问一台服务器来解析名称,而这些服务器可以递归地询问其他的服务器。DNS请求可以通过TCP来发送,包括使用TLS加密,但也可以通过UDP (User Datagram Protocol,用户数据报协议)来发送。我们将在这里使用DNS,因为它对我们的学习目标(HTIP)很有用。

为了说明从域名到IP地址的转换是如何进行的,我们会创建一个小应用程序来执行这个转换。这个程序的名字是 resolve,在清单8.9中给出了源代码。resolve 会使用公共DNS服务,但是你也可以使用 -s参数来轻松添加自己的DNS服务。

resolve 仅能了解DNS协议的一小部分,但这一小部分就足以满足我们的需要了。此项目使用了外部的包,trust-dns,用以完成繁重的工作。trust-dns非常忠实地实现了RFC1035(定义了DNS)以及多个后来的RFC,并使用了从中衍生的术语。表8.1概要地列出了一些的术语,这些术语对于理解DNS很有帮助。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

构造请求的示例如下:

// https:/ /github.com/rust-in-action/code rust-in-action/ch8/ch8-resolve
use std::net::{SocketAddr, UdpSocket};
use std::time::Duration;

use clap::{App, Arg};
use rand;
use trust_dns::op::{Message, MessageType, OpCode, Query};
use trust_dns::rr::domain::Name;
use trust_dns::rr::record_type::RecordType;
use trust_dns::serialize::binary::*;

fn main() {
  let app = App::new("resolve")
    .about("A simple to use DNS resolver")
    .arg(Arg::with_name("dns-server").short("s").default_value("1.1.1.1"))
    .arg(Arg::with_name("domain-name").required(true))
    .get_matches();

  let domain_name_raw = app.value_of("domain-name").unwrap(); // 把命令行参数转换为一个有类型的域名。
  let domain_name = Name::from_ascii(&domain_name_raw).unwrap();

  let dns_server_raw = app.value_of("dns-server").unwrap();
  let dns_server: SocketAddr = format!("{}:53", dns_server_raw).parse().expect("invalid address"); // 把命令行参数转换为一个有类型的DNS服务器。

  let mut request_as_bytes: Vec<u8> = Vec::with_capacity(512); // 在此清单的后面,解释了为什么要使用两种初始化形式。
  let mut response_as_bytes: Vec<u8> = vec![0; 512];

  let mut msg = Message::new(); // Message表示一个DNS报文,它是一个容器,可以用于保存查询,也可以保存其他信息,例如应答。
  msg
    .set_id(rand::random::<u16>())
    .set_message_type(MessageType::Query) // 在这里指定了这是一个DNS查询,而不是DNS应答。在通过网络传输时,这两者具有相同的表示形式,但在Rust的类型系统中则是不同的。
    .add_query(Query::query(domain_name, RecordType::A))
    .set_op_code(OpCode::Query)
    .set_recursion_desired(true);

  let mut encoder = BinEncoder::new(&mut request_as_bytes); // 使用BinEncoder把这个Message类型转换为原始字节。
  msg.emit(&mut encoder).unwrap();

  let localhost = UdpSocket::bind("0.0.0.0:0").expect("cannot bind to local socket"); // 0.0.0.0:0表示在一个随机的端口号上监听所有的地址,实际的端口号将由操作系统来分配。
  let timeout = Duration::from_secs(3);
  localhost.set_read_timeout(Some(timeout)).unwrap();
  localhost.set_nonblocking(false).unwrap();

  let _amt = localhost
    .send_to(&request_as_bytes, dns_server)
    .expect("socket misconfigured");

  let (_amt, _remote) = localhost.recv_from(&mut response_as_bytes).expect("timeout reached");

  let dns_message = Message::from_vec(&response_as_bytes).expect("unable to parse response");

  for answer in dns_message.answers() {
    if answer.record_type() == RecordType::A {
      let resource = answer.rdata();
      let ip = resource.to_ip_addr().expect("invalid IP address received");
      println!("{}", ip.to_string());
    }
  }
}



// code result:
y% cargo run - --help
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `target/debug/resolve - --help`
resolve 
A simple to use DNS resolver

USAGE:
    resolve [OPTIONS] <domain-name>

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

OPTIONS:
    -s <dns-server>         [default: 1.1.1.1]

ARGS:
    <domain-name>  

在这里插入图片描述

8.5 用 Result 处理错误

单个错误如下:

y% cat io-error.rs 
use std::fs::File;

fn main() -> Result<(), std::io::Error> {
    let _f = File::open("invisible.txt")?;

    Ok(())
}%                                                                                                                                                                                                                                                                   
y% rustc io-error.rs 
y% ls
io-error  io-error.rs
y% ./io-error 
Error: Os { code: 2, kind: NotFound, message: "No such file or directory" }

多个错误如下:

y% cat multierror.rs 
use std::fs::File;
use std::net::Ipv6Addr;

fn main() -> Result<(), std::io::Error> {
  let _f = File::open("invisible.txt")?;

  let _localhost = "::1".parse::<Ipv6Addr>()?;
     
  Ok(())
}

y% rustc multierror.rs && ./multierror
error[E0277]: `?` couldn't convert the error to `std::io::Error`
 --> multierror.rs:8:25
  |
4 | fn main() -> Result<(), std::io::Error> {
  |              -------------------------- expected `std::io::Error` because of this
...
8 |     .parse::<Ipv6Addr>()?;
  |                         ^ the trait `From<AddrParseError>` is not implemented for `std::io::Error`
  |
  = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
  = help: the following other types implement trait `From<T>`:
            <std::io::Error as From<ErrorKind>>
            <std::io::Error as From<IntoInnerError<W>>>
            <std::io::Error as From<NulError>>
  = note: required for `Result<(), std::io::Error>` to implement `FromResidual<Result<Infallible, AddrParseError>>`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.

在这里插入图片描述
在这里插入图片描述

y% cat traiterror.rs 
use std::fs::File;
use std::error::Error;
use std::net::Ipv6Addr;
fn main() -> Result<(), Box<dyn Error>> {
    let _f = File::open("invisible.txt")?;
    let _localhost = "::1".parse::<Ipv6Addr>()?;

    Ok(())
}

// code result:
y% rustc traiterror.rs && ./traiterror
Error: Os { code: 2, kind: NotFound, message: "No such file or directory" }

8.5.2 自定义错误类型,包装下游的错误

use std::io;
use std::fmt;
use std::net;
use std::fs::File;
use std::net::Ipv6Addr;

#[derive(Debug)]
enum UpstreamError{
  IO(io::Error), 				// 函数
  Parsing(net::AddrParseError), // 函数
}

impl fmt::Display for UpstreamError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:?}", self) // 借用debug trait实现了display trait
    }
}

fn main() -> Result<(), UpstreamError> {
	// map_err()函数可以把一个错误映射到一个函数中
    let _f = File::open("invisible.txt").map_err(UpstreamError::IO)?;
    let _localhost = "::1".parse::<Ipv6Addr>().map_err(UpstreamError::Parsing)?;

    Ok(())
}

// code result:
Error: IO(Os { code: 2, kind: NotFound, message: "No such file or directory" })                                             

也可以实现 std::convert::From,这样就不用再调用 map_err() 了,代码如下:

use std::io;
use std::fmt;
use std::net;
use std::fs::File;
use std::net::Ipv6Addr;

#[derive(Debug)]
enum UpstreamError{
  IO(io::Error),
  Parsing(net::AddrParseError),
}

impl fmt::Display for UpstreamError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:?}", self) // <1> Implement Display in terms of Debug
    }
}

impl From<io::Error> for UpstreamError {
    fn from(error: io::Error) -> Self {
        UpstreamError::IO(error)
    }
}

impl From<net::AddrParseError> for UpstreamError {
    fn from(error: net::AddrParseError) -> Self {
        UpstreamError::Parsing(error)
    }
}

fn main() -> Result<(), UpstreamError> {
    let _f = File::open("invisible.txt").map_err(UpstreamError::IO)?;
    let _localhost = "::1".parse::<Ipv6Addr>().map_err(UpstreamError::Parsing)?;

    Ok(())
}

// code result:
Error: IO(Os { code: 2, kind: NotFound, message: "No such file or directory" })                 

当然也可用 unwrap() 和 expect() 处理错误。

8.6 MAC 地址

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8.6.1 生成 MAC 地址

extern crate rand;

use rand::RngCore;
use std::fmt;
use std::fmt::Display;

#[derive(Debug)]
struct MacAddress([u8; 6]); // 使用newtype(新类型)模式包装一个数组,没有任何额外的开销

impl Display for MacAddress {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    let octet = &self.0;
    write!(
      f,
      "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", // 把每个字节都转换为十六进制的表示形式
      octet[0],octet[1],octet[2],octet[3],octet[4],octet[5] 
    )
  }
}

impl MacAddress {
  fn new() -> MacAddress {
    let mut octets: [u8; 6] = [0; 6];
    rand::thread_rng().fill_bytes(&mut octets);
    octets[0] |= 0b_0000_0011; // 把MAC地址设置为本地分配和单播的模式
    MacAddress { 0: octets }
  }

  fn is_local(&self) -> bool {
    (self.0[0] & 0b_0000_0010) == 0b_0000_0010
  }

  fn is_unicast(&self) -> bool {
    (self.0[0] & 0b_0000_0001) == 0b_0000_0001
  }
}

fn main() {
  let mac = MacAddress::new();
  assert!(mac.is_local());
  assert!(mac.is_unicast());
  println!("mac: {}", mac);
}

8.7 用 enum 实现状态机

enum HttpState {
    Connect,
    Request,
    Response,
}

loop {
    state = match state {
        HttpState::Connect if !socket.is_active() => {
            socket.connect();
            HttpState::Request
        }

        HttpState::Request if socket.may_send() => {
            socket.send(data);
            HttpState::Response
        }

        HttpState::Response if socket.can_recv() => {
            received = socket.recv();
            HttpState::Response
        }

        HttpState::Response if !socket.may_recv() => {
            break;
        }
        _ => state,
    }
}

8.9 创建一个虚拟网络设备

在这里插入图片描述

操作过程如下:
在这里插入图片描述

8.10 原始 HTTP

在这里插入图片描述

$ cargo new mget
$ cd mget
$ cargo install cargo-edit
$ cargo add clap@2
$ cargo add url@02
$ cargo add rand@0.7
$ cargo add trust-dns@0.16 --no-default-features
$ cargo add smoltcp@0.6 --features='proto-igmp proto-ipv4 verbose log'

Cargo.toml 如下:

[package]
name = "mget"
version = "0.1.0"
authors = ["Tim McNamara <author@rustinaction.com>"]
edition = "2018"

[dependencies]
clap = "2"    	# 提供命令行参数解析的功能。
rand = "0.7"    # 用来选择一个随机的端口号。
smoltcp = {    	# 提供了一个TCP的实现。
  version = "0.6",
  features = ["proto-igmp", "proto-ipv4", "verbose", "log"]
}
trust-dns = {   # 允许连接到DNS服务器。
  version = "0.16",
  default-features = false
}
url = "2"    	# 用于URL的解析和验证。

九、时间 和 NTP

#[cfg(windows)]
use kernel32;
#[cfg(not(windows))]
use libc;
#[cfg(windows)]
use winapi;

use byteorder::{BigEndian, ReadBytesExt};
use chrono::{
  DateTime, Duration as ChronoDuration, TimeZone, Timelike,
};
use chrono::{Local, Utc};
use clap::{App, Arg};
use std::mem::zeroed;
use std::net::UdpSocket;
use std::time::Duration;

const NTP_MESSAGE_LENGTH: usize = 48;               <1>
const NTP_TO_UNIX_SECONDS: i64 = 2_208_988_800;
const LOCAL_ADDR: &'static str = "0.0.0.0:12300";   <2>

#[derive(Default, Debug, Copy, Clone)]
struct NTPTimestamp {
  seconds: u32,
  fraction: u32,
}

struct NTPMessage {
  data: [u8; NTP_MESSAGE_LENGTH],
}

#[derive(Debug)]
struct NTPResult {
  t1: DateTime<Utc>,
  t2: DateTime<Utc>,
  t3: DateTime<Utc>,
  t4: DateTime<Utc>,
}

impl NTPResult {
  fn offset(&self) -> i64 {
    let delta = self.delay();
    delta.abs() / 2
  }

  fn delay(&self) -> i64 {
    let duration = (self.t4 - self.t1) - (self.t3 - self.t2);
    duration.num_milliseconds()
  }
}

impl From<NTPTimestamp> for DateTime<Utc> {
  fn from(ntp: NTPTimestamp) -> Self {
    let secs = ntp.seconds as i64 - NTP_TO_UNIX_SECONDS;
    let mut nanos = ntp.fraction as f64;
    nanos *= 1e9;
    nanos /= 2_f64.powi(32);

    Utc.timestamp(secs, nanos as u32)
  }
}

impl From<DateTime<Utc>> for NTPTimestamp {
  fn from(utc: DateTime<Utc>) -> Self {
    let secs = utc.timestamp() + NTP_TO_UNIX_SECONDS;
    let mut fraction = utc.nanosecond() as f64;
    fraction *= 2_f64.powi(32);
    fraction /= 1e9;

    NTPTimestamp {
      seconds: secs as u32,
      fraction: fraction as u32,
    }
  }
}

impl NTPMessage {
  fn new() -> Self {
    NTPMessage {
      data: [0; NTP_MESSAGE_LENGTH],
    }
  }

  fn client() -> Self {
    const VERSION: u8 = 0b00_011_000;   <3>
    const MODE: u8    = 0b00_000_011;   <3>

    let mut msg = NTPMessage::new();

    msg.data[0] |= VERSION;             <4>
    msg.data[0] |= MODE;                <4>
    msg                                 <5>
  }

  fn parse_timestamp(
    &self,
    i: usize,
  ) -> Result<NTPTimestamp, std::io::Error> {
    let mut reader = &self.data[i..i + 8];        <6>
    let seconds    = reader.read_u32::<BigEndian>()?;
    let fraction   = reader.read_u32::<BigEndian>()?;

    Ok(NTPTimestamp {
      seconds:  seconds,
      fraction: fraction,
    })
  }

  fn rx_time(
    &self
  ) -> Result<NTPTimestamp, std::io::Error> {     <7>
    self.parse_timestamp(32)
  }

  fn tx_time(
    &self
  ) -> Result<NTPTimestamp, std::io::Error> {     <8>
    self.parse_timestamp(40)
  }
}

fn weighted_mean(values: &[f64], weights: &[f64]) -> f64 {
  let mut result = 0.0;
  let mut sum_of_weights = 0.0;

  for (v, w) in values.iter().zip(weights) {
    result += v * w;
    sum_of_weights += w;
  }

  result / sum_of_weights
}

fn ntp_roundtrip(
  host: &str,
  port: u16,
) -> Result<NTPResult, std::io::Error> {
  let destination = format!("{}:{}", host, port);
  let timeout = Duration::from_secs(1);

  let request = NTPMessage::client();
  let mut response = NTPMessage::new();

  let message = request.data;

  let udp = UdpSocket::bind(LOCAL_ADDR)?;
  udp.connect(&destination).expect("unable to connect");

  let t1 = Utc::now();

  udp.send(&message)?;
  udp.set_read_timeout(Some(timeout))?;
  udp.recv_from(&mut response.data)?;
  let t4 = Utc::now();

  let t2: DateTime<Utc> =
    response
      .rx_time()
      .unwrap()
      .into();
  let t3: DateTime<Utc> =
    response
      .tx_time()
      .unwrap()
      .into();

  Ok(NTPResult {
    t1: t1,
    t2: t2,
    t3: t3,
    t4: t4,
  })
}

fn check_time() -> Result<f64, std::io::Error> {
  const NTP_PORT: u16 = 123;

  let servers = [
    "time.nist.gov",
    "time.apple.com",
    "time.euro.apple.com",
    "time.google.com",
    "time2.google.com",
    //"time.windows.com",
  ];

  let mut times = Vec::with_capacity(servers.len());

  for &server in servers.iter() {
    print!("{} =>", server);

    let calc = ntp_roundtrip(&server, NTP_PORT);

    match calc {
      Ok(time) => {
        println!(" {}ms away from local system time", time.offset());
        times.push(time);
      }
      Err(_) => {
        println!(" ? [response took too long]")
      }
    };
  }

  let mut offsets = Vec::with_capacity(servers.len());
  let mut offset_weights = Vec::with_capacity(servers.len());

  for time in &times {
    let offset = time.offset() as f64;
    let delay = time.delay() as f64;

    let weight = 1_000_000.0 / (delay * delay);
    if weight.is_finite() {
      offsets.push(offset);
      offset_weights.push(weight);
    }
  }

  let avg_offset = weighted_mean(&offsets, &offset_weights);

  Ok(avg_offset)
}

struct Clock;

impl Clock {
  fn get() -> DateTime<Local> {
    Local::now()
  }

  #[cfg(windows)]
  fn set<Tz: TimeZone>(t: DateTime<Tz>) -> () {
    use chrono::Weekday;
    use kernel32::SetSystemTime;
    use winapi::{SYSTEMTIME, WORD};

    let t = t.with_timezone(&Local);

    let mut systime: SYSTEMTIME = unsafe { zeroed() };

    let dow = match t.weekday() {
      Weekday::Mon => 1,
      Weekday::Tue => 2,
      Weekday::Wed => 3,
      Weekday::Thu => 4,
      Weekday::Fri => 5,
      Weekday::Sat => 6,
      Weekday::Sun => 0,
    };

    let mut ns = t.nanosecond();
    let is_leap_second = ns > 1_000_000_000;

    if is_leap_second {
      ns -= 1_000_000_000;
    }

    systime.wYear = t.year() as WORD;
    systime.wMonth = t.month() as WORD;
    systime.wDayOfWeek = dow as WORD;
    systime.wDay = t.day() as WORD;
    systime.wHour = t.hour() as WORD;
    systime.wMinute = t.minute() as WORD;
    systime.wSecond = t.second() as WORD;
    systime.wMilliseconds = (ns / 1_000_000) as WORD;

    let systime_ptr = &systime as *const SYSTEMTIME;
    unsafe {
      SetSystemTime(systime_ptr);
    }
  }

  #[cfg(not(windows))]
  fn set<Tz: TimeZone>(t: DateTime<Tz>) -> () {
    use libc::settimeofday;
    use libc::{suseconds_t, time_t, timeval, timezone};

    let t = t.with_timezone(&Local);
    let mut u: timeval = unsafe { zeroed() };

    u.tv_sec = t.timestamp() as time_t;
    u.tv_usec = t.timestamp_subsec_micros() as suseconds_t;

    unsafe {
      let mock_tz: *const timezone = std::ptr::null();
      settimeofday(&u as *const timeval, mock_tz);
    }
  }
}

fn main() {
  let app = App::new("clock")
    .version("0.1.3")
    .about("Gets and sets the time.")
    .after_help(
      "Note: UNIX timestamps are parsed as whole seconds since 1st \
       January 1970 0:00:00 UTC. For more accuracy, use another \
       format.",
    )
    .arg(
      Arg::with_name("action")
        .takes_value(true)
        .possible_values(&["get", "set", "check-ntp"])
        .default_value("get"),
    )
    .arg(
      Arg::with_name("std")
        .short("s")
        .long("use-standard")
        .takes_value(true)
        .possible_values(&["rfc2822", "rfc3339", "timestamp"])
        .default_value("rfc3339"),
    )
    .arg(Arg::with_name("datetime").help(
      "When <action> is 'set', apply <datetime>. Otherwise, ignore.",
    ));

  let args = app.get_matches();

  let action = args.value_of("action").unwrap();
  let std = args.value_of("std").unwrap();

  if action == "set" {
    let t_ = args.value_of("datetime").unwrap();

    let parser = match std {
      "rfc2822" => DateTime::parse_from_rfc2822,
      "rfc3339" => DateTime::parse_from_rfc3339,
      _ => unimplemented!(),
    };

    let err_msg =
      format!("Unable to parse {} according to {}", t_, std);
    let t = parser(t_).expect(&err_msg);

    Clock::set(t);

  } else if action == "check-ntp" {
    let offset = check_time().unwrap() as isize;

    let adjust_ms_ = offset.signum() * offset.abs().min(200) / 5;
    let adjust_ms = ChronoDuration::milliseconds(adjust_ms_ as i64);

    let now: DateTime<Utc> = Utc::now() + adjust_ms;

    Clock::set(now);
  }

  let maybe_error =
    std::io::Error::last_os_error();
  let os_error_code =
    &maybe_error.raw_os_error();

  match os_error_code {
    Some(0) => (),
    Some(_) => eprintln!("Unable to set the time: {:?}", maybe_error),
    None => (),
  }

  let now = Clock::get();

  match std {
    "timestamp" => println!("{}", now.timestamp()),
    "rfc2822" => println!("{}", now.to_rfc2822()),
    "rfc3339" => println!("{}", now.to_rfc3339()),
    _ => unreachable!(),
  }
}

十、进程、线程、容器

10.2 线程

10.2.1 闭包

如果要【捕获】父级作用域的变量,需用 move 移动所有权(因为 rust 总是要保证访问的数据是有效的,故需将相应数据的所有权移动到闭包里)。

  • 如果想避免编译时的一些问题,也可以用 copy。
  • 来自外部作用域的值,可能需要静态生命周期。
  • 子线程可能会比父线程活的更久,故需用 move 将所有权转义到子线程中。
thread::spawn(move || {
	// ...
});

10.2.2 产生线程

一个简单的任务,让 CPU 休眠 300ms,如果你有一个 3GHz 的 CPU,这意味着会让程序休息 10亿个 CPU 周期,休息时这些电子会非常放松。

use std::{thread, time};

fn main() {
  let start = time::Instant::now();

  let handler = thread::spawn(|| {
    let pause = time::Duration::from_millis(300);
    thread::sleep(pause.clone());
  });

  handler.join().unwrap();

  let finish = time::Instant::now();
  println!("{:02?}", finish.duration_since(start));
}
// code result:
300.490649ms

join(连接)是线程隐喻的一个引申。当产生新线程的时候,这些线程被认为是从它们的父线程中复刻(forked)出来的。连接这些线(线程)的意思是把这些线(线程)重新编织在一起。而在实际的操作中,join的意思是等待另一个线程结束工作。join()函数会指示操作系统推迟对正在调用的线程的调度,直到另一个线程完成工作为止。

10.2.3 产生线程的效果

如下例,创建两个线程和创建一个线程的时间差不多。

use std::{thread, time};

fn main() {
  let start = time::Instant::now();

  let handler_1 = thread::spawn(move || {
    let pause = time::Duration::from_millis(300);
    thread::sleep(pause.clone());
  });

  let handler_2 = thread::spawn(move || {
    let pause = time::Duration::from_millis(300);
    thread::sleep(pause.clone());
  });

  handler_1.join().unwrap();
  handler_2.join().unwrap();

  let finish = time::Instant::now();
  println!("{:02?}", finish.duration_since(start));
}

// code result:
300.234848ms

如果你接触过这个领域,那么可能听说过“线程不能扩展”(threads don’t scale)这句话。这又是什么意思呢?

每个线程都需要有自己的内存,言下之意就是,(如果我们创建了非常多的新线程)我们最终会耗尽系统的内存。不过,在还没有出现这种终极状况之前,新线程的创建就已经开始让其他一些方面的性能降低了。随着需要调度的线程数量的增加,操作系统调度器的工作量也在增加。当存在很多线程需要调度时,要决定下一个应该调度的是哪个线程,这个决定的过程也相应地会花费更多的时间。

10.2.4 产生很多个线程的效果

产生新线程并不是没有成本的。这个过程是需要消耗内存资源和CPU时间的,而且线程间的切换还会让缓存失效。
图10.1展示了清单10.4连续运行很多次以后所产生的数据。可以看到,当每个批次所产生的线程数大致低于400个时,多次运行的变化量相对还是较小的。但是从这个点往后看,你几乎没法确定一个20ms的休眠究竟会花费多长时间。

在这里插入图片描述
在这里插入图片描述

10.2.5 重新生成这些结果

现在我们已经看到线程的效果了,接下来让我们来看一看产生图10.1和图10.2所示的输入数据的代码。欢迎你来重新生成这些结果。要想重新生成这些结果,就需要把清单10.4和清单10.5的输出信息分别写到两个文件中,然后对这些结果数据进行分析。
清单10.4展示了使用休眠来让线程暂停20ms的代码,此源代码保存在c10/ch10- multijoinlsrc/main.rs文件中。sleep(休眠)会向操作系统发出一个请求,让线程暂停执行,直到休眠的时间结束为止。清单10.5展示了使用忙等待(busy waiting,也叫作忙循环或者自旋循环)策略来暂停20ms的代码,此源代码保存在c10/ch10-busythreads/src/main.rs文件中。

use std::{thread, time};

fn main() {
  for n in 1..1001 {
    let mut handlers: Vec<thread::JoinHandle<()>> = Vec::with_capacity(n);

    let start = time::Instant::now();
    for _m in 0..n {
      let handle = thread::spawn(|| {
        let pause = time::Duration::from_millis(20);
        thread::sleep(pause);
      });
      handlers.push(handle);
    }

    while let Some(handle) = handlers.pop() {
      handle.join();
    }

    let finish = time::Instant::now();
    if n % 50 == 1 {
      println!("{}\t{:02?}", n, finish.duration_since(start));
    }
  }
}

// code result:
1       20.162059ms
51      21.044974ms
101     23.101888ms
151     23.18179ms
201     24.467632ms
251     25.155952ms
301     25.700023ms
351     26.790791ms
401     27.743707ms
451     28.589303ms
501     29.71202ms
use std::{thread, time};

fn main() {
  for n in 1..501 {
    let mut handlers: Vec<thread::JoinHandle<()>> = Vec::with_capacity(n);

    let start = time::Instant::now();
    for _m in 0..n {
      let handle = thread::spawn(|| {
        let start = time::Instant::now();
        let pause = time::Duration::from_millis(20);
        while start.elapsed() < pause {
          thread::yield_now();
        }
      });
      handlers.push(handle);
    }

    while let Some(handle) = handlers.pop() {
      handle.join();
    }

    let finish = time::Instant::now();
    if n % 50 == 1 {
      println!("{}\t{:02?}", n, finish.duration_since(start));
    }
  }
}

// code result:
1       20.130187ms
51      30.203125ms
101     39.237232ms
151     47.478327ms
201     61.974729ms
251     53.723767ms
301     49.225965ms
351     56.981376ms
401     139.46488ms
451     80.24628ms

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

10.2.6 共享的变量

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

10.3 闭包

在这里插入图片描述

10.4 多线程解析器、头像生成器

10.4.1 render-hex 运行效果

y% echo 'Rust in Action' | sha1sum | cut -f1 -d' '   
5deaed72594aaa10edda990c5a5eed868ba8915e

cargo run 5deaed72594aaa10edda990c5a5eed868ba8915e

y% ls
5deaed72594aaa10edda990c5a5eed868ba8915e.svg  Cargo.lock  Cargo.toml  src  target

y% cat 5deaed72594aaa10edda990c5a5eed868ba8915e.svg
<svg height="400" style='style="outline: 5px solid #800000;"' viewBox="0 0 400 400" width="400" xmlns="http://www.w3.org/2000/svg">
<rect fill="#ffffff" height="400" width="400" x="0" y="0"/>
<path d="M200,200 L200,400 L200,400 L200,400 L200,400 L200,400 L200,400 L480,400 L120,400 L-80,400 L560,400 L40,400 L40,400 L40,400 L40,400 L40,360 L200,200 L200,200 L200,200 L200,200 L200,200 L200,560 L200,-160 L200,200 L200,200 L400,200 L400,200 L400,0 L400,0 L400,0 L400,0 L80,0 L-160,0 L520,0 L200,0 L200,0 L520,0 L-160,0 L240,0 L440,0 L200,0" fill="none" stroke="#2f2f2f" stroke-opacity="0.9" stroke-width="5"/>
<rect fill="#ffffff" fill-opacity="0.0" height="400" stroke="#cccccc" stroke-width="15" width="400" x="0" y="0"/>
</svg>%

在这里插入图片描述

10.5 并发和任务虚拟化

在这里插入图片描述

十一、内核

11.1 初级 os

11.1.1 搭建开发环境

 $ apt-get install qemu # https://www.cnblogs.com/Rainingday/p/15068414.html
 $ sudo apt-get install qemu-system

 $ cargo install cargo-binutils
         ...
            Installed package 'cargo-binutils v0.3.3' (executables 'cargo-cov',
            'cargo-nm', 'cargo-objcopy', 'cargo-objdump', 'cargo-profdata',
            'cargo-readobj', 'cargo-size', 'cargo-strip', 'rust-ar', 'rust-cov',
            'rust-ld', 'rust-lld', 'rust-nm', 'rust-objcopy', 'rust-objdump',
            'rust-profdata', 'rust-readobj', 'rust-size', 'rust-strip')

 $ cargo install bootimage
         ...
            Installed package 'bootimage v0.10.3' (executables 'bootimage',
            'cargo-bootimage')

 $ rustup toolchain install nightly
         info: syncing channel updates for 'nightly-x86_64-unknown-linux-gnu'
         ...

 $ rustup default nightly
         info: using existing install for 'nightly-x86_64-unknown-linux-gnu'
         info: default toolchain set to 'nightly-x86_64-unknown-linux-gnu'
         ...

 $ rustup component add rust-src
         info: downloading component 'rust-src'
         ...

 $ rustup component add llvm-tools-preview    ⇽----  随着时间的推移,这可能会成为llvm-tools组件。
         info: downloading component 'llvm-tools-preview'
         ...

11.1.2 验证开发环境

在这里插入图片描述

11.2 第一次引导启动

在这里插入图片描述
在这里插入图片描述

11.2.3 源清单

fledgeos-0
├── Cargo.toml    ⇽----  见清单11.1。
├── fledge.json    ⇽----  见清单11.2。
├── .cargo
│    └── config.toml    ⇽----  见清单11.3。
└── src
      └── main.rs    ⇽----  见清单11.4。

十二、信号、中断、异常

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

12.4 硬件中断

在这里插入图片描述

12.5 信号处理

12.5.1 默认的行为

在这里插入图片描述

use std::process;
use std::thread::sleep;
use std::time;

fn main() {
  let delay = time::Duration::from_secs(1);

  let pid = process::id();
  println!("{}", pid);

  for i in 1..=60 {
    sleep(delay);
    println!(". {}", i);
  }
}

在这里插入图片描述

在这里插入图片描述

从Rust程序员的角度来看,LLVM可以看作Rust的编译器rustc的一个子组件。LLVM是与rustc捆绑在一起的一个外部工具。Rust程序员可以利用它提供的工具。在LLVM提供的这些工具中,有一组工具就是固有函数。LLVM自己就是一个编译器。它的作用如图12.6所示。

LLVM将rustc产生的代码,即LLVM IR(中间语言)转换为机器可读的汇编语言。更为复杂的是,必须要使用另外一种称为链接器的工具,把多个库拼接到一起。在Windows上,Rust使用的是一个由微软提供的程序link.exe来作为其链接器。在一些其他操作系统上,使用的是GNU的链接器1d。

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Rust】2、实战:文件、网络、时间、进程-线程-容器、内核、信号-中断-异常 的相关文章

随机推荐

  • SSM商城项目实战:后台管理用户认证

    SSM商城项目实战 后台管理用户认证 1 项目概述 本项目是一个基于SSM Spring SpringMVC MyBatis 框架的商城后台管理系统 用于管理商品 订单和用户等信息 在本篇博客中 我们将重点介绍如何实现后台管理用户的认证功能
  • opengl点的绘制

    include stdafx h include
  • unity的错误解决办法:NullReferenceException: Object reference not set to an instance of an object;tiny proje

    普通unity项目 正常的项目中 这是个非常初级的错误 比较常见的原因是就是在对象被引用前没有实例化 即是说 你要管理好程序的生命周期 解决办法1 设置不同的生命周期 生命周期就是脚本中的Awake Start Update等方法 他们会以
  • WIN 10环境下安装pytorch环境并跑通Yolox-demo

    文章目录 前言 一 Pytorch是什么 二 搭建Pytorch框架步骤 1 安装Anaconda 2 安装CUDA和CUDNN 3 安装Pytorch 三 Pycharm上跑通Yolox 1 下载Yolox项目 2 导入pycharm并设
  • 使用IDEA快速部署到Docker云端

    一 Docker对外提供服务 1 编辑服务配置文件 vim lib systemd system docker service 2 定位到ExecStart开头行 将原本的ExecStart usr bin dockerd H fd con
  • js aes加密_威feng网站的aes算法破解

    网站是 aHR0cHM6Ly93d3cuZmVuZy5jb20v 话说这个网站在过年前使用了aes算法 当然过年后也是aes 但就是把秘钥换了 换成更需要解密一段字符串 然后获得秘钥 最后请求时候再去用这个秘钥加密 并且最后发现秘钥和偏移是
  • mysql语句添加字段和注释

    简单点的 工作中需要对某个表进行添加字段 输出脚本 添加字段关键字 alter 注释关键字 comment ALTER TABLE 表名 ADD 字段名 varchar 20 COMMENT 注释内容 不要注释 去掉comment就行了 o
  • python opencv图片二值化后取出图片中心区域的轮廓

    python opencv图片二值化后取出图片中心区域的轮廓 1 导入必要的库 import cv2 import numpy as np 2 读取图片并将其转为灰度图像 image cv2 imread image jpg gray cv
  • vue封装指针样式

    新建style js文件 HandPointer 鼠标经过 BodyPointer 默认指针 可以设置多个样式 const HandPointer gt return cursor url require 图片路径 auto const B
  • Antd中RangePicker组件弹出的日历中英混杂,月份和周显示为英问题

    在开发中需要用到antd中的RangePicker组件 但是出现了一个问题 日历中英混杂 年份是中文 月份和周都是英文 搜索了一下这个问题主要是moment造成的 可能有很多种情况 需要根据项目的自身情况来修改 首先确定一下项目中packa
  • 计算机视觉基础(十)—— HOG特征描述算子之行人检测

    本次任务将学习一种在深度学习之前非常流行的图像特征提取技术 方向梯度直方图 Histogram of Oriented Gradients 简称HOG特征 HOG特征是在2005年CVPR的会议发表 在图像手工特征提取方面具有里程碑式的意义
  • JAVA基础:线程池的使用

    目录 1 概述 2 线程池的优势 2 1 线程池为什么使用自定义方式 2 2 封装的线程池工具类有什么好处 3 线程池的七大参数 3 线程池的创建 3 1 固定数量的线程池 3 2 带缓存的线程池 3 3 执 定时任务 3 4 定时任务单线
  • Linux查看tomcat安装路径

    查看tomcat安装路径 查看tomcat安装路径 sudo find name tomcat
  • python调用c++之pybind11

    之前一直从事c 相关算法及代码的相关工作 因公司内部代码管理需要 需将算法封装待python平台使用 根据此需求 对python调用c 代码的方式进行了学习 最终综合考虑封装难度及多代码管理使用pybind11进行了相关功能的实现 pybi
  • css实现图片和文字水平居中对齐

    方式一 vertical align middle 通过vertical align middle实现现图片与文字水平对齐 需要区分html是行内元素 还是块状元素 例如 标签img span是行内元素 标签p是块状元素则需要将标签p通过d
  • 【云原生之kubernetes实战】在k8s环境下部署Halo博客系统

    云原生之kubernetes实战 在k8s环境下部署Halo博客系统 一 Halo介绍 1 Halo简介 2 Halo特点 二 环境规划 1 本次实践环境规划 2 本次实践说明 三 环境检查 1 检查工作节点状态 2 检查系统pod状态 四
  • Github注册中,邮箱验证通不过解决办法

    使用谷歌注册的时候 验证收不到消息 1 注册出现如图问题 2 在注册失败的页面 点返回的箭头 点了之后不要动 3 然后进入开启一个新的页面 复制下面的链接 进去页面 可能有点慢 等一等 OctoCaptchahttps octocaptch
  • python爬虫的学习总结

    背景 基于django框架完成jira网页数据的爬取 由于对爬虫知识知道的太少 我开始了新的学习之旅 本文前半部分都是记录这一周主要的错误 如果想直接看最终成果 可以跳到本文 成功爬取 部分浏览 学习爬虫知识 在知道了本项目可能需要爬虫后
  • C语言求解由1,2,3,4,四位数字构成的互不相同且无重复数字的四位数

    采用多重循环的方式即可 首先明确一共有四个数字供选择 组成的是四位数 那么在个 十 百 千的取值上 就只能有一位是1 一位是2 一位是3 一位是4 代码如下 include
  • 【Rust】2、实战:文件、网络、时间、进程-线程-容器、内核、信号-中断-异常

    文章目录 七 文件和存储 7 2 serde 与 bincode 序列化 7 3 实现一个 hexdump 7 4 操作文件 7 4 1 打开文件 7 4 2 用 std fs Path 交互 7 5 基于 append 模式实现 kv数据