枚举定义和使用

enum IpAddrKind {
    V4,
    V6,
}
let four = IpAddrKind::V4; // 枚举值
let six = IpAddrKind::V6; // 枚举值

// enum 附带数据
enum IpAddr {
    V4(String),
    V6(String),
}
let four = IpAddrKind::V4(String::from("127.0.0.1"));
let six = IpAddrKind::V6(String::from("::1"));

// 实现枚举方法(与struct 类似)
impl IpAddr {
    // ...
}

Opthion枚举

// 预导入库中定义了结构
enum Option<T> {
    Some(T),
    None,
}

模式匹配

  • refutable 可反驳模式
  • irrefutable 不可反驳模式

match 匹配

let some_number = Some(5);
let some_string = Some("string");
let undefined_number: Option<i32> = None;
// 必须穷举所有可能
match some_number {
    Some(s) => {
        println!("{}", s + 1);
        Some(s + 1)
    }
    None => None,
};
// - => () 通配符
match some_number {
    Some(3) | Some(5) => println!("3 or 5"),
    Some(x) => println!("{}", x),
    _ => println!("None"),
};

if let 匹配

if let [类型] = [变量] { }

let some_number = Some(5);
if let Some(3) = some_number {
    println!("{}", 3);
} else if let Some(x) = some_number {
    println!("{}", x);
} else {
    println!("None");
}

定义结构体

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}
// 没有结构体继承
struct User2 {
    a: String,
    ..User,
} // error

// unit-like struct 空结构体
struct Empty;

初始化结构体变量

let user1 = User {
    username: String::from("aaa"),
    email: String::from("abc@abc.com"),
    sign_in_count: 1,
    active: true,
};
// 同名简写
let username = String::from("bbb");
let user2 = User {
    username,
    email: String::from("abc@abc.com"),
    sign_in_count: 1,
    active: true,
};
// 更新简写
let user3 = User {
    username: String::from("ccc"),
    ..user2
};

定义方法

struct Rectangle {
    width: u32,
    length: u32,
}
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.length
    }
    // 关联函数 associate function(静态方法) 第一个参数不是self
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            length: size,
        }
    }
}

tuple struct

想给类型相同的tuple做区分,但是里面的元素没名

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

结构体打印调试信息

// struct 上面加 #[derive(Debug)]
#[derive(Debug)]
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}
let user = User {
    username: String::from("sss"),
    email: String::from("abc@abc.com"),
    sign_in_count: 1,
    active: true,
};
println!("{:?}", user);
println!("{:#?}", user);    // 带换行

struct 数据所有权

  • struct 拥有其所有数据所有权
  • 如果 struct 存引用,则需要使用生命周期。(生命周期保证struct实例有效,里面的引用就有效)

所有权规则

  • Rust中每一个值都有一个对应的变量作为它的所有者
  • 在同一时间内,值有且仅有一个所有者
  • 当所有者离开自己的作用域时,它持有的值就会被释放掉
let mut s = String::from("hello");
println!("{}", s);
s.push_str(" world");
println!("{}", s);

let s2 = s; // 所有权转移
println!("{}",s); //error: borrow of moved value: `s`

let s2 = s.clone(); // clone()对stack和Heap数据进行深拷贝
println!("{}", s); // ok

Copy trait vs Drop trait

  1. 实现了 Copy trait 旧变量赋值后仍然可以使用
  2. 实现了Drop trait 就不允许实现Copy trait
  3. 简单标量类型都实现了Copy
  4. 需要Heap分配的类型都没实现Copy
  5. tuple 内所有字段都是实现Copy就可以Copy

引用和借用

  • &与c++类似,引用某值不获取所有权
  • 将引用作为函数参数的行为称之为借用

数据竞争三条规则

  • 两个或多个指针同时访问同一个数据
  • 至少有一个指针用于写入数据
  • 没有使用任何机制来同步对数据的访问

如果都满足,就有可能出现数据竞争、报错。

Rust的引用规则:

  • 一个可变的引用
  • 任意数量不可变引用
    只能满足其一

悬垂引用 dangling refrences

  • 类似与cpp的野指针
  • rust内 编译器可保证引用永远都不是悬垂引用

切片

let s = String::from("hello world");
println!("{}", &s[0..5]);
println!("{}", &s[6..]);
println!("{}", &s[..]);

if 表达式

let number = 3;
if number < 5 {
    println!("less five")
} else if number > 5 && number < 10 {
    println!("more five, less ten")
} else {
    println!("more ten")
}

// 因为是表达式可以赋值
let result = if true {
    3
} else {
    5
};

loop 表达式

let mut count = 3;
let result = loop {
    if count == 5 {
        break count;
    }
    count += 1;
};

while 循环

while 循环内不能使用break关键字

let mut number = 3;
while number > 0 {
    number -= 1
}

for 循环

// for in
let arr = [1, 2, 3, 4, 5];
for element in arr.iter() {
    println!("t1 the value is: {}", element);
}

// for range
// (0...arr.len()) 只能正着,反转需要rev()
for i in (0..arr.len()) {
    println!("t2 the value is: {}", arr[i]);
}
//(0..a.len()).rev() 反转
for i in (0..arr.len()).rev() {
    println!("t3 the value is: {}", arr[i]);
}

函数

函数名使用蛇形命名法snake case

fn main() {
    let result = add_four(7);
    println!("result:{}", result);
}

// parameter 形参 argument 实参
// add_four 蛇形命名法
fn add_four(x: i32) -> i32 {
    let y = {
        let x = x + 1;
        x + 3
    };
    y // return y; 可以使用return y; 也可以直接使用表达式返回
}

表达式与语句区别

  • 语句(statement)执行操作但不返回值的命令。
  • 表达式(expression)会进行计算并返回一个结果的指令。
// 语句:
    let x = 5;
// 表达式:
    {
        let x = 1;
        x + 3    // 无分号。返回最后一个表达式的结果
    }

变量的可变性

可变变量需要加上mut关键字

let a = 1;
a = 2; // cannot assign twice to immutable variable `a`

let mut b = 1;
b = 2; // ok!

常量

const MAX: i32 = 100;

隐藏

隐藏(shadow)

let x = 5;
let x = x + 1;

标量类型(scalar type)

let u1: u8 = b'A';
let u2: u16 = 0xff;
let u3: u32 = 0o77;
let u4: u64 = 232_212;
let u5: u128 = 232_212;

let i1: i8 = 127;
let i2: i16 = 254;
let i3: i32 = 254;
let i4: i64 = 254;
let i5: i128 = 254;

let f1: f32 = 254.0;
let f2: f64 = 254.0;

let b: bool = false;

let c1: char = '我';
let c2: char = '😂';

// 默认值为i32和f64
let i = 254; // let i: i32 = 254;
let f = 254.0; // let f: f64 = 254.0;

debug模式下会检测溢出,代码触发panic,release模式下不会检测溢出,会执行二进制补码“环绕”。

复合类型(compound type)

元组(tuple)

let t: (u8, f64, bool) = (240, 3.2, false);
let t2 = (240, 3.2, false);
println!("{},{},{}", t.0, t.1, t.2);

数组(array)

let arr = [7; 5]; // [elem, len] 表示: 5个7
let arr1: [i32; 5] = [1, 2, 3, 4, 5];
let arr2 = [3, 3, 3, 3];
println!("{},{}", arr.len(), arr2.len());

println!("{}", arr2[arr[0]]);// 访问越界 thread 'main' panicked at 'index out of bounds: the len is 4 but the index is 7', src/main.rs:21:20

安装

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

更新

rustup update

卸载

rustup self uninstall

编译

rustc xxx.rs

命名规范

snake_case: 文件名
CamelCase:

Cargo

cargo 是rust的构建系统和包管理工具。

创建项目

cargo new xxx # 创建二进制项目
cargo new xxx --lib # 创建库项目

安装依赖

cargo install xxxx

编译

cargo build
cargo build --release

运行

cargo run

tests

cargo test

检查

检查代码,cargo checkcargo build运行时间快的多

cargo check

panic 中断

Cargo.toml

[profile.release]
panic = 'abort'

使用外部package

在 Cargo.toml的
[dependencies] 添加package

cargo 换源

.cargo/config

[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"

replace-with = 'tuna'
[source.tuna]
registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"

[net]
git-fetch-with-cli = true

瀑布式开发

瀑布式开发的基本流程是 需求 → 设计 → 开发 → 测试 , 是一个更倾向于严格控制的管理模式 。 要求有明确的需求,大家按照需求一步步做好规划,每一阶段工作的完成是下一阶段工作开始的前提,每一阶段都要进行严格的评审,保证各阶段的工作做得足够好时才允许进入下一阶段。这种模式一般适用于需求比较明确、to B 端的项目。

不得不说瀑布项目失败率会比较高,因为它有一个很大的缺陷, 就是受各种条件的制约。当产品研发完成后, 到了产品测试阶段 万一发现问题 ,或者发现其无法满足市场需求, 那么就需要重新开发,甚至需要重新规划产品,这 间接导致了产品延期发布的高发性 与不确定性。

微软 的瀑布式开发模式就是个很好的例子。随着用户对软件的需求越来越苛刻,微软的软件产品曾经遭受了大家的不满,原因并非是产品的使用问题,而是其更新周期太过漫长 。

比如微软Office 、 Windows 等主打产品的更新周期长达 3 年左右,软件延期发布实属家常便饭,此时微软的瀑布式开发模式已经难以满足新型软件的开发要求,不得不改变产品的研发策略。

随着网络的逐渐兴起,软件交付模式发生了巨大变化,也正是在 那 个时候,“敏捷开发”模式被国外的软件先行者们探索出来了。

敏捷式开发

简单的说,敏捷开发是一种以用户需求进化为核心、迭代、循序渐进的开发方法。首先把 用户(客户 )最关注的软件原型做出来,交付或上线,在实际场景中去 快速 修改弥补需求中的不足,再次发布版本。通过一些敏捷实践方式,细化story ,提供更小的迭代。如此循环,直到用户(客户)满意。适用于需求不明确、创新性或者需要抢占市场的项目。

还是拿微软来说,微软的Visual Studio 2010是公司内部首个因敏捷开发模式而受益的Visual Studio版本,该软件发布于2010年4月,耗费了两年的时间完成开发,但随后研发团队发现软件中的许多模板对于敏捷开发者来说太过笼统,几乎没有太大的实际意义,微软的软件研发策略也就从此开始发生了巨大变化。以往的产品更新周期为两到三年,目前的版本更新速度已经缩短至一个季度左右,这在瀑布式开发模式下是难以想象的。

Scrum

Scrum的英文意思是橄榄球运动的一个专业术语,表示“争球”的动作;把一个开发流程的名字取名为Scrum,我想你一定能想象出你的开发团队在开发一个项目时,大家像打橄榄球一样迅速、富有战斗激情、人人你争我抢地完成它,你一定会感到非常兴奋的。

而Scrum就是这样的一个开发流程,运用该流程,你就能看到你团队高效的工作。

【Scrum开发流程中的三大角色】

产品负责人(Product Owner)

主要负责确定产品的功能和达到要求的标准,指定软件的发布日期和交付的内容,同时有权力接受或拒绝开发团队的工作成果。

流程管理员(Scrum Master)

主要负责整个Scrum流程在项目中的顺利实施和进行,以及清除挡在客户和开发工作之间的沟通障碍,使得客户可以直接驱动开发。

开发团队(Scrum Team)

主要负责软件产品在Scrum规定流程下进行开发工作,人数控制在5~10人左右,每个成员可能负责不同的技术方面,但要求每成员必须要有很强的自我管理能力,同时具有一定的表达能力;成员可以采用任何工作方式,只要能达到Sprint的目标。

如何进行Scrum开发?

1、我们首先需要确定一个Product Backlog(按优先顺序排列的一个产品需求列表),这个是由Product Owner 负责的;

2、Scrum Team根据Product Backlog列表,做工作量的预估和安排;

3、有了Product Backlog列表,我们需要通过 Sprint Planning Meeting(Sprint计划会议) 来从中挑选出一个Story作为本次迭代完成的目标,这个目标的时间周期是1~4个星期,然后把这个Story进行细化,形成一个Sprint Backlog;

4、Sprint Backlog是由Scrum Team去完成的,每个成员根据Sprint Backlog再细化成更小的任务(细到每个任务的工作量在2天内能完成);

5、在Scrum Team完成计划会议上选出的Sprint Backlog过程中,需要进行 Daily Scrum Meeting(每日站立会议),每次会议控制在15分钟左右,每个人都必须发言,并且要向所有成员当面汇报你昨天完成了什么,并且向所有成员承诺你今天要完成什么,同时遇到不能解决的问题也可以提出,每个人回答完成后,要走到黑板前更新自己的 Sprint burn down(Sprint燃尽图);

6、做到每日集成,也就是每天都要有一个可以成功编译、并且可以演示的版本;很多人可能还没有用过自动化的每日集成,其实TFS就有这个功能,它可以支持每次有成员进行签入操作的时候,在服务器上自动获取最新版本,然后在服务器中编译,如果通过则马上再执行单元测试代码,如果也全部通过,则将该版本发布,这时一次正式的签入操作才保存到TFS中,中间有任何失败,都会用邮件通知项目管理人员;

7、当一个Story完成,也就是Sprint Backlog被完成,也就表示一次Sprint完成,这时,我们要进行 Srpint Review Meeting(演示会议),也称为评审会议,产品负责人和客户都要参加(最好本公司老板也参加),每一个Scrum Team的成员都要向他们演示自己完成的软件产品(这个会议非常重要,一定不能取消);

8、最后就是 Sprint Retrospective Meeting(回顾会议),也称为总结会议,以轮流发言方式进行,每个人都要发言,总结并讨论改进的地方,放入下一轮Sprint的产品需求中;

TDD 测试驱动开发

TDD,所谓测试驱动开发,那么一定是测试现行。TDD的过程中,我们需要的是专注于一个需求的开发,直到测试通过。这里我说针对一个需求,我想强调的,这里的测试,应当是不依赖于实现的系统测试,而不是单元测试。否则,TDD将难以展开。

大多数情况下,开发者会先开发再测试,很可能出现的一种情况是,过程中一系列的单元测试都是通过的,但最终的功能依旧无法保证,这时再开展系统测试,可以说是比较耗费时间的,毕竟,原本你的目标可以变成:通过眼前这一项测试。

先这么多吧~期待后面会有更多不一样的感受。

生成ssl证书

首次安装

sudo -iu root
apt-get install socat
curl https://get.acme.sh | sh -s email=test1@test.com #安装完成
yourdomain=xxx.xxx.xxx.xxx #你的域名

签名证书

因为mac、ios系统vpn不支持ec-256证书格式,在此申请RSA证书

/root/.acme.sh/acme.sh --issue -d ${yourdomain} --debug --standalone

证书续期

写个autoCa.sh放入.acme.sh文件夹中

#!/bin/bash   
yourdomain="xxx.xxx.xxx.xxx"
systemctl stop trojan-web                                       #停掉web服务
/root/.acme.sh/acme.sh --cron --home /root/.acme.sh             #续期
cert_file="/root/.acme.sh/${yourdomain}/${yourdomain}.cer"
key_file="/root/.acme.sh/${yourdomain}/${yourdomain}.key"
ca_file="/root/.acme.sh/${yourdomain}/ca.cer"
cp -f $cert_file /usr/local/etc/ipsec.d/certs/server.cert.pem   #拷贝证书到ipsec目录下
cp -f $key_file /usr/local/etc/ipsec.d/private/server.pem
cp -f $ca_file /usr/local/etc/ipsec.d/cacerts/ca.cert.pem
systemctl start trojan-web                                      #启动trojan web 服务
/usr/local/sbin/ipsec restart                                   #重启  ipsec

定时任务更新证书

bash输入 crontab -e 修改定时任务

#每个月2号执行
0 0 2 * * bash /root/.acme.sh/autoCa.sh > /dev/null

安装 strongswan

复制生成好的证书到当前目录下

yourdomain="xxx.xxx.xxx.xxx"
cert_file="/root/.acme.sh/${yourdomain}_ecc/${yourdomain}.cer"
key_file="/root/.acme.sh/${yourdomain}_ecc/${yourdomain}.key"
ca_file="/root/.acme.sh/${yourdomain}_ecc/ca.cer"
cp -f $cert_file server.cert.pem
cp -f $key_file server.pem
cp -f $cert_file client.cert.pem
cp -f $key_file client.pem
cp -f $ca_file ca.cert.pem

安装 strongswan

source <(curl -sL https://raw.githubusercontent.com/wanyvic/one-key-ikev2-vpn/master/one-key-ikev2.sh)

提示: Would you want to import existing cert? You NEED copy your cert file to the same directory of this script 选 yes

配置 ikev2密码

vim /usr/local/etc/ipsec.secrets

trojan-go

安装Jrohy的一键trojan面板脚本

source <(curl -sL https://git.io/trojan-install)

删除

source <(curl -sL https://git.io/trojan-install) --remove

切换 trojan-go

trojan
# 交互输入1 和 6
1
6

或通过{https://yourdomain}打开网页手动切换内核

更改trojan-go配置文件以支持websocket

vim /usr/local/etc/trojan/config.json
#在mysql后面追加
"websocket": {
        "enabled": true,
        "path": "/DFE4545DFDED/", # ws路径
        "host": "你的域名"
    },
    "mux": {
        "enabled": true,
        "concurrency": 8,
        "idle_timeout": 60
    }
trojan restart #重启trojan

更改trojan-go配置的证书路径

安装脚本默认生成ec-256证书,在此不使用ec-256证书。使用之前生成的RSA证书代替

#删除证书路径中的ec-256证书
rm -rf /root/.acme.sh/${yourdomain}_ecc
sed -i "s/${yourdomain}_ecc/${yourdomain}/g" /usr/local/etc/trojan/config.json

客户端

ipsec

ipsec 直接使用系统ikev2 vpn连接即可。

trojan-go

GUI使用Qv2ray + trojan-go 内核程序 + trojan-go qv2ray 插件

Qv2ray插件文档

运作

  • Qv2ray 设置sock5/http代理到本地

  • 浏览器使用SwitchyOmega插件连接本地代理端口

  • 终端使用
    设置环境变量

export http_proxy=http://127.0.0.1:8082
export https_proxy=http://127.0.0.1:8082
# 或者
# export http_proxy="socks5://127.0.0.1:1080"
# export https_proxy="socks5://127.0.0.1:1080"