前回の記事で,RustによるTCP/IPプログラムを作成しました.
実際の運用では,非同期TCP/IPによって効率的な処理を行うことが一般的です.
この記事では,Rustによる非同期TCP/IP送受信を行うプログラムを作成する方法を記載します.
前回の記事はこちらです.
非同期通信とは
同期処理とは,タスクを順番に一つずつ完了させる方式です.
一方で,非同期処理は複数のタスクを同時にまたはオーバーラップして実行する方式です.
例えば,ファイルを複数同時にダウンロードする場面を考えると,同期処理の場合,一つのファイルが完全にダウンロードされるまで次のファイルのダウンロードは開始されません.しかし,非同期処理の場合,複数のファイルのダウンロードを同時にまたはオーバーラップして実行できます.これにより,全体の通信の効率や応答速度が向上します。
この記事ではRustの非同期ランタイムとして有名な”Tokio”を利用します.Tokioは,特にネットワークアプリケーションの高性能化に重視しており,タスクスケジューリングの効率化や非同期I/Oなどをサポートしています.
プログラム例
前回の同期TCP/IPプログラムとの変更点は下記の通りです.
1. 標準ライブラリの代わりにTokioの利用します.
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
2. 関数をasync/await化します.下記が一例です.
#[tokio::main]
async fn main() {
...
match listener.accept().await {
...
サーバ側プログラム
server.rs
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
#[tokio::main]
async fn main() {
let port_number = 12345;
let listener = TcpListener::bind(SocketAddr::new(
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
port_number,
))
.await
.expect("Failed to bind to address");
println!("Listening for connections on port {}", port_number);
match listener.accept().await {
Ok((mut socket, addr)) => {
println!("Accepted connection from {:?}", addr);
let mut buffer = [0; 1024];
match socket.read(&mut buffer).await {
Ok(bytes_read) => {
let message = String::from_utf8_lossy(&buffer[..bytes_read]);
println!("Received message is \"{}\"", message);
}
Err(e) => {
eprintln!("Error reading from client: {}", e);
std::process::exit(1);
}
}
let response = "Hello from server";
if let Err(e) = socket.write_all(response.as_bytes()).await {
eprintln!("Error sending message to client: {}", e);
std::process::exit(1);
} else {
println!("Message sent to client");
}
}
Err(e) => {
eprintln!("Accept error: {}", e);
std::process::exit(1);
}
}
}
クライアント側プログラム
client.rs
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
#[tokio::main]
async fn main() {
let port_number = 12345;
let server_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port_number);
let mut stream = match TcpStream::connect(server_address).await {
Ok(stream) => stream,
Err(_) => {
println!("\nConnection Failed \n");
std::process::exit(1);
}
};
let message = "Hello from client";
match stream.write_all(message.as_bytes()).await {
Ok(_) => println!("Message sent to server"),
Err(e) => {
eprintln!("Error sending message: {}", e);
std::process::exit(1);
}
}
let mut buffer = [0u8; 1024];
match stream.read(&mut buffer).await {
Ok(bytes_read) => {
let received_message = String::from_utf8_lossy(&buffer[..bytes_read]);
println!("Received message is \"{}\"", received_message);
}
Err(e) => {
eprintln!("Error reading from server: {}", e);
std::process::exit(1);
}
}
}
ビルドと実行
まず,server.rs
とclient.rs
を同時にビルドします.
cargo build
次に,サーバプログラムを立ち上げて待ち受け状態にします.
cargo run --bin server
...
Listening for connections on port 12345
サーバが立ち上がったら,クライアントプログラムを実行します.実行するとサーバと通信して次のような出力が得られます.
cargo run --bin client
...
Message sent to server
Received message is "Hello from server"
サーバ側では次のような出力となります.
Listening for connections on port 12345
Accepted connection from 127.0.0.1:64993
Received message is "Hello from client"
Message sent to client
クロスコンパイル
このプログラムはクロスプラットフォームに対応しており,WindowsおよびLinux環境で動作します.
クロスコンパイルの方法に関しては下記記事をご覧ください.
まとめ
今回の記事では,前回記事で作成したTCP/IPプログラムを非同期処理に変更しました.
Tokioを使うことでかなり簡単に非同期処理を行うことができました.