ããã¯ããªã«ãããããŠæžãããã®ïŒ
åã«ãRustã®async-awaitã«ã€ããŠã¡ã¢ããŠããŸããã
Rustのasync-awaitに関するドキュメントなどのメモ - CLOVER🍀
Rustã§ã¯ãµãŒããŒãµã€ãã®ã¢ããªã±ãŒã·ã§ã³ãæžãæã«ã¯éåæåŠçãåºãŠããããšãå€ãããã§ãæ £ããŠãããæ¹ãããããã§ãã
ãŸãRustã§éåæåŠçãæ±ãããšæããšãªã«ãããå°å ¥ããªããŠã¯ãªããªãããããtokioãšãããã®ã詊ããŠããããšæããŸãã
tokio
tokioã¯Rustã®éåæã©ã³ã¿ã€ã ã§ãã
Tokio is an asynchronous runtime for the Rust programming language.
Tokio - An asynchronous Rust runtime
GitHubãªããžããªãŒã¯ãã¡ãã
tokioã®ãããããŒãžãèŠããšãç¹åŸŽã¯ä»¥äžã®ããã«æžãããŠããŸãã
- ãªã©ã€ã¢ãã«ïŒä¿¡é Œæ§ãããïŒ
- é«é
- ãã«ãã¹ã¬ããã®ã¯ãŒã¯ã¹ãã¢ãªã³ã°ã¹ã±ãžã¥ãŒã©ãŒãæäŸ
- ç°¡å
- async-awaitã䜿ã£ãéåæã¢ããªã±ãŒã·ã§ã³äœæã®è€éãã軜æž
- ãã¬ãã·ãã«
- ããã«äœ¿ããããã©ã«ãèšå®ãä»å±ããŠããããæ§ã ãªãŠãŒã¹ã±ãŒã¹ã«åãããŠåŸ®èª¿æŽããããšãå¯èœ
Tokio - An asynchronous Rust runtime
ããã«äžã®æ¹ãèŠããšã以äžã®ãããªããšãç¹åŸŽãšããŠæžãããŠããŸãã
- æ¬çªç°å¢ã§ããã«äœ¿ãããã«å¿ èŠãªãã®ãæã£ãŠãã
- IOãã¿ã€ããŒããã¡ã€ã«ã·ã¹ãã ãåæãããã³ã¹ã±ãžã¥ãŒã«æ©èœãå«ãåºç€ãæäŸ
- HTTP 1ãš2ããµããŒã
- gRPCããµããŒã
- ä¿¡é Œæ§ã®é«ãã¯ã©ã€ã¢ã³ããšãµãŒããŒãæ§ç¯ããããã®ã¢ãžã¥ãŒã«ã³ã³ããŒãã³ããæäŸ
- å詊è¡ãè² è·åæ£ããã£ã«ã¿ãªã³ã°ãã¬ãŒããªããããªã©ãå«ãŸãã
- OSã®ã€ãã³ãIO APIãããŒã¹ã«ããæå°éã®ããŒã¿ãã«API
- æ§é åãããã€ãã³ãããŒã¹ã®ããŒã¿åéãšãã®ã³ã°æ©èœãæäŸ
- ãã€ãé åãæäœããããã®è±å¯ãªãŠãŒãã£ãªãã£
ããã¥ã¡ã³ãã¯ãã¡ãã§ãã
Tutorial | Tokio - An asynchronous Rust runtime
æŠèŠããŒãžã«ã¯ãããŸã§èŠãŠããããšãæžãããŠããŸãããtokioã䜿ããªãæ¹ãããã±ãŒã¹ã«ã€ããŠãæžãããŠããŸãã
- CPUããŠã³ããªåŠçã§äžŠåæ§ãæ±ããå Žå
- tokioã¯IOããŠã³ããªã¢ããªã±ãŒã·ã§ã³åãã«èšèšãããŠãã
- ãã®ãããªã±ãŒã¹ã§ã¯Rayonã䜿ã£ãæ¹ãããïŒçµã¿åãããããšãå¯èœïŒ
- 倧éã®ãã¡ã€ã«ã®èªã¿åã
- OSãäžè¬ã«éåæãã¡ã€ã«APIãæäŸããŠããªããã
- åäžã®Webãªã¯ãšã¹ããéä¿¡ããå Žå
Tutorial / When not to use Tokio
䜿çšããã©ã€ãã©ãªãŒãåæAPIãæäŸããªãå Žåã¯ã以äžã®ããŒãžãåèã«ãããšããã¿ããã§ãã
Bridging with sync code | Tokio - An asynchronous Rust runtime
APIããã¥ã¡ã³ãã¯ãã¡ãã
ã§ã¯ãããã¥ã¡ã³ãã®ããŒãžã®ããã€ããèŠãªãããTCP EchoãµãŒããŒïŒã¯ã©ã€ã¢ã³ãããé¡ã«ãŸãã¯tokioã䜿ã£ãŠã¿ãŸãããã
åã«æžããTCP EchoãµãŒããŒïŒã¯ã©ã€ã¢ã³ãã®å 容ãåŒãç¶ãã€ã€ã
RustでTCP Echoサーバー/クライアントを書いてみる - CLOVER🍀
ãã®ããããåèã«æžããŠããããšæããŸãã
Hello Tokio | Tokio - An asynchronous Rust runtime
Spawning | Tokio - An asynchronous Rust runtime
I/O | Tokio - An asynchronous Rust runtime
ç°å¢
ä»åã®ç°å¢ã¯ãã¡ãã
$ rustup --version rustup 1.27.1 (54dd3d00f 2024-04-24) info: This is the version for the rustup toolchain manager, not the rustc compiler. info: The currently active `rustc` version is `rustc 1.84.0 (9fc6b4312 2025-01-07)`
æºå
Cargoããã±ãŒãžã®äœæã
$ cargo new --vcs none tokio-tcp-echo $ cd tokio-tcp-echo
tokioã®ã€ã³ã¹ããŒã«ã
$ cargo add tokio --features full
ãã£ãŒãã£ãŒã«fullãä»ããŠããŸãããããã¯tokioã®æ©èœããã¹ãŠå«ããã®ã ããã§ãã
å®éã«äœ¿ãæã«ã¯å¿
èŠãªãã£ãŒãã£ãŒã«çµã£ãæ¹ãè¯ãããã§ãããã²ãšãŸãtokioã®ãã¥ãŒããªã¢ã«ã§ã¯fullãåæã«ãªã£ãŠããŸãã
æå®ã§ãããã£ãŒãã£ãŒã¯ãã¡ãã
ããšã¯ãã°åºåçšã«logãenv_loggerãchronoã¯ã¬ãŒãã远å ã
$ cargo add log env_logger chrono
Cargo.toml
[package]
name = "tokio-tcp-echo"
version = "0.1.0"
edition = "2021"
[dependencies]
chrono = "0.4.39"
env_logger = "0.11.6"
log = "0.4.25"
tokio = { version = "1.43.0", features = ["full"] }
ãµãŒããŒãšã¯ã©ã€ã¢ã³ãããããããã€ããªãŒã¯ã¬ãŒããšããŠæ±ãããšã«ããŸããmain.rsã¯èŠããªãã®ã§åé€ã
$ rm src/main.rs
ããã§æºåãã§ããŸããã
ãã€ããªãŒã¯ã¬ãŒãã§äœ¿çšããã©ã€ãã©ãªãŒã¯ã¬ãŒããäœæãã
å
ãã¿ãšåãããã«ããµãŒããŒïŒã¯ã©ã€ã¢ã³ãã®ãã€ããªãŒã¯ã¬ãŒããã䜿ãã³ãŒãã¯ãã©ã€ãã©ãªãŒã¯ã¬ãŒããšããŠ
äœæããããšã«ããŸãã
ã²ãšãŸãå®æåœ¢ã¯ãã¡ãã
src/lib.rs
use log::{error, info}; use tokio::{ io::{split, AsyncBufReadExt, AsyncWriteExt, BufReader, Result}, net::{TcpListener, TcpStream}, spawn, }; pub async fn start_server(address: &str, port: i32) -> Result<()> { let listener = TcpListener::bind(format!("{}:{}", address, port)).await?; info!("tcp echo server[{}:{}] startup.", address, port); loop { let (stream, address) = listener.accept().await?; info!("accept[{}]", address); spawn(async move { let result = reply(stream).await; match result { Ok(_) => {} Err(error) => { error!("{}", error) } } }); } } async fn reply(mut stream: TcpStream) -> Result<()> { let mut reader = BufReader::new(&mut stream); let mut line = String::new(); reader.read_line(&mut line).await?; let message = line.trim(); info!("received message = {}", message); let response_message = format!("â â â {}â â â ", message); stream.write_all(response_message.as_bytes()).await?; stream.flush().await?; Ok(()) } pub struct Client { stream: TcpStream, } impl Client { pub async fn send(self, message: &str) -> Result<String> { let (mut rd, mut wr) = split(self.stream); let msg = message.to_string(); let _ = spawn(async move { wr.write_all(msg.as_bytes()).await?; wr.write_all("\r\n".as_bytes()).await?; wr.flush().await?; Ok::<_, tokio::io::Error>(()) }) .await?; let mut reader = BufReader::new(&mut rd); let mut line = String::new(); reader.read_line(&mut line).await?; let received_message = line.trim(); Ok(String::from(received_message)) } } pub async fn connect(address: &str, port: i32) -> Result<Client> { let stream = TcpStream::connect(format!("{}:{}", address, port)).await?; Ok(Client { stream }) }
ãããšèŠãuseã§æå®ããŠããã¢ãžã¥ãŒã«åããéããã®ã®ãRustã®æšæºã©ã€ãã©ãªãŒã䜿ã£ãŠäœã£ããã®ãšããªã䌌ãæãã«
ãªã£ãŠããŸãã
ããããã®ã¯ãã§ãtokioã®åã¯Rustã®æšæºã©ã€ãã©ãªãŒïŒåæåïŒãšåãååãä»ããããŠããããã§ããã
éãã¯éåæïŒasync fnïŒã§ããããšã§ãã
Many of Tokio's types are named the same as their synchronous equivalent in the Rust standard library. When it makes sense, Tokio exposes the same APIs as std but using async fn.
ãµãŒããŒåŽ
ãŸãã¯ãµãŒããŒåŽããèŠãŠãããŸããããtokioã®åã¯Rustã®æšæºã©ã€ãã©ãªãŒãšåãååãä»ããããŠãããšãã話ã ã£ãã®ã§ã
TCPãœã±ããããªãã¹ã³ããã®ãTcpListenerã§ãã
pub async fn start_server(address: &str, port: i32) -> Result<()> { let listener = TcpListener::bind(format!("{}:{}", address, port)).await?; info!("tcp echo server[{}:{}] startup.", address, port);
ãã®é¢æ°ã¯ãµãŒããŒåŽã®ã¯ã¬ãŒãããåŒã³åºããšã³ããªãŒãã€ã³ãã«ãªããŸãããasync fnãšããŠå®çŸ©ããŸãã
ResultãåããŠäœ¿ã圢ãªã®ã§ãããããã¯std::result::Resultã§ã¯ãªãstd::io::Resultã§ãããããŠtokioãstd::io::Resultã
åãšã¯ã¹ããŒãããŠtokio::io::ResultãšããŠäœ¿ããããã«ããŠããŸãã
ããã¯std::result::Resultãå°ãç°¡åã«äœ¿ããããã«ããtypeã§ããã
pub type Result<T> = Result<T, Error>;
ã¯ã©ã€ã¢ã³ãããã¡ãã»ãŒãžãèªã¿åºããã¡ãã»ãŒãžãè¿ããšããã颿°ãasync fnãšãªããå颿°ã®åŒã³åºãã«await?ã
ä»ããŠãã以å€ã¯æšæºã©ã€ãã©ãªãŒãšã»ãŒåãã§ãã
async fn reply(mut stream: TcpStream) -> Result<()> { let mut reader = BufReader::new(&mut stream); let mut line = String::new(); reader.read_line(&mut line).await?; let message = line.trim(); info!("received message = {}", message); let response_message = format!("â â â {}â â â ", message); stream.write_all(response_message.as_bytes()).await?; stream.flush().await?; Ok(()) }
async fnã¯ãstd::future::Futureãè¿ã颿°ã§ãããããŠå®éã«æäœãå®è¡ããŠæ»ãå€ãåŸãã«ã¯ãawaitã䜿ããŸãã
Hello Tokio / Compile-time green-threading
Hello Tokio / Using async/await
颿°ã®æ»ãå€ã«ã¯Resultã䜿ã£ãŠããŸãã
pub async fn start_server(address: &str, port: i32) -> Result<()> {
ããã¯tokioã®ããã¥ã¡ã³ãã®äŸã«ç¿ã£ãŠãããã®ã§ãããuseããŠããã®ã¯tokio::io::Resultã§ãã
use tokio::{ io::{split, AsyncBufReadExt, AsyncWriteExt, BufReader, Result}, net::{TcpListener, TcpStream}, spawn, };
ããã¯std::io::Resultãåãšã¯ã¹ããŒããããã®ã§ãã
ãããŠstd::io::Resultã¯std::result::Resultã®åãšã€ãªã¢ã¹ïŒtypeïŒã§ãã
pub type Result<T> = Result<T, Error>;
ã€ãŸãErrorãçç¥ã§ãããšããæãã§ããã
ã¯ã©ã€ã¢ã³ãããã®æ¥ç¶ãæ±ãéšåãèŠãŠã¿ãŸãã
loop { let (stream, address) = listener.accept().await?; info!("accept[{}]", address); spawn(async move { let result = reply(stream).await; match result { Ok(_) => {} Err(error) => { error!("{}", error) } } }); }
loopã§ã«ãŒããç¹°ãè¿ããŠããŸãããç¹åŸŽçãªã®ã¯spawnãšasync modeã§ããã
tokio::spawnã¯éåæã¿ã¹ã¯ãéå§ããtokioã®é¢æ°ã§ãã
Spawning / Concurrency / Tasks
tokioã®ã¿ã¹ã¯ã¯éåæã°ãªãŒã³ã¹ã¬ããã§ãasyncãããã¯ãtokio::spawn颿°ã«æž¡ãããšã§äœæããŸãã
tokio::spawnã¿ã¹ã¯ã¯JoinHandleãè¿ãããŸãéåæã¿ã¹ã¯ã¯æ»ãå€ãæã€ããšãã§ããŸãã
A Tokio task is an asynchronous green thread. They are created by passing an async block to tokio::spawn. The tokio::spawn function returns a JoinHandle, which the caller may use to interact with the spawned task. The async block may have a return value.
éåæã¿ã¹ã¯ãã©ã®ã¹ã¬ããã§å®è¡ããããã¯ã¹ã±ãžã¥ãŒã©ãŒã«ãã£ãŠæ±ºãŸããã¿ã¹ã¯ãçæããã¹ã¬ãããšåãã¹ã¬ããã§
å®è¡ãããããšãããã°ãå¥ã®ã¹ã¬ããã§å®è¡ãããããšããããããã«ã¹ã¬ããéãç§»åããããšããããŸãã
Tasks are the unit of execution managed by the scheduler. Spawning the task submits it to the Tokio scheduler, which then ensures that the task executes when it has work to do. The spawned task may be executed on the same thread as where it was spawned, or it may execute on a different runtime thread. The task can also be moved between threads after being spawned.
Spawning / Concurrency / Tasks
tokioã®ã¿ã¹ã¯ã¯éåžžã«è»œéã§ãå
éšçã«ã¯1床ã®ã¢ãã±ãŒã·ã§ã³ãš64ãã€ãã®ã¡ã¢ãªãŒãå¿
èŠãšããŸããã¢ããªã±ãŒã·ã§ã³ã¯
æ°åãæ°çŸäžã®ã¿ã¹ã¯ãçæã§ããŸãã
Tasks in Tokio are very lightweight. Under the hood, they require only a single allocation and 64 bytes of memory. Applications should feel free to spawn thousands, if not millions of tasks.
tokioã§éåæã¿ã¹ã¯ãçæããå Žåããã®æå¹æéã¯'staticã§ããå¿
èŠããããŸããã€ãŸããã¿ã¹ã¯å€éšã§ææãããŠãã
ããŒã¿ãžã®åç
§ãå«ããããšãã§ããŸããã
When you spawn a task on the Tokio runtime, its type's lifetime must be 'static. This means that the spawned task must not contain any references to data owned outside the task.
Spawning / Concurrency / 'static bound
ããã§async moveã䜿ããšãã¿ã¹ã¯ã®å€éšã§çæããã倿°ã®æææš©ãéåæã¿ã¹ã¯åŽã«ç§»åã§ããããã«ãªããŸãã
spawn(async move { let result = reply(stream).await;
Changing line 7 to task::spawn(async move { will instruct the compiler to move v into the spawned task. Now, the task owns all of its data, making it 'static.
ã¡ãªã¿ã«ãåäžã®ããŒã¿ã«è€æ°ã®ã¿ã¹ã¯ããåæã«ã¢ã¯ã»ã¹ãããããªå Žåã¯ãArcãšãããã®ã䜿ãããã§ãã
If a single piece of data must be accessible from more than one task concurrently, then it must be shared using synchronization primitives such as Arc.
éåæã¿ã¹ã¯ã¯ãSendãã¬ã€ããå®è£
ããŠããå¿
èŠããããããã§tokioã¯awaitã§äžæãããŠããã¿ã¹ã¯ãã¹ã¬ããéã§
ç§»åã§ããŸãã
Tasks spawned by tokio::spawn must implement Send. This allows the Tokio runtime to move the tasks between threads while they are suspended at an .await.
Spawning / Concurrency / Send bound
ã¯ã©ã€ã¢ã³ãåŽ
ç¶ããŠã¯ã¯ã©ã€ã¢ã³ãåŽã§ãã
ä»åãæ§é äœãçšæããŸãããTcpStreamã¯tokioã®ãã®ã§ãã
pub struct Client { stream: TcpStream, }
ãµãŒããŒã«ã¡ãã»ãŒãžãéä¿¡ããã¡ãœããå®çŸ©ã
impl Client { pub async fn send(self, message: &str) -> Result<String> { let (mut rd, mut wr) = split(self.stream); let msg = message.to_string(); let _ = spawn(async move { wr.write_all(msg.as_bytes()).await?; wr.write_all("\r\n".as_bytes()).await?; wr.flush().await?; Ok::<_, tokio::io::Error>(()) }) .await?; let mut reader = BufReader::new(&mut rd); let mut line = String::new(); reader.read_line(&mut line).await?; let received_message = line.trim(); Ok(String::from(received_message)) } }
æž¡ãããã¡ãã»ãŒãžãçŽ çŽã«TcpStreamã䜿ã£ãŠæžã蟌ã¿ããã®åŸã§èªã¿åºãã°ããã®ã§ãããä»åã¯ããããããŠ
æžã蟌ã¿ãšèªã¿èŸŒã¿ãåå²ããããšã«ããŸããã
I/O / Echo server / Splitting a reader + writer
ããã¥ã¡ã³ãã®ããã«ãªã£ãŠããŸãããtokio::io::splitã§TcpStreamã`tokio::io::AsyncReadãštokio::io::AsyncWriteã«
åå²ããŸãã
let (mut rd, mut wr) = split(self.stream);
AsyncWrite in tokio::io - Rust
ãŸãã¯æžã蟌ã¿ãéåæã¿ã¹ã¯ã§è¡ããŸãã
let _ = spawn(async move { wr.write_all(msg.as_bytes()).await?; wr.write_all("\r\n".as_bytes()).await?; wr.flush().await?; Ok::<_, tokio::io::Error>(()) }) .await?;
ããã§async moveã䜿ããAsyncWriteã®æææš©ãéåæã¿ã¹ã¯ã«ç§»ããŠããŸãã
æžã蟌ã¿ãawaitã§åŸ
ã£ãåŸãAsyncReadã䜿ã£ãŠãµãŒããŒããè¿ã£ãŠããçµæãèªã¿åºããŸãã
let mut reader = BufReader::new(&mut rd); let mut line = String::new(); reader.read_line(&mut line).await?; let received_message = line.trim(); Ok(String::from(received_message))
ããã§tokio::io::splitã䜿ããã«TcpStreamããã®ãŸãŸéåæã¿ã¹ã¯ã«æž¡ããŠããŸããšãæææš©ãéåæã¿ã¹ã¯ã«ç§»ã£ãŠ
ããŸãã®ã§ãã®åŸã®ã¡ãã»ãŒãžã®èªã¿åºãã§å°ã£ãããšã«ãªããŸãã
ãã®ãããªçšéã®ããã«TcpStreamãèªã¿èŸŒã¿ãšæžã蟌ã¿ã§åå²ã§ããããã«ããŠããããã§ãã
ãã®èª¬æã¯ãtokio::io::copyã䜿ãæã«èªã¿èŸŒã¿å
ãæžã蟌ã¿å
ãTcpStreamãšãªã£ãŠããŸãåé¡ãåé¿ããäŸãšããŠ
æžãããŠããŸãã
As seen earlier, this utility function takes a reader and a writer and copies data from one to the other. However, we only have a single TcpStream. This single value implements both AsyncRead and AsyncWrite. Because io::copy requires &mut for both the reader and the writer, the socket cannot be used for both arguments.
I/O / Echo server / Using io::copy()
æ®ãã¯ãTcpStream::connectã§æ¥ç¶ã確ç«ããã³ãŒãã§ãã
pub async fn connect(address: &str, port: i32) -> Result<Client> { let stream = TcpStream::connect(format!("{}:{}", address, port)).await?; Ok(Client { stream }) }
ãµãŒããŒåŽã®ãã€ããªãŒã¯ã¬ãŒããäœæãã
ã§ã¯ããµãŒããŒåŽã®ãã€ããªãŒã¯ã¬ãŒããäœæããŸããsrc/biné
äžã«äœæããŸãã
src/bin/server.rs
use std::env::args; use std::io::Write; use std::thread; use chrono::Local; use tokio::io::Result; use tokio_tcp_echo::start_server; #[tokio::main] async fn main() -> Result<()> { env_logger::builder() .format(|buf, record| { writeln!( buf, "[{} {} server] {} - {}", Local::now().format("%Y-%m-%d %H:%M:%S"), record.level(), thread::current().name().unwrap(), record.args() ) }) .init(); let address = args().nth(1).unwrap(); let port = args().nth(2).unwrap().parse::<i32>().unwrap(); start_server(&address, port).await?; Ok(()) }
ã³ãã³ãã©ã€ã³åŒæ°ã¯ããã€ã³ãããã¢ãã¬ã¹ãšãªãã¹ã³ããŒãã§ãããŸãenv_loggerã®åæåã®éã«ãã¹ã¬ããåã
ãã°ã®å
容ã«å«ããããã«ããŠããŸãã
1çªã®ãã€ã³ãã¯ãmain颿°ã®æžãæ¹ã§ããã
#[tokio::main] async fn main() -> Result<()> {
#[tokio::main]屿§ãä»ããŸããããã¯ãã¯ãã§ã#[tokio::main]ã䜿ãããšã§ä»¥äžã®main颿°ãæžããš
#[tokio::main] async fn main() { println!("hello"); }
以äžã®ããã«å€æãããããã§ãã
fn main() { let mut rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(async { println!("hello"); }) }
Hello Tokio / Breaking it down / Async main function
èµ·åããŠã¿ãŸãããã
$ RUST_LOG=info cargo run --bin server localhost 5000
確èªã
$ echo hello | nc localhost 5000 â â â helloâ â â $ echo ããã«ã¡ã¯ãäžç | nc localhost 5000 â â â ããã«ã¡ã¯ãäžçâ â â
OKã§ããã
ãã®æã®ãã°ãèŠããšãéåæã¿ã¹ã¯ã¯tokioã®ã¹ã¬ããã§åäœããŠããããšã確èªã§ããŸãã
[2025-01-25 14:39:13 INFO server] main - accept[127.0.0.1:46604] [2025-01-25 14:39:13 INFO server] tokio-runtime-worker - received message = hello [2025-01-25 14:39:14 INFO server] main - accept[127.0.0.1:46620] [2025-01-25 14:39:14 INFO server] tokio-runtime-worker - received message = ããã«ã¡ã¯ãäžç
ã¯ã©ã€ã¢ã³ãåŽã®ãã€ããªãŒã¯ã¬ãŒããäœæãã
æåŸã¯ã¯ã©ã€ã¢ã³ãåŽã®ãã€ããªãŒã¯ã¬ãŒããäœæããŸãã
src/bin/client.rs
use std::env::args; use log::info; use tokio::io::Result; use tokio_tcp_echo::connect; #[tokio::main] async fn main() -> Result<()> { env_logger::init(); let address = args().nth(1).unwrap(); let port = args().nth(2).unwrap().parse::<i32>().unwrap(); let message = args().nth(3).unwrap(); let client = connect(&address, port).await?; info!("connected tcp server[{}:{}]", &address, port); info!("send message = {}", message); let received_message = client.send(&message).await?; info!("received message = {}", received_message); info!("disconnect"); Ok(()) }
åŒæ°ã¯3ã€ã§ãæ¥ç¶å ã®ã¢ãã¬ã¹ãããŒãããããŠéä¿¡ããã¡ãã»ãŒãžã§ãã
ç¹åŸŽçãªãã®ã¯ããŸããªãã®ã§ããã®ãŸãŸå®è¡
$ RUST_LOG=info cargo run --bin client localhost 5000 hello
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/client localhost 5000 hello`
[2025-01-25T05:41:55Z INFO client] connected tcp server[localhost:5000]
[2025-01-25T05:41:55Z INFO client] send message = hello
[2025-01-25T05:41:55Z INFO client] received message = â
â
â
helloâ
â
â
[2025-01-25T05:41:55Z INFO client] disconnect
$ RUST_LOG=info cargo run --bin client localhost 5000 ããã«ã¡ã¯ãäžç
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/client localhost 5000 'ããã«ã¡ã¯ãäžç'`
[2025-01-25T05:42:13Z INFO client] connected tcp server[localhost:5000]
[2025-01-25T05:42:13Z INFO client] send message = ããã«ã¡ã¯ãäžç
[2025-01-25T05:42:13Z INFO client] received message = â
â
â
ããã«ã¡ã¯ãäžçâ
â
â
[2025-01-25T05:42:13Z INFO client] disconnect
OKã§ããã
ãããã«
åã®ãšã³ããªãŒã®çŒãçŽãã§ãããtokioã䜿ã£ãŠTCP EchoãµãŒããŒïŒã¯ã©ã€ã¢ã³ããæžããŠã¿ãŸããã
asyncãawaitèªäœã¯ããããããªãã£ãã®ã§ãããtokioã®åã®æžãæ¹ã§ããã£ãããæææš©ã®ç§»åã®ãšããã§
ããããããªããªã£ãããEchoã¯ã©ã€ã¢ã³ãã®æžãæ¹ãã³ãããšå¿ããŠãããããŠãã£ããèŠåŽããŸããã
ãŸãã¯åºæ¬çãªãšãããã確èªããŠãããŠããã£ãã§ãããã¡ãã£ãšãã€æ £ããŠãããªããšã§ããã