wmproxy

wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,内网穿透,后续将实现websocket代理等,会将实现过程分享出来,感兴趣的可以一起造个轮子

项目地址

国内: https://gitee.com/tickbh/wmproxy

github: https://github.com/tickbh/wmproxy

有请主角上场

Socket是集万千宠爱为一身的王子,在操作系统的王国里,他负责对外的所有通讯,所以要想沟通邻国的公主必须经过他,所以大家对他都是万般友好。

这天一个Rust城市里的大臣tokio对他发起了邀请,邀请他来参观严谨的逻辑庄园。

tokio庄园

庄园中的各成员对即将到来的王子议论纷纷。

  大管家mio说:“大家都想想等下怎么向socket王子介绍自己,好让他配合大家的工作。”

  大管家miotokio的基石,一切和王国打交道的都交由他去打理,他是保证庄园高效运转的关键,此刻他准备好了欢迎宴会。

  在宴会上,socket听说tokio庄园是这座城市异步运行的重要基石,就很好奇的让大伙介绍介绍下怎么工作的。

  庄园主tokio就说:“我是依靠着大管家mio帮我负责处理底层的事,Waker来提醒我有新的事情,PollEvented来帮我管理事件的。下面先让mio来介绍下。”

  管家mio说:“我负责收集庄园中的所有信息,他们告诉我他们要关心的什么比较,比如您的到来(可读),或者您有什么话想说(可写),我会负责和王国的底层进行沟通,我在这个国家用的是epoll,据说在遥远的另一个国家用的是iocp,如果有相应的需求,我将会通知Waker,由他去提醒庄主来及时的处理,这场宴会也是提前得到通知而进行准备的。”

  通知Waker说:“我所做的事情就是微不足道,我的对接对象是PollEvented,当他关心读事件,我会向mio去发起poll_read请求,如果此时mio那边已经知道有新的消息了,那我就直接把他们读出来交给民众Poll::Ready,如果此时还没有新消息,那我会告诉管家,有新消息的时候通知我Poll::Pending,此时我就在这里等待,直到有新的消息到达我就通知给民众。当他关心写事件,我会向mio请求poll_write请求,后续的和收消息的一致。现在给你们展示下包装了一层我的Context和我能换醒的虚表。”

/// 这个在代码里就是经常看到,它就是我的一层浅封装啦。
pub struct Context<'a> {
waker: &'a Waker,
_marker: PhantomData<fn(&'a ()) -> &'a ()>,
}
/// 我通过他来控制回调,保证唤醒的时候能正确的通知
pub struct RawWakerVTable {
clone: unsafe fn(*const ()) -> RawWaker,
wake: unsafe fn(*const ()),
wake_by_ref: unsafe fn(*const ()),
drop: unsafe fn(*const ()),
}

  事件PollEvented说:“庄主要处理的事情太多了,而有些事情又需要等待一层层的反馈,他没法把精力放在一件事情上一直等待,所以就有了我出马,庄主告诉我他关心什么事,我就把它记下来,这样子庄主就可以去处理其它的事,等事件到来的时候我就告诉庄主,这样子庄主就可以高效的处理所有的事件。”

王子觉得他们说了一堆有点啰嗦

  “带我看看你们实际的工坊,我要实地考查下。”王子说。

庄主就带着王子来到了,受理工坊,受理工坊正在处理建立受理点:

TcpListener::bind(addr).await

受理点的内容就是PollEvent

pub struct TcpListener {
io: PollEvented<mio::net::TcpListener>,
}

当他接受新的受理者的时候:

pub async fn accept(&self) -> io::Result<(TcpStream, SocketAddr)> {
let (mio, addr) = self
.io
.registration()
.async_io(Interest::READABLE, || self.io.accept())
.await?; let stream = TcpStream::new(mio)?;
Ok((stream, addr))
}

他向PollEvent注册了可读事情有的时候通知他,此时PollEvent就建立了一个Waker对象,当有符合条件的时候就来告诉他:

/// 建立一个可读的Future对象
fn readiness_fut(&self, interest: Interest) -> Readiness<'_> {
Readiness {
scheduled_io: self,
state: State::Init,
waiter: UnsafeCell::new(Waiter {
pointers: linked_list::Pointers::new(),
waker: None,
is_ready: false,
interest,
_p: PhantomPinned,
}),
}
}

底层有mio和系统交互,一旦有数据就通知Waker,他建在runtime/io/driver.rs上面

fn turn(&mut self, handle: &Handle, max_wait: Option<Duration>) {
let events = &mut self.events;
// 高效的监听端口
match self.poll.poll(events, max_wait) {
Ok(_) => {}
} for event in events.iter() {
let token = event.token(); if token == TOKEN_WAKEUP {
} else if token == TOKEN_SIGNAL {
} else {
let ready = Ready::from_mio(event);
let ptr: *const ScheduledIo = token.0 as *const _; let io: &ScheduledIo = unsafe { &*ptr };
io.set_readiness(Tick::Set(self.tick), |curr| curr | ready);
// 有相应的事件,进行唤醒然后通知上层处理相应的事件
io.wake(ready); }
} }

然后看到Waker工坊上处理:

pub(crate) fn wake_all(&mut self) {
assert!(self.curr <= NUM_WAKERS);
while self.curr > 0 {
self.curr -= 1;
let waker = unsafe { ptr::read(self.inner[self.curr].as_mut_ptr()) };
waker.wake();
}
}
pub fn wake(self) {
// 存在的回调函数及对应的数据,好进行调用
let wake = self.waker.vtable.wake;
let data = self.waker.data; crate::mem::forget(self); // 用回调函数的方式处理刚等待执行的线程
unsafe { (wake)(data) };
}

最后又回到了受理工坊,我们知道了一个新的来源TcpStream的到来,期间在等待的时候,我们可以去处理其它的事情,不至于空有许多人力物力,却在等我的宝的事情到来没法快速处理事情。

  王子说:“在只有一个受理的时候你们这么高效,如果有同时多个受理,又需要在处理完访问相同的数据,你们又能处理吗?”

  庄主说:“那么接下来就让我带你参观下Poll工坊,他用同步的方式可以同时处理多个链接。”

参观完异步工坊,庄主又带着王子来到了

只见Poll工坊大屏幕上就出现了一个例子:

#[tokio::main]
async fn main() -> std::io::Result<()> {
use std::{future::poll_fn, task::Poll, pin::Pin};
use tokio::net::TcpListener;
let mut listeners = vec![];
// 同时监听若干个端口
for i in 1024..1099 {
listeners.push(TcpListener::bind(format!("127.0.0.1:{}", i)).await?);
}
loop {
let mut pin_listener = Pin::new(&mut listeners);
// 同时监听若干个端口,只要有任一个返回则返回数据
let fun = poll_fn(|cx| {
for l in &*pin_listener.as_mut() {
match l.poll_accept(cx) {
v @ Poll::Ready(_) => return v,
Poll::Pending => {},
}
}
Poll::Pending
});
let (conn, addr) = fun.await?;
println!("receiver conn:{:?} addr:{:?}", conn, addr);
}
}

他可快速的在函数中同时等待多个端口数据,这种同步的逻辑可以在复杂结构时很方便的书写代码的逻辑。

王子看完后:“现在你的处理能力已经高效又灵活,真正的可甜可咸了,把我的能力发挥完全又简化了操作。”

点击 [关注][在看][点赞] 是对作者最大的支持

21. 从零用Rust编写正反向代理,tokio竟然这样对待socket!的更多相关文章

  1. 正反向代理、负载均衡、Nginx配置实现

    一.正反向代理 1.前提 我们曾经使用翻墙软件,访问google:使用了代理软件时,需要在浏览器选项中配置代理的地址,我们仅仅有代理这个概念,并不清楚代理还有正向和反向之分. 2.正向代理(代替客户端 ...

  2. bloom-server 基于 rust 编写的 rest api cache 中间件

    bloom-server 基于 rust 编写的 rest api cache 中间件,他位于lb 与api worker 之间,使用redis 作为缓存内容存储, 我们需要做的就是配置proxy,同 ...

  3. Nginx正反向代理、负载均衡等功能实现配置

    版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[+]   系统环境: VirtualBox Manager Centos6.4 nginx1.10.0 IP对应的机器名: IP ...

  4. 使用Squid部署代理缓存服务(标准正向、透明正反向代理)

    正向代理让用户可以通过Squid服务程序获取网站页面等数据,具体工作形式又分为标准代理模式与透明代理模式.标准正向代理模式: 将网站的数据缓存在服务器本地,提高数据资源被再次访问时的效率,但用户必需在 ...

  5. ip代理池的爬虫编写、验证和维护

    打算法比赛有点累,比赛之余写点小项目来提升一下工程能力.顺便陶冶一下情操 本来是想买一个服务器写个博客或者是弄个什么FQ的东西 最后刷知乎看到有一个很有意思的项目,就是维护一个「高可用低延迟的高匿IP ...

  6. 用欧拉计划学Rust语言(第17~21题)

    最近想学习Libra数字货币的MOVE语言,发现它是用Rust编写的,所以先补一下Rust的基础知识.学习了一段时间,发现Rust的学习曲线非常陡峭,不过仍有快速入门的办法. 学习任何一项技能最怕没有 ...

  7. 在 Pisa-Proxy 中,如何利用 Rust 实现 MySQL 代理

    一.前言 背景 在 Database Mesh 中,Pisanix 是一套以数据库为中心的治理框架,为用户提供了诸多治理能力,例如:数据库流量治理,SQL 防火墙,负载均衡和审计等.在 Pisanix ...

  8. net-snmp子代理(SubAgent)编写详述

    net-snmp子代理(SubAgent)编写 net-snmp子代理(SubAgent)编写 Netsnmp_Node_Handler MIB/OID定义 1.头文件test.h的编写 2.test ...

  9. Rust Aya 编写 eBPF 程序

    本文地址:https://www.ebpf.top/post/ebpf_rust_aya 1. 前言 Linux 内核 6.1 版本中有一个非常引人注意的变化:引入了对 Rust 编程语言的支持.Ru ...

  10. Java动态代理实现及实际应用

    一.代理的概念 动态代理技术是整个java技术中最重要的一个技术,它是学习java框架的基础,不会动态代理技术,那么在学习Spring这些框架时是只知应用不懂实现. 动态代理技术就是用来产生一个对象的 ...

随机推荐

  1. python:导入库、模块失败

    一般发生在程序开始部分: from pymodbus.client.sync import ModbusSerialClient from pymodbus.payload import Binary ...

  2. DDD架构为什么应该首选六边形架构?

    一.传统分层架构 分层架构的一个重要原则是:每层只能与位于其下方的层发生耦合. 分层架构分两种:一种是严格分层架构,规定某层只能与直接位于其下方的层发生耦合:另一种是松散分层架构,允许任意上方层与任意 ...

  3. altas2.1.0编译、安装、集成CDH6.3.2

    目录 altas2.1.0编译.安装.集成CDH6.3.2 一: Atlas源码下载 二: Atlas源码编译 1.修改altas项目主pom文件,即需要编译的CDH6.3.2对应版本信息 2.Atl ...

  4. Unity UGUI的Outline(描边)组件的介绍及使用

    Unity UGUI的Outline(描边)组件的介绍及使用 1. 什么是Outline(描边)组件? Outline(描边)组件是Unity UGUI中的一种特效组件,用于给UI元素添加描边效果.通 ...

  5. ChatGPT插件开发实战

    1.概述 ChatGPT是一款由OpenAI推出的先进对话模型,其强大的自然语言处理能力使得它成为构建智能对话系统和人机交互应用的理想选择.为了进一步拓展ChatGPT的功能和适应不同领域的需求,Op ...

  6. Hugging News #0731: 新课程重磅发布、用户交流群邀请你加入、真实图像编辑方法 LEDTIS 来啦!

    每一周,我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新,包括我们的产品和平台更新.社区活动.学习资源和内容更新.开源库和模型更新等,我们将其称之为「Hugging Ne ...

  7. [prometheus]基于influxdb2实现远端存储

    前言 Prometheus自带的时序数据库胜在使用方便,缺点在于难以维护,如果数据有问题,可能需要删除存储目录.重建目录再重启Prometheus,才能恢复正常.而且Prometheus自带的时序数据 ...

  8. doris单机安装部署

    原文出处 doris单机安装部署 下载Doris 环境要求 Linux系统:Centos7.x或Ubantu16.04及以上版本 Java运行环境: JDK8 java -version 在windo ...

  9. QA|Pycharm中的git分支提交冲突问题和解决|GIT

    前天,Pycharm中的git分支提交冲突了,原因是我PC上改了文件没有提交,笔记本又本地改代码,笔记本提交时就出现报错:提交拒绝,但pull也被拒绝,网上试了rebase等方法,均没得到解决,最终自 ...

  10. charts自适应

    大屏echarts自适应文字大小 1.在utils中创建 setFontSize.js function setFontSize(val) { const baseSize = 50; let sca ...