web-dev-qa-db-ja.com

無限ループでクライアントをサーバーに非同期的に再接続する

サーバーに接続しようとするクライアントを作成できません。

  • サーバーがダウンしている場合は、無限ループで再試行する必要があります
  • サーバーが起動して接続が成功した場合、接続が失われたとき(つまり、サーバーがクライアントを切断したとき)、クライアントはサーバーへの接続を試みるために無限ループを再開する必要があります

サーバーに接続するためのコードは次のとおりです。現在、接続が失われると、プログラムは終了します。それを実装するための最良の方法が何であるかはわかりません。多分私は無限ループで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" }
22
Danilo Silva

重要な質問は次のようです: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_elseand_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();
}
18
aochagavia