40. 干货系列从零用Rust编写负载均衡及代理,websocket的实现
wmproxy
wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透,后续将实现websocket代理等,会将实现过程分享出来,感兴趣的可以一起造个轮子
项目地址
国内: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
简单介绍websocket
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。WebSocket 通信协议于 2011 年被 IETF 定为标准 RFC 6455,并由 RFC7936 补充规范。WebSocket API 也被 W3C 定为标准。
也就是在web环境中,websocket就是socket的一种标准形式的体现。类似的还要SSE基于HTTP中的text/event-stream
源码文件含义
协议层的编码解码主要在webparse/ws
- frame_header协议头的解码与编码
- dataframe 基础单位为帧,存在多帧组成一个数据包的情况
- message 协议的基本信息,包含
Text必须为UTF-8字符串文本,Binary二进制数据流,Close关闭信息,Ping,Pong用来做心跳包相关的信息。 - mask 是否为数据进行基本的加密,服务端要求客户端传来的数据必须加密
网络处理层的源码主要在wenmeng/ws
- codec/framed_read 每一帧的读,以帧为单位进行读取
- codec/framed_write 每一帧的写,以帧为单位进行写入
- state/state_handshake websocket连接内部的握手状态
- client_connection 客户端的状态连接
- server_connection 服务端的状态连接
- control 状态的控制,写入读取的pending等,核心处理源码
- handshake 定义
on_open回调后的WsHandshake类 - option 定义
on_open回调后返回的WsOption类,当下只包含定时器,即客户端多久时间唤醒一次interval - ws_trait websocket的核心回调
#[async_trait]
pub trait WsTrait: Send {
/// 通过请求连接构建出返回的握手连接信息
#[inline]
fn on_request(&mut self, req: &RecvRequest) -> ProtResult<RecvResponse> {
// warn!("Handler received request:\n{}", req);
WsHandshake::build_request(req)
}
/// 握手完成后之后的回调,服务端返回了Response之后就认为握手成功
fn on_open(&mut self, shake: WsHandshake) -> ProtResult<Option<WsOption>>;
/// 接受到远端的关闭消息
async fn on_close(&mut self, reason: &Option<CloseData>) {}
/// 服务内部出现了错误代码
async fn on_error(&mut self, err: ProtError) {}
/// 收到来在远端的ping消息, 默认返回pong消息
async fn on_ping(&mut self, val: Vec<u8>) -> ProtResult<OwnedMessage> {
return Ok(OwnedMessage::Pong(val));
}
/// 收到来在远端的pong消息, 默认不做任何处理, 可自定义处理如ttl等
async fn on_pong(&mut self, val: Vec<u8>) {}
/// 收到来在远端的message消息, 必须覆写该函数
async fn on_message(&mut self, msg: OwnedMessage) -> ProtResult<()>;
/// 定时器定时按间隔时间返回
async fn on_interval(&mut self, option: &mut Option<WsOption>) -> ProtResult<()> {
Ok(())
}
/// 将当前trait转化成Any,仅需当需要重新获取回调处理的时候进行处理
fn as_any(&self) -> Option<&dyn Any> {
None
}
/// 将当前trait转化成mut Any,仅需当需要重新获取回调处理的时候进行处理
fn as_any_mut(&mut self) -> Option<&mut dyn Any> {
None
}
}
服务端基础demo
建立一个本地监听8081的ws端口,完整源码ws_server
建立监听类:
struct Operate {
sender: Option<Sender<OwnedMessage>>,
}
#[async_trait]
impl WsTrait for Operate {
fn on_open(&mut self, shake: WsHandshake) -> ProtResult<Option<WsOption>> {
self.sender = Some(shake.sender);
Ok(Some(WsOption::new(Duration::from_secs(10))))
}
async fn on_message(&mut self, msg: OwnedMessage) -> ProtResult<()> {
println!("callback on message = {:?}", msg);
let _ = self
.sender
.as_mut()
.unwrap()
.send(OwnedMessage::Text("from server".to_string()))
.await;
let _ = self.sender.as_mut().unwrap().send(msg).await;
Ok(())
}
async fn on_interval(&mut self, _option: &mut Option<WsOption>) -> ProtResult<()> {
println!("on_interval!!!!!!!");
Ok(())
}
}
然后启动服务器监听:
async fn run_main() -> Result<(), Box<dyn Error>> {
let addr = "127.0.0.1:8081".to_string();
let server = TcpListener::bind(&addr).await?;
println!("Listening on: {}", addr);
loop {
let (stream, addr) = server.accept().await?;
tokio::spawn(async move {
let mut server = Server::new(stream, Some(addr));
let operate = Operate { sender: None };
// 设置服务回调
server.set_callback_ws(Box::new(operate));
let e = server.incoming().await;
println!("close server ==== addr = {:?} e = {:?}", addr, e);
});
}
}
此时即可实现websocket的监听及处理。
客户端demo
当下客户端demo需要能接受终端的输入,并向服务器发送数据,所以需要自己构建sender
建立客户端连接,在这里我们手动构建了一个sender/receiver对。
async fn run_main() -> ProtResult<()> {
// 自己手动构建数据对,并将receiver传给服务端
let (sender, receiver) = channel(10);
let sender_clone = sender.clone();
tokio::spawn(async move {
let url = "ws://127.0.0.1:8081";
let mut client = Client::builder()
.url(url)
.unwrap()
.connect()
.await
.unwrap();
client.set_callback_ws(Box::new(Operate { sender:Some(sender_clone), receiver: Some(receiver) }));
client.wait_ws_operate().await.unwrap();
});
loop {
let mut buffer = String::new();
let stdin = io::stdin(); // We get `Stdin` here.
stdin.read_line(&mut buffer)?;
sender.send(OwnedMessage::Text(buffer)).await?;
}
Ok(())
}
监听实现
struct Operate {
sender: Option<Sender<OwnedMessage>>,
receiver: Option<Receiver<OwnedMessage>>,
}
#[async_trait]
impl WsTrait for Operate {
fn on_open(&mut self, shake: WsHandshake) -> ProtResult<Option<WsOption>> {
// 将receiver传给控制中心, 以让其用该receiver做接收
let mut option = WsOption::new(Duration::from_secs(1000));
if self.receiver.is_some() {
option.set_receiver(self.receiver.take().unwrap());
}
if self.sender.is_none() {
self.sender = Some(shake.sender);
}
Ok(Some(option))
}
async fn on_message(&mut self, msg: OwnedMessage) -> ProtResult<()> {
println!("callback on message = {:?}", msg);
let _ = self
.sender
.as_mut()
.unwrap()
.send(OwnedMessage::Text("from client".to_string()))
.await;
let _ = self.sender.as_mut().unwrap().send(msg).await;
Ok(())
}
async fn on_interval(&mut self, _option: &mut Option<WsOption>) -> ProtResult<()> {
println!("on_interval!!!!!!!");
Ok(())
}
}
接口说明
Client和Server为了同时兼容HTTP服务,即握手用的为HTTP的前半段请求,选择了回调用Box<dyn Trait>的形式来做回调函数的处理。
pub struct Server<T>
where
T: AsyncRead + AsyncWrite + Unpin + Sized,
{
/// http的接口回调, 处理http服务器
callback_http: Option<Box<dyn HttpTrait>>,
/// websocket的接口回调, 处理websocket服务器
callback_ws: Option<Box<dyn WsTrait>>,
// ...
}
他们两个可能是同时存在,或者单个存在的,即当作服务的时候,可能仅对/ws进行websocket的升级,其它的仅仅是http服务,所以需要能单独又能聚合的处理数据。而单存的websocket仅需WsTrait回调。
即在pub async fn incoming(&mut self) -> ProtResult<()>处理服务的时候不在传入回调地址,改成预先设置。达到灵活处理的目的。且接口比较清晰。
小结
wenmeng库当前已支持HTTP1.1/HTTP2/WEBSOCKET,在浏览器的环境中websocket是必不可缺少的存在,当然有很多原生的服务中用的都是socket,下一章中,我们将实现websocket与tcp的互转,以便一些tcp的程序可以服务web的服务。
点击 [关注],[在看],[点赞] 是对作者最大的支持
40. 干货系列从零用Rust编写负载均衡及代理,websocket的实现的更多相关文章
- 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台服务器平均分担负载,不会因为某一台服务器负载高宕机而影响用户访问网站,负载均衡至少需要三台服务器, 既 ...
随机推荐
- 使用Triton部署chatglm2-6b模型
一.技术介绍 NVIDIA Triton Inference Server是一个针对CPU和GPU进行优化的云端和推理的解决方案. 支持的模型类型包括TensorRT.TensorFlow.PyTor ...
- 用go封装一下二级认证功能
用go封装一下二级认证 本篇为用go设计开发一个自己的轻量级登录库/框架吧 - 秋玻 - 博客园 (cnblogs.com)的二级认证业务篇,会讲讲二级认证业务的实现,给库/框架增加新的功能. 源码: ...
- Destoon模板存放及调用规则
一.模板存放及调用规则 模板存放于系统 template 目录,template 目录下的一个目录例如 template/default/ 即为一套模板模板文件以 .htm 为扩展名,可直接存放于模板 ...
- Java-网络编程(TCP-UDP)
Java-网络编程(TCP-UDP) 网络基础 网络编程最主要的工作就是在发送端把信息通过规定好的协议进行组装包,在接收端按照规定好的协议把包进行解析,从而提取出对应的信息,达到通信的目的.中间最主要 ...
- CF1343C
题目简化和分析: 给您一个序列,您要在其中选择若干个数使得: 相邻两数异号 长度最大,总和最大 我们可以牢牢抓住长度且总和最大,这一特性. 说明我们必须在每一个连续的同号的子串中被迫选择最大的,以满足 ...
- 关于Android Stuido2.3和Eclipse4.4
近3年没有做Android开发了,当时用是ECLISPE电脑配置2g,用的还可以. 现在又重新开始做安卓程序,发现大家都用AS了,作为技术人员,也就开始用了. (几年前AS已经发布,不过是0.x版本, ...
- 小景的Dba之路--如何导出0记录表以及数据泵的使用
小景最近在系统压测相关的工作,其中涉及了数据备份导出的操作.今天的问题是:exp命令不会导出0记录表,那么我们探讨下如何导出0记录表以及数据泵的使用. 首先,我们先刨析一下问题现象及原因: 在 Ora ...
- MAC安装pwntools记录
1.使用python3安装pwntools pip3 install pwntools 2.安装成功后测试 测试不成功提示安装 binutils pwnlib.exception.PwnlibExce ...
- 命令vue inspect > output.js报错:在此系统上禁止运行脚本
用的这个命令去看output.js文件,结果报错. 解决方案是去对应目录下删掉vue.ps1就OK了 .
- Python 作用域:局部作用域、全局作用域和使用 global 关键字
变量只在创建它的区域内可用.这被称为作用域. 局部作用域 在函数内部创建的变量属于该函数的局部作用域,并且只能在该函数内部使用. 示例:在函数内部创建的变量在该函数内部可用: def myfunc() ...