📜 ⬆️ ⬇️

Tanchiki in the console, article three: "Server and client"

Good day to all!


And with the holidays!

My repository is at the bottom of this article.

Perhaps I'll start by rewriting everything again, but this did not affect the structures. And over the past time, I made a lot of changes (both in the server and in the client), but for now there are still a couple of aspects (I have not made a program that builds them and arranges the initial positions of the players (so far everything is manually)), which will be eliminated in soon.
')
I hear criticism and try to write an interesting article reviewing this project.

Chapter 1: “Work Client”


Good title, yes? But behind it lies the meaning of the client. I wanted to make a small core, but over time, exams came and refactoring became a bit difficult.

Before we begin to disassemble the client (by the KODstripkam) I have to tell how our client-server interacts:

1. The client says: “Hey, server, I came to you!”.
2. The server responds to it: “OK, client, here’s the coordinates of the walls and players.
3. The client again says: "Now we will communicate."

And so between them arose ... communication on TCP sockets.

The main methods were changed and the surprise -> we start from a different namespace and class. In the future, I will redo it (I remember that I promised to split everything into different files, but in connection with holidays and exams it turned out to be difficult, so I resorted to another namespace).

The main variables that actually work in the scheme above are the port and the address of the server.

The client can be divided into two groups:
1st, this is the serving group, i.e. functions that perform calculations and print messages to us from the server in the console.
2nd, this is a group of our interaction algorithm (as I indicated above).

Service group and everything


This group, which is mainly in the first namespace, includes such classes / structures as:

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

I have already considered them posts earlier, I have not changed anything in them (except for a couple of fields, made them public).

Changing the name, the meaning of the method has not changed - we print the tank depending on its coordinates:

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

And our main method:

 static void Work() 

This method does a great job - it processes keystrokes, collects data through other methods (which are located in structures), and sends them to the send method on the server.

Network group


A group of methods that communicates with the server.

Here they are:

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

The first method (Eventlistener ()) starts in the second thread and listens to the server, while the main thread processes keystrokes and sends the modified data to the server (using the MessageToServer () method). The remaining methods are used only at startup / shutdown of the client.

Chapter 2: Server Bike


Our server (its main part) works in multi-threaded mode, i.e. multi-threaded reading and sending in multiple threads.

An interesting fact, with the maximum load (we assume that it is 6 people), the number of simultaneously running threads (both for reading and sending) is 6 for reading, and 6 * 6 = 36 - for simultaneous transmission to everyone (the sum is 42), which seems to be it would be logical, but in reality the client can do 2-4 actions per second (considering ping), which multiplies the number of streams (by transfer), respectively, by 2-4.

That is, we get the formula: Count + Count * i + 1, where Count is the number of users, i is the number of simultaneously performed actions and +1, because we take into account the main flow.

Why multi-threaded reading? it's so convenient for me, it's much easier for me to break everything into different streams and process information from them to send to customers and one more bonus - we don't break connection with the client, which is very important (because if we don't do this, then on the client side the port stops transmitting data and disconnected (the next port is used for each next dispatch)).

The connection between the streams is implemented by the transmitter-receiver tuple, which is created by calling the std :: sync :: mpsc :: channel () function from the standard library.

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

But this method has limitations, because it is impossible for the Transmitter not to transmit (talk) messages to the receiver. Since the compiler does not know what type is used in message passing.

Why do we need the first stream and why the transmitter-receiver? This is thread parallelization so that the main thread creates threads to send data to all destinations.

That is, we get the scheme:

Server interaction diagram

Where the squares are separate streams, and the arrow from one to the other is the .send () method in the Transmitter (that is, send data to the receiver).

But in a stream that accepts data there are many streams (as we saw from the formula above), the complete scheme will look like this:

image

Let's start the analysis of all this. We need to read data from files before launching the network interaction, we need to send something and the cards should not be sewn into the server (after all, we will write a program to create these same cards).

I use functions from my lib.rs (mod text) to read and process files.

A small scheme of our server:

image

And here is the code:

The function allows you to manually create a server (from the net_server module)
 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()); }); } } } 

And we call it from main:
 fn main() { net_server::net_server::start_game_handle(); } 


This is how the bike turned out, later I’ll add a c # server to this article (and to my repository).

Conclusion!


What I failed:

1. Version on WinForm.
2. The program for the visual creation of levels.
3. The start of the match after n-seconds when reaching the minimum possible number of players.

My repository

First article
Second article

I am waiting for your wishes and corrections, many thanks for the criticism and all the best for you!

Source: https://habr.com/ru/post/346256/


All Articles