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台服务器平均分担负载,不会因为某一台服务器负载高宕机而影响用户访问网站,负载均衡至少需要三台服务器, 既 ...
随机推荐
- AtCoder Beginner Contest 327 (ABC327)
A. ab 直接根据题意模拟即可. Code B. A^A 直接枚举 \(i= 1, 2,\dots, 15\),每次看看 \(i ^ i\) 是否等于 \(A\) 即可. Code C. Numbe ...
- linux 使用crontab 创建定时任务
转载请注明出处: 在服务器中需要创建一个定时任务,每天执行去清理很早之前备份的文件,所以想到在linux上创建一个shell脚本,通过linux的 crontab 命令定时去执行该shell脚本,从而 ...
- AtCoder Beginner Contest 328 (ABC328)
A. Not Too Hard 模拟. Code B. 11/11 模拟. Code C. Consecutive Description 给你一个字符串 \(S\),有 \(Q\) 次询问,每次输入 ...
- [ABC246D] 2-variable Function
Problem Statement Given an integer $N$, find the smallest integer $X$ that satisfies all of the cond ...
- 第一章 JavaEE应用和开发环境
1.1 java EE应用概述 1.java EE的分层模型 数据库--[提供持久化服务]-->Domain Object层 --[封装]--〉DAO层--[提供数据访问服务]-->业务逻 ...
- 搭建前端项目时出现了.../dist/index.mjs:128 if (!require.cache) { ^ ReferenceError: require is not defined...
具体报错如下: 修改node_modules/vite-plugin-mock/dist/index.mjs 加入如下内容 // 解决报错问题 import { createRequire } fro ...
- 华企盾DSC邮件发送成功,但是不解密也没有任何提示(未添加白名单)
用Debugview监控整个过程,若日志中有信任邮箱未添加说明,白名单邮箱未添加或者添加错了(检查空格之类的或重新添加)
- 漆包线工厂生产管理MES系统解决方案
漆包线行业老板痛点: 1.漆包线比较传统的行业,一般都是靠人工去管理,老板想及时知道工厂的生产,销售.出入库.库存情况: 2.型号多称重打印易错,没有系统前 :称重打印,出入库,财务脱节,库存和 ...
- NC65单据模板公式使用
单据模板公式使用 (一) 公式使用场景 用户使用产品时,往往对单据上的字段取值有各种不同的需求.为此单据模板提供 了模板公式功能,可以让实施顾问或者用户通过配置各种公式,并且不用修改代码,从 而满足用 ...
- CentOS7 安装MySQL 8.0.28+
MySQL的三大版本 a)MySQL Enterprise Edition:企业版本(付费)b)MySQL Cluster CGE:高级集群版(收费)c)MySQL Community Server: ...