使用rust写一个Web服务器——async-std版本

news2024/10/3 11:23:18

文章目录

    • 实现异步代码
    • 并发地处理连接
    • 使用多线程提升性能

使用rust实现一个异步运行时是async-std的单线程Web服务器。

仓库地址: 1037827920/web-server: 使用rust编写的简单web服务器 (github.com)

在之前的单线程版本的Web服务器代码上进行修改,具体代码在给的仓库地址中。

实现异步代码

首先将handle_connection修改为async实现:

async fn handle_connection(mut stream: TcpStream) {}

该修改会将函数的返回值从()变成Future<Output = ()>,因此直接运行将不再有任何效果,只用通过.await或执行器的poll。

使用async-std作为异步运行时:

async-std运行时允许使用属性#[async_std::main]将我们的fn main函数变成async fn main,这样就可以在main函数中直接调用其他async函数,否则你得用block_on方法来让main去阻塞等待异步函数的完成,但是这种简单粗暴的阻塞等待方式并不灵活

Cargo.toml:

[dependencies]
futures = "0.3"

[dependencies.async-std]
version = "1.6"
features = ["attributes"]

下面将main函数修改为异步的,并在其中调用前面修改的异步版本handle_connection:

use std::{
    io::{prelude::*, BufReader},
    net::{TcpListener, TcpStream},
    fs,
    time::Duration,
};
extern crate async_std;
use async_std::task;

#[async_std::main]
async fn main() {
    let listener = TcpListener::bind("localhost:8080").unwrap();
    for stream in listener.incoming() {
        let stream = stream.unwrap();
        // 这里还是无法并发
        handle_connection(stream).await;
    }
}

实现异步版本的handle_connection:

/// # 函数作用
/// 处理连接:读取请求,回应请求
async fn handle_connection(mut stream: TcpStream) {
    let buf_reader = BufReader::new(&mut stream);
    // 使用next而不是lines,因为我们只需要读取第一行,判断具体的request方法
    let request_line = buf_reader.lines().next().unwrap().unwrap();

    // 根据请求的不同,返回不同的响应
    let (status_line, filename) = match &request_line[..] {
        "GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"), // 请求 / 资源
        "GET /sleep HTTP/1.1" => { // 请求 /sleep 资源
            // 没有使用std::thread::sleep进行睡眠,原因是该函数是阻塞的,它会让当前线程陷入睡眠中,导致其他任务无法继续运行
            task::sleep(Duration::from_secs(5)).await;
            ("HTTP/1.1 200 OK", "hello.html")
        }
        _ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
    };

    let contents = fs::read_to_string(filename).unwrap();
    let length = contents.len();
    
    let response = format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");

    // write_all接收&[u8]类型作为参数,这里需要用as_bytes将字符串转换为字节数组
    stream.write_all(response.as_bytes()).unwrap();
}

可以看出,只是把函数变成async往往是不够的,还需要将它内部的代码也都变成异步兼容,阻塞线程绝对是不可行的

但是线程web服务器还是不能进行并发处理请求,原因是listener.incoming()是阻塞的迭代器。当listener在等待连接时,执行器是无法执行其他Future的,而且只有当我们处理完已有的连接后,才能接收新的连接。

并发地处理连接

上面的解决方法是将listener.incoming()从一个阻塞的迭代器变成一个非阻塞的Stream

use std::{
    fs,
    time::Duration,
};
extern crate async_std;
use async_std::{
    net::{TcpListener, TcpStream},
    io::{prelude::*, BufReader},
    task,
};
use futures::StreamExt;

#[async_std::main]
async fn main() {
    let listener = TcpListener::bind("localhost:8080").await.unwrap();
    
    listener
        .incoming()
        .for_each_concurrent(None, |tcpstream| async move {
            let tpcstream = tcpstream.unwrap();
            handle_connection(tpcstream).await;
        })
        .await;
}

异步版本的TcpListener为listener.incoming()实现了Stream trait,这样listener.incoming()不再阻塞,且使用for_each_concurrent可以并发地处理从Stream获取的元素。

现在关键在于handle_connection不能再阻塞:

/// # 函数作用
/// 处理连接:读取请求,回应请求
async fn handle_connection(mut stream: TcpStream) {
    let buf_reader = BufReader::new(&mut stream);
    // 使用next而不是lines,因为我们只需要读取第一行,判断具体的request方法
    let request_line = buf_reader.lines().next().await.unwrap().unwrap();

    // 根据请求的不同,返回不同的响应
    let (status_line, filename) = match &request_line[..] {
        "GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"), // 请求 / 资源
        "GET /sleep HTTP/1.1" => { // 请求 /sleep 资源
            // 没有使用std::thread::sleep进行睡眠,原因是该函数是阻塞的,它会让当前线程陷入睡眠中,导致其他任务无法继续运行
            task::sleep(Duration::from_secs(5)).await;
            ("HTTP/1.1 200 OK", "hello.html")
        }
        _ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
    };

    let contents = fs::read_to_string(filename).unwrap();
    let length = contents.len();
    
    let response = format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");

    // write_all接收&[u8]类型作为参数,这里需要用as_bytes将字符串转换为字节数组
    stream.write_all(response.as_bytes()).await.unwrap();
}

在将数据读写改造成异步后,现在该函数也彻底变成了异步版本,可以并发地处理连接

使用多线程提升性能

async并发和多线程其实并不冲突,async-std包也允许我们使用多个线程去处理,由于handle_connection实现了Send trait不会阻塞,因此使用async_std::task::spawn是非常安全的:

use async_std::task::spawn;

#[async_std::main]
async fn main() {
    let listener = TcpListener::bind("localhost:8080").await.unwarp():
    listener
    	.incoming()
    	.for_each_concurrent(None, |stream| async move {
            let stream = stream.unwrap();
            spawn(handle_connection(stream));
    })
    .await;
}

但是这里是为每个请求都单独创建了一个线程,实际上需要限制创建线程的数量,可以通过线程池来实现。具体可以看这篇无async的多线程版本的Web服务器

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2186110.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux相关概念和重要知识点(11)(进程调度、Linux内核链表)

1.Linux调度算法 上篇文章我粗略讲过queue[140]的结构&#xff0c;根据哈希表&#xff0c;我们可以将40个不同优先级的进程借助哈希桶链入queue[140]中。调度器会根据queue的下标来进行调度。但这个具体的调度过程是怎样的呢&#xff1f;以及runqueue和queue[140]的关系是什么…

谷歌给到的185个使用生成式AI的案例

很多公司从利用AI回答问题&#xff0c;进而使用AI进行预测&#xff0c;向使用生成式AI Agent转变。AI Agent的独特之处在于它们可以采取行动以实现特定目标&#xff0c;比如引导购物者找到合适的鞋子&#xff0c;帮助员工寻找合适的健康福利&#xff0c;或在护理人员交接班期间…

python之输入输出

1、输入 Python在控制台输入内容&#xff0c;需要使用input函数。input函数会在控制台等待用户输入&#xff0c;直到用户按下了回车键才算完成输入。 注意&#xff1a;input函数接收的内容为字符串。 str1 input("请输入内容\n") print(str1) print(type(str1))1…

Python酷库之旅-第三方库Pandas(132)

目录 一、用法精讲 591、pandas.DataFrame.plot方法 591-1、语法 591-2、参数 591-3、功能 591-4、返回值 591-5、说明 591-6、用法 591-6-1、数据准备 591-6-2、代码示例 591-6-3、结果输出 592、pandas.DataFrame.plot.area方法 592-1、语法 592-2、参数 592-…

9.28学习笔记

1.ping 网址 2.ssh nscc/l20 3.crtl,打开vscode的setting 4.win 10修改ssh配置文件及其密钥权限为600 - 晴云孤魂 - 博客园 整体来看&#xff1a; 使用transformer作为其主干网络&#xff0c;代替了原先的UNet 在latent space进行训练&#xff0c;通过transformer处理潜…

查缺补漏----该不该考虑不可屏蔽中断

可以看看这个视频&#xff1a; 讨论中断时&#xff0c;该不该考虑不可屏蔽中断&#xff1f;_哔哩哔哩_bilibili 首先要知道一个概念&#xff1a;可屏蔽中断和不可屏蔽中断 可屏蔽中断&#xff1a; 可屏蔽中断是可通过中断屏蔽字来启用或禁用的中断。对于多级中断而言&#…

①EtherCAT转ModbusTCP, EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关

EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关https://item.taobao.com/item.htm?ftt&id822721028899 协议转换通信网关 EtherCAT 转 ModbusTCP GW系列型号 MS-GW15 简介 MS-GW15 是 EtherCAT 和 Modbus TCP 协议转换网关&#xff0c;为用户提供一种 …

map_set的使用

map_set的使用 关联式容器树形结构的关联式容器setset的介绍set的使用 multisetmultiset的介绍multiset的使用 mapmap的介绍map的使用键值对 multimapmultimap的介绍 &#x1f30f;个人博客主页&#xff1a;个人主页 关联式容器 在初阶阶段&#xff0c;我们已经接触过STL中的部…

黑科技外绘神器:一键扩展图像边界

黑科技外绘神器&#xff1a;一键扩展图像边界 Diffusers Image Outpaint✨是一个开源工具&#xff0c;能智能扩展图像边界&#xff0c;创造完美视觉效果&#x1f3de;️。用户可自定义风格&#xff0c;生成高清图像&#x1f929;&#xff0c;应用场景广泛&#xff0c;释放你的…

大模型~合集6

我自己的原文哦~ https://blog.51cto.com/whaosoft/11566566 # 深度模型融合&#xff08;LLM/基础模型/联邦学习/微调等&#xff09; 23年9月国防科大、京东和北理工的论文“Deep Model Fusion: A Survey”。 深度模型融合/合并是一种新兴技术&#xff0c;它将多个深度学习模…

爬虫——爬取小音乐网站

爬虫有几部分功能&#xff1f;&#xff1f;&#xff1f; 1.发请求&#xff0c;获得网页源码 #1.和2是在一步的 发请求成功了之后就能直接获得网页源码 2.解析我们想要的数据 3.按照需求保存 注意&#xff1a;开始爬虫前&#xff0c;需要给其封装 headers {User-…

本地化测试对游戏漏洞修复的影响

本地化测试在游戏开发的质量保证过程中起着至关重要的作用&#xff0c;尤其是在修复bug方面。当游戏为全球市场做准备时&#xff0c;它们通常会被翻译和改编成各种语言和文化背景。这种本地化带来了新的挑战&#xff0c;例如潜在的语言错误、文化误解&#xff0c;甚至是不同地区…

C++ 双端队列(deque)的深入理解

前言&#xff1a; 双端队列deque看起来是一个相当牛的容器&#xff0c;表面看起来将list和vector进行结合起来&#xff0c;形成了一个看起来很完美的容器&#xff0c;但是事实不是这样&#xff0c;要是deque如此完美&#xff0c;数据结构也就没list和vector的事情了&#xff0c…

多系统萎缩患者必看!这些维生素助你对抗病魔

亲爱的朋友们&#xff0c;今天我们来聊聊一个相对陌生但重要的健康话题——多系统萎缩&#xff08;MSA&#xff09;。这是一种罕见的神经系统疾病&#xff0c;影响着患者的自主神经系统、运动系统和平衡功能。面对这样的挑战&#xff0c;科学合理的饮食和营养补充显得尤为重要。…

暴力数据结构——AVL树

1.认识AVL树 AVL树最先发明的⾃平衡⼆叉查找树,AVL可以是⼀颗空树,或者具备下列性质的⼆叉搜索树&#xff1a; • 它的左右⼦树都是AV树&#xff0c;且左右⼦树的⾼度差的绝对值不超过1 • AVL树是⼀颗⾼度平衡搜索⼆叉树&#xff0c; 通过控制⾼度差去控制平衡 AVL树整体结点…

路由交换实验指南

案例 01&#xff1a;部署使用 eNSP 平台实验需求&#xff1a; 安装华为 eNSP 网络模拟平台打开 eNSP 平台&#xff0c;新建拓扑并绘制网络能够成功启动交换机、计算机设备 实验步骤&#xff1a; 安装华为 eNSP 网络模拟平台启动安装程序 配置安装内容 防护墙允许 eNSP 程序的…

IDTL:茶叶病害识别数据集(猫脸码客 第205期)

Identifying Disease in Tea Leaves茶叶病害识别数据集 一、引言 在农业领域&#xff0c;茶叶作为一种重要的经济作物&#xff0c;其生产过程中的病害防治是确保茶叶质量和产量的关键环节。然而&#xff0c;传统的病害识别方法主要依赖于人工观察和经验判断&#xff0c;这不仅…

从零开始实现RPC框架---------项目介绍及环境准备

一&#xff0c;介绍 RPC&#xff08;Remote Procedure Call&#xff09;远程过程调⽤&#xff0c;是⼀种通过⽹络从远程计算机上请求服务&#xff0c;⽽不需要 了解底层⽹络通信细节。RPC可以使⽤多种⽹络协议进⾏通信&#xff0c; 如HTTP、TCP、UDP等&#xff0c; 并且在 TCP/…

匿名方法与Lambda表达式+泛型委托

匿名方法 和委托搭配使用&#xff0c;方便我们快速对委托进行传参&#xff0c;不需要我们定义一个新的函数&#xff0c;直接用delegate关键字代替方法名&#xff0c;后面跟上参数列表与方法体。 格式&#xff1a;delegate(参数列表){方法体} lambda表达式 是匿名方法的升级…

Brave编译指南2024 MacOS篇-环境配置(四)

引言 在上一篇文章中&#xff0c;我们成功获取了Brave浏览器的源代码。现在&#xff0c;我们将进入编译过程的关键阶段&#xff1a;环境配置。正确的环境配置对于成功编译Brave浏览器至关重要&#xff0c;它能确保所有必要的工具和依赖项都已就位&#xff0c;并且版本兼容。 …