以前にC/C++でTCI/IPの送受信プログラムを作成しました.
この記事では,RustでTCP/IPの送受信プログラムを作成する方法を記載します.
目次
ソケット通信とは
ソケットは通信の出入口のようなものです.TCP/IPなどに代表されるHTTP通信などもソケット通信の一つです.すなわち,ソケットを使用することで異なるマシン間(もしくは同一マシン上)の異なるプロセス間で通信を可能にします.
Unixソケットと比較すると,TCP/IPソケットはクロスプラットフォームにも対応できるというメリットがあります.
ソケットプログラミング
C/C++用に作成した際と同様に,下記のサンプル問題を考えます.
- ソケットを作成
- 接続を確立
- クライアントが文字列を送信し,サーバが受け取る
- サーバが文字列を送信し,クライアントが受け取る
- 接続を閉じる
プロジェクト作成
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.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環境で動作します.
クロスコンパイルの方法に関しては下記記事をご覧ください.
まとめ
以前作成したC/C++のソケットプログラムと比較すると,メモリ安全の観点で明らかな安心感があり,クロスコンパイルも容易にできます.この辺りは,Rustのメリットといったところです.
実運用を考えたら非同期処理にすることが有効です.非同期処理を導入したプログラムは下記記事にまとめました.