サーバーに接続しようとするクライアントを作成できません。
サーバーに接続するためのコードは次のとおりです。現在、接続が失われると、プログラムは終了します。それを実装するための最良の方法が何であるかはわかりません。多分私は無限ループでFuture
を作成する必要がありますか?
extern crate tokio_line;
use tokio_line::LineCodec;
fn get_connection(handle: &Handle) -> Box<Future<Item = (), Error = io::Error>> {
let remote_addr = "127.0.0.1:9876".parse().unwrap();
let tcp = TcpStream::connect(&remote_addr, handle);
let client = tcp.and_then(|stream| {
let (sink, from_server) = stream.framed(LineCodec).split();
let reader = from_server.for_each(|message| {
println!("{}", message);
Ok(())
});
reader.map(|_| {
println!("CLIENT DISCONNECTED");
()
}).map_err(|err| err)
});
let client = client.map_err(|_| { panic!()});
Box::new(client)
}
fn main() {
let mut core = Core::new().unwrap();
let handle = core.handle();
let client = get_connection(&handle);
let client = client.and_then(|c| {
println!("Try to reconnect");
get_connection(&handle);
Ok(())
});
core.run(client).unwrap();
}
次のコマンドでトキオラインクレートを追加します。
tokio-line = { git = "https://github.com/tokio-rs/tokio-line" }
重要な質問は次のようです:Tokioを使用して無限ループを実装するにはどうすればよいですか?この質問に答えることで、切断時に無限に再接続するという問題に取り組むことができます。 非同期コードを書いた私の経験から、再帰はこの問題の簡単な解決策のようです。
[〜#〜] update [〜#〜]:Shepmaster(およびTokio Gitterの人々)が指摘したように、私の元の答えはメモリリークします反復ごとに成長する先物のチェーンを構築するためです。これが新しいものです:
loop_fn
を使用futures
クレートには、必要なことを正確に実行する関数があります。 loop_fn
と呼ばれます。 main関数を次のように変更することで使用できます。
fn main() {
let mut core = Core::new().unwrap();
let handle = core.handle();
let client = future::loop_fn((), |_| {
// Run the get_connection function and loop again regardless of its result
get_connection(&handle).map(|_| -> Loop<(), ()> {
Loop::Continue(())
})
});
core.run(client).unwrap();
}
この関数はforループに似ており、get_connection
の結果に応じて続行または中断できます( Loop
列挙型のドキュメントを参照してください)。この場合、常に続行することを選択するため、無限に再接続し続けます。
エラーが発生した場合(クライアントがサーバーに接続できない場合など)、バージョンのget_connection
がパニックになることに注意してください。エラー後にも再試行する場合は、panic!
への呼び出しを削除する必要があります。
誰かがそれを面白いと思う場合に備えて、これが私の古い答えに従います。
[〜#〜]警告[〜#〜]:以下のコードを使用すると、メモリが無制限に増加します。
get_connection
を無限にループさせるクライアントが切断されるたびにget_connection
関数を呼び出したいので、まさにそれを実行します(reader.and_then
の後のコメントを見てください)。
fn get_connection(handle: &Handle) -> Box<Future<Item = (), Error = io::Error>> {
let remote_addr = "127.0.0.1:9876".parse().unwrap();
let tcp = TcpStream::connect(&remote_addr, handle);
let handle_clone = handle.clone();
let client = tcp.and_then(|stream| {
let (sink, from_server) = stream.framed(LineCodec).split();
let reader = from_server.for_each(|message| {
println!("{}", message);
Ok(())
});
reader.and_then(move |_| {
println!("CLIENT DISCONNECTED");
// Attempt to reconnect in the future
get_connection(&handle_clone)
})
});
let client = client.map_err(|_| { panic!()});
Box::new(client)
}
get_connection
は非ブロッキングであることに注意してください。 Box<Future>
を作成するだけです。これは、再帰的に呼び出す場合でも、ブロックしないことを意味します。代わりに、and_then
を使用して前の未来にリンクできる新しい未来を取得します。ご覧のとおり、スタックは反復ごとに大きくならないため、これは通常の再帰とは異なります。
handle
のクローンを作成し(handle_clone
を参照)、それをreader.and_then
に渡されたクロージャに移動する必要があることに注意してください。これが必要なのは、クロージャが関数よりも長く存続するためです(これは、将来返される予定です)。
指定したコードは、クライアントがサーバーに接続できない場合(またはその他のエラー)を処理しません。上記と同じ原則に従って、get_connection
の末尾を次のように変更することでエラーを処理できます。
let handle_clone = handle.clone();
let client = client.or_else(move |err| {
// Note: this code will infinitely retry, but you could pattern match on the error
// to retry only on certain kinds of error
println!("Error connecting to server: {}", err);
get_connection(&handle_clone)
});
Box::new(client)
or_else
はand_then
に似ていますが、将来生成されるエラーで動作することに注意してください。
main
から不要なコードを削除する最後に、main
関数でand_then
を使用する必要はありません。 main
を次のコードに置き換えることができます。
fn main() {
let mut core = Core::new().unwrap();
let handle = core.handle();
let client = get_connection(&handle);
core.run(client).unwrap();
}