41. 干货系列从零用Rust编写负载均衡及代理,websocket与tcp的映射,WS与TCP互转
wmproxy
wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透,后续将实现websocket代理等,会将实现过程分享出来,感兴趣的可以一起造个轮子
项目地址
国内: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
项目设计目标
针对有一些应用场景需要将TCP转成websocket的,就比如旧的客户端或者旧的服务端比较不合适进行改造,但是又需要借助阿里的全站加速DCDN等这类服务或者其它可能需要特定浏览器协议的情况下,需要进行协议的转化而服务。
Tcp转Websocket
流程图
以下展示Tcp转Websocket的流程图,就纯粹的Tcp客户端在不经过任何源码修改的情况下成功连接websocket服务端
A[tcp客户端] -->|连接服务| B[服务节点]
B -->|服务转化| C[websocket客户端]
C -->|连接服务| D[websocket服务端]
比较适合原生客户端,又不想引入第三方库,又能在需要的时候直接使用websocket来做配合。
源码实现
实现源码在stream_to_ws
/// 将tcp的流量转化成websocket的流量
pub struct StreamToWs<T: AsyncRead + AsyncWrite + Unpin> {
url: Url,
io: T,
}
- 需要传入的参数为原生的tcp,此处tcp是具备异步读异步写功能的虚拟tcp
- 传入连接websocket的url地址,可以连接到websocket的服务端地址
定义回调类:
struct Operate {
/// 将tcp来的数据流转发到websocket
stream_sender: Sender<Vec<u8>>,
/// 从websocket那接收信息
receiver: Option<Receiver<OwnedMessage>>,
}
- stream_sender将数据进行发送到websocket中
- receiver从websocket中获取信息流
核心转发逻辑:
pub async fn copy_bidirectional(self) -> ProtResult<()> {
let (ws_sender, ws_receiver) = channel::<OwnedMessage>(10);
let (stream_sender, stream_receiver) = channel::<Vec<u8>>(10);
let url = self.url;
tokio::spawn(async move {
if let Ok(mut client) = Client::builder().url(url).unwrap().connect().await {
client.set_callback_ws(Box::new(Operate {
stream_sender,
receiver: Some(ws_receiver),
}));
let _e = client.wait_ws_operate().await;
}
});
Self::bind(self.io, ws_sender, stream_receiver).await?;
Ok(())
}
创建两对发送接收对分别为OwnedMessage及Vec<u8>来进行双向绑定,并在协程中发起对websocket的连接请求。更多的逻辑请查看源码。
测试demo
示例文件ws_stw,当下监听8082的流量并将其转发到8081的websocket服务上,测试借助websocat做测试服务端
cargo run --example ws_stw启动转发监听8082websocat -s 8081监听8081telnet 127.0.0.1 8082手动建立8082的端口

成功测试转发
Websocket转Tcp
流程图
以下展示Websocket转Tcp的流程图,通常由浏览器环境中发起(因为浏览器的标准全双工就是websocket)。然后服务器这边由TCP的方案
A[websocket客户端] -->|连接服务| B[服务节点]
B -->|服务转化| C[tcp客户端]
C -->|连接服务| D[tcp服务端]
比较适合原生服务端,又不想引入第三方库,又能兼容TCP及websocket协议,适合在这个做个中间层。
源码实现
实现源码在ws_to_stream
/// 将websocket的流量转化成的tcp流量
pub struct WsToStream<T: AsyncRead + AsyncWrite + Unpin + Send + 'static, A: ToSocketAddrs> {
addr: A,
io: T,
}
- 需要传入的参数为原生的tcp,此处tcp是具备异步读异步写功能的虚拟tcp,其中
'static表示io为一个类,而不是引用 - 传入连接tcp的SocketAddr地址,可以连接到Tcp的服务端地址
定义回调类:
struct Operate {
/// 将tcp来的数据流转发到websocket
stream_sender: Sender<Vec<u8>>,
/// 从websocket那接收信息
receiver: Option<Receiver<OwnedMessage>>,
}
- stream_sender将数据进行发送到websocket中
- receiver从websocket中获取信息流
核心转发逻辑:
pub async fn copy_bidirectional(self) -> ProtResult<()> {
let (ws_sender, ws_receiver) = channel(10);
let (stream_sender, stream_receiver) = channel::<Vec<u8>>(10);
let stream = TcpStream::connect(self.addr).await?;
let io = self.io;
tokio::spawn(async move {
let mut server = Server::new(io, None);
server.set_callback_ws(Box::new(Operate {
stream_sender,
receiver: Some(ws_receiver),
}));
let e = server.incoming().await;
println!("close server ==== addr = {:?} e = {:?}", 0, e);
});
Self::bind(stream, ws_sender, stream_receiver).await?;
Ok(())
}
与tcp转websocket类似,但是此时是将io流量转成Server的处理函数。
测试demo
示例文件ws_wts,当下监听8082的流量并将其转发到8081的websocket服务上,测试借助websocat做测试服务端
新建测试TCP的监听,原样转发的测试代码:
#[tokio::main]
async fn main() -> std::io::Result<()> {
use tokio::{net::TcpListener, io::{AsyncReadExt, AsyncWriteExt}};
let tcp_listener = TcpListener::bind(format!("127.0.0.1:{}", 8082)).await?;
loop {
let mut stream = tcp_listener.accept().await?;
tokio::spawn(async move {
let mut buf = vec![0;20480];
loop {
if let Ok(size) = stream.0.read(&mut buf).await {
println!("receiver = {:?} size = {:?}", &buf[..size], size);
let _ = stream.0.write_all(b"from tcp:").await;
let _ = stream.0.write_all(&buf[..size]).await;
} else {
break;
}
}
});
}
}
cargo run --example tcp监听8082的端口,收到数据原样转发cargo run --example ws_wts启动转发监听8081websocat ws://127.0.0.1:8081用websocket的方式连接到8081

成功测试转发
组合方案
当我们现存的网络方案为Tcp到Tcp或者为Websocket到Websocket而我们在中间的传输过程中如想利用DCDN做源地址保护,而他只支持Websocket,此时我们就可以利用数据的转化,将我们的数据包通过DCDN做转发:
A[TCP客户端] -->|连接服务| B[服务节点]
B -->|转化成websocket通过加速| C[DCDN全站加速]
C -->|连接服务| E[服务节点]
E -->|转化成Tcp并串连到服务端| F[TCP服务端]
这样子我们就可以利用基础网络中的CDN或者DCDN等服务,又不用对旧的数据进行修改或者无法修改的程序就比如远程服务通过CDN进行加速等。
小结
协议的自由转化可以帮助我们创建更合适的网络环境,可以让运维更自由的构建系统。利用转化可以用好全站加速DCDN这类的功能,可以更好的保护源站,防止被DDOS攻击。
点击 [关注],[在看],[点赞] 是对作者最大的支持
41. 干货系列从零用Rust编写负载均衡及代理,websocket与tcp的映射,WS与TCP互转的更多相关文章
- Docker系列-(3) Docker-compose使用与负载均衡
上一篇文章介绍了docker镜像的制作与发布,本文主要介绍实际docker工程部署中经常用到的docker-compose工具,以及docker的网络配置和负载均衡. Docker-compose介绍 ...
- Nginx服务器部署 负载均衡 反向代理
Nginx服务器部署负载均衡反向代理 LVS Nginx HAProxy的优缺点 三种负载均衡器的优缺点说明如下: LVS的优点: 1.抗负载能力强.工作在第4层仅作分发之用,没有流量的产生,这个特点 ...
- [架构]辨析: 高可用 | 集群 | 主从 | 负载均衡 | 反向代理 | 中间件 | 微服务 | 容器 | 云原生 | DevOps | ...
词汇集 灾备 冷备份 双机热备份 异地容灾备份 云备份 灾难演练 磁盘阵列(RAID) 故障切换 心跳监测 高可用 集群 主从复制(Master-Slave) 多集群横向扩容(master-clust ...
- 死磕nginx系列--使用upsync模块实现负载均衡
问题描述 nginx reload是有一定损耗的,如果你使用的是长连接的话,那么当reload nginx时长连接所有的worker进程会进行优雅退出,并当该worker进程上的所有连接都释放时,进程 ...
- hbase源码系列(一)Balancer 负载均衡
看源码很久了,终于开始动手写博客了,为什么是先写负载均衡呢,因为一个室友入职新公司了,然后他们遇到这方面的问题,某些机器的硬盘使用明显比别的机器要多,每次用hadoop做完负载均衡,很快又变回来了. ...
- 架构之Nginx(负载均衡/反向代理)
Nginx ("engine x") 是一个高性能的 HTTP 和 反向代理 服务器 ,也是一个 IMAP/POP3/SMTP 代理 服务器 . Nginx 是由 Igor Sys ...
- nginx负载均衡(反向代理)
6,安装nginx 6.1 依赖库安装 要安装在root根目录里,不要装在虚拟环境里面 yum install gcc-c++ pcre pcre-devel zlib zlib-devel ope ...
- nginx域名转发 负载均衡 反向代理
公司有三台机器在机房,因为IP不够用,肯定要分出来,所以要建立单IP 多域名的反向代理, 就是当请求www.abc.com 跳转到本机, 请求www.bbc.com 跳转到192.168.0.35 机 ...
- Nginx HTTP负载均衡/反向代理的相关参数测试
原文地址:http://www.cnblogs.com/xiaochaohuashengmi/archive/2011/03/15/1984976.html 测试目的 (1)弄清楚HTTP Upstr ...
- nginx 负载均衡 反向代理
nginx 通过方向代理实现负载均衡,负载均衡是大流量网站要做的措施,单从字面上的意思来理解为N台服务器平均分担负载,不会因为某一台服务器负载高宕机而影响用户访问网站,负载均衡至少需要三台服务器, 既 ...
随机推荐
- NOIP2023 游寄
NOIP2023 游寄 Day -2 遗憾生病离场回家. Day -1 速度赶往杭州,稍作复习. Day 1 正式开寄. 开题后,发现把所有题看了一遍,一如既往的又臭又长. T3 和 T4 感觉很不可 ...
- 旺店通·企业奇门与用友BIP旺店通销售出库单对接销售订单
源系统平台:旺店通·企业奇门 源系统接口: 查询销售出库单wdt.stockout.order.query.trade 目标系统平台: 用友BIP目标系统接口: 销售订单单个保存/yonbip/sd/ ...
- java String字符串总结
这里我们将总结字符串相关的知识,除了总结String的API用法,同时我们还会总结一些相关的知识点,包括字符串常量池.StringBuffer.StringBuilder,以及equals和==的用法 ...
- java监听全局组合键
1. jintellitype pom <!-- 不能注册多个组合键比如alt+abc --> <!-- https://mvnrepository.com/artifact/com ...
- springboot下添加全局异常处理和自定义异常处理
前言 在spring项目中,优雅处理异常,好处是可以将系统产生的全部异常统一捕获处理,自定义的异常也由全局异常来捕获,如果涉及到validator参数校验器使用全局异常捕获也是较为方便. 相关代码: ...
- 2023年的PHP项目部署笔记。什么?还有人用PHP?
前言 这是我第一次用 PHP 的包管理工具 composer 一开始用 docker 进行部署,但一直出问题,最后还是选择直接在服务器上安装 php-fpm 搭配 nginx 的方案了. PS:doc ...
- 使用Visual Studio 2022 创建lib和dll并使用
对于一个经常写javaWeb的人来说,使用Visual Studio似乎没什么必要,但是对于使用ffi的人来说,使用c或c++编译器,似乎是必不可少的,下面我将讲述如何用Visual Studio 2 ...
- MySQL按照日期查询
MySQL按时间查询 今天 select * from 表名 where TO_DAYS(时间字段名) = TO_DAYS(now()); 昨天 SELECT * FROM 表名 WHERE TO_D ...
- SLR(1)分析法
由于LR(0)的能力实在是太弱了.例如: I = { X=>α·bβ, A=>α·, B=>α· } 这时候就存在两个冲突. 1.移进和规约的冲突: 2.规约和规约的冲突. SLR( ...
- C++ Qt开发:QItemDelegate 自定义代理组件
Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍QStyled ...