29. 干货系列从零用Rust编写正反向代理,异步回调(async trait)的使用
wmproxy
wmproxy
已用Rust
实现http/https
代理, socks5
代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透,后续将实现websocket
代理等,会将实现过程分享出来,感兴趣的可以一起造个轮子
项目地址
国内: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
关于 ++trait++
trait是Rust
中的概念,类似于其他语言中的接口(interface)
。
在Rust
中不存在继承的概念,所有关于结构体的拓展功能全部均由trait
来代替。比如std::io::Read
这是一个关于io的trait
,在TcpStream
中和在File
中均实现了该功能,这样子如果上层只关心读操作的,我们就可以将其转化成std::io::Read
的一个对象,比如io: std::io::Read
,后面我们也可以把他包裹成BufferReader
等实现功能的转化。
为什么不在Rust中使用继承
- 继承破坏了封装性,父类的改变会影响子类。如果父类的出现发生变化,所有继承自该父类的子类都需要相应地进行修改,这会增加代码的维护成本。
- Rust追求内存安全和无数据竞争,继承不利于编译器进行静态检查。
- 继承关系的耦合度高。子类和父类之间是紧密耦合的关系,这会影响代码的灵活性和可移植性。
- 继承往往被过度使用,导致子类与父类功能紧密耦合。
下面举下例子,设计关于车的通用基类,能跑能停等
public class BaseCar {
//... 省略其他属性和方法...
public void run() { //... }
public void stop() { //... }
}
一开始自行车都很完美,接下来设计摩托车,摩托车需要加油,那么基类被改成
public class BaseCar {
//... 省略其他属性和方法...
public void run() { //... }
public void stop() { //... }
public void refuel() { //... }
}
但是自行车又没有加油的需求
// 自行车
public class Bicycle extends BaseCar {
//... 省略其他属性和方法...
public void refuel() {
throw new UnSupportedMethodException("我不需要加油!");
}
}
如果接下来又有修理引擎的接口,那基类又得加repairEngine
的接口。自行车继承这个基类将会产生严重的负担,不继承又得重新写一些关于基础能力的函数,又会增加重复代码。
那么接下来是以trait
方案的实现
pub trait Base {
fn run(&self);
fn stop(&self);
}
pub trait Refuel {
fn refuel(&mut self);
}
pub trait RepairEngine {
fn repair_engine(&mut self);
}
那么自行车只需要实现Base
能力,然后摩托车在自行车的基础上实现Refuel
及RepairEngine
即可实现解耦。
异步的trait
在程序中均使用的是异步(async)
编程,那么我们可能需要将trait实现成:
pub trait Base {
async fn run(&self);
async fn stop(&self);
}
当我们如此写的时候编译器就会提示我们:
functions in traits cannot be declared `async`
`async` trait functions are not currently supported
consider using the `async-trait` crate: https://crates.io/crates/async-trait
see issue #91611 <https://github.com/rust-lang/rust/issues/91611> for more informationrustcClick for full compiler diagnostic
原来异步的trait实现还没有进入到stable
阶段,暂时只能有预览版即nightly
版本进行使用。
那么本文将探讨该功能在未stable
前如何实现异步的trait
。
假如返回一个异步的Future
trait Base {
type FetchData<'a>: std::future::Future<Output = String> + 'a where Self: 'a;
fn run<'a>(&'a self) -> Self::FetchData<'a>;
}
那么实现自行车的函数将为:
trait Base {
type FetchData<'a>: /* 将要何种类型呢?? */;
fn run<'a>(&'a self) -> Self::FetchData<'a>;
}
我们尝试过各种类型,编译器都无法通过编译,所以我们需要进行返回值的修改,我们将通过运行时类型擦除来实现。
首先,我们可以通过用 擦除 future 类型来避免编写 future 类型。以上面的例子为例,你可以这样写你的特征:dyn
trait Base {
fn run<'a>(&'a self) -> Pin<Box<dyn Future<Output = String> + Send + '_>>;
}
那么实现将为:
impl Base for Bicycle {
fn run<'a>(&'a self) -> std::pin::Pin<Box<dyn std::future::Future<Output = String> + Send + '_>> {
Box::pin(async {
"ok".to_string()
})
}
}
可以看出整个函数非常的冗余,相当的让人难受。
那么此时我们可以借助async-trait
的宏处理库,他将帮我们自动处理掉无用的数据,那么我们的代码将变成如下:
#[async_trait]
trait Base {
async fn run(&self) -> String;
}
#[async_trait]
impl Base for Bicycle {
async fn run(&self) -> String {
"ok".to_string()
}
}
当然现在此方法会造成额外的开销,像Box
,Send
等都会造成一定的性能损失,如果要零损失实现异步还可以尝试以下方案
手动实现Poll
需要零开销或在no_std上下文中工作的特征还有另一种选择:它们可以从 Future 特征中获取轮询的概念,并将其直接构建到它们的界面中。如果 future 已完成,并且 future 正在等待其他事件,则该方法将返回。Future::poll
,Poll::Ready(Output)
,Poll::Pending
pub trait Base {
type Item;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>
) -> Poll<Option<Self::Item>>;
}
当然控制Poll的方式相当的麻烦,只要在对性能要求极高的情况下在进行此操作。
预期的官方实现
在最新的Beta或者nightly版本中可以用#![feature(async_fn_in_trait)]
来启用该能力,那么我们就可以如下编程:
#![feature(async_fn_in_trait)]
trait Base {
async fn run(&self) -> String;
}
impl Base for Bicycle {
async fn run(&self) -> String {
"ok".to_string()
}
}
这样子就和普通的实现没有什么差别了。
实现该功能的难点
理论上来说,一个异步只有你在调用await的时候他才会真正的被调用,如果在此前有引用对话的存在,那么他的生命周期管理才是比较麻烦的存在。
小结
当前的Rust版本为1.74.0
,好消息的是当前async trait
已经Beta Channel
了,如果不出意外的话下一次发布版本的稳定版将会拥有该能力了。该功能的官方实现将会给异步编程的带来极大的方便。让async/await
能力越来越强。预期2023年末就可以直接使用了。下一章节我们将讲async trait
在项目中的应用。
点击 [关注],[在看],[点赞] 是对作者最大的支持
29. 干货系列从零用Rust编写正反向代理,异步回调(async trait)的使用的更多相关文章
- (转)Spring Boot干货系列:(七)默认日志logback配置解析
转:http://tengj.top/2017/04/05/springboot7/ 前言 今天来介绍下Spring Boot如何配置日志logback,我刚学习的时候,是带着下面几个问题来查资料的, ...
- (转)Spring Boot干货系列:(四)开发Web应用之Thymeleaf篇
转:http://tengj.top/2017/03/13/springboot4/ 前言 Web开发是我们平时开发中至关重要的,这里就来介绍一下Spring Boot对Web开发的支持. 正文 Sp ...
- 【转】Spring Boot干货系列:(一)优雅的入门篇
转自Spring Boot干货系列:(一)优雅的入门篇 前言 Spring一直是很火的一个开源框架,在过去的一段时间里,Spring Boot在社区中热度一直很高,所以决定花时间来了解和学习,为自己做 ...
- Spring Boot干货系列:(八)数据存储篇-SQL关系型数据库之JdbcTemplate的使用
Spring Boot干货系列:(八)数据存储篇-SQL关系型数据库之JdbcTemplate的使用 原创 2017-04-13 嘟嘟MD 嘟爷java超神学堂 前言 前面几章介绍了一些基础,但都是静 ...
- Spring Boot干货系列:(七)默认日志框架配置
Spring Boot干货系列:(七)默认日志框架配置 原创 2017-04-05 嘟嘟MD 嘟爷java超神学堂 前言 今天来介绍下Spring Boot如何配置日志logback,我刚学习的时候, ...
- Spring Boot干货系列:(五)开发Web应用JSP篇
Spring Boot干货系列:(五)开发Web应用JSP篇 原创 2017-04-05 嘟嘟MD 嘟爷java超神学堂 前言 上一篇介绍了Spring Boot中使用Thymeleaf模板引擎,今天 ...
- Spring Boot干货系列:(四)Thymeleaf篇
Spring Boot干货系列:(四)Thymeleaf篇 原创 2017-04-05 嘟嘟MD 嘟爷java超神学堂 前言 Web开发是我们平时开发中至关重要的,这里就来介绍一下Spring Boo ...
- Spring Boot干货系列:(一)优雅的入门篇
Spring Boot干货系列:(一)优雅的入门篇 2017-02-26 嘟嘟MD 嘟爷java超神学堂 前言 Spring一直是很火的一个开源框架,在过去的一段时间里,Spring Boot在社 ...
- Java多线程干货系列—(四)volatile关键字
原文地址:http://tengj.top/2016/05/06/threadvolatile4/ <h1 id="前言"><a href="#前言&q ...
- Spring Boot干货系列:(十二)Spring Boot使用单元测试(转)
前言这次来介绍下Spring Boot中对单元测试的整合使用,本篇会通过以下4点来介绍,基本满足日常需求 Service层单元测试 Controller层单元测试 新断言assertThat使用 单元 ...
随机推荐
- QPushButton按钮的使用
1 import sys 2 from PyQt5.QtCore import * 3 from PyQt5.QtGui import * 4 from PyQt5.QtWidgets import ...
- VBA与VB的区别
从语言结构上讲,VBA是VB的一个子集,它们的语法结构是一样的.两者的开发环境也几乎相同.但是,VB是独立的开发工具,它不需要依附于任何其他应用程序,它有自己完全独立的工作环境和编译.链接系统.VBA ...
- 王道oj/problem23
网址:oj.lgwenda.problem/23 代码: #define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include<stri ...
- 清理MySQL的binlog历史文件
前言 系统版本:centos 7 MySQL版本:5.7 mysql的binlog文件最好不要手动删,避免删错导致bin log同步异常. 步骤 查看当前的binlog文件 show binary l ...
- Spring Cloud Gateway Actuator API SpEL表达式注入命令执行漏洞(CVE-2022-22947)
描述: Spring Cloud Gateway 是基于 Spring Framework 和 Spring Boot 构建的 API 网关,它旨在为微服务架构提供一种简单.有效.统一的 API 路由 ...
- [数据分析与可视化] Python绘制数据地图5-MovingPandas绘图实例
MovingPandas是一个基于Python和GeoPandas的开源地理时空数据处理库,用于处理移动物体的轨迹数据.关于MovingPandas的使用见文章:MovingPandas入门指北,本文 ...
- python独立脚本应用Django项目的环境
一.需求说明 一直用 Django 在开发一个网站项目,其中的注册用户和登录,都是使用Django自带的认证系统.主要是对密码的加密,在注册或者登录的时候,前端传递多来的密码,我会使用Django的s ...
- WPF学习:Slider — 冒泡显示值
想做一个下图所示的Slider,以冒泡的方式显示其Value值,该怎么做呢? 功能要求,当鼠标放在滑块上的时候,冒"泡"显示值:当滑块移动的时候,"泡"跟随移动 ...
- Web应用防火墙--规则防护
一.什么是Web应用防火墙? Web应用防火墙对网站.APP的业务流量安全及合规性保护,对业务流量的识别恶意特征提取.分析识别出恶意流量并进行处理, 将正常安全的流量回源到业务服务器, 保护网站核心业 ...
- 腾讯事务处理技术验证系统3TS-Coo模块的项目环境安装使用说明
本篇文章将详细说明3TS-Coo模板的安装和使用,帮助您快速上手项目 第一部分是简单的基础Docker相关概念,精炼的几句小白话快速理解即可: 第二部分是快速安装项目环境的安装文档,简单几行命令搞定, ...