simple/simple.rs
1use spunet::{
2 Protocol,
3 client::{
4 Client,
5 ClientError,
6 },
7 server::{
8 Server,
9 ServerError,
10 },
11};
12use std::net::{
13 SocketAddr,
14 IpAddr,
15 Ipv4Addr,
16};
17use std::time::Duration;
18use std::io::{ self, Write };
19
20// Server address stuff.
21const SERVER_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
22const SERVER_PORT: u16 = 6969;
23const SERVER_ADDRESS: SocketAddr = SocketAddr::new(SERVER_IP, SERVER_PORT);
24
25// Buffer size for received messages.
26const BUFFER_SIZE: usize = 1024;
27
28// Timeout for reading messages on the receiving thread
29// (this library runs parallel threads for reading which is a blocking operation).
30// This should be set to something small if you want to receive
31// messages quickly, but it shouldn't be zero since that
32// doesn't even give the receiver a chance to receive.
33const READ_TIMEOUT: Duration = Duration::from_nanos(1);
34
35// Optional timeout for when a client is connecting to a server.
36const CONNECTION_TIMEOUT: Option<Duration> = None;
37
38fn main() {
39 let args: Vec<String> = std::env::args().collect();
40
41 if args.len() == 2 {
42 let protocol = match input("Protocol [tcp/udp]: ").to_lowercase().as_str() {
43 "tcp" => Protocol::TCP,
44 "udp" => Protocol::UDP,
45 _ => {
46 eprintln!("Invalid protocol! Please choose either TCP or UDP!");
47
48 std::process::exit(1);
49 },
50 };
51
52 match args[1].as_str() {
53 "server" => spawn_server(protocol).unwrap(),
54 "client" => spawn_client(protocol).unwrap(),
55 _ => {
56 eprintln!("Invalid option: {}", args[1]);
57
58 std::process::exit(1);
59 },
60 };
61 }
62
63 else {
64 eprintln!("Usage: {} <COMMAND>", args[0]);
65 eprintln!("");
66 eprintln!("Commands:");
67 eprintln!(" server ... Spawn the server.");
68 eprintln!(" client ... Spawn a client and connect to the server.");
69
70 std::process::exit(1);
71 }
72}
73
74fn input(prefix: &str) -> String {
75 print!("{}", prefix);
76
77 io::stdout().flush().unwrap();
78
79 let mut user_input = String::new();
80
81 io::stdin().read_line(&mut user_input).unwrap();
82
83 return user_input.trim().to_string();
84}
85
86// This function is what spawns the server for this example.
87fn spawn_server(protocol: Protocol) -> Result<(), ServerError> {
88 println!("Starting server...");
89
90 // Creating the server and binding it to a local address to listen on.
91 let mut server: Server<BUFFER_SIZE> = Server::bind(SERVER_ADDRESS, READ_TIMEOUT)?;
92
93 // Server loop.
94 loop {
95 // Check to see if the server is still running.
96 if server.is_running() == false {
97 break;
98 }
99
100 // Flush the server.
101 //
102 // I just now, as I was writing this comment, realized
103 // that you are flushing using a handle to the underlying
104 // TCP stream! Ha! Flushing... using a handle! :D
105 server.flush()?;
106
107 // There is a separate thread that accepts incoming client
108 // connections, and this function simply gets a list of
109 // any client IDs for clients that joined since the last call
110 // of this function. If this function was never called, it
111 // will return all clients, which means this function is guarenteed
112 // to return all client IDs through out the runtime of the program.
113 let new_clients = server.new_clients()?;
114
115 // Just iterating through all the new clients and welcoming them.
116 for client_id in new_clients {
117 // This should always return Some(...) since the client ID
118 // is guarenteed to be a valid client's ID.
119 let client_info = server.get_client_info(client_id).unwrap();
120
121 println!("New client {}: {}", client_id.id, client_info.client_address);
122 server.send(
123 client_id,
124 protocol,
125 format!(
126 "Hello client #{}! Your address is: {}",
127 client_id.id,
128 client_info.client_address,
129 ).as_bytes(),
130 )?;
131 }
132
133 // Iterate through every client connected to the server.
134 for client_id in server.clients() {
135 // If this returns false, this is most likely because the
136 // server kicked this client in a previous iteration.
137 if server.is_valid_client(client_id) == false {
138 println!("Invalid client, skipping... (#{})", client_id.id);
139
140 continue;
141 }
142
143 // Unlike the vanilla implementations of TCP and UDP in the standard libraries
144 // of programming languages, this library's recv() function is non-blocking,
145 // so it returns either data or nothing.
146 // The read timeout (discussed earlier) is for the blocking recv() call in the
147 // reading thread so the reading thread doesn't get stuck.
148 if let Some((message_raw, message_size)) = server.recv(client_id, protocol)? {
149 // The reason that a slice of the raw bytes as opposed to the whole
150 // thing is being used is because the string will actually have a lot
151 // of termination characters at the end since the raw bytes message
152 // is the same size as the BUFFER_SIZE constant. So by passing a slice,
153 // this string will only be made with bytes that were received as opposed
154 // to the entire buffer.
155 let message = String::from_utf8_lossy(&message_raw[..message_size]).to_string();
156
157 println!("<Client #{}> {}", client_id.id, message);
158
159 match message.as_str() {
160 "quit" => {
161 println!("Kicking client #{}...", client_id.id);
162 server.kick(client_id)?;
163 },
164 "stop" => {
165 println!("Stopping server...");
166 server.stop()?;
167 },
168 "protocol" => {
169 println!("Protocol: {:?}", protocol);
170 },
171 _ => (),
172 };
173 }
174 }
175 }
176
177 Ok(())
178}
179
180// This function is what spawns a client for this example.
181fn spawn_client(protocol: Protocol) -> Result<(), ClientError> {
182 println!("Creating a client...");
183
184 // Pretty self-explanatory, just creating a client. :)
185 let mut client: Client<BUFFER_SIZE> = Client::new(READ_TIMEOUT);
186
187 println!("Connecting to server... ({})", SERVER_ADDRESS);
188 client.connect(SERVER_ADDRESS, CONNECTION_TIMEOUT)?;
189 client.send(protocol, "Hello, server!".as_bytes())?;
190 client.flush()?; // Send the message immediately by flushing the client.
191
192 loop {
193 // Check whether or not the client is still connected.
194 if client.is_connected() == false {
195 break;
196 }
197
198 // Flush the client.
199 client.flush()?;
200
201 // Pretty much the same as the server example.
202 if let Some((message_raw, message_size)) = client.recv(protocol)? {
203 let message = String::from_utf8_lossy(&message_raw[..message_size]).to_string();
204
205 println!("<Server> {}", message);
206 }
207
208 let user_message = input("[ Message To Server ]: ");
209
210 client.send(protocol, user_message.as_bytes())?;
211
212 match user_message.as_str() {
213 "quit" => {
214 client.disconnect()?; // This needs to be handled client-side too,
215 // since TCP and UDP (vanilla implementations)
216 // do not let the clients know that they have
217 // been disconnected. Clients also don't let
218 // the server know that they have disconnected.
219 },
220 "stop" => {
221 println!("Sent stop request to server...");
222 client.disconnect()?; // Same reason as the above comment. ^
223 },
224 "protocol" => {
225 println!("Protocol: {:?}", protocol);
226 },
227 _ => {
228 if let Ok(times) = user_message.parse::<usize>() {
229 for _ in 0..times {
230 client.send(protocol, "Ping!".as_bytes())?;
231 }
232 }
233 },
234 };
235 }
236
237 Ok(())
238}