std::net
std::net 是 Rust 标准库的网络编程模块(注意: 不是 std::io::net, 那是笔误), 提供了 TCP/UDP 通信所需的类型
介绍
std::net 中比较重要的类型:
| 类型 | 作用 |
|---|---|
| TcpListener | TCP 服务器端, 监听端口接收连接 |
| TcpStream | TCP 连接(客户端和服务器都能用) |
| UdpSocket | UDP 套接字(无连接, 数据报) |
| IpAddr | IP 地址(IPv4 或 IPv6) |
| SocketAddr | 地址: "IP:端口" 的组合 |
IP 地址与端口
IP 地址解析
| 方法 | 作用 |
|---|---|
| IpAddr::is_ipv4 | 是否是 IPv4 地址 |
| IpAddr::is_ipv6 | 是否是 IPv6 地址 |
| IpAddr::is_loopback | 是否是回环地址(127.0.0.1 / ::1) |
| IpAddr::is_private | 是否是内网地址(192.168.x.x 等) |
rust
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
fn main() {
// 1. 字符串解析为 IP 地址
let ip: IpAddr = "127.0.0.1".parse().expect("解析失败");
println!("{ip}"); // 127.0.0.1
// 2. 直接构造
let v4 = Ipv4Addr::new(192, 168, 1, 100);
println!("{v4}"); // 192.168.1.100
// IPv6 地址 ::1 (即 0:0:0:0:0:0:0:1)
let v6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1);
println!("{v6}"); // ::1
// 3. 常用判断方法
let loopback: IpAddr = "127.0.0.1".parse().expect("解析失败");
println!("is_loopback = {}", loopback.is_loopback()); // is_loopback = true
println!("is_ipv4 = {}", loopback.is_ipv4()); // is_ipv4 = true
// is_private 只定义在 Ipv4Addr 上, 需要先取出 IPv4 变体
let private: IpAddr = "192.168.1.1".parse().expect("解析失败");
if let IpAddr::V4(v4) = &private {
println!("is_private = {}", v4.is_private()); // is_private = true
}
// 4. 匹配具体的 IPv4 变体
if let IpAddr::V4(v4) = private {
println!("octets = {:?}", v4.octets()); // octets = [192, 168, 1, 1]
}
}SocketAddr
SocketAddr 是 "IP:端口" 的组合, 可以用一个字符串直接解析
rust
use std::net::{IpAddr, SocketAddr};
fn main() {
// 1. 字符串解析 "IP:端口"
let addr: SocketAddr = "127.0.0.1:8080".parse().expect("解析失败");
println!("{addr}"); // 127.0.0.1:8080
// 2. 获取 IP 和端口
println!("ip = {}", addr.ip()); // ip = 127.0.0.1
println!("port = {}", addr.port()); // port = 8080
// 3. 手动构造
let ip: IpAddr = "127.0.0.1".parse().expect("解析失败");
let addr2 = SocketAddr::new(ip, 8080);
println!("{addr2}"); // 127.0.0.1:8080
// 4. 判断 IPv4 还是 IPv6 (IPv6 用 [::1]:8080 这种写法)
let v6: SocketAddr = "[::1]:8080".parse().expect("解析失败");
println!("is_ipv6 = {}", v6.is_ipv6()); // is_ipv6 = true
}TCP 服务器与客户端
项目准备
sh
cargo new tcp_demo全部代码
rust
use std::io::{self, Read, Write, stdin};
use std::{env, net};
fn main() -> io::Result<()> {
let args: Vec<String> = env::args().skip(1).collect();
if args.is_empty() {
eprintln!("please prvider an arguments(client or server)");
return Ok(());
}
let target = &args[0];
match target.as_str() {
"server" | "s" => run_tcp_server(),
"client" | "c" => run_tcp_client(),
_ => {
println!("argument must be 'client' or 'server' ");
Ok(())
}
}
}
fn run_tcp_server() -> io::Result<()> {
println!("server running on 8000");
// 绑定监听地址和端口
let listener = net::TcpListener::bind("127.0.0.1:8000")?;
// 监听客户端是否有链接进来
for stream in listener.incoming() {
let mut stream = stream.expect("failed to receive stream");
// 主动读取数据(循环读取)直到连接关闭(read 返回 0)或出错
// 使用固定大小缓冲区(使用 Vec<u8> 可能立刻返回 0)
// 这里应该使用线程池: 因为可能有多个客户端, 如果单线程,
// 它只会处理第一个TCP客户端链接的内容, 后续的链接会卡住
// 直到, 第一个 TCP 客户端断开链接(跳出loop), 才会处理其他链接
// 这里只是演示下: 客户端与服务端的通信, 就不使用多线程了
let mut buffer = [0; 1024];
loop {
match stream.read(&mut buffer) {
Ok(0) => {
println!("connection closed");
break;
}
Ok(n) => {
// 解析接收的内容
let received = String::from_utf8_lossy(&buffer[..n]);
println!("[server]: {}", received);
// 将收到的内容处理后(添加[from server]后缀)发回给客户端
let mut new_msg = received.to_string();
new_msg.push_str("[from server]");
stream.write_all(&new_msg.into_bytes())?;
}
Err(e) => {
eprintln!("read error: {}", e);
break;
}
}
}
}
Ok(())
}
fn run_tcp_client() -> io::Result<()> {
let mut stream = net::TcpStream::connect("127.0.0.1:8000")?;
let mut buffer = [0; 1024];
// 服务器已经链接
println!("server connected");
loop {
// 从命令行中读取输入内容,发送给服务端
println!("请输入内容: ");
let mut input = String::new();
stdin().read_line(&mut input)?;
let bytes = input.trim().to_string().into_bytes();
stream.write_all(&bytes)?;
// 主动从连接中读取服务端发送过来的内容
match stream.read(&mut buffer) {
Ok(0) => {
println!("[client]connection closed");
break;
}
Ok(n) => {
// 解析接收的内容, 然后原样输出
let received = String::from_utf8_lossy(&buffer[..n]);
println!("[client]: {}", received);
}
Err(e) => {
eprintln!("[client]failed to read data from stream: {}", e);
break;
}
}
}
Ok(())
}TCP 服务端
使用 TcpListener 创建 TCP 服务器
| 方法 | 作用 |
|---|---|
| bind | 绑定地址和端口(监听) |
| accept | 阻塞等待客户端连接 |
| incoming | 客户端连接的迭代器 |
| local_addr | 获取监听的地址 |
rust
fn run_tcp_server() -> io::Result<()> {
println!("server running on 8000");
// 绑定监听地址和端口
let listener = net::TcpListener::bind("127.0.0.1:8000")?;
// 监听客户端是否有链接进来
for stream in listener.incoming() {
let mut stream = stream.expect("failed to receive stream");
// 主动读取数据(循环读取)直到连接关闭(read 返回 0)或出错
// 使用固定大小缓冲区(使用 Vec<u8> 可能立刻返回 0)
// 这里应该使用线程池: 因为可能有多个客户端, 如果单线程,
// 它只会处理第一个TCP客户端链接的内容, 后续的链接会卡住
// 直到, 第一个 TCP 客户端断开链接(跳出loop), 才会处理其他链接
// 这里只是演示下: 客户端与服务端的通信, 就不使用多线程了
let mut buffer = [0; 1024];
loop {
match stream.read(&mut buffer) {
Ok(0) => {
println!("connection closed");
break;
}
Ok(n) => {
// 解析接收的内容
let received = String::from_utf8_lossy(&buffer[..n]);
println!("[server]: {}", received);
// 将收到的内容处理后(添加[from server]后缀)发回给客户端
let mut new_msg = received.to_string();
new_msg.push_str("[from server]");
stream.write_all(&new_msg.into_bytes())?;
}
Err(e) => {
eprintln!("read error: {}", e);
break;
}
}
}
}
Ok(())
}注意点
accept 是阻塞的, 一次只能处理一个客户端, 真正的服务器会在循环中不断 accept。 上面示例处理一次连接就退出, 是为了演示方便
TCP 客户端
使用 TcpStream 连接服务器
| 方法 | 作用 |
|---|---|
| connect | 连接服务器(返回 Result) |
| read | 读取数据(Read trait) |
| write_all | 写入数据(Write trait) |
| set_read_timeout | 设置读取超时, 防止一直阻塞 |
经典场景: 一个程序内同时启动服务器和客户端, 完成一次通信(回显)
rust
fn run_tcp_client() -> io::Result<()> {
let mut stream = net::TcpStream::connect("127.0.0.1:8000")?;
let mut buffer = [0; 1024];
// 服务器已经链接
println!("server connected");
loop {
// 从命令行中读取输入内容,发送给服务端
println!("请输入内容: ");
let mut input = String::new();
stdin().read_line(&mut input)?;
let bytes = input.trim().to_string().into_bytes();
stream.write_all(&bytes)?;
// 主动从连接中读取服务端发送过来的内容
match stream.read(&mut buffer) {
Ok(0) => {
println!("[client]connection closed");
break;
}
Ok(n) => {
// 解析接收的内容, 然后原样输出
let received = String::from_utf8_lossy(&buffer[..n]);
println!("[client]: {}", received);
}
Err(e) => {
eprintln!("[client]failed to read data from stream: {}", e);
break;
}
}
}
Ok(())
}UDP
UDP 是无连接的协议, 使用 UdpSocket 发送/接收数据报
| 方法 | 作用 |
|---|---|
| bind | 绑定地址和端口 |
| send_to | 发送数据到指定地址 |
| recv_from | 接收数据(返回 数据 和 来源地址) |
| connect | 只和指定地址通信(之后可用 send/recv) |
rust
use std::net::UdpSocket;
fn main() {
// 1. 创建两个 socket: 一个当服务器, 一个当客户端
let server = UdpSocket::bind("127.0.0.1:0").expect("绑定失败");
let client = UdpSocket::bind("127.0.0.1:0").expect("绑定失败");
let server_addr = server.local_addr().expect("获取地址失败");
println!("服务器地址: {server_addr}");
// 2. 客户端发送数据
client.send_to(b"hello udp", server_addr).expect("发送失败");
// 3. 服务器接收数据
// recv_from 返回: (收到的字节数, 发送方地址)
let mut buf = [0u8; 1024];
let (n, from) = server.recv_from(&mut buf).expect("接收失败");
println!("收到 {n} 字节, 来自 {from}"); // 收到 9 字节, 来自 127.0.0.1:xxxxx
println!("内容: {}", String::from_utf8_lossy(&buf[..n])); // 内容: hello udp
}HTTP 相关开源库
标准库的 std::net 只提供最底层的 TCP/UDP, 但是由于开发时用到最多的协议并不是底层的 TCP/UDP 而是基于 TCP 封装而来的 HTTP/SMTP/FTP/SSH 等等: