wmproxy
是由Rust
编写,已实现http/https
代理,socks5
代理, 反向代理,静态文件服务器,内网穿透,配置热更新等, 后续将实现websocket
代理等,同时会将实现过程分享出来, 感兴趣的可以一起造个轮子法
gite: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
在tcp的流传输过程中,可以看做是一堆的字节的集合体,是一种“流”式协议,就像河里的水,中间没有边界。或者好比不懂汉语的来看古文,因为古文里没有任何的句读,不知何时另起一行。那我们如何正确的做到拆包解包,保证数据格式的正确呢?
以下是客户端发送两个30字节的包(P1及P2),服务端读取数据可能读出来的可能
gantt title 粘包的可能,例每个包30字节 %% This is a comment dateFormat X axisFormat %s section 示例1 P2 :a1, 1, 30 P1 :after a1, 60 section 示例2 P2,P1 :1,60 section 示例3 P2部分 :a3, 1, 20 P2部分P1全部 :after a3, 60 section 示例4 P2全部P1部分 :a4, 1, 40 P1部分 :after a4, 60
若没有事先约定好格式,在服务端部分无法正确的解析出P1包和P2包,也就意味着无法理解客户端发的内容。若此时我们约定每个包的大小固定为30字节,那么2,3,4三种可能不管收到多少,都必须等待30字节填充完毕后解析出P1,剩余的数据待待60字节接收完毕后解析P2包
对于粘包和拆包问题,常见的解决方案有四种:
选择了分为头部和消息体方案,头部分为8个字节,然后前3个字节表示包体的长度,单包支持长度为8-167777215也就是16m的大小,足够应对大多数情况。
因为每个链接的处理函数均在不同的协程里,所以这里用了Sender/Receiver来同步数据。
flowchart TD
A[中心客户端/CenterClient]<-->|tls加密连接或普通连接|B[中心服务端/CenterServer]
C[客户端链接]<-->|Sender/Receiver|A
B<-->|Sender/Receiver|D[服务端链接]
协议相关的类均在
prot
目录下面,统一对外的为枚举ProtFrame
,类的定义如下
rustpub enum ProtFrame {
/// 收到新的Socket连接
Create(ProtCreate),
/// 收到旧的Socket连接关闭
Close(ProtClose),
/// 收到Socket的相关数据
Data(ProtData),
}
主要涉及类的编码及解析在方法encode
,parse
,定义如下
rust/// 把字节流转化成数据对象
pub fn parse<T: Buf>(
header: ProtFrameHeader,
buf: T,
) -> ProxyResult<ProtFrame> {
}
/// 把数据对象转化成字节流
pub fn encode<B: Buf + BufMut>(
self,
buf: &mut B,
) -> ProxyResult<usize> {
}
任何消息优先获取包头信息,从而才能进行相应的类型解析,类为
ProtFrameHeader
,定义如下,总共8个字节
rustpub struct ProtFrameHeader {
/// 包体的长度, 3个字节, 最大为16m
pub length: u32,
/// 包体的类型, 如Create, Data等
kind: ProtKind,
/// 包体的标识, 如是否为响应包等
flag: ProtFlag,
/// 3个字节, socket在内存中相应的句柄, 客户端发起为单数, 服务端发起为双数
sock_map: u32,
}
暂时目前定义三种类型,
Create
,Close
,Data
,
ProtCreate
rust/// 新的Socket连接请求,
/// 接收方创建一个虚拟链接来对应该Socket的读取写入
#[derive(Debug)]
pub struct ProtCreate {
sock_map: u32,
mode: u8,
domain: Option<String>,
}
ProtClose
rust/// 旧的Socket连接关闭, 接收到则关闭掉当前的连接
#[derive(Debug)]
pub struct ProtClose {
sock_map: u32,
}
ProtData
rust/// Socket的数据消息包
#[derive(Debug)]
pub struct ProtData {
sock_map: u32,
data: Binary,
}
我是一段数据,我要去找服务器获得详细的数据
首先我得和服务器先能沟通上,建立一条可以通讯的线
flowchart TD
A[我]-->|请求连接建立|B[客户端代理]
B-->|把链接交由|C[中心客户端]
C-->|生成sock_map如1,并发送ProtCreate|D[中心服务端]
D-->|根据ProtCreate创建与sock_map对应的唯一id|E[虚拟TCP连接]
E-->|根据相应信息连接到服务端|F[服务端]
此时我已经和服务端构建起了一条通讯渠道,接下来我要和他发送数据了
flowchart TD
A[我]-->|发送字节数据|B[客户端代理]
B-->|读出数据交由|C[中心客户端]
C<-->|加工成ProtData发送|D[中心服务端]
D-->|根据ProtData的sock_map发送给对应|E[虚拟TCP连接]
E-->|解析成数据流写入|F[服务端]
F-->|把数据流返回|E
E-->|读出数据交由|D
C-->|根据ProtData的sock_map发送给对应|B
B-->|解析成数据流写入|A
至此一条我与服务端已经可以说悄悄话啦。
内网穿秀本质上从中心服务端反向交由中心客户端构建起一条通讯渠道,如今数据协议已经建立,可由服务端推送数据到客户端进行处理,后续实现请看下篇
本文作者:问蒙服务框架
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!