けいぞうのメモ帳

言語設計のお勉強

rust1.7.0のTcpStreamに関する所見

tl;dr;

rust 不慣れな感じがバリバリ出ている僕のための覚え書きエントリ

intro

今後様々なプロトコルを様座な言語で実装する遊びを行うに当たってはじめにrustでなんか実装することにしたから、rust 1.7.0のstd::net::TcpStreamに
関する取り回しを考えてみた。 おそらく僕はそれらを忘れると思うので、念の為にまとめてみたいと思う。
 rustに関する説明は https://www.rust-lang.org/www.rust-lang.org
 またソースコードgithub.com
 手始めに覚えたい人は このtutorialを見ると良い。
 最後のtutorial。tutorialとか言いつつ結構色々あって一週間くらい毎日読まないと全部読み終わらないっぽいのが強い。

ハマったコード

HTTP/1.1を叩くだけの簡単なプログラムを書いた

use std::io::prelude::*;
use std::vec::Vec;
use std::net::TcpStream;
use std::string::String;

fn main()
{
    {

    let url = String::from("google.com:80");
    let mut stream = TcpStream::connect(url.as_str()).unwrap();

    let _ = stream.write(b"GET / HTTP/1.1\nHost: google.com\n\n");
    let mut s : Vec<u8> = Vec::new();
    let _ = stream.read_to_end(&mut s);
    println!("{}", String::from_utf8_lossy(s.as_slice()));
    }
}

このコードはgoogle.comの80番ポートにあるHTTP1.1サーバにGETを要求するだけの簡単なプログラムだが、
ずっとblockingしてstdoutへの出力が一向に出てこない。

tcp timeout

この時点での問題は std::net::TcpStreamのdocumentにnoticeとして書いてある。

doc.rust-lang.org

TcpStreamにはreadとwriteのtimeoutがあり、これを設定せずにNoneのままにしておくとobj.readが無限にブロックする。 つまりstream.read_to_endが現状ではblockしてprintln!まで出てこない。 よって obj.set_read_timeoutを設定すれば良い。

use std::net::Duration;

//中略
     let _ = stream.set_write_timeout(Some(Duration(10,0)));
     let _ = stream.read_to_end(&mut s);
    println!("{}", String::from_utf8_lossy(s.as_slice()));
    }
}

timeout目一杯まで待ってしまう

 先ほどの変更を加えるとresponseを受け取れる様になった。が、timeout時間目一杯までresponseがblockした。

shutdownを呼び出す

使い終わったstreamに対しshutdownを呼び出す事によって、timeoutまで待たない処理を書けるようになる。 shutdown関数はstreamの処理を直ちに終了しその時点で適切な返り値を設定する。例えば、Result型におけるOkを返すようにするなどする。 これを呼び出すと望み通り動く

use std::net::Shutdown;

//中略

    let _ = stream.write(b"GET / HTTP/1.1\nHost: google.com\n\n");
    let _ = stream.flush();
    let _ = stream.shutdown(Write);
    let mut s = String::new();
    let _ = stream.read_to_string(&mut s);
    println!("{}", s);
    }
}

shutdownはShutdown型をうけつけShutdown型はBoth, Write, Readのenumだ。 でもコレ以降TcpStreamを受け取れなくなるからナンセンスな感じがすごいする。 try_cloneして複数オブジェクトからTcpStreamを共有したときにこの方法は使えない

※追記

Connection: closedを明示するとちゃんと動く。これはHTTP/1.1の仕様に基づくものでrust lang側の問題ではない。 僕の過失である。ざんねん。

結果

ほんまどうにもなんねぇな

ここから所見

ust langは並列並行処理を強くサポートしメモリ管理を厳しく行う言語である。
そのため、標準ライブラリのAPI設計も強くメモリ管理を意識した設計になっていて、
更に問題を並列化で解決させようとするきらいがある。

ひとつのTCPコネクションを取り回して設計したりするとき、rustが求める使い方は
TcpStream::connectでstreamを取得したら、ブロックでスコープを分けて、try_cloneでオブジェクトを複製して
スコープが閉じるタイミングあたりでオブジェクトの寿命が来るように書いて、複製したメモリは
region infferenceで回収させるといったかんじにするべきなのだと思う。 しかしどうあがいてもtimeout目一杯までのまつ問題は解決できなかったためこれどうなってのまじで。
おしまい