1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RustによるTCP Clientの実装例(C#との比較)

Posted at

C#のコード

C#
using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

public class TcpClientExample
{
    public static async Task Main(string[] args)
    {
        string serverIp = "127.0.0.1"; // サーバーの IP アドレス
        int serverPort = 8888;        // サーバーのポート番号
        string messageToSend = "Hello from client!"; // 送信するメッセージ

        try
        {
            // TcpClient のインスタンスを作成
            using (TcpClient client = new TcpClient())
            {
                // サーバーに接続
                Console.WriteLine($"Connecting to {serverIp}:{serverPort}...");
                await client.ConnectAsync(serverIp, serverPort);
                Console.WriteLine("Connected!");

                // ネットワークストリームを取得
                using (NetworkStream stream = client.GetStream())
                using (StreamWriter writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
                using (StreamReader reader = new StreamReader(stream, Encoding.UTF8, 1024, true))
                {
                   // メッセージを送信
                   Console.WriteLine($"Sending message: {messageToSend}");
                   await writer.WriteLineAsync(messageToSend);
                   await writer.FlushAsync(); // バッファを強制的に書き出す

                   // サーバーからの応答を受信
                    Console.WriteLine("Waiting for response...");
                    string response = await reader.ReadLineAsync();
                    Console.WriteLine($"Received response: {response}");
                }

            }
        }
        catch (Exception e)
        {
            Console.WriteLine($"Error: {e.Message}");
        }

        Console.WriteLine("Client finished.");
    }
}

Rustのコード(※コンパイルが通らないとあとから判明)

Rust.rs
use std::io::{self, BufRead, BufReader, Write};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream as AsyncTcpStream;
use tokio::runtime::Runtime;

fn main() -> io::Result<()> {
    let server_ip = "127.0.0.1";
    let server_port = 8888;
    let message_to_send = "Hello from client!";

    let rt = Runtime::new().unwrap();
    rt.block_on(async {
        // TCPストリームを作成
        let stream = AsyncTcpStream::connect(format!("{}:{}", server_ip, server_port)).await?;
        println!("Connected!");
        let (mut reader, mut writer) = stream.into_split();


        // メッセージを送信
        println!("Sending message: {}", message_to_send);
        writer.write_all(message_to_send.as_bytes()).await?;
        writer.write_all("\n".as_bytes()).await?; // 改行を追加

        // サーバーからの応答を受信
        println!("Waiting for response...");
        let mut buf = String::new();
        reader.read_line(&mut buf).await?;
        println!("Received response: {}", buf);


        Ok::<(), io::Error>(()) // エラー型を明示的に指定する必要がある
    })
}

次のエラーがでコンパイルは通らない

error[E0599]: no method named `read_line` found for struct `OwnedReadHalf` in the current scope
    --> src\main.rs:27:16
     |
27   |         reader.read_line(&mut buf).await?;
     |                ^^^^^^^^^
     |

Rustメモ

非同期処理が標準ライブラリに含まれていない

let rt = Runtime::new().unwrap();はtokioクレートの非同期ランタイムを作っているらしい。

unwrap()

.unwrap() メソッドは、Result 型の値に対して使用できるメソッド。エラーじゃなければ値を取り出せるがエラーならパニックになるので、ここでエラーハンドリングすることも考慮する

パニックになった場合はアプリ全体または発生したスレッドが単独で終了する。親スレッドが
.unwrapの代わりにexpectを使用すると、パニック時に指定したメッセージが表示できる

let result: Result<i32, String> = Err("error message".to_string());
  let value = result.expect("error value"); // パニック時に指定したメッセージを表示

unwrapをそもそも使わずにmatch式(option型)で処理する

    match result {
        Ok(value) => println!("value is: {}", value),
        Err(err) => println!("error: {}", err)
    }

await?(?を除けば使い方はC#と似てるが動作は違うかもしれない)

Futureを返す関数の結果Resultを待つのがawait
Resultとしてエラーが返された時にそのままリターンするのがawait?

AIによる説明

C#の非同期処理は、asyncメソッドの呼び出しでタスクがスケジュールされ、その後、awaitによって処理が待機されます。await自体は非同期処理の進行を待つために使いますが、非同期タスク自体は呼び出し時点で始まっているという点が、Rustの仕組みと少し異なります。

Rustの場合、非同期処理はawaitを呼ぶタイミングで初めて開始され、その前にスケジュールされるのは「準備」の段階です。

そのため、C#とRustでは非同期処理の開始タイミングとその管理の方法に違いがあり、C#では非同期処理が比較的自動的に始まるのに対し、Rustではより明示的な管理が必要です。

async(使い方はC#と似てる)

C#だとasyncつけた関数はTaskを返すが、RustはFutureを返す
async関数またはasyncクロージャー、asyncブロック中でawaitが使える

asyncブロック

async ブロックは、 Future を返すクロージャとして扱われ(ただしクロージャそのものではない、クロージャに置き換えるとコンパイルが通らない)、その中で非同期の処理を記述することができます。

async ブロック自体は、非同期処理の設計図のようなものであり、それだけでは何も実行されません。 block_on や tokio::spawn のような関数を使って、実行する必要があります。

コンパイルが通るまで修正したコード

修正後のコード
use std::io::{self, Write};
use tokio::io::{AsyncWriteExt, AsyncBufReadExt, BufReader};
use tokio::net::TcpStream as AsyncTcpStream;
use tokio::runtime::Runtime;

fn main() -> io::Result<()> {
    let server_ip = "127.0.0.1";
    let server_port = 8888;
    let message_to_send = "Hello from client!";

    let rt = Runtime::new().unwrap();
    rt.block_on(async {
        // TCPストリームを作成
        let stream = AsyncTcpStream::connect(format!("{}:{}", server_ip, server_port)).await?;
        println!("Connected!");
        let (reader, mut writer) = stream.into_split();
        let mut reader = BufReader::new(reader);
        // メッセージを送信
        println!("Sending message: {}", message_to_send);
        writer.write_all(message_to_send.as_bytes()).await?;
        writer.write_all("\n".as_bytes()).await?; // 改行を追加

        // サーバーからの応答を受信
        println!("Waiting for response...");
        let mut buf = String::new();
        reader.read_line(&mut buf).await?;
        println!("Received response: {}", buf);


        Ok::<(), io::Error>(()) // エラー型を明示的に指定する必要がある
    })
}

非同期ライブラリtokioを使わないコードはどうなるか?

以下のコードでもコンパイルも通りました

use std::io::{self, Read, BufRead, BufReader, Write};
use std::net::TcpStream;

fn main() -> io::Result<()> {
    let server_ip = "127.0.0.1";
    let server_port = 8888;
    let message_to_send = "Hello from client!";

    // TCPストリームを作成
    let mut stream = TcpStream::connect(format!("{}:{}", server_ip, server_port))?;
    println!("Connected!");

    // メッセージを送信
    println!("Sending message: {}", message_to_send);
    stream.write_all(message_to_send.as_bytes())?;
    stream.write_all("\n".as_bytes())?; // 改行を追加

    // サーバーからの応答を受信
    println!("Waiting for response...");
    let mut reader = BufReader::new(&stream);
    let mut buf = String::new();
    reader.read_line(&mut buf)?;
    println!("Received response: {}", buf);


    Ok::<(), io::Error>(()) // エラー型を明示的に指定する必要がある
}
1
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?