Rust网络编程框架-Tokio进阶

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 我们在上文《小朋友也能听懂的Rust网络编程框架知识-Tokio基础篇》对于Tokio的基础知识进行了一下初步的介绍,本文就对于Tokio的用法及原理进行进一步的介绍与说明。目前市面上绝大多数编程语言所编写的程序,执行程序与代码编写顺序完全相同,当然有的读者可能会提到CPU的乱序执行机制,但乱序执行从本质上讲还是顺序提交的,程序在第一行执行完成之后再去执行下一行,并以此类推,是通用的编程模式。


我们在上文《小朋友也能听懂的Rust网络编程框架知识-Tokio基础篇》对于Tokio的基础知识进行了一下初步的介绍,本文就对于Tokio的用法及原理进行进一步的介绍与说明。

目前市面上绝大多数编程语言所编写的程序,执行程序与代码编写顺序完全相同,当然有的读者可能会提到CPU的乱序执行机制,但乱序执行从本质上讲还是顺序提交的,程序在第一行执行完成之后再去执行下一行,并以此类推,是通用的编程模式。

在这种传统的式编程范式中,当程序遇到耗时操作时,会一直阻塞直到操作完成。比如建立TCP连接可能需要与网络上的对端节点进行若干次握手,这可能会花费相当多的时间。在此期间,线程被阻塞而无法完成其它操作。

在传统的编程范式中往往使用回调机制来进行资源调配的优化,对于不能立即完成的操作将被挂起到后台,这种情况下线程不会被阻塞,可以继续执行其它任务。一旦操作完成,该任务的回调函数将被调用,从而使任务最终完成。尽管回调模式可以带来使应用程序的效率更高,但也会导致程序更复杂。开发者需要跟踪异步操作完成后恢复工作所需的所有状态,从我的经验来看,这是一项特别乏味而且极容易出错的工作任务。

为什么需要异步调用

以下例程部分依赖于mini-redis模块在执行了cargo install mini-redis之后,并在Cargo.toml最后加入以下配置项之后,

tokio = { version = "1", features = ["full"] }
mini-redis = "0.4"

image.gif

即可顺利执行下列代码:

use tokio::net::{TcpListener, TcpStream};
use mini_redis::{Connection, Frame};
#[tokio::main]
async fn main() {
// 绑定端口
    let listener = TcpListener::bind("127.0.0.1:6379").await.unwrap();
    loop {
        // 监控端口消息,对于每个socket请求,都启动一个folk进程,进行处理
        let (socket, _) = listener.accept().await.unwrap();
        Process(socket).await;
    }
}
async fn Process(socket: TcpStream) {
    let mut connection = Connection::new(socket);
    if let Some(frame) = connection.read_frame().await.unwrap() {
        println!("WE GOT: {:?}", frame);
        let response = Frame::Error("not finished".to_string());
        connection.write_frame(&response).await.unwrap();
    }
}

image.gif

以上代码可能我们在其它语言编程中所经常遇到的,对于每个Socket连接都通过一个线程来处理(当然这里只是以Rust为例说明,在Tokio中不推荐这种做法,我也就没有另行启动线程)并且最关键的一点是process(socket).await;是同步调用,也就是说在线程阻塞在process函数时并没有其它事情可做,整个线程必须要等到响应被完全写入socket stream才能返回。而这种并发处理与我们尽可能多的同时处理更多请求的初衷是不一致的。

这里笔者必须要指出,并发和并行完全是两件事。多个任务交替执行是并发,并行是有多个人,一个人负责一个任务。而Rust的Tokio最大就是并发效率很高,线程并不需要去等待那些无效的任务,众多并发任务之间由Tokio去统一调度。

Tokio的答案

Rust使用spawn关键字来建立此类并发任务的任务池,按照笔者的理解,这和线程池不是一个概念,因为并发的任务可能有多个线程共同处理,也可能只有一个线程就搞定了。在使用Rust这种并发任务的异步函数使用async关键字修饰,在异步函数的函数体内任何类似于await的阻塞调用用都会使任务将控制权交还给线程。当操作进程在后台时,线程可以做其他工作。操作产生的结果也将形成一个Future,也就是未来才会产生的值被系统以变通的方式优化处理,改写后的代码如下:

use tokio::net::{TcpListener, TcpStream};
use mini_redis::{Connection, Frame};
#[tokio::main]
async fn main() {
// 绑定端口
    let listener = TcpListener::bind("127.0.0.1:6379").await.unwrap();
    loop {
        // 监控端口消息,对于每个socket请求,都启动一个folk进程,进行处理
        let (socket, _) = listener.accept().await.unwrap();
        tokio::spawn(async move {
            process(socket).await;
        });
    }
}
async fn Process(socket: TcpStream) {
    let mut connection = Connection::new(socket);
    if let Some(frame) = connection.read_frame().await.unwrap() {
        println!("WE GOT: {:?}", frame);
        let response = Frame::Error("not finished".to_string());
        connection.write_frame(&response).await.unwrap();
    }
}

image.gif

Tokio任务通过tokio::spawn来创建spawn函数返回一个JoinHandle,调用者可以使用JoinHandle它与Tokio的任务进行交互。async修饰的函数的返回值以Future方式返回。调用者可以使用.awai来Future的执行结果。代码如下:

#[tokio::main]async fn main() {
    let handle = tokio::spawn(async {
          "hello beyondma"
    });
     let out = handle.await.unwrap();
    println!("GOT {}", out);
}

image.gif

上述程序运行结果为

GOT hello beyondma

当Tokio任务执行过程中遇到错误时,JoinHandle将返回一个Err。当任务失败时,或者当任务被强制关闭时,是铁定会返回ERR的。Tokio任务由Tokio调度器管理的最小可执行单元。正如上文所说Tokio的任务可能在同一个线程上执行,也可能在不同的线程上执行这种多路复用机制可以参考上文《小朋友也能听懂的Rust网络编程框架知识-Tokio基础篇

Tokio任务之间的同步与通信

我们知道Rust有着比较独特的变量生命周期机制,在之前的示例代码当中都是用了move关键字来强制传递变量所属关系的,如下:

tokio::spawn(asyncmove {

           process(socket).await;

       });

那么如何在各个Tokio任务之间进行通信与状态同步也是个值得在本文中讨论的问题。

这里我们先来讨论比较简单的情况,可以用Arc<Mutex<_>>类型,也就是加互斥锁的哈希表来进行任务间的信息传递与同步,使用clone方法来为每个任务获取自己的哈希表实例。具体如下:

use tokio::net::TcpListener;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[tokio::main]
async fn main() {
    let listener = TcpListener::bind("127.0.0.1:6379").await.unwrap();
    println!("Listening");
    let hashMap= Arc::new(Mutex::new(HashMap::new()));
    loop {
        let (socket, _) = listener.accept().await.unwrap();
        // Clone the handle to the hash map.
        let thisMap = hashMap.clone();
        println!("Accepted");
        tokio::spawn(async move {
             let mut thisMap = thisMap .lock().unwrap();
             thisMap .insert("hello", "beyondma");
             println!("{:?}",thisMap );
        });
    }
}

image.gif

这样随便找其它终端或者在本机上执行telnet 服务器IP 6379

就可以看到以下结果

Listening
Accepted
{"hello": "beyondma"}

image.gif

这里这个hashMap的确可以在进程之间进行信息的共享与同步,但是在这种高并发的框架中一般还是推荐使用管道(channel)来进行相关操作。有关channel的话题我们会在下次再深入讲解。

Tokio的任务非常轻,只需要一个64字节的上下文即可,考虑到Rust中也没有GC机制,因此基于Tokio理论上完全可以做出比Golang支持更多并发的应用程序,这也是笔者会计划用3篇左右的系列文章来对于Tokio进行详细介绍的原因。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
3月前
|
数据采集 存储 数据处理
Scrapy:Python网络爬虫框架的利器
在当今信息时代,网络数据已成为企业和个人获取信息的重要途径。而Python网络爬虫框架Scrapy则成为了网络爬虫工程师的必备工具。本文将介绍Scrapy的概念与实践,以及其在数据采集和处理过程中的应用。
23 1
|
3月前
|
NoSQL Linux Redis
Redis 的网络框架是实现了 Reactor 模型吗?
Redis 的网络框架是实现了 Reactor 模型吗?
|
3月前
|
缓存 Rust 前端开发
【一起学Rust | 框架篇 | Tauri2.0框架】Tauri2.0环境搭建与项目创建
【一起学Rust | 框架篇 | Tauri2.0框架】Tauri2.0环境搭建与项目创建
208 0
|
3月前
|
Web App开发 Rust 前端开发
【一起学Rust | 框架篇 | Tauri2.0框架】Tauri App开启远程调试功能
【一起学Rust | 框架篇 | Tauri2.0框架】Tauri App开启远程调试功能
212 0
|
14天前
|
网络协议 Java API
Python网络编程基础(Socket编程)Twisted框架简介
【4月更文挑战第12天】在网络编程的实践中,除了使用基本的Socket API之外,还有许多高级的网络编程库可以帮助我们更高效地构建复杂和健壮的网络应用。这些库通常提供了异步IO、事件驱动、协议实现等高级功能,使得开发者能够专注于业务逻辑的实现,而不用过多关注底层的网络细节。
|
存储 设计模式 网络协议
Netty网络框架(一)
Netty网络框架
32 1
|
1月前
|
网络协议 安全 网络安全
网络基础与通信原理:构建数字世界的框架
网络基础与通信原理:构建数字世界的框架
46 1
|
2月前
|
JSON Rust 前端开发
Rust Web框架概览:Actix-Web与Yew的探索之旅
本文深入探讨了Rust编程语言中两个备受瞩目的Web框架:Actix-Web和Yew。我们将详细介绍这两个框架的核心特性、应用场景、性能优势以及如何使用它们构建高效、安全的Web应用。通过本文,您将更全面地了解Rust在Web开发领域的潜力和实践。
|
2月前
|
Rust Java 调度
Rust中的异步编程利器:Tokio框架解析
在Rust生态系统中,Tokio已经成为异步编程的首选框架。本文将对Tokio进行深入探讨,分析其关键特性、工作原理以及如何在Rust项目中使用Tokio进行高效的异步编程。我们将通过示例代码展示Tokio如何简化异步操作,提升程序性能,并讨论Tokio在处理并发任务时的优势。
|
3月前
|
数据采集 存储 机器人
Scrapy网络爬虫框架——从入门到实践
网络爬虫已经成为了信息获取的必备工具之一,而Scrapy作为Python中最流行的网络爬虫框架之一,具有高效、可扩展、易用等特点。本文将深入介绍Scrapy框架的概念和实践,帮助读者快速掌握构建高质量网络爬虫的方法。
60 0