前言

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
cargo build

运行

1
2
./target/debug/server -s "0:1234" # 运行服务端
./target/debug/client -a 127.0.0.1:1234 -d "hello world" #运行客户端