【Rust】 非同期TCP/IPの送受信を行うプログラム

前回の記事で,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.rsclient.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を使うことでかなり簡単に非同期処理を行うことができました.

よかったらシェアしてね!
目次