基于Rust的多线程 Web 服务器

构建多线程 Web 服务器

  • 在 socket 上监听 TCP 连接
  • 解析少量的 HTTP 请求
  • 创建一个合适的 HTTP 响应
  • 使用线程池改进服务器的吞吐量
  • 优雅的停机和清理
  • 注意:并不是最佳实践

创建项目

~/rust
➜ cargo new helloCreated binary (application) `hello` package~/rust
➜

main.rs 文件

use std::net::TcpListener;fn main() {let listener = TcpListener::bind("127.0.0.1:7878").unwrap();for stream in listener.incoming() {let stream = stream.unwrap();println!("Connection established!");}
}

修改一:

use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;fn main() {let listener = TcpListener::bind("127.0.0.1:7878").unwrap();for stream in listener.incoming() {let stream = stream.unwrap();handle_connection(stream);}
}fn handle_connection(mut stream: TcpStream) {let mut buffer = [0; 512];stream.read(&mut buffer).unwrap();println!("Request: {}", String::from_utf8_lossy(&buffer[..]));
}

修改二:

use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;fn main() {let listener = TcpListener::bind("127.0.0.1:7878").unwrap();for stream in listener.incoming() {let stream = stream.unwrap();handle_connection(stream);}
}fn handle_connection(mut stream: TcpStream) {let mut buffer = [0; 512];stream.read(&mut buffer).unwrap();// 请求// Method Request-URI HTTP-Version CRLF// headers CRLF// message-body// 响应// HTTP-Version Status-Code Reason-Phrase CRLF// headers CRLF// message-bodylet response = "HTTP/1.1 200 OK\r\n\r\n";stream.write(response.as_bytes()).unwrap();stream.flush().unwrap();println!("Request: {}", String::from_utf8_lossy(&buffer[..]));
}

修改三:

use std::fs;
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;fn main() {let listener = TcpListener::bind("127.0.0.1:7878").unwrap();for stream in listener.incoming() {let stream = stream.unwrap();handle_connection(stream);}
}fn handle_connection(mut stream: TcpStream) {let mut buffer = [0; 512];stream.read(&mut buffer).unwrap();// 请求// Method Request-URI HTTP-Version CRLF// headers CRLF// message-body// 响应// HTTP-Version Status-Code Reason-Phrase CRLF// headers CRLF// message-bodylet contents = fs::read_to_string("hello.html").unwrap();let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", contents);stream.write(response.as_bytes()).unwrap();stream.flush().unwrap();
}

修改四:

use std::fs;
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;fn main() {let listener = TcpListener::bind("127.0.0.1:7878").unwrap();for stream in listener.incoming() {let stream = stream.unwrap();handle_connection(stream);}
}fn handle_connection(mut stream: TcpStream) {let mut buffer = [0; 512];stream.read(&mut buffer).unwrap();let get = b"GET / HTTP/1.1\r\n";if buffer.starts_with(get) {let contents = fs::read_to_string("hello.html").unwrap();let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", contents);stream.write(response.as_bytes()).unwrap();stream.flush().unwrap();} else {let status_line = "HTTP/1.1 404 NOT FOUND\r\n\r\n";let contents = fs::read_to_string("404.html").unwrap();let response = format!("{}{}", status_line, contents);stream.write(response.as_bytes()).unwrap();stream.flush().unwrap();}
}

修改五:

use std::fs;
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;fn main() {let listener = TcpListener::bind("127.0.0.1:7878").unwrap();for stream in listener.incoming() {let stream = stream.unwrap();handle_connection(stream);}
}fn handle_connection(mut stream: TcpStream) {let mut buffer = [0; 512];stream.read(&mut buffer).unwrap();let get = b"GET / HTTP/1.1\r\n";let (status_line, filename) = if buffer.starts_with(get) {("HTTP/1.1 200 OK\r\n\r\n", "hello.html")} else {("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")};let contents = fs::read_to_string(filename).unwrap();let response = format!("{}{}", status_line, contents);stream.write(response.as_bytes()).unwrap();stream.flush().unwrap();
}

hello.html 文件

<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><title>Hello</title></head><body><h1>Hello</h1><p>Hi from Rust</p></body>
</html>

404.html 文件

<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><title>Hello!</title></head><body><h1>Oops!</h1><p>Sorry, I don't know what you're asking for.</p></body>
</html>

单线程Web服务器

use std::fs;
use std::thread;
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;
use std::time::Duration;fn main() {let listener = TcpListener::bind("127.0.0.1:7878").unwrap();for stream in listener.incoming() {let stream = stream.unwrap();handle_connection(stream);}
}fn handle_connection(mut stream: TcpStream) {let mut buffer = [0; 512];stream.read(&mut buffer).unwrap();let get = b"GET / HTTP/1.1\r\n";let sleep = b"GET /sleep HTTP/1.1\r\n";let (status_line, filename) = if buffer.starts_with(get) {("HTTP/1.1 200 OK\r\n\r\n", "hello.html")} else if buffer.starts_with(sleep) {thread::sleep(Duration::from_secs(5));("HTTP/1.1 200 OK\r\n\r\n", "hello.html")} else {("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")};let contents = fs::read_to_string(filename).unwrap();let response = format!("{}{}", status_line, contents);stream.write(response.as_bytes()).unwrap();stream.flush().unwrap();
}

开启线程

use std::fs;
use std::thread;
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;
use std::time::Duration;fn main() {let listener = TcpListener::bind("localhost:7878").unwrap();for stream in listener.incoming() {let stream = stream.unwrap();thread::spawn(|| {handle_connection(stream);});}
}fn handle_connection(mut stream: TcpStream) {let mut buffer = [0; 512];stream.read(&mut buffer).unwrap();let get = b"GET / HTTP/1.1\r\n";let sleep = b"GET /sleep HTTP/1.1\r\n";let (status_line, filename) = if buffer.starts_with(get) {("HTTP/1.1 200 OK\r\n\r\n", "hello.html")} else if buffer.starts_with(sleep) {thread::sleep(Duration::from_secs(5));("HTTP/1.1 200 OK\r\n\r\n", "hello.html")} else {("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")};let contents = fs::read_to_string(filename).unwrap();let response = format!("{}{}", status_line, contents);stream.write(response.as_bytes()).unwrap();stream.flush().unwrap();
}

lib.rs 文件

use std::thread;pub struct ThreadPool {threads: Vec<thread::JoinHandle<()>>,
}impl ThreadPool {/// Creates a new ThreadPool./// /// The size is the number of threads in the pool./// /// # Panics/// /// The `new` function will panic if the size is zero.pub fn new(size: usize) -> ThreadPool {assert!(size > 0);let mut threads = Vec::with_capacity(size);for _ in 0..size {// create some threads and store them in the vector}ThreadPool { threads }}pub fn execute<F>(&self, f: F)whereF: FnOnce() + Send + 'static,{}
}

lib.rs 修改一

use std::thread;pub struct ThreadPool {workers: Vec<Worker>,
}impl ThreadPool {/// Creates a new ThreadPool.////// The size is the number of threads in the pool.////// # Panics////// The `new` function will panic if the size is zero.pub fn new(size: usize) -> ThreadPool {assert!(size > 0);let mut workers = Vec::with_capacity(size);for id in 0..size {workers.push(Worker::new(id));}ThreadPool { workers }}pub fn execute<F>(&self, f: F)whereF: FnOnce() + Send + 'static,{}
}struct Worker {id: usize,thread: thread::JoinHandle<()>,
}impl Worker {fn new(id: usize) -> Worker {let thread = thread::spawn(|| {});Worker { id, thread }}
}

lib.rs 修改二

use std::thread;
use std::sync::mpsc;pub struct ThreadPool {workers: Vec<Worker>,sender: mpsc::Sender<Job>,
}struct Job;
impl ThreadPool {/// Creates a new ThreadPool.////// The size is the number of threads in the pool.////// # Panics////// The `new` function will panic if the size is zero.pub fn new(size: usize) -> ThreadPool {assert!(size > 0);let (sender, receiver) = mpsc::channel();let mut workers = Vec::with_capacity(size);for id in 0..size {workers.push(Worker::new(id, receiver));}ThreadPool { workers, sender }}pub fn execute<F>(&self, f: F)whereF: FnOnce() + Send + 'static,{}
}struct Worker {id: usize,thread: thread::JoinHandle<()>,
}impl Worker {fn new(id: usize, receiver: mpsc::Receiver<Job>) -> Worker {let thread = thread::spawn(|| {receiver;});Worker { id, thread }}
}

lib.rs 修改三

use std::thread;
use std::sync::mpsc;
use std::sync::Arc;
use std::sync::Mutex;pub struct ThreadPool {workers: Vec<Worker>,sender: mpsc::Sender<Job>,
}struct Job;
impl ThreadPool {/// Creates a new ThreadPool.////// The size is the number of threads in the pool.////// # Panics////// The `new` function will panic if the size is zero.pub fn new(size: usize) -> ThreadPool {assert!(size > 0);let (sender, receiver) = mpsc::channel();let receiver = Arc::new(Mutex::new(receiver));let mut workers = Vec::with_capacity(size);for id in 0..size {workers.push(Worker::new(id, Arc::clone(&receiver)));}ThreadPool { workers, sender }}pub fn execute<F>(&self, f: F)whereF: FnOnce() + Send + 'static,{}
}struct Worker {id: usize,thread: thread::JoinHandle<()>,
}impl Worker {fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {let thread = thread::spawn(|| {receiver;});Worker { id, thread }}
}

lib.rs 修改四

use std::thread;
use std::sync::mpsc;
use std::sync::Arc;
use std::sync::Mutex;pub struct ThreadPool {workers: Vec<Worker>,sender: mpsc::Sender<Job>,
}// struct Job;
type Job = Box<FnOnce() + Send + 'static>;
impl ThreadPool {/// Creates a new ThreadPool.////// The size is the number of threads in the pool.////// # Panics////// The `new` function will panic if the size is zero.pub fn new(size: usize) -> ThreadPool {assert!(size > 0);let (sender, receiver) = mpsc::channel();let receiver = Arc::new(Mutex::new(receiver));let mut workers = Vec::with_capacity(size);for id in 0..size {workers.push(Worker::new(id, Arc::clone(&receiver)));}ThreadPool { workers, sender }}pub fn execute<F>(&self, f: F)whereF: FnOnce() + Send + 'static,{let job = Box::new(f);self.sender.send(job).unwrap();}
}struct Worker {id: usize,thread: thread::JoinHandle<()>,
}impl Worker {fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {let thread = thread::spawn(move || loop {let job = receiver.lock().unwrap().recv().unwrap();println!("Worker {} got a job; executing.", id);(*job)();});Worker { id, thread }}
}

lib.rs 修改五

use std::thread;
use std::sync::mpsc;
use std::sync::Arc;
use std::sync::Mutex;pub struct ThreadPool {workers: Vec<Worker>,sender: mpsc::Sender<Job>,
}// struct Job;
// type Job = Box<FnOnce() + Send + 'static>;
impl ThreadPool {/// Creates a new ThreadPool.////// The size is the number of threads in the pool.////// # Panics////// The `new` function will panic if the size is zero.pub fn new(size: usize) -> ThreadPool {assert!(size > 0);let (sender, receiver) = mpsc::channel();let receiver = Arc::new(Mutex::new(receiver));let mut workers = Vec::with_capacity(size);for id in 0..size {workers.push(Worker::new(id, Arc::clone(&receiver)));}ThreadPool { workers, sender }}pub fn execute<F>(&self, f: F)whereF: FnOnce() + Send + 'static,{let job = Box::new(f);self.sender.send(job).unwrap();}
}struct Worker {id: usize,thread: thread::JoinHandle<()>,
}trait FnBox {fn call_box(self: Box<Self>);
}impl<F: FnOnce()> FnBox for F {fn call_box(self: Box<F>) {(*self)()}
}type Job = Box<dyn FnBox + Send + 'static>;impl Worker {fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {let thread = thread::spawn(move || loop {let job = receiver.lock().unwrap().recv().unwrap();println!("Worker {} got a job; executing.", id);// (*job)();job.call_box();});Worker { id, thread }}
}

lib.rs 修改六

use std::sync::mpsc;
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;pub struct ThreadPool {workers: Vec<Worker>,sender: mpsc::Sender<Job>,
}// struct Job;
// type Job = Box<FnOnce() + Send + 'static>;
impl ThreadPool {/// Creates a new ThreadPool.////// The size is the number of threads in the pool.////// # Panics////// The `new` function will panic if the size is zero.pub fn new(size: usize) -> ThreadPool {assert!(size > 0);let (sender, receiver) = mpsc::channel();let receiver = Arc::new(Mutex::new(receiver));let mut workers = Vec::with_capacity(size);for id in 0..size {workers.push(Worker::new(id, Arc::clone(&receiver)));}ThreadPool { workers, sender }}pub fn execute<F>(&self, f: F)whereF: FnOnce() + Send + 'static,{let job = Box::new(f);self.sender.send(job).unwrap();}
}struct Worker {id: usize,thread: thread::JoinHandle<()>,
}trait FnBox {fn call_box(self: Box<Self>);
}impl<F: FnOnce()> FnBox for F {fn call_box(self: Box<F>) {(*self)()}
}type Job = Box<dyn FnBox + Send + 'static>;impl Worker {fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {let thread = thread::spawn(move || loop {while let Ok(job) = receiver.lock().unwrap().recv() {println!("Worker {} got a job; executing.", id);job.call_box();}});Worker { id, thread }}
}

优雅的停机和清理

use std::sync::mpsc;
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;pub struct ThreadPool {workers: Vec<Worker>,sender: mpsc::Sender<Job>,
}// struct Job;
// type Job = Box<FnOnce() + Send + 'static>;
impl ThreadPool {/// Creates a new ThreadPool.////// The size is the number of threads in the pool.////// # Panics////// The `new` function will panic if the size is zero.pub fn new(size: usize) -> ThreadPool {assert!(size > 0);let (sender, receiver) = mpsc::channel();let receiver = Arc::new(Mutex::new(receiver));let mut workers = Vec::with_capacity(size);for id in 0..size {workers.push(Worker::new(id, Arc::clone(&receiver)));}ThreadPool { workers, sender }}pub fn execute<F>(&self, f: F)whereF: FnOnce() + Send + 'static,{let job = Box::new(f);self.sender.send(job).unwrap();}
}impl Drop for ThreadPool {fn drop(&mut self) {for worker in &mut self.workers {println!("Shutting down worker {}", worker.id);worker.thread.join().unwrap()}}
}struct Worker {id: usize,thread: thread::JoinHandle<()>,
}trait FnBox {fn call_box(self: Box<Self>);
}impl<F: FnOnce()> FnBox for F {fn call_box(self: Box<F>) {(*self)()}
}type Job = Box<dyn FnBox + Send + 'static>;impl Worker {fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {let thread = thread::spawn(move || loop {while let Ok(job) = receiver.lock().unwrap().recv() {println!("Worker {} got a job; executing.", id);job.call_box();}});Worker { id, thread }}
}

修改优化一:

use std::sync::mpsc;
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;pub struct ThreadPool {workers: Vec<Worker>,sender: mpsc::Sender<Job>,
}// struct Job;
// type Job = Box<FnOnce() + Send + 'static>;
impl ThreadPool {/// Creates a new ThreadPool.////// The size is the number of threads in the pool.////// # Panics////// The `new` function will panic if the size is zero.pub fn new(size: usize) -> ThreadPool {assert!(size > 0);let (sender, receiver) = mpsc::channel();let receiver = Arc::new(Mutex::new(receiver));let mut workers = Vec::with_capacity(size);for id in 0..size {workers.push(Worker::new(id, Arc::clone(&receiver)));}ThreadPool { workers, sender }}pub fn execute<F>(&self, f: F)whereF: FnOnce() + Send + 'static,{let job = Box::new(f);self.sender.send(job).unwrap();}
}impl Drop for ThreadPool {fn drop(&mut self) {for worker in &mut self.workers {println!("Shutting down worker {}", worker.id);if let Some(thread) = worker.thread.take() {thread.join().unwrap();}}}
}struct Worker {id: usize,thread: Option<thread::JoinHandle<()>>,
}trait FnBox {fn call_box(self: Box<Self>);
}impl<F: FnOnce()> FnBox for F {fn call_box(self: Box<F>) {(*self)()}
}type Job = Box<dyn FnBox + Send + 'static>;impl Worker {fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {let thread = thread::spawn(move || loop {while let Ok(job) = receiver.lock().unwrap().recv() {println!("Worker {} got a job; executing.", id);job.call_box();}});Worker {id,thread: Some(thread),}}
}

最终版 lib.rs 文件

use std::sync::mpsc;
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;enum Message {NewJob(Job),Terminate,
}pub struct ThreadPool {workers: Vec<Worker>,sender: mpsc::Sender<Message>,
}// struct Job;
// type Job = Box<FnOnce() + Send + 'static>;
impl ThreadPool {/// Creates a new ThreadPool.////// The size is the number of threads in the pool.////// # Panics////// The `new` function will panic if the size is zero.pub fn new(size: usize) -> ThreadPool {assert!(size > 0);let (sender, receiver) = mpsc::channel();let receiver = Arc::new(Mutex::new(receiver));let mut workers = Vec::with_capacity(size);for id in 0..size {workers.push(Worker::new(id, Arc::clone(&receiver)));}ThreadPool { workers, sender }}pub fn execute<F>(&self, f: F)whereF: FnOnce() + Send + 'static,{let job = Box::new(f);self.sender.send(Message::NewJob(job)).unwrap();}
}impl Drop for ThreadPool {fn drop(&mut self) {println!("Sending terminate message to all workers.");for _ in &mut self.workers {self.sender.send(Message::Terminate).unwrap();}println!("Shutting down all workers.");for worker in &mut self.workers {println!("Shutting down worker {}", worker.id);if let Some(thread) = worker.thread.take() {thread.join().unwrap();}}}
}struct Worker {id: usize,thread: Option<thread::JoinHandle<()>>,
}trait FnBox {fn call_box(self: Box<Self>);
}impl<F: FnOnce()> FnBox for F {fn call_box(self: Box<F>) {(*self)()}
}type Job = Box<dyn FnBox + Send + 'static>;impl Worker {fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Message>>>) -> Worker {let thread = thread::spawn(move || loop {let message = receiver.lock().unwrap().recv().unwrap();match message {Message::NewJob(job) => {println!("Worker {} got a job; executing.", id);job.call_box();}Message::Terminate => {println!("Worker {} got a job; executing.", id);break;}}});Worker {id,thread: Some(thread),}}
}

最终版 main.rs 文件

use hello::ThreadPool;
use std::fs;
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;
use std::thread;
use std::time::Duration;fn main() {let listener = TcpListener::bind("localhost:7878").unwrap();let pool = ThreadPool::new(4);for stream in listener.incoming().take(2) {let stream = stream.unwrap();pool.execute(|| {handle_connection(stream);});}println!("Shutting down.");
}fn handle_connection(mut stream: TcpStream) {let mut buffer = [0; 512];stream.read(&mut buffer).unwrap();let get = b"GET / HTTP/1.1\r\n";let sleep = b"GET /sleep HTTP/1.1\r\n";let (status_line, filename) = if buffer.starts_with(get) {("HTTP/1.1 200 OK\r\n\r\n", "hello.html")} else if buffer.starts_with(sleep) {thread::sleep(Duration::from_secs(5));("HTTP/1.1 200 OK\r\n\r\n", "hello.html")} else {("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")};let contents = fs::read_to_string(filename).unwrap();let response = format!("{}{}", status_line, contents);stream.write(response.as_bytes()).unwrap();stream.flush().unwrap();
}

运行

hello on  master [?] is 📦 0.1.0 via 🦀 1.67.1 
➜ cargo run  Compiling hello v0.1.0 (/Users/qiaopengjun/rust/hello)Finished dev [unoptimized + debuginfo] target(s) in 0.43sRunning `target/debug/hello`
Worker 0 got a job; executing.
Shutting down.
Sending terminate message to all workers.
Shutting down all workers.
Shutting down worker 0
Worker 1 got a job; executing.
Worker 2 got a job; executing.
Worker 3 got a job; executing.
Worker 1 got a job; executing.
Worker 0 got a job; executing.
Shutting down worker 1
Shutting down worker 2
Shutting down worker 3hello on  master [?] is 📦 0.1.0 via 🦀 1.67.1 took 21.9s 
➜ 

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql的多表查询和子查询

多表查询&#xff1a;查询数据时&#xff0c;需要使用多张表来查询 多表查询分类&#xff1a; 1.内连接查询 2.外连接查询 3.子查询 笛卡尔积&#xff1a; create table class (id int primary key auto_increment,name varchar(10) ); create table student (id int primar…

Vue 组件单元测试深度探索:细致解析与实战范例大全

Vue.js作为一款广受欢迎的前端框架&#xff0c;以其声明式的数据绑定、组件化开发和灵活的生态系统赢得了广大开发者的心。然而&#xff0c;随着项目规模的增长&#xff0c;确保组件的稳定性和可靠性变得愈发关键。单元测试作为软件质量的守护神&#xff0c;为Vue组件的开发过程…

uniapp-css多颜色渐变:左右+上下

案例展示 案例代码&#xff1a; 代码灵感&#xff1a;使用伪类进行处理 <view class"headBox"></view>.headBox {height: 200rpx;background: linear-gradient(to right, #D3D5F0, #F0DCF3, #F7F6FB, #DAE8F2, #E1D3EE);position: relative; }.headBox…

数据结构:时间复杂度和空间复杂度

目录 1. 如何衡量一个算法的好坏2. 算法效率3. 时间复杂度3.1 时间复杂度的概念3.2 大O的渐进表示法3.3 推导大O阶方法3.4 常见时间复杂度计算举例 3.空间复杂度 1. 如何衡量一个算法的好坏 下面求斐波那契数列的算法好还是不好&#xff0c;为什么&#xff1f;该如何衡量一个算…

单一职责原则介绍

单一职责原则&#xff08;Single Responsibility Principle&#xff0c;简称SRP&#xff09;是面向对象设计中的一个重要原则&#xff0c;它指出一个类应该只有一个引起变化的原因。换句话说&#xff0c;每个类都应该有一个明确的任务&#xff0c;只做一件事&#xff0c;并做好…

vue 3 —— 笔记(模板语法,响应式变量)

模板语法&#xff1a; Vue 使用一种基于 html 的模板语法&#xff0c;使我们能声明式将其组件实例绑定到呈现的 dom 上 文本插值 基础数据绑定形式 双大括号 会替换相应组件实例 msg 属性的值 原始html 双大括号会将数据解释为纯文本 不是html 想插入html 使用 v-html 指令 &…

Kubernetes - CentOS7搭建k8s_v1.18集群高可用(kubeadm/二进制包部署方式)实测配置验证手册

Kubernetes - CentOS7搭建k8s集群高可用&#xff08;kubeadm/二进制包部署方式&#xff09;实测配置验证手册 前言概述&#xff1a; 一、Kubernetes—k8s是什么 Kubernetes 这个名字源于希腊语&#xff0c;意为“舵手“或”飞行员"。 Kubernetes&#xff0c;简称K8s&#…

【声网】实现web端与uniapp微信小程序端音视频互动

实现web端与uniapp微信小程序端音视频互动 利用声网实现音视频互动 开通声网服务 注册声网账号 进入Console 成功登录控制台后&#xff0c;按照以下步骤创建一个声网项目&#xff1a; 展开控制台左上角下拉框&#xff0c;点击创建项目按钮。 在弹出的对话框内&#xff0c;依…

Angular创建项目

Angular创建项目 文章目录 Angular创建项目1. 创建项目1.1 直接安装1.2 跳过npm i安装 2. 运行程序 1. 创建项目 ng new 项目名称 1.1 直接安装 ng new angulardemo --同时会安装依赖包&#xff0c;执行的命令就是npm i 1.2 跳过npm i安装 ng new angulardemo --skip-inst…

SpringWebFlux RequestBody多出双引号问题——ProxyPin抓包揪出真凶

缘起 公司有个服务做埋点收集的&#xff0c;可以参考我之前的文章埋点日志最终解决方案&#xff0c;今天突然发现有些数据日志可以输出&#xff0c;但是没法入库。 多出的双引号 查看Flink日志发现了JSON解析失败&#xff0c;Flink是从Kafka拿数据&#xff0c;Kafka本身不处…

Servlet和Tomcat运作过程

记录一下前后端请求交互过程&#xff08;不涉及Spring框架&#xff09;&#xff1a; 编写一个UserServlet 在web.xml文件中编写映射路径 编写前端

chrome浏览器安装elasticsearch的head可视化插件

head插件简介 elasticsearch-head被称为是弹性搜索集群的web前端&#xff0c;head插件主要是用来和elastic Cluster交互的Web前端 head插件历史 elasticsearch-head插件在0.x-2.x版本的时候是集成在elasticsearch内的&#xff0c;由elasticsearch的bin/elasticsearch-plugin…