コンソールの記事3、「サーバーとクライアント」

すべての人に良い一日を!



そして、今後の休日に!



私のリポジトリには、この記事の最後にあるコードが含まれています。



おそらく、すべてを再び書き直したという事実から始めますが、これは構造に影響しませんでした。 過去に多くの変更を行いましたが(サーバーとクライアントの両方で)、これまでにいくつかの側面が残っています(それらを作成してプレーヤーの初期位置を配置するプログラムを作成しませんでした(これまではすべて手動))。すぐに来ます。



批判を聞いて、このプロジェクトの分析で興味深い記事を書いてみます。



第1章:「作業クライアント」



いい名前ですね しかし、その背後にはクライアントの意味があります。 小さなスケルトンを作りたかったのですが、時間が経つにつれて試験が行われ、リファクタリングが少し難しくなりました。



(CODEによる)クライアントの解析を開始する前に、クライアントサーバーがどのように相互作用するかを説明する必要があります。



1.クライアントが言います:「サーバーよ、あなたのところに来ました!」

2.サーバーは彼に応答します:「クライアント、ここに壁とプレイヤーの座標があります。」

3.クライアントは再び「今から話します」と言います。



そして、それらの間で発生しました... TCPソケット上の通信。



主なメソッドが変更され、驚きました->別の名前空間とクラスから始めます。 将来、これをやり直します(すべてを別のファイルに分割することを約束しましたが、休日や試験に関連して困難であることが判明したため、別の名前空間に頼りました)。



上記のスキームで実際に機能する主な変数は、サーバーのポートとアドレスです。



クライアントは条件付きで2つのグループに分割できます。

まず、これはサービスグル​​ープです。 計算を実行し、サーバーから当社にコンソールにメッセージを出力する関数。

2番目に、これは相互作用アルゴリズムのグループです(上で示したように)。



サービスグル​​ープとすべてすべて



主に最初の名前空間にあるこのグループには、次のようなクラス/構造が含まれます。



//  "" public struct WallState //  "" public struct ShotState //      public class Position //    public struct PlayerState
      
      





私はすでにそれらを以前の投稿でレビューしましたが、その中の何も変更しませんでした(いくつかのフィールドを除き、それらを公開しました)。



名前を変更しても、メソッドの意味は変わりません-座標に応じてタンクを印刷します:



 static void PrintCoordinate(int x, int y, int dir, int id)
      
      





そして、私たちの主な方法:



 static void Work()
      
      





このメソッドは素晴らしい仕事をします-押されたキーを処理し、他のメソッド(構造内にある)を通してデータを収集し、それらをサーバーへの送信メソッドに送信します。



ネットワークグループ



サーバーと通信するメソッドのグループ。



ここにあります:



 //   static void Eventlistener() //          static void Connect() //    static void Disconnect() //       static void MessageToServer(string data)
      
      





最初のメソッド(Eventlistener())は2番目のスレッドで開始され、サーバーをリッスンします。一方、メインスレッドは押されたキーを処理し、変更されたデータをサーバーに送信します(MessageToServer()メソッドを使用)。 残りのメソッドは、クライアントの起動/シャットダウン時にのみ使用されます。



第2章:自転車サーバー



サーバー(その主要部分)はマルチスレッドモードで動作します。 複数のストリームでのマルチスレッド読み取りおよび送信。



興味深い事実は、最大ワークロード(6人と想定)で、同時に起動されるスレッド(読み取りと送信の両方)の数は読み取りで6、6 * 6 = 36-全員への同時送信(合計-42)であり、それは論理的ですが、実際には、クライアントは1秒あたり2〜4アクション(pingを考慮に入れる)を実行できます。これにより、スレッド数が(送信によって)2〜4倍になります。



つまり、次の公式が得られます。Count+ Count * i +1。Countはユーザー数、iは同時に実行されるアクションの数、+ 1はメインストリームを考慮するためです。



なぜマルチスレッド読み取りですか? 私にとって非常に便利で、すべてを異なるストリームに分割し、それらからの情報を処理してクライアントに送信したり、別のボーナスを送信したりするのがはるかに簡単です-クライアントとの接続を切断することはありません。これは非常に重要ですデータはオフになります(次の各送信に次のポートが使用されます)。



ストリーム間の接続は、標準ライブラリからstd :: sync :: mpsc :: channel()関数を呼び出すことで作成されるTransmitter-Receiverタプルによって実装されます。



 use std::sync::mpsc; let (sender, receiver) = mpsc::channel();
      
      





ただし、この方法には制限があります。トランスミッターがメッセージをレシーバーに送信(発言)しないことは不可能だからです。 なぜなら コンパイラーは、メッセージの受け渡しにどのタイプが使用されているかを知りません。



なぜ最初のストリームが必要なのか、なぜ送信機と受信機が必要なのか? これはスレッド並列化であるため、メインスレッドはすべての宛先にデータを送信するためのスレッドを作成します。



つまり、スキームを取得します。



サーバー相互作用スキーム



ボックスが別々のストリームであり、一方から他方への矢印がトランスミッタの.send()メソッドです(つまり、レシーバにデータを送信します)。



しかし、データを受信するストリームには多くのストリームがあります(上記の式で見たように)、完全なスキームは次のようになります。



画像



このすべての解析を始めましょう。 ネットワークインタラクションを開始する前に、ファイルからデータを読み取る必要があります。何かを送信する必要があり、カードをサーバーに縫い付けないでください(これらのカードを作成するプログラムを作成するため)。



lib.rs(mod Text)の関数を使用してファイルを読み取り、処理します。



サーバーの小さなスキーム:



画像



そして、ここにコードがあります:



サーバーを手動で作成できる機能(net_serverモジュールから)
 fn start_game_handle(){ let mut i:usize = 0; println!(". - :"); let mut number_player = String::new(); //io::stdin().read_line(&mut number_player) // .unwrap(); io::stdin().read_line(&mut number_player) .unwrap(); let number_player: u32 = number_player.trim().parse().unwrap(); /* (1) ->(2) ->    (3) */ let mut addrs:Vec<SocketAddr> = Vec::new(); println!(" IP:PORT :"); let mut ip_port = String::new(); io::stdin().read_line(&mut ip_port) .unwrap(); ip_port = slim(ip_port, ' '); ip_port = slim(ip_port, '\n'); ip_port = slim(ip_port, '\r'); ip_port = slim(ip_port, '\0'); println!("{:?}",ip_port); println!(" IP:PORT -(+{}  ):",number_player); let mut game_port = String::new(); io::stdin().read_line(&mut game_port) .unwrap(); game_port = slim(game_port, ' '); game_port = slim(game_port, '\n'); game_port = slim(game_port, '\r'); game_port = slim(game_port, '\0'); let _port = slim_vec(game_port.clone(), ':');//   -    //       let _port: u32 = _port[1].trim().parse().unwrap(); //let game_port: u32 = game_port.trim().parse().unwrap(); let mut exit_id: Vec<u32> = Vec::new(); //    id ,     println!("[ !]"); let listener = TcpListener::bind(ip_port.as_str()).unwrap(); println!("{:?}", listener); let (sender, receiver) = mpsc::channel(); //let(sen_, recv_) = mpsc::channel(); let mut Connects:Vec<Connect> = Vec::new(); let mut k = 0; for i in 0..number_player { //   println!("  :[{}]", i+1); match listener.accept(){ Ok((mut stream, addr)) => { /*let sender_clone = mpsc::Sender::clone(&sender); let (send_, recv_) = mpsc::channel(); thread::spawn(move|| { {send_.send(stream.try_clone().expect(" ..")).unwrap();} let q:[u8;8] = [0;8]; let mut buf:[u8; 256] = [0; 256]; println!(" [{}]", k); loop { stream.read(&mut buf); if buf.starts_with(&q) == false { sender_clone.send((String::from_utf8(buf.to_vec()).unwrap(), k)).unwrap(); } } }); {*/ addrs.push(addr); //let s_s = recv_.recv().unwrap(); Connects.push(Connect::new(stream, i)); /*k+=1; }*/ }, Err(e) => { }, }} let mut Connects_copy:Vec<TcpStream> = Vec::new(); //let mut Connects_copy_:Vec<TcpStream> = Vec::new(); { let mut i:usize = Connects.len() - 1; loop { match Connects[i].stream.try_clone() { Ok(mut srm) => { Connects_copy.push(srm); }, Err(e) => { Connects[i].stream.shutdown(Shutdown::Both).is_ok(); Connects.remove(i); }, } if i != 0{ i -= 1; } else { break; } }} for mut item in Connects_copy{ let sender_clone = mpsc::Sender::clone(&sender); thread::spawn(move ||{ let q:[u8;8] = [0;8]; let mut buf:[u8; 256] = [0; 256]; loop { item.read(&mut buf); println!("  [{:?}]", item); if buf.starts_with(&q) == false { sender_clone.send(String::from_utf8(buf.to_vec()).unwrap()).unwrap(); } } }); } for item_ in receiver{ println!(" "); let mut Connects_copy_:Vec<TcpStream> = Vec::new(); { let mut i:usize = Connects.len() - 1; loop { match Connects[i].stream.try_clone() { Ok(mut srm) => { Connects_copy_.push(srm); }, Err(e) => { Connects[i].stream.shutdown(Shutdown::Both).is_ok(); Connects.remove(i); }, } if i != 0{ i -= 1; } else { break; } }} for mut item in Connects_copy_{ let (sender_, recv_) = mpsc::channel(); sender_.send(item_.clone()).unwrap(); thread::spawn(move ||{ let s = recv_.recv().unwrap(); item.write(&s.into_bytes()); println!("{:?}", item.local_addr()); }); } } }
      
      





そして、メインから呼び出します:

 fn main() { net_server::net_server::start_game_handle(); }
      
      







これが自転車の仕組みです。将来、この記事(および私のリポジトリ)にc#でサーバーを追加します。



結論!



私が失敗したこと:



1. WinFormバージョン。

2.レベルを視覚的に作成するためのプログラム。

3.可能な最小プレイヤー数に達したときのn秒での試合の開始。



私のリポジトリ



最初の記事

第二の記事



あなたの願いと訂正を待って、批判と多くのあなたに最高に感謝します!



All Articles