关于本项目
本项目是一个由Rust语言编写的终端简易P2P聊天室,基于TCP协议利用端口复用进行打洞,经测试在NAT1(Full Cone NAT,全锥形NAT)下完全可以。项目已发布到github(地址)。项目使用的基本都是一些基本库,未使用类似 libp2p 的现成的p2p库,使用的是tokio异步运行时框架(其实从0开始写也不是不可以,但是我非常不赞成重复造轮子的)。
声明:本项目只是个人练习项目,写得不是很好,大佬们轻喷
效果预览
Windows 10:
WSL2 和安卓
服务器日志
项目分析
项目分为三部分:网络传输、客户端、服务端。
内容比较多,我就挑一些重要的内容简单说一下。
客户端
客户端连接服务器:创建本地socket并绑定到随机端口,设置socket端口复用功能(这里有个坑,在linux下的TCP端口复用需要用TcpSocket::set_reuseport
设置,而Windows不用。。)
let loc_addr = {
let mut rng = rand::thread_rng();
let port = rng.gen_range(4000..9000);
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), port))
};
let mut server_stream = {
let server_sock = TcpSocket::new_v4().unwrap();
#[cfg(target_family = "unix")]
{server_sock.set_reuseport(true).unwrap();}
server_sock.set_reuseaddr(true).unwrap();
server_sock.bind(loc_addr).unwrap();
match server_sock.connect(server_addr.parse().unwrap()).await {
Ok(sock) => { sock },
Err(e) => {
eprintln!("无法连接到服务器。{}", e);
return;
},
}
};
加入房间,将socket绑定到连接服务端的地址和端口然后connect客户端就可以了。
// 接收服务端发送过来的所有房间内的peer
let clients: Vec<ClientInfo> = serde_json::from_slice(&net::read(&mut server_stream).await.unwrap()).unwrap();
...
for ci in clients {
...
let mut stm = {
let sock = TcpSocket::new_v4().unwrap();
#[cfg(target_family = "unix")]
{sock.set_reuseport(true).unwrap();}
sock.set_reuseaddr(true).unwrap();
sock.bind(addr.clone()).unwrap();
if let Ok(stm) = sock.connect(ci.addr.clone()).await {
stm
} else {
warn!("连接{:?}失败", &ci);
return Err(ci);
}
};
...
tokio::spawn(Process::new(
&other, stm, msg_tx, cin_rx
).poll());
}
当有新的peer加入的时候服务端会将另一个peer的信息发送过来,这时候创建一个新的socket,设置端口复用,将socket绑定到连接服务端的地址和端口。最后connect新客户端就可以了。
let sock = TcpSocket::new_v4().unwrap();
#[cfg(target_family = "unix")]
{sock.set_reuseport(true).unwrap();}
sock.set_reuseaddr(true).unwrap();
if let Err(e) = sock.bind(addr.clone()) {
error!("Fail to bind {} {}", &addr, e);
continue;
};
let mut sock = {
match sock.connect(ci.addr.clone()).await {
Ok(s) => s,
Err(e) => {
warn!("Fail to connent {:?} : {}", theci, e);
continue;
}
}
};
服务端
TODO