当前位置: 首页 > news >正文

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

文章目录

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

使用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服务器

相关文章:

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

文章目录 实现异步代码并发地处理连接使用多线程提升性能 使用rust实现一个异步运行时是async-std的单线程Web服务器。 仓库地址&#xff1a; 1037827920/web-server: 使用rust编写的简单web服务器 (github.com) 在之前的单线程版本的Web服务器代码上进行修改&#xff0c;具体…...

C语言复习概要(一)

本文 C语言入门详解&#xff1a;从基础概念到分支与循环1. C语言常见概念1.1 程序的基本结构1.2 变量作用域和存储类1.3 输入输出1.4 编译与运行 2. C语言中的数据类型和变量2.1 基本数据类型2.2 变量的声明与初始化2.3 常量与枚举 3. C语言的分支结构3.1 if语句3.2 if-else语句…...

二、kafka生产与消费全流程

一、使用java代码生产、消费消息 1、生产者 package com.allwe.client.simple;import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.pr…...

本地搭建OnlyOffice在线文档编辑器结合内网穿透实现远程协作

文章目录 前言1. 安装Docker2. 本地安装部署ONLYOFFICE3. 安装cpolar内网穿透4. 固定OnlyOffice公网地址 前言 本篇文章讲解如何使用Docker在本地Linux服务器上安装ONLYOFFICE&#xff0c;并结合cpolar内网穿透实现公网访问本地部署的文档编辑器与远程协作。 Community Editi…...

ScrapeGraphAI 大模型增强的网络爬虫

在数据驱动的动态领域&#xff0c;从在线资源中提取有价值的见解至关重要。从市场分析到学术研究&#xff0c;对特定数据的需求推动了对强大的网络抓取工具的需求。 NSDT工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线…...

PDF转换为TIF,JPG的一个简易工具(含下载链接)

目录 0.前言&#xff1a; 1.工具目录 2.工具功能&#xff08;效果&#xff09;&#xff0c;如何运行 效果 PDF转换为JPG&#xff08;带颜色&#xff09; PDF转换为TIF&#xff08;LZW形式压缩&#xff0c;可以显示子的深浅&#xff09; PDF转换为TIF&#xff08;CCITT形…...

Wireshark 解析QQ、微信的通信协议|TCP|UDP

写在前面 QQ&#xff0c;微信这样的聊天软件。我们一般称为im&#xff0c;Instant Messaging&#xff0c;即时通讯系统。那大家会不会有疑问&#xff0c;自己聊天内容会不会被黑客或者不法分子知道&#xff1f;这种体量的im是基于tcp还是udp呢&#xff1f;这篇文章我们就来探索…...

网络编程(5)——模拟伪闭包实现连接的安全回收

六、day6 今天学习如何利用C11模拟伪闭包实现连接的安全回收&#xff0c;之前的异步服务器为echo模式&#xff0c;但存在安全隐患&#xff0c;在极端情况下客户端关闭可能会导致触发写和读回调函数&#xff0c;二者都进入错误处理逻辑&#xff0c;进而造成二次析构。今天学习如…...

C#绘制动态曲线

前言 用于实时显示数据动态曲线&#xff0c;比如&#xff1a;SOC。 //用于绘制动态曲线&#xff0c;可置于定时函数中&#xff0c;定时更新数据曲线 void DrawSocGraph() {double f (double)MainForm.readData[12]; //display datachart1.Series[0].Points.Add(f);if (ch…...

用Python实现运筹学——Day 10: 线性规划的计算机求解

一、学习内容 1. 使用 Python 的 scipy.optimize.linprog 进行线性规划求解 scipy.optimize.linprog 是 Python 中用于求解线性规划问题的函数。它实现了单纯形法、内点法等算法&#xff0c;能够处理求解最大化或最小化问题&#xff0c;同时满足线性约束条件。 线性规划问题的…...

[C++]使用C++部署yolov11目标检测的tensorrt模型支持图片视频推理windows测试通过

官方框架&#xff1a; https://github.com/ultralytics/ultralytics yolov8官方最近推出yolov11框架&#xff0c;标志着目标检测又多了一个检测利器&#xff0c;于是尝试在windows下部署yolov11的tensorrt模型&#xff0c;并最终成功。 重要说明&#xff1a;安装环境视为最基…...

霍夫曼树及其与B树和决策树的异同

霍夫曼树是一种用于数据压缩的二叉树结构&#xff0c;通常应用于霍夫曼编码算法中。它的主要作用是通过对符号进行高效编码&#xff0c;减少数据的存储空间。霍夫曼树在压缩领域扮演着重要角色&#xff0c;与B树、决策树等数据结构都有一些相似之处&#xff0c;但又在应用场景和…...

CompletableFuture常用方法

一、获得结果和触发计算 1.获取结果 &#xff08;1&#xff09;public T get() public class CompletableFutureAPIDemo{public static void main(String[] args) throws ExecutionException, InterruptedException{CompletableFuture<String> completableFuture Com…...

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

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

使用rust实现rtsp码流截图

中文互联网上的rust示例程序源码还是太稀少&#xff0c;找资料很是麻烦&#xff0c;下面是自己用业余时间开发实现的一个对批量rtsp码流源进行关键帧截图并存盘的rust demo源码记录。 要编译这个源码需要先安装vcpkg&#xff0c;然后用vcpkg install ffmpeg安装最新版本的ffmpe…...

Cpp::STL—string类的模拟实现(12)

文章目录 前言一、string类各函数接口总览二、默认构造函数string(const char* str "");string(const string& str);传统拷贝写法现代拷贝写法 string& operator(const string& str);传统赋值构造现代赋值构造 ~string(); 三、迭代器相关函数begin &…...

一文搞懂SentencePiece的使用

目录 1. 什么是 SentencePiece&#xff1f;2. SentencePiece 基础概念2.1 SentencePiece 的工作原理2.2 SentencePiece 的优点 3. SentencePiece 的使用3.1 安装 SentencePiece3.2 训练模型与加载模型3.3 encode&#xff08;高频&#xff09;3.4 decode&#xff08;高频&#x…...

一个简单的摄像头应用程序1

这个Python脚本实现了一个基于OpenCV的简单摄像头应用,我们在原有的基础上增加了录制视频等功能,用户可以通过该应用进行拍照、录制视频,并查看已拍摄的照片。以下是该脚本的主要功能和一些使用时需要注意的事项: 功能 拍照: 用户可以通过点击界面上的“拍照”按钮或按…...

通过PHP获取商品详情

在电子商务的浪潮中&#xff0c;数据的重要性不言而喻。商品详情信息对于电商运营者来说尤为宝贵。PHP&#xff0c;作为一种广泛应用的服务器端脚本语言&#xff0c;为我们提供了获取商品详情的便捷途径。 了解API接口文档 开放平台提供了详细的API接口文档。你需要熟悉商品详…...

【Android】获取备案所需的公钥以及签名MD5值

目录 重要前提 获取签名MD5值 获取公钥 重要前提 生成jks文件以及gradle配置应用该文件。具体步骤请参考我这篇文章&#xff1a;【Android】配置Gradle打包apk的环境_generate signed bundle or apk-CSDN博客 你只需要从头看到该文章的配置build.gradle&#xff08;app&…...

spring:实例工厂方法获取bean

spring处理使用静态工厂方法获取bean实例&#xff0c;也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下&#xff1a; 定义实例工厂类&#xff08;Java代码&#xff09;&#xff0c;定义实例工厂&#xff08;xml&#xff09;&#xff0c;定义调用实例工厂&#xff…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案

随着新能源汽车的快速普及&#xff0c;充电桩作为核心配套设施&#xff0c;其安全性与可靠性备受关注。然而&#xff0c;在高温、高负荷运行环境下&#xff0c;充电桩的散热问题与消防安全隐患日益凸显&#xff0c;成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

MySQL用户和授权

开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务&#xff1a; test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

快刀集(1): 一刀斩断视频片头广告

一刀流&#xff1a;用一个简单脚本&#xff0c;秒杀视频片头广告&#xff0c;还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农&#xff0c;平时写代码之余看看电影、补补片&#xff0c;是再正常不过的事。 电影嘛&#xff0c;要沉浸&#xff0c;…...

API网关Kong的鉴权与限流:高并发场景下的核心实践

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中&#xff0c;API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关&#xff0c;Kong凭借其插件化架构…...

AxureRP-Pro-Beta-Setup_114413.exe (6.0.0.2887)

Name&#xff1a;3ddown Serial&#xff1a;FiCGEezgdGoYILo8U/2MFyCWj0jZoJc/sziRRj2/ENvtEq7w1RH97k5MWctqVHA 注册用户名&#xff1a;Axure 序列号&#xff1a;8t3Yk/zu4cX601/seX6wBZgYRVj/lkC2PICCdO4sFKCCLx8mcCnccoylVb40lP...

未授权访问事件频发,我们应当如何应对?

在当下&#xff0c;数据已成为企业和组织的核心资产&#xff0c;是推动业务发展、决策制定以及创新的关键驱动力。然而&#xff0c;未授权访问这一隐匿的安全威胁&#xff0c;正如同高悬的达摩克利斯之剑&#xff0c;时刻威胁着数据的安全&#xff0c;一旦触发&#xff0c;便可…...

spring boot使用HttpServletResponse实现sse后端流式输出消息

1.以前只是看过SSE的相关文章&#xff0c;没有具体实践&#xff0c;这次接入AI大模型使用到了流式输出&#xff0c;涉及到给前端流式返回&#xff0c;所以记录一下。 2.resp要设置为text/event-stream resp.setContentType("text/event-stream"); resp.setCharacter…...

[特殊字符] Spring Boot底层原理深度解析与高级面试题精析

一、Spring Boot底层原理详解 Spring Boot的核心设计哲学是约定优于配置和自动装配&#xff0c;通过简化传统Spring应用的初始化和配置流程&#xff0c;显著提升开发效率。其底层原理可拆解为以下核心机制&#xff1a; 自动装配&#xff08;Auto-Configuration&#xff09; 核…...