• 21. 从零用Rust编写正反向代理,tokio竟然这样对待socket!


    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,
    }

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

    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) {
    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);
    }
    }

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

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

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

  • 相关阅读:
    NVIDIA DPU — 安装部署
    94%预测准确率!手把手搭建CNN-AlexNet卷积神经网络框架
    [C++ 网络协议] 多线程服务器端
    Linux常用命令——consoletype命令
    Allure使用手册
    技术分享| 应急指挥调度平台需要这些技术支撑
    【无标题】
    Java中ArrayList和Vector的区别是什么呢?
    主从模式详解
    亚马逊云AI应用科技创新下的Amazon SageMaker使用教程
  • 原文地址:https://www.cnblogs.com/wmproxy/p/17802214.html