commit
088bb6c83f
4
.gitignore
vendored
4
.gitignore
vendored
@ -2,5 +2,5 @@
|
|||||||
/target
|
/target
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
*.lock
|
*.lock
|
||||||
|
*~
|
||||||
Lucien-Rust/target/
|
Rust/target/
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "chat-reseau"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Phuntsok Drak-pa <drakpa@drakpa.fr>"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
@ -1,177 +0,0 @@
|
|||||||
use std::env;
|
|
||||||
use std::net::{TcpListener, TcpStream};
|
|
||||||
use std::thread;
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use std::io::{stdin, stdout, Read, Write};
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// //
|
|
||||||
// Client //
|
|
||||||
// //
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
fn get_entry() -> String {
|
|
||||||
let mut buf = String::new();
|
|
||||||
|
|
||||||
stdin().read_line(&mut buf).unwrap();
|
|
||||||
buf.replace("\n", "").replace("\r", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn exchange_with_server(mut stream: TcpStream) {
|
|
||||||
let stdout = std::io::stdout();
|
|
||||||
let mut io = stdout.lock();
|
|
||||||
let buf = &mut [0; 3];
|
|
||||||
|
|
||||||
println!("Enter `quit` when you want to leave");
|
|
||||||
loop {
|
|
||||||
write!(io, "> ").unwrap();
|
|
||||||
io.flush().unwrap();
|
|
||||||
match &*get_entry() {
|
|
||||||
"quit" => {
|
|
||||||
println!("bye!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
"exit" => {
|
|
||||||
println!("bye!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
line => {
|
|
||||||
write!(stream, "{}\n", line).unwrap();
|
|
||||||
match stream.read(buf) {
|
|
||||||
Ok(received) => {
|
|
||||||
if received < 1 {
|
|
||||||
println!("Perte de la connexion avec le serveur");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
println!("Perte de la connexion avec le serveur");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// println!("Réponse du serveur : {:?}", buf);
|
|
||||||
let reponse = String::from_utf8(buf.to_vec()).unwrap();
|
|
||||||
println!("Réponse du serveur : {}", reponse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn client(server_address: String) {
|
|
||||||
println!("Tentative de connexion a serveur...");
|
|
||||||
match TcpStream::connect(server_address) {
|
|
||||||
Ok(stream) => {
|
|
||||||
println!("Connexion au serveur réussie !");
|
|
||||||
exchange_with_server(stream);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("La connection au serveur a échoué : {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// //
|
|
||||||
// Server //
|
|
||||||
// //
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
fn handle_client(mut stream: &TcpStream, adresse: &str) {
|
|
||||||
let mut msg: Vec<u8> = Vec::new();
|
|
||||||
loop {
|
|
||||||
let buf = &mut [0; 10];
|
|
||||||
|
|
||||||
match stream.read(buf) {
|
|
||||||
Ok(received) => {
|
|
||||||
// si on a reçu 0 octet, ça veut dire que le client s'est déconnecté
|
|
||||||
if received < 1 {
|
|
||||||
println!("Client disconnected {}", adresse);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut x = 0;
|
|
||||||
|
|
||||||
for c in buf {
|
|
||||||
// si on a dépassé le nombre d'octets reçus, inutile de continuer
|
|
||||||
if x >= received {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
x += 1;
|
|
||||||
if *c == '\n' as u8 {
|
|
||||||
println!(
|
|
||||||
"message reçu {} : {}",
|
|
||||||
adresse,
|
|
||||||
// on convertit maintenant notre buffer en String
|
|
||||||
String::from_utf8(msg).unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
stream.write(b"ok\n").unwrap();
|
|
||||||
|
|
||||||
msg = Vec::new();
|
|
||||||
} else {
|
|
||||||
msg.push(*c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
println!("Client disconnected {}", adresse);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serveur(port: String) {
|
|
||||||
println!("Port: {}", port);
|
|
||||||
let mut serv = String::from("127.0.0.1:");
|
|
||||||
serv.push_str(&port);
|
|
||||||
let listener = TcpListener::bind(serv.to_string()).unwrap();
|
|
||||||
|
|
||||||
println!("En attente d’un client...");
|
|
||||||
|
|
||||||
// Multi-client ///////////////////////////////////////////////////////////
|
|
||||||
for stream in listener.incoming() {
|
|
||||||
match stream {
|
|
||||||
Ok(stream) => {
|
|
||||||
let adresse = match stream.peer_addr() {
|
|
||||||
Ok(addr) => format!("[adresse : {}]", addr),
|
|
||||||
Err(_) => "inconnue".to_owned(),
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("Nouveau client {}", adresse);
|
|
||||||
thread::spawn(move || handle_client(&stream, &*adresse));
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("La connexion du client a échoué : {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!("En attente d’un autre client...");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let args: Vec<String> = env::args().collect();
|
|
||||||
if args.len() == 2 {
|
|
||||||
///////////////////////////////////////////////////////////////////////
|
|
||||||
// Server opened //
|
|
||||||
///////////////////////////////////////////////////////////////////////
|
|
||||||
println!("Opening server on port {}", args[1]);
|
|
||||||
serveur(args[1].clone());
|
|
||||||
} else if args.len() == 3 {
|
|
||||||
///////////////////////////////////////////////////////////////////////
|
|
||||||
// Client opened //
|
|
||||||
///////////////////////////////////////////////////////////////////////
|
|
||||||
println!("Client connecting on server {}:{}", args[1], args[2]);
|
|
||||||
let mut serv = if args[1] == String::from("localhost") {
|
|
||||||
String::from("127.0.0.1")
|
|
||||||
} else {
|
|
||||||
args[1].clone()
|
|
||||||
};
|
|
||||||
serv.push(':');
|
|
||||||
serv.push_str(&args[2]);
|
|
||||||
client(serv);
|
|
||||||
} else {
|
|
||||||
println!("Usage: {} [server ip] port", args[0]);
|
|
||||||
}
|
|
||||||
}
|
|
9
Rust/Cargo.toml
Normal file
9
Rust/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "chat-reseau"
|
||||||
|
version = "0.5.7"
|
||||||
|
authors = ["Lucien Cartier-Tilet <drakpa@drakpa.fr>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
colored = "1.6"
|
||||||
|
chrono = "0.4"
|
||||||
|
term_size = "0.3"
|
353
Rust/src/client.rs
Normal file
353
Rust/src/client.rs
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
extern crate chrono;
|
||||||
|
extern crate colored;
|
||||||
|
extern crate term_size;
|
||||||
|
use std;
|
||||||
|
use std::io::*;
|
||||||
|
use std::net::TcpStream;
|
||||||
|
use std::thread;
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
use self::colored::*;
|
||||||
|
use self::chrono::Local;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
0.1 [X]
|
||||||
|
1.1 [X]
|
||||||
|
1.2 [ ]
|
||||||
|
1.3 [X]
|
||||||
|
1.4 [X]
|
||||||
|
1.5 [X]
|
||||||
|
1.6 [X]
|
||||||
|
1.7 [X]
|
||||||
|
1.8 [X]
|
||||||
|
1.9 [X]
|
||||||
|
2.1 [X]
|
||||||
|
2.2 [X]
|
||||||
|
3.1 [ ] // pas utile avec Rust
|
||||||
|
3.2 [X]
|
||||||
|
4.1.1 [X]
|
||||||
|
4.1.2 [X]
|
||||||
|
4.2.1 [X]
|
||||||
|
4.2.2 [X]
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: Implement requests 1.2
|
||||||
|
|
||||||
|
fn hash_name(name: &str) -> usize {
|
||||||
|
let mut s = DefaultHasher::new();
|
||||||
|
let name = String::from(name);
|
||||||
|
name.hash(&mut s);
|
||||||
|
s.finish() as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_time() -> String {
|
||||||
|
let date = Local::now();
|
||||||
|
date.format("[%H:%M:%S]").to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_line(name: &str, name_hash: usize, msg: &str, first_line: &mut bool, colors: &Vec<&str>) {
|
||||||
|
let date = get_time();
|
||||||
|
if *first_line == true {
|
||||||
|
println!(
|
||||||
|
"{}{}{}{}",
|
||||||
|
date.dimmed(),
|
||||||
|
name.color(colors[name_hash]),
|
||||||
|
" |".green(),
|
||||||
|
msg.yellow().dimmed()
|
||||||
|
);
|
||||||
|
*first_line = false;
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"{}{}",
|
||||||
|
" | ".green(),
|
||||||
|
msg.yellow().dimmed()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_entry() -> String {
|
||||||
|
let mut buf = String::new();
|
||||||
|
stdin().read_line(&mut buf).unwrap();
|
||||||
|
buf.replace("\n", "").replace("\r", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_name() -> String {
|
||||||
|
'mainloop: loop {
|
||||||
|
println!("{}", "Please enter your name:".yellow().dimmed());
|
||||||
|
let mut name = &*get_entry();
|
||||||
|
name = name.trim();
|
||||||
|
if name.len() > 20 {
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
"Nickname too long, it must be at most 20 characters long".red()
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for c in name.chars() {
|
||||||
|
if !c.is_ascii() {
|
||||||
|
println!(
|
||||||
|
"{}{}{}",
|
||||||
|
"Character ".red(),
|
||||||
|
&format!("{}", c).green(),
|
||||||
|
" is not an ASCII character.".red()
|
||||||
|
);
|
||||||
|
continue 'mainloop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match name {
|
||||||
|
"" => {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let spliced_name: Vec<&str> = name.split_whitespace().collect();
|
||||||
|
if spliced_name.len() != 1 {
|
||||||
|
println!("{}", "Cannot use whitespace in name".red());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return String::from(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to_server(stream: TcpStream) {
|
||||||
|
let mut writer = BufWriter::new(&stream);
|
||||||
|
writeln!(writer, "REQ CLIENTS").unwrap();
|
||||||
|
writer.flush().unwrap();
|
||||||
|
loop {
|
||||||
|
let line = &*get_entry();
|
||||||
|
line.trim();
|
||||||
|
match line {
|
||||||
|
"" => {}
|
||||||
|
"/quit" => {
|
||||||
|
writeln!(writer, "BYE").unwrap();
|
||||||
|
writer.flush().unwrap();
|
||||||
|
return ();
|
||||||
|
}
|
||||||
|
"/clients" => {
|
||||||
|
writeln!(writer, "REQ CLIENTS").unwrap();
|
||||||
|
writer.flush().unwrap();
|
||||||
|
}
|
||||||
|
line => {
|
||||||
|
if line.len() > 2000 {
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
"Cannot send a message longer than 2000 characters".bright_red()
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
writeln!(writer, "MSG {}", line).unwrap();
|
||||||
|
writer.flush().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exchange_with_server(stream: TcpStream) {
|
||||||
|
let server = stream.peer_addr().unwrap();
|
||||||
|
println!("Connected to {}", server);
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let COLORS: Vec<&str> = vec![
|
||||||
|
// "black",
|
||||||
|
"red",
|
||||||
|
"green",
|
||||||
|
"yellow",
|
||||||
|
"blue",
|
||||||
|
"magenta",
|
||||||
|
"cyan",
|
||||||
|
// "white",
|
||||||
|
];
|
||||||
|
|
||||||
|
let stream_cpy = stream.try_clone().unwrap();
|
||||||
|
let mut reader = BufReader::new(&stream_cpy);
|
||||||
|
let mut writer = BufWriter::new(&stream_cpy);
|
||||||
|
|
||||||
|
macro_rules! receive {
|
||||||
|
() => ({
|
||||||
|
let mut line = String::new();
|
||||||
|
match reader.read_line(&mut line) {
|
||||||
|
Ok(len) => {
|
||||||
|
if len == 0 {
|
||||||
|
// Reader is at EOF. Could use ErrorKind::UnexpectedEOF,
|
||||||
|
// but still unstable.
|
||||||
|
let ret = std::io::Error::new(
|
||||||
|
std::io::ErrorKind::Other, "test");
|
||||||
|
return
|
||||||
|
Err(ret): std::result::Result<&str,std::io::Error>;
|
||||||
|
}
|
||||||
|
line.pop();
|
||||||
|
}
|
||||||
|
Err(e) => { return Err(e); }
|
||||||
|
};
|
||||||
|
line
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// entrée du nom d'utilisateur
|
||||||
|
writeln!(writer, "PROT {} CONNECT NEW", ::PROTOCOL).unwrap();
|
||||||
|
writer.flush().unwrap();
|
||||||
|
let _name: String = match (|| loop {
|
||||||
|
let _answer = receive!();
|
||||||
|
if _answer != "NAME REQ" {
|
||||||
|
return Err(Error::new(ErrorKind::Other, _answer));
|
||||||
|
}
|
||||||
|
let nick = get_name();
|
||||||
|
writeln!(writer, "NAME {}", nick).unwrap();
|
||||||
|
writer.flush().unwrap();
|
||||||
|
match receive!().as_str() {
|
||||||
|
"NAME OK" => {
|
||||||
|
println!("NAME OK");
|
||||||
|
nick
|
||||||
|
}
|
||||||
|
|
||||||
|
"NAME FAILURE" => {
|
||||||
|
println!("{}", "Username refused by server.".red());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
answer => {
|
||||||
|
println!("{}{}", "Server answered: ".yellow().dimmed(), answer);
|
||||||
|
return Err(Error::new(ErrorKind::Other, answer));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})()
|
||||||
|
{
|
||||||
|
Ok(name) => String::from(name),
|
||||||
|
Err(_) => {
|
||||||
|
println!("{}", ">>> Login successful".green());
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
write_to_server(stream.try_clone().unwrap());
|
||||||
|
});
|
||||||
|
|
||||||
|
match (|| loop {
|
||||||
|
let input: String = String::from(receive!());
|
||||||
|
let spliced_input: Vec<&str> = input.split(" ").collect();
|
||||||
|
match spliced_input[0] {
|
||||||
|
"BAD" => {
|
||||||
|
println!("{}", "Bad request from client".red());
|
||||||
|
}
|
||||||
|
"WELCOME" => println!(
|
||||||
|
"{}\n{}\n{}",
|
||||||
|
">>> Login Successful <<<".green(),
|
||||||
|
"Type /clients to get the list of users connected",
|
||||||
|
"Type /quit to disconnect and quit"
|
||||||
|
),
|
||||||
|
|
||||||
|
"FROM" => {
|
||||||
|
let date = Local::now();
|
||||||
|
let mut first_line = true;
|
||||||
|
let name_hash = hash_name(spliced_input[1]) % COLORS.len();
|
||||||
|
|
||||||
|
// Formatting name
|
||||||
|
let mut name = String::new();
|
||||||
|
for _i in 0..(20 - spliced_input[1].to_string().len()) {
|
||||||
|
name.push(' ');
|
||||||
|
}
|
||||||
|
name.push('<');
|
||||||
|
name.push_str(spliced_input[1]);
|
||||||
|
name.push('>');
|
||||||
|
|
||||||
|
// Display message with soft-wrap
|
||||||
|
if let Some((w, _)) = term_size::dimensions() {
|
||||||
|
let mut msg = String::new();
|
||||||
|
let w = w - 34;
|
||||||
|
|
||||||
|
// format message
|
||||||
|
for mut i in 3..spliced_input.len() {
|
||||||
|
// If the width of the terminal allows the length of the
|
||||||
|
// current width of the message plus the new word, then
|
||||||
|
// add the latter, otherwise print the former and create
|
||||||
|
// a new line from the current message
|
||||||
|
if w > msg.len() + spliced_input[i].len() + 1 {
|
||||||
|
msg.push(' ');
|
||||||
|
msg.push_str(spliced_input[i]);
|
||||||
|
} else {
|
||||||
|
print_line(&name, name_hash, &msg, &mut first_line, &COLORS);
|
||||||
|
msg = String::from(spliced_input[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// print leftovers
|
||||||
|
print_line(&name, name_hash, &msg, &mut first_line, &COLORS);
|
||||||
|
} else {
|
||||||
|
let mut msg = String::new();
|
||||||
|
for i in 3..spliced_input.len() {
|
||||||
|
msg.push_str(" ");
|
||||||
|
msg.push_str(spliced_input[i]);
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
"{}{}{}",
|
||||||
|
date.format("[%H:%M:%S]").to_string().dimmed(),
|
||||||
|
name.color(COLORS[name_hash]),
|
||||||
|
msg.yellow().dimmed()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"BYE" => return Ok("Ok"),
|
||||||
|
|
||||||
|
"LIST" => {
|
||||||
|
println!(
|
||||||
|
"{}{}{}",
|
||||||
|
">>>> LIST OF CLIENTS CONNECTED (".bold().yellow(),
|
||||||
|
spliced_input[2],
|
||||||
|
") <<<<".bold().yellow()
|
||||||
|
);
|
||||||
|
for i in 3..spliced_input.len() {
|
||||||
|
println!("\t\t{}", spliced_input[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"JOIN" => {
|
||||||
|
let date = get_time();
|
||||||
|
let name_hash: usize = hash_name(spliced_input[1]) % COLORS.len();
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{}{}{}{}",
|
||||||
|
date.dimmed(),
|
||||||
|
" ------> ".green(),
|
||||||
|
spliced_input[1].color(COLORS[name_hash]),
|
||||||
|
" has joined".green()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"LOGOUT" => {
|
||||||
|
let date = get_time();
|
||||||
|
let name_hash = hash_name(spliced_input[1]) % COLORS.len();
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{}{}{}{}",
|
||||||
|
date.dimmed(),
|
||||||
|
" <------ ".red(),
|
||||||
|
spliced_input[1].color(COLORS[name_hash]),
|
||||||
|
" has left".red()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => println!("{}", input),
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
{
|
||||||
|
Ok(_) => {
|
||||||
|
println!("{}", ">>> Logout successful <<<".green());
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
println!("{}", "Error: Connection with server lost".red());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn client(server_address: String) {
|
||||||
|
println!("Trying to connect to the server...");
|
||||||
|
match TcpStream::connect(server_address) {
|
||||||
|
Ok(stream) => {
|
||||||
|
exchange_with_server(stream);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("{} {}", "Connection to server failed:".red(), e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
Rust/src/main.rs
Normal file
37
Rust/src/main.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#![feature(type_ascription)]
|
||||||
|
#![feature(stmt_expr_attributes)]
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
static PROTOCOL: &'static str = "0.1";
|
||||||
|
|
||||||
|
pub mod client;
|
||||||
|
pub mod server;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
if args.len() == 2 {
|
||||||
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
// Server opened //
|
||||||
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
println!("Opening server on port {}", args[1]);
|
||||||
|
// serveur(args[1].clone());
|
||||||
|
let mut serv = String::from("0.0.0.0:");
|
||||||
|
serv.push_str(&args[1]);
|
||||||
|
server::serveur(serv);
|
||||||
|
} else if args.len() == 3 {
|
||||||
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
// Client opened //
|
||||||
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
println!("Client connecting on server {}:{}", args[1], args[2]);
|
||||||
|
let mut serv: String = if args[1] == "localhost" {
|
||||||
|
String::from("127.0.0.1")
|
||||||
|
} else {
|
||||||
|
args[1].clone()
|
||||||
|
};
|
||||||
|
serv.push(':');
|
||||||
|
serv.push_str(&args[2]);
|
||||||
|
client::client(serv);
|
||||||
|
} else {
|
||||||
|
println!("Usage: {} [server ip] port", args[0]);
|
||||||
|
}
|
||||||
|
}
|
478
Rust/src/server.rs
Normal file
478
Rust/src/server.rs
Normal file
@ -0,0 +1,478 @@
|
|||||||
|
extern crate chrono;
|
||||||
|
use std::io::*;
|
||||||
|
use std::net::{SocketAddr, TcpListener, TcpStream};
|
||||||
|
use std::thread;
|
||||||
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use self::chrono::Local;
|
||||||
|
|
||||||
|
// TODO: add server-side controls: display clients list, kick client, shutdown
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Evolution implementation protocole //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
0.1 [X]
|
||||||
|
1.1 [X]
|
||||||
|
1.2 [X]
|
||||||
|
1.3 [X]
|
||||||
|
1.4 [X]
|
||||||
|
1.5 [X]
|
||||||
|
1.6 [X]
|
||||||
|
1.7 [X]
|
||||||
|
1.8 [X]
|
||||||
|
1.9 [X]
|
||||||
|
2.1 [X]
|
||||||
|
2.2 [X]
|
||||||
|
3.1 [ ] // pas utile avec Rust
|
||||||
|
3.2 [X]
|
||||||
|
4.1.1 [X]
|
||||||
|
4.1.2 [X]
|
||||||
|
4.2.1 [X]
|
||||||
|
4.2.2 [X]
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TYPES //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Map for all connected clients containing their name and stream
|
||||||
|
type UserMapValue = (String, TcpStream);
|
||||||
|
type UserMap = HashMap<SocketAddr, UserMapValue>;
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// CODE //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
fn get_time() -> String {
|
||||||
|
let date = Local::now();
|
||||||
|
date.format("[%H:%M:%S]").to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn distribute_message(
|
||||||
|
msg: &str,
|
||||||
|
not_to: &SocketAddr,
|
||||||
|
lock: &mut MutexGuard<UserMap>,
|
||||||
|
everyone: bool,
|
||||||
|
) {
|
||||||
|
let mut name = String::new();
|
||||||
|
for (client, entry) in (*lock).iter() {
|
||||||
|
if client == not_to {
|
||||||
|
name = entry.0.clone();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (other_client, entry) in (*lock).iter() {
|
||||||
|
let other_name = &entry.0;
|
||||||
|
let other_stream = &entry.1;
|
||||||
|
if everyone == false && other_client == not_to {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match (|| -> Result<()> {
|
||||||
|
let mut writer = BufWriter::new(other_stream);
|
||||||
|
// test if message begins with "MSG " /////////////////////////
|
||||||
|
if &msg[..4] == "MSG " {
|
||||||
|
try!(writeln!(writer, "FROM {} {}", name, msg));
|
||||||
|
} else {
|
||||||
|
try!(writeln!(writer, "{}", msg));
|
||||||
|
}
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
try!(writer.flush());
|
||||||
|
return Ok(());
|
||||||
|
})()
|
||||||
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
println!(
|
||||||
|
"{} Client {} <{}> disappeared during message distribution: {}",
|
||||||
|
get_time(),
|
||||||
|
other_client,
|
||||||
|
other_name,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_clients_name(to: &SocketAddr, lock: &mut MutexGuard<UserMap>) {
|
||||||
|
let mut clients = String::new();
|
||||||
|
let mut num_client = 0usize;
|
||||||
|
for (client, entry) in (*lock).iter() {
|
||||||
|
num_client += 1;
|
||||||
|
clients.push_str(&format!(
|
||||||
|
"{}{} ",
|
||||||
|
&entry.0.trim(),
|
||||||
|
if client == to { "(you)" } else { "" }
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let clients = clients.trim();
|
||||||
|
for (client, entry) in (*lock).iter() {
|
||||||
|
if client == to {
|
||||||
|
let stream = &entry.1;
|
||||||
|
let mut writer = BufWriter::new(stream);
|
||||||
|
let mut req = String::from(format!("{}{} ", "LIST CLIENTS ", num_client));
|
||||||
|
req.push_str(clients);
|
||||||
|
println!(
|
||||||
|
"{time} to {nick}@{addr} : {message}",
|
||||||
|
time = get_time(),
|
||||||
|
nick = &entry.0,
|
||||||
|
addr = &entry.1.peer_addr().unwrap(),
|
||||||
|
message = req
|
||||||
|
);
|
||||||
|
writeln!(writer, "{}", req).unwrap();
|
||||||
|
writer.flush().unwrap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disconnect_user(name: &str, client: &SocketAddr, lock: &mut MutexGuard<UserMap>) {
|
||||||
|
(*lock).remove(&client);
|
||||||
|
distribute_message(&format!("LOGOUT {}", name), client, lock, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_client(stream: TcpStream, clients: Arc<Mutex<UserMap>>) {
|
||||||
|
// Get client IP and port
|
||||||
|
let client = stream.peer_addr().unwrap();
|
||||||
|
println!("{} New connection from {}", get_time(), client);
|
||||||
|
|
||||||
|
// Buffered reading and writing
|
||||||
|
let mut reader = BufReader::new(&stream);
|
||||||
|
let mut writer = BufWriter::new(&stream);
|
||||||
|
|
||||||
|
// Write an entire line to the client
|
||||||
|
// Can fail on IO errors, du to try! macro
|
||||||
|
macro_rules! send {
|
||||||
|
($line:expr) => ({
|
||||||
|
try!(writeln!(writer, "{}", $line));
|
||||||
|
try!(writer.flush());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read an entire line from the client
|
||||||
|
// Can fail on IO errors or when EOF is reached
|
||||||
|
macro_rules! receive {
|
||||||
|
() => ({
|
||||||
|
let mut line = String::new();
|
||||||
|
match reader.read_line(&mut line) {
|
||||||
|
Ok(len) => {
|
||||||
|
if len == 0 {
|
||||||
|
// Reader is at EOF.
|
||||||
|
return Err(Error::new(ErrorKind::Other, "unexpected EOF"));
|
||||||
|
}
|
||||||
|
line.pop();
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
line
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user's name
|
||||||
|
let name: String = match (|| loop {
|
||||||
|
match receive!() {
|
||||||
|
input => {
|
||||||
|
println!(
|
||||||
|
"{time} Client {addr} : {message}",
|
||||||
|
time = get_time(),
|
||||||
|
addr = client,
|
||||||
|
message = input
|
||||||
|
);
|
||||||
|
let spliced_input: Vec<&str> = input.split_whitespace().collect();
|
||||||
|
if spliced_input.len() != 4 && spliced_input.len() != 5
|
||||||
|
|| spliced_input[0] != "PROT"
|
||||||
|
{
|
||||||
|
return Err(Error::new(ErrorKind::Other, "BAD REQ"));
|
||||||
|
}
|
||||||
|
if spliced_input[1] != ::PROTOCOL {
|
||||||
|
return Err(Error::new(ErrorKind::Other, "BAD PROT"));
|
||||||
|
}
|
||||||
|
if spliced_input.len() == 5 {
|
||||||
|
if spliced_input[2] == "CONNECT" && spliced_input[3] == "USER" {
|
||||||
|
let username = String::from(spliced_input[4]);
|
||||||
|
let mut ascii_nick = true;
|
||||||
|
for c in username.chars() {
|
||||||
|
if !c.is_ascii() {
|
||||||
|
ascii_nick = false;
|
||||||
|
println!(
|
||||||
|
"{time} to client {addr} : {message}",
|
||||||
|
time = get_time(),
|
||||||
|
addr = client,
|
||||||
|
message = "NAME FAILURE"
|
||||||
|
);
|
||||||
|
send!("NAME FAILURE");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ascii_nick {
|
||||||
|
let mut used = false;
|
||||||
|
{
|
||||||
|
let lock = clients.lock().unwrap();
|
||||||
|
for (_, entry) in (*lock).iter() {
|
||||||
|
if username == entry.0 {
|
||||||
|
used = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if used == false {
|
||||||
|
println!(
|
||||||
|
"{time} to client {addr} : {message}",
|
||||||
|
time = get_time(),
|
||||||
|
addr = client,
|
||||||
|
message = "NAME OK"
|
||||||
|
);
|
||||||
|
send!("NAME OK");
|
||||||
|
return Ok(username);
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"{time} to client {addr} : {message}",
|
||||||
|
time = get_time(),
|
||||||
|
addr = client,
|
||||||
|
message = "NAME FAILURE"
|
||||||
|
);
|
||||||
|
send!("NAME FAILURE");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(ErrorKind::Other, "BAD REQ"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
println!(
|
||||||
|
"{time} to client {addr} : {message}",
|
||||||
|
time = get_time(),
|
||||||
|
addr = client,
|
||||||
|
message = "NAME REQ"
|
||||||
|
);
|
||||||
|
send!("NAME REQ");
|
||||||
|
match receive!() {
|
||||||
|
input => {
|
||||||
|
println!(
|
||||||
|
"{time} Client {addr} : {message}",
|
||||||
|
time = get_time(),
|
||||||
|
addr = client,
|
||||||
|
message = input
|
||||||
|
);
|
||||||
|
let spliced_input: Vec<&str> = input.split_whitespace().collect();
|
||||||
|
if spliced_input.len() != 2 || spliced_input[0] != "NAME" {
|
||||||
|
return Err(Error::new(ErrorKind::Other, "BAD REQ"));
|
||||||
|
}
|
||||||
|
let username = String::from(spliced_input[1]);
|
||||||
|
let mut ascii_nick = true;
|
||||||
|
for c in username.chars() {
|
||||||
|
if !c.is_ascii() {
|
||||||
|
ascii_nick = false;
|
||||||
|
println!(
|
||||||
|
"{time} to client {addr} : {message}",
|
||||||
|
time = get_time(),
|
||||||
|
addr = client,
|
||||||
|
message = "NAME FAILURE"
|
||||||
|
);
|
||||||
|
send!("NAME FAILURE");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ascii_nick {
|
||||||
|
let mut used = false;
|
||||||
|
{
|
||||||
|
let lock = clients.lock().unwrap();
|
||||||
|
for (_, entry) in (*lock).iter() {
|
||||||
|
if username == entry.0 {
|
||||||
|
used = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if used == false {
|
||||||
|
println!(
|
||||||
|
"{time} to client {addr} : {message}",
|
||||||
|
time = get_time(),
|
||||||
|
addr = client,
|
||||||
|
message = "NAME OK"
|
||||||
|
);
|
||||||
|
send!("NAME OK");
|
||||||
|
return Ok(username);
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"{time} to client {addr} : {message}",
|
||||||
|
time = get_time(),
|
||||||
|
addr = client,
|
||||||
|
message = "NAME FAILURE"
|
||||||
|
);
|
||||||
|
send!("NAME FAILURE");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
{
|
||||||
|
Ok(name) => name,
|
||||||
|
Err(e) => {
|
||||||
|
println!(
|
||||||
|
"{time} client {addr} encountered an error: {err}",
|
||||||
|
time = get_time(),
|
||||||
|
addr = client,
|
||||||
|
err = e
|
||||||
|
);
|
||||||
|
writeln!(writer, "{}", e).unwrap();
|
||||||
|
writer.flush().unwrap();
|
||||||
|
return ();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add user to global map. Lock will be released at the end of the scope
|
||||||
|
{
|
||||||
|
let mut lock = clients.lock().unwrap();
|
||||||
|
(*lock).insert(client, (name.clone(), stream.try_clone().unwrap()));
|
||||||
|
distribute_message(&format!("JOIN {}", name), &client, &mut lock, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(writer, "WELCOME").unwrap();
|
||||||
|
writer.flush().unwrap();
|
||||||
|
|
||||||
|
// Chat loop: Receive messages from users once connected
|
||||||
|
match (|| loop {
|
||||||
|
match receive!().as_str() {
|
||||||
|
input => {
|
||||||
|
println!(
|
||||||
|
"{time} {nick}@{addr}: {message}",
|
||||||
|
time = get_time(),
|
||||||
|
addr = client,
|
||||||
|
nick = name,
|
||||||
|
message = input
|
||||||
|
);
|
||||||
|
|
||||||
|
match input {
|
||||||
|
"BYE" => {
|
||||||
|
println!(
|
||||||
|
"{time} to {nick}@{addr} : {message}",
|
||||||
|
time = get_time(),
|
||||||
|
addr = client,
|
||||||
|
message = "BYE",
|
||||||
|
nick = name
|
||||||
|
);
|
||||||
|
send!("BYE");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
"PING" => {
|
||||||
|
println!(
|
||||||
|
"{time} to {nick}@{addr} : {message}",
|
||||||
|
time = get_time(),
|
||||||
|
addr = client,
|
||||||
|
message = "NAME FAILURE",
|
||||||
|
nick = name
|
||||||
|
);
|
||||||
|
send!("PONG");
|
||||||
|
}
|
||||||
|
|
||||||
|
"REQ CLIENTS" => {
|
||||||
|
let mut lock = clients.lock().unwrap();
|
||||||
|
send_clients_name(&client, &mut lock);
|
||||||
|
}
|
||||||
|
input => {
|
||||||
|
let spliced_input: Vec<&str> = input.split_whitespace().collect();
|
||||||
|
match spliced_input[0] {
|
||||||
|
"MSG" => {
|
||||||
|
let mut message = String::new();
|
||||||
|
for i in 1..spliced_input.len() {
|
||||||
|
message.push_str(spliced_input[i]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let mut lock = clients.lock().unwrap();
|
||||||
|
distribute_message(
|
||||||
|
&format!("{}", input),
|
||||||
|
&client,
|
||||||
|
&mut lock,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
println!(
|
||||||
|
"{time} to client {addr} : \"{message}\", cause : {inmessage}",
|
||||||
|
time = get_time(),
|
||||||
|
addr = client,
|
||||||
|
message = "BAD REQ",
|
||||||
|
inmessage = input
|
||||||
|
);
|
||||||
|
send!("BAD REQ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// {
|
||||||
|
// let mut lock = clients.lock().unwrap();
|
||||||
|
// distribute_message(&format!("{}", input), &client, &mut lock, true);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
{
|
||||||
|
Ok(_) => {
|
||||||
|
println!("{} Client {} <{}> left", get_time(), client, name);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!(
|
||||||
|
"{} Client {} <{}> disappeared during chat: {}",
|
||||||
|
get_time(),
|
||||||
|
client,
|
||||||
|
name,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove user from global map
|
||||||
|
{
|
||||||
|
let mut lock = clients.lock().unwrap();
|
||||||
|
disconnect_user(&name, &client, &mut lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serveur(addr: String) {
|
||||||
|
// Manage UserMap in a mutex
|
||||||
|
let clients = Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
let serv_addr = addr.clone();
|
||||||
|
|
||||||
|
// Start a TCP Listener
|
||||||
|
let listener = match TcpListener::bind(serv_addr.as_str()) {
|
||||||
|
Ok(listener) => listener,
|
||||||
|
Err(e) => panic!("Could not read start TCP listener: {}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{} Successfully started the server on {}",
|
||||||
|
get_time(),
|
||||||
|
serv_addr
|
||||||
|
);
|
||||||
|
|
||||||
|
for stream in listener.incoming() {
|
||||||
|
match stream {
|
||||||
|
Ok(stream) => {
|
||||||
|
let clients = clients.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
//connection succeeded
|
||||||
|
handle_client(stream, clients)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
writeln!(stderr(), "Connection failed: {}", e).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// close the socket server
|
||||||
|
drop(listener);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user