【小沐学Rust】Rust实现TCP网络通信
文章目录
- 1、简介
- 2、安装
- 2.1 安装Rust
- 2.2 VsCode安装Rust插件
- 3、快速入门
- 3.1 命令行构建
- 3.2 Cargo构建
- 3.3 Cargo添加依赖
- 4、基本语法
- 4.1 main 的函数
- 4.2 代码缩进
- 4.3 todo! 宏
- 4.4 println! 宏
- 4.5 变量的使用
- 4.6 元组
- 4.7 结构
- 4.8 枚举
- 4.9 函数
- 5、TCP通信
- 5.1 测试一
- 5.1.1 TCP服务端
- 5.1.2 TCP客户端
- 5.2 测试二
- 5.2.1 TCP服务端
- 5.2.2 TCP客户端
- 结语
1、简介
Rust 语言是一种高效、可靠的通用高级语言。其高效不仅限于开发效率,它的执行效率也是令人称赞的,是一种少有的兼顾开发效率和执行效率的语言。
Rust 语言由 Mozilla 开发,最早发布于 2014 年 9 月。
-
Rust的用途
开发人员可在网络软件(如 Web 服务器、邮件服务器和 Web 浏览器)领域使用 Rust。 Rust 的身影还会出现在编译器和解释器、虚拟化和软件容器、数据库、操作系统和加密中。 你还可以利用 Rust 编译适用于嵌入式设备的游戏、命令行程序、Web 程序集程序和应用程序。 -
Rust的内存机制
Rust 是现有系统软件语言(如 C 和 C++)的一种安全替代语言。 与 C 和 C++ 一样,Rust 没有大型运行时或垃圾回收器,这几乎与所有其他现代语言形成了鲜明对比。 但是,与 C 和 C++ 不同的是,Rust 保证了内存安全。 Rust 可以避免很多与在 C 和 C++ 中遇到的内存使用错误相关的 bug。 -
Rust的特点
- 类型安全:编译器可确保不会将任何操作应用于错误类型的变量。
- 内存安全:Rust 指针(称为“引用”)始终引用有效的内存。
- 无数据争用:Rust 的 borrow 检查器通过确保程序的多个部分不能同时更改同一值来保证线程安全。
- 零成本抽象:Rust 允许使用高级别概念,例如迭代、接口和函数编程,将性能成本控制在最低,甚至不会产生成本。 这些抽象的性能与手工编写的底层代码一样出色。
- 最小运行时:Rust 具有极小的可选运行时。 为了有效地管理内存,此语言也不具有垃圾回收器。 在这一点上,Rust 非常类似于 C 和 C++ 之类的语言。
- 面向裸机:Rust 可以用于嵌入式和“裸机”编程,因此适合用于编写操作系统内核或设备驱动程序。
-
Rust的编译器
虽然可以直接使用 Rust 编译器 (rustc) 来生成箱,但大多数项目都使用 Rust 生成工具和名为 Cargo 的依赖项管理器。
Cargo 可以为你做许多事情,包括:- 使用 cargo new 命令创建新的项目模板。
- 使用 cargo build 编译项目。
- 使用 cargo run 命令编译并运行项目。
- 使用 cargo test 命令测试项目。
- 使用 cargo check 命令检查项目类型。
- 使用 cargo doc 命令编译项目的文档。
- 使用 cargo publish 命令将库发布到 crates.io。
- 通过将箱的名称添加到 Cargo.toml 文件来将依赖箱添加到项目。
-
Rust的在线开发环境:
操场是 Rust 开发的 IDE,可在 https://play.rust-lang.org/ Internet 上访问。 任何人都可以访问操场。 你可以编写代码,然后在相同的环境中编译和运行代码。 以下屏幕截图显示了操场环境。 在工具栏的最右侧,“CONFIG”菜单的选项可以设置环境的首选项。
https://play.rust-lang.org/
浏览器运行如下:
2、安装
2.1 安装Rust
https://www.rust-lang.org/zh-CN/tools/install
在Windows上要使用 Rust,请下载安装器,然后运行该程序并遵循屏幕上的指示。当看到相应提示时,您可能需要安装 Microsoft C++ 生成工具。如果您不在 Windows 上,参看 “其他安装方式”。
下载rustup-init.exe,鼠标直接双击运行之。
(1)选择安装方式
(2)rust下载中
(3)rust安装完成
(4)查看rust版本号
rustc -V
# rustc --version
cargo --version
2.2 VsCode安装Rust插件
(1)rust-analyzer:它会实时编译和分析你的 Rust 代码,提示代码中的错误,并对类型进行标注。你也可以使用官方的 rust 插件取代。
rust-analyzer是官方维护的rls(rust语言服务器)2.0版本,已有VSCode插件。
Rust:这是官方开发的;
rust-analyzer:这是社区开发的;
(2)rust syntax:为代码提供语法高亮。
(3)Dependi:帮助你分析当前项目的依赖是否是最新的版本。
(4)Even Better TOML:Rust 使用 toml 做项目的配置管理。
(5)Tabnine:基于 AI 的自动补全,可以帮助你更快地撰写代码。
3、快速入门
3.1 命令行构建
- (1)创建一个新目录
对于 Windows 命令提示符,请运行以下命令:
mkdir hello-yxy
cd hello-yxy
- (2)编写你的第一个 Rust 程序
创建一个名为 main.rs 的新文件,并使用编辑器将以下代码写入其中:
fn main() {println!("Hello, yxy!");
}
在 Windows 中可以运行以下命令:
rustc main.rs
.\main.exe
3.2 Cargo构建
-(3)使用 Cargo 创建项目
让我们使用 Cargo 编写并运行相同的程序。
并运行以下命令:
cargo new hello-cargo-yxy
命令如下:
此命令生成名为 hello-cargo 的新目录(其中包含 src 子目录),并自动添加两个文件:
hello-cargo/Cargo.tomlsrc/main.rs
Cargo.toml 是 Rust 的清单文件。 这个文件可用于保存项目及依赖项的元数据。
Src 子目录中的 main.rs 文件可用于编写应用程序代码。
-(4)使用 Cargo 生成并运行你的程序
使用 cargo run 命令:
cd hello-cargo-yxy
cargo run
3.3 Cargo添加依赖
- 新建项目
Cargo 创建一个新项目:
cargo new hello-rust
这会生成一个名为 hello-rust 的新目录,其中包含以下文件:
hello-rust
|- Cargo.toml
|- src|- main.rs
Cargo.toml 为 Rust 的清单文件。其中包含了项目的元数据和依赖库。
src/main.rs 为编写应用代码的地方。
- 代码编译
cargo run
- 添加依赖
为应用添加依赖。您可以在 crates.io,即 Rust 包的仓库中找到所有类别的库。在 Rust 中,我们通常把包称作“crates”。
Ferris-Says是一个Rust编程语言编写的库,它以Rust的标志性吉祥物Ferris为特色,帮助你在命令行中打印出带有Ferris元素的文本。不仅如此,该项目还提供了一个名为fsays的二进制工具,使用户可以方便地从标准输入或文件读取文本,并以独特的ASCII艺术风格呈现。
在 Cargo.toml 文件中添加以下信息(从 crate 页面上获取):
[dependencies]
ferris-says = "0.3.1"
Cargo 就会安装该依赖。
cargo build
运行此命令会创建一个新文件 Cargo.lock,该文件记录了本地所用依赖库的精确版本。
打开 main.rs,添加示意代码:
use ferris_says::say; // from the previous step
use std::io::{stdout, BufWriter};fn main() {let stdout = stdout();let message = String::from("Hello fellow Rustaceans!");let width = message.chars().count();let mut writer = BufWriter::new(stdout.lock());say(&message, width, &mut writer).unwrap();
}
运行代码:
cargo run
4、基本语法
4.1 main 的函数
每个 Rust 程序都必须有一个名为 main 的函数。 main 函数中的代码始终是 Rust 程序中运行的第一个代码。 我们可以从 main 函数内部或从其他函数内部调用其他函数。
fn main() {println!("Hello, world!");
}
4.2 代码缩进
在函数体中,大多数代码语句以分号 ; 结尾。
起始代码语句从左边距缩进四个空格。
4.3 todo! 宏
Rust 中的宏类似于采用可变数量的输入参数的函数。 todo! 宏用于标识 Rust 程序中未完成的代码。
fn main() {// Display the message "Hello, world!"todo!("Display the message by using the println!() macro");
}
4.4 println! 宏
println! 宏需要一个或多个输入参数,这些参数会显示在屏幕或标准输出中。
fn main() {// Our main function does one task: show a message// println! displays the input "Hello, world!" to the screenprintln!("Hello, world!");
}
- {} 参数的值替换
println! 宏将文本字符串中的每个大括号 {} 实例替换为列表中下一个参数的值。
fn main() {// Call println! with three arguments: a string, a value, a valueprintln!("The first letter of the English alphabet is {} and the last letter is {}.", 'A', 'Z');
}
4.5 变量的使用
在 Rust 中,变量用关键字 let 声明。
每个变量都有一个唯一的名称。
在 Rust 中,变量绑定默认不可变。
// Declare a variable
// let a_number;
let a_number = 10;// Declare a second variable and bind the value
let a_word = "Ten";// Bind a value to the first variable
a_number = 10;println!("The number is {}.", a_number);
println!("The word is {}.", a_word);
若要更改值,必须先使用 mut 关键字将变量绑定设为可变。
// The `mut` keyword lets the variable be changed
let mut a_number = 10;
println!("The number is {}.", a_number);// Change the value of an immutable variable
a_number = 15;
println!("Now the number is {}.", a_number);
将 number 变量创建为 32 位整数。
let number: u32 = 14;
println!("The number is {}.", number);
Rust 附带一些用于表达数字、文本和真实信息的内置基元数据类型。
内置数据类型:
整数数字
浮点数
布尔型
字符
- (1)数字:整数和浮点值。
let number_64 = 4.0; // compiler infers the value to use the default type f64
let number_32: f32 = 5.0; // type f32 specified via annotation
- (2)布尔值:True 或 false
// Declare variable to store result of "greater than" test, Is 1 > 4? -- false
let is_bigger = 1 > 4;
println!("Is 1 > 4? {}", is_bigger);
- (3)文本:字符和字符串
字符:
let uppercase_s = 'S';
let lowercase_f = 'f';
let smiley_face = '😃';
字符串:
// Specify the data type "char"
let character_1: char = 'S';
let character_2: char = 'f';// Compiler interprets a single item in quotations as the "char" data type
let smiley_face = '😃';// Compiler interprets a series of items in quotations as a "str" data type and creates a "&str" reference
let string_1 = "miley ";// Specify the data type "str" with the reference syntax "&str"
let string_2: &str = "ace";println!("{} is a {}{}{}{}.", smiley_face, character_1, string_1, character_2, string_2);
4.6 元组
元组是集中到一个复合值中的不同类型值的分组。 元组中的各个值称为元素。 这些值指定为括在括号中的逗号分隔列表 (, , …)。
// Declare a tuple of three elements
let tuple_e = ('E', 5i32, true);// Use tuple indexing and show the values of the elements in the tuple
println!("Is '{}' the {}th letter of the alphabet? {}", tuple_e.0, tuple_e.1, tuple_e.2);
4.7 结构
结构是多个其他类型的组合体。 结构中的元素称为字段。 与元组一样,结构中的字段可以具有不同的数据类型。 结构类型的一个显著好处是,可以命名每个字段,以便清楚展示相应值的含义。
// Classic struct with named fields
struct Student { name: String, level: u8, remote: bool }// Tuple struct with data types only
struct Grades(char, char, char, char, f32);// Unit struct
struct Unit;
实例化结构:
// Instantiate classic struct, specify fields in random order, or in specified order
let user_1 = Student { name: String::from("Constance Sharma"), remote: true, level: 2 };
let user_2 = Student { name: String::from("Dyson Tan"), level: 5, remote: false };// Instantiate tuple structs, pass values in same order as types defined
let mark_1 = Grades('A', 'A', 'B', 'A', 3.75);
let mark_2 = Grades('B', 'A', 'A', 'C', 3.25);println!("{}, level {}. Remote: {}. Grades: {}, {}, {}, {}. Average: {}", user_1.name, user_1.level, user_1.remote, mark_1.0, mark_1.1, mark_1.2, mark_1.3, mark_1.4);
println!("{}, level {}. Remote: {}. Grades: {}, {}, {}, {}. Average: {}", user_2.name, user_2.level, user_2.remote, mark_2.0, mark_2.1, mark_2.2, mark_2.3, mark_2.4);
4.8 枚举
与结构一样,枚举变体可以具有命名字段,但也可以具有没有名称的字段或根本没有字段。 与结构类型一样,枚举类型也采用大写形式。
enum WebEvent {// An enum variant can be like a unit struct without fields or data typesWELoad,// An enum variant can be like a tuple struct with data types but no named fieldsWEKeys(String, char),// An enum variant can be like a classic struct with named fields and their data typesWEClick { x: i64, y: i64 }
}
4.9 函数
- (1)定义函数
Rust 中的函数定义以 fn 关键字开头。
fn main() {println!("Hello, world!");goodbye();
}fn goodbye() {println!("Goodbye.");
}
- (2)传递输入参数
如果函数具有输入参数,请命名每个参数并在函数声明的开头指定数据类型。
fn goodbye(message: &str) {println!("\n{}", message);
}fn main() {let formal = "Formal: Goodbye.";let casual = "Casual: See you later!";goodbye(formal);goodbye(casual);
}
- (3)返回值
当函数返回某个值时,请在函数参数列表后面和函数体的左大括号前面添加语法 -> 。
fn divide_by_5(num: u32) -> u32 {num / 5
}fn main() {let num = 25;println!("{} divided by 5 = {}", num, divide_by_5(num));
}
5、TCP通信
5.1 测试一
5.1.1 TCP服务端
use std::net::TcpListener;fn main() {// 监听地址: 127.0.0.1:9090let listener = TcpListener::bind("127.0.0.1:9090").unwrap();for stream in listener.incoming() {let stream = stream.unwrap();println!("Connection established!");}
}
5.1.2 TCP客户端
fn main() {use std::net::TcpStream;if let Ok(stream) = TcpStream::connect("127.0.0.1:9090") {println!("Connected to the server!");} else {println!("Couldn't connect to server...");}
}
5.2 测试二
5.2.1 TCP服务端
//
//@dev server用于监听
//
use std::net::{TcpListener, TcpStream};
use std::thread;
//std::thread库的引入,对输入的每一个流创建一个线程
use std::time;
use std::io::{self, Read, Write};
//引入io库,为了处理错误fn handle_client(mut stream: TcpStream) -> io::Result<()> {//该函数用来处理client(就是这个流),流的格式或者说他的类型就是TcpStreamlet mut buf = [0; 512];//创建一个叫buf的数组,内容为0,长度为512loop {//该循环表示server端永久提供服务,因为默认服务器为永不关闭的let bytes_read = stream.read(&mut buf)?;//从流里面读内容,读到buf中if bytes_read == 0 {return Ok(());//如果读到的为空(即0),则说明已经结束了}stream.write(&buf[..bytes_read])?;//否则把它写回去thread::sleep(time::Duration::from_secs(1));//调用sleep函数实现服务的间隔,间隔1slet s = String::from_utf8(buf.to_vec());println!("receive data from client: {:?}", &s);}
}fn main() -> io::Result<()> {let listener = TcpListener::bind("127.0.0.1:8080")?;//定义一个listener,bind函数里面填写的是监听的的ip与端口号,?是一种简写,等价于except,unwraplet mut thread_vec: Vec<thread::JoinHandle<()>> = Vec::new();//创建一个容器,用来放线程的句柄for stream in listener.incoming() {println!("new client is connected.");let stream = stream.expect("failed");//转换一下stream流,出现问题,提示“失败”,没有问题,继续下面的操作let handle = thread::spawn(move || {handle_client(stream).unwrap_or_else(|error| eprintln!("{:?}", error));});//对输入的每一个流来创建一个线程,利用必包进行一个处理thread_vec.push(handle);//把handle加到容器里面}for handle in thread_vec {//此循环为了等待线程的结束handle.join().unwrap();//等待结束的具体实现}Ok(())
}
5.2.2 TCP客户端
//
//@dev server端进行监听,在client端发起链接
//
use std::io::{self, prelude::*, BufReader, Write};
use std::net::TcpStream;
use std::str;fn main() -> io::Result<()> {let mut stream = TcpStream::connect("127.0.0.1:8080")?;//创建变量stream,直接连接sever端for _ in 0..10 {println!("please input:");let mut input = String::new();//定义一个String类型的输入io::stdin().read_line(&mut input).expect("Failed to read!");//从标准输入读入一行,读入input里面,如果有问题的话,提示“读取失败”stream.write(input.as_bytes()).expect("Failed to write!");//把input读取的内容,转换成bytes后,写到stream流里面去,如果写入失败,提示“写入失败”let mut reader = BufReader::new(&stream);//从stream流创建一个读,目的是要从我们的server端读,let mut buffer: Vec<u8> = Vec::new();//用Vector创建一个buffer变量 reader.read_until(b'\n', &mut buffer).expect("Failed to read into buffer");//一直读到换行为止(b'\n'中的b表示字节),读到buffer里面println!("read from server: {}", str::from_utf8(&buffer).unwrap());//把读取到buffer中的内容打印出来println!("");//再来一个换行,美化输出}Ok(())
}
结语
如果您觉得该方法或代码有一点点用处,可以给作者点个赞,或打赏杯咖啡;
╮( ̄▽ ̄)╭
如果您感觉方法或代码不咋地//(ㄒoㄒ)//,就在评论处留言,作者继续改进;
o_O???
如果您需要相关功能的代码定制化开发,可以留言私信作者;
(✿◡‿◡)
感谢各位大佬童鞋们的支持!
( ´ ▽´ )ノ ( ´ ▽´)っ!!!