【Rust】 TCP/IPで送受信を行うプログラム

以前にC/C++でTCI/IPの送受信プログラムを作成しました.

この記事では,RustでTCP/IPの送受信プログラムを作成する方法を記載します.

目次

ソケット通信とは

ソケットは通信の出入口のようなものです.TCP/IPなどに代表されるHTTP通信などもソケット通信の一つです.すなわち,ソケットを使用することで異なるマシン間(もしくは同一マシン上)の異なるプロセス間で通信を可能にします.

Unixソケットと比較すると,TCP/IPソケットはクロスプラットフォームにも対応できるというメリットがあります.

ソケットプログラミング

C/C++用に作成した際と同様に,下記のサンプル問題を考えます.

  1. ソケットを作成
  2. 接続を確立
  3. クライアントが文字列を送信し,サーバが受け取る
  4. サーバが文字列を送信し,クライアントが受け取る
  5. 接続を閉じる

プロジェクト作成

Cargoによりプロジェクト作成を作成します.


cargo new tcpip

ディレクトリ構造は次のようなものを想定します.


tcpip/
├── Cargo.toml
└── src/
    └── bin/
        ├── client.rs
        └── server.rs

サーバ側プログラム

server.rs


use std::io::{Read, Write};
use std::net::{IpAddr, Ipv4Addr, Shutdown, SocketAddr, TcpListener};

fn main() {
    let port_number = 12345;
    let listener = TcpListener::bind(SocketAddr::new(
        IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
        port_number,
    ))
    .expect("Failed to bind to address");

    println!("Listening for connections on port {}", port_number);

    match listener.accept() {
        Ok((mut socket, addr)) => {
            println!("Accepted connection from {:?}", addr);

            let mut buffer = [0; 1024];
            match socket.read(&mut buffer) {
                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()) {
                eprintln!("Error sending message to client: {}", e);
                std::process::exit(1);
            } else {
                println!("Message sent to client");
            }

            // Close the connection.
            socket.shutdown(Shutdown::Both).expect("Shutdown failed");
        }
        Err(e) => {
            eprintln!("Accept error: {}", e);
            std::process::exit(1);
        }
    }
}

クライアント側プログラム

client.rs


use std::io::{Read, Write};
use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream};

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) {
        Ok(stream) => stream,
        Err(_) => {
            println!("\nConnection Failed \n");
            std::process::exit(1);
        }
    };

    let message = "Hello from client";
    match stream.write_all(message.as_bytes()) {
        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) {
        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環境で動作します.

クロスコンパイルの方法に関しては下記記事をご覧ください.

まとめ

以前作成したC/C++のソケットプログラムと比較すると,メモリ安全の観点で明らかな安心感があり,クロスコンパイルも容易にできます.この辺りは,Rustのメリットといったところです.

実運用を考えたら非同期処理にすることが有効です.非同期処理を導入したプログラムは下記記事にまとめました.

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