前言
tcp是现在通信的基础,在http3
没有到来之前这句话还是很对的。
因为tcp是流式传输,客户端一次write 1600个字节,服务端也许只读了1540个字节。
这就是著名的粘包问题
,要解决这个问题也很简单,client 和 server约定下解析方式。
比如客户端发10个字节,可以把10这个信息也一起发过来,先发4个字节保存长度,再发数据就行。
server也按这个顺序解析。
一、创建工程
1
|
cargo new tcp-client-and-server --bin
|
Cargo.toml
配置文件
1
2
3
4
5
6
7
8
9
10
|
[[bin]]
name="client"
path="./src/client.rs"
[[bin]]
name="server"
path="./src/server.rs"
[dependencies]
structopt = "0.3"
|
二、工具函数,解析和写入TLV数据
1.实现
- 位于
src/utils.rs
write_head_and_bytes
先向TcpStream
写入数据头,再写入数据payload
read_head_and_bytes
先读取4
字节定长数据,再读取数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
use std::io;
use std::io::{Read, Write};
use std::net::TcpStream;
pub fn write_head_and_bytes(mut stream: &TcpStream, data: &[u8]) -> io::Result<()> {
// 存放头
let buffer = (data.len() as u32).to_be_bytes();
// 写入头
stream.write_all(&buffer)?;
// 写入body
stream.write_all(data)?;
Ok(())
}
pub fn read_head_and_bytes(mut stream: &TcpStream) -> io::Result<Vec<u8>> {
let mut buffer = [0u8; 4];
// 读取4个字节定长数据
stream.read_exact(&mut buffer[..])?;
// 计算buffer长度
let size = u32::from_be_bytes(buffer);
// 数据body
let mut payload = vec![0; size as usize];
// 读取数据body
stream.read_exact(&mut payload[..])?;
Ok(payload)
}
|
2.修改lib.rs
把utils.rs添加到模块里面,rust编译器有点懒,要编译的模块一个要告知,比如注册到lib.rs
。不像go放到目录里面就给编译
1
|
echo 'pub mod utils;' >> src/lib.rs
|
三、tcp client
- 一开始连接服务端
- 然后写入4字节头,写入数据
- 最后读取4字节端,读取数据,然后打印到终端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
use std::io;
use std::net::TcpStream;
use structopt::StructOpt;
use tcp_client_and_server::utils::*;
#[derive(StructOpt, Debug)]
struct Client {
/// tcp server address
#[structopt(short, long)]
addr: String,
/// data
#[structopt(short, long)]
data: String,
}
fn main() -> io::Result<()> {
// 解析命令行
let c = Client::from_args();
// 1.连接服务端
let stream = TcpStream::connect(c.addr).unwrap();
// 2.写入请求
write_head_and_bytes(&stream, c.data.as_bytes())?;
// 3.读取rsp数据
let payload = read_head_and_bytes(&stream)?;
// 4.返回终端
println!("{}", std::str::from_utf8(&payload).unwrap());
Ok(())
}
|
四、tcp server
- 读取4字节头,读取数据
- 在客户端的数据加上"#Server read" 然后返回给客户端(head + playload)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
use std::io;
use std::net::{TcpListener, TcpStream};
use structopt::StructOpt;
use tcp_client_and_server::utils::*;
#[derive(StructOpt, Debug)]
struct Server {
/// server address
#[structopt(short, long)]
server_addr: String,
}
fn work_conn(stream: TcpStream) -> io::Result<()> {
// 1.读取payload
let mut payload = read_head_and_bytes(&stream)?;
// 2.追加一些数据,然后返回
payload.append(&mut "#Server read".as_bytes().to_vec());
// 3.写入响应
write_head_and_bytes(&stream, &payload)?;
Ok(())
}
impl Server {
fn main_loop(&self) {
// 创建listen
let listener = TcpListener::bind(&self.server_addr).unwrap();
for stream in listener.incoming() {
let stream = match stream {
Ok(stream) => stream,
Err(e) => {
/* connection failed */
println!("{:#?}", e);
continue;
}
};
work_conn(stream).unwrap();
}
}
}
fn main() {
let s = Server::from_args();
s.main_loop();
}
|
五、编译和运行
编译
运行
1
2
|
./target/debug/server -s "0:1234" # 运行服务端
./target/debug/client -a 127.0.0.1:1234 -d "hello world" #运行客户端
|