added gitignore
[clientserver.git] / Client.java
blob8a5c7cbf2d223f451dbd5912f979acfed8554e93
1 import java.net.*;
2 import java.util.*;
3 import java.io.*;
5 /**
6 * Client, connects to a Server and acts as the
7 * user interface with which users can chat with
8 * other users on the same server and in the same
9 * chatroom.
11 * TODO: add run-time support to change the port
12 * used to send messages to the server.
14 * @author "Victor Zamanian" <victor.zamanian@gmail.com>
15 * Time-stamp: "Tue 11 Sep 2007 11:15:49 AM CEST"
17 public class Client {
19 // The username for the user of this client
20 private String nickName = null;
21 // The host to connect to
22 private InetAddress hostAddress = null;
23 // The port to connect on
24 private int port;
25 // The socket to connect with
26 private DatagramSocket clientSocket = null;
28 /**
29 * Constructor for Client, creates a socket
30 * to send and receive data through.
32 public Client() {
33 // Create socket
34 try {
35 this.clientSocket = new DatagramSocket();
36 } catch (SocketException e) {
37 System.err.println(e);
41 /**
42 * getHelp returns the help text that describes
43 * how to use the different commands and what they do.
45 * return String helpString
47 public String getHelp() {
49 return "Use \'/\' in front of a command word " +
50 "to invoke that command.\n" +
51 "Commands are as follows:\n\n" +
53 "help: display this help message.\n" +
55 "login: login to a server with a nickname.\n" +
56 "login new: login to another server (should only be used if\n" +
57 " already logged in to a server).\n" +
59 "lschans: list the channels (chat rooms) that\n" +
60 " have been created on the server.\n" +
62 "lsusers: list the users that have currently joined\n" +
63 " the same channel as you have.\n" +
65 "createchannel channelName: create a channel on the server.\n" +
67 "join channelName: join a channel on the server.\n" +
69 "leave: leave the channel you are in.\n" +
71 "quit: quit (exit) the Client.\n"
72 // doesn't seem to work on all terminal sessions.
75 "clear: clear the screen of text (or at least " +
76 "move it up a bit)."
81 // GET METHODS
83 /**
84 * getNickName returns the nickname that
85 * this user is logged in with on the server.
87 * @return String nickname
89 public String getNickName() {
90 return this.nickName;
93 /**
94 * getHost returns the InetAddress that
95 * the server has.
97 * @return InetAddress hostAddress
99 public InetAddress getHost() {
100 return this.hostAddress;
104 * getPort returns the port the server listens to
105 * for new messages from users.
107 * @return int port
109 public int getPort() {
110 return this.port;
114 * getSocket returns the DatagramSocket that this
115 * client uses to send and receive messages.
117 * @return DatagramSocket clientSocket
119 public DatagramSocket getSocket() {
120 return this.clientSocket;
123 // SET methods
126 * setNickName sets this client's nickname.
127 * This method isn't really to be used other
128 * than when loggin into the server. No support
129 * exists (yet) for changing one's nickname once
130 * logged into the server.
132 * @param String nickName
134 public void setNickName(String nickName) {
135 this.nickName = nickName;
139 * setHost sets the host (server IP address)
140 * that this client is connected to.
142 * @param InetAddress host
144 public void setHost(InetAddress host) {
145 this.hostAddress = host;
149 * setPort sets the port which the server, which
150 * this client is connected to, listens on for
151 * incoming messages.
153 * @param int port
155 public void setPort(int port) {
156 this.port = port;
160 * setSocket sets the socket which this client
161 * uses to send and receive messages to a new socket.
163 * @param DatagramSocket socket
165 public void setSocket(DatagramSocket socket) {
166 // close current socket
167 this.clientSocket.close();
168 // switch over to new socket
169 this.clientSocket = socket;
173 * login prompts the user for a nickname
174 * and information about the server to login to.
176 public void login() throws IOException {
178 // temporary variables to help the process
179 String tempInput = null;
181 // Prompt for the nickname to use at login
182 do {
183 tempInput = Library.prompt("Enter your desired username: ");
184 } while (tempInput.length() < 1 || tempInput.length() > 255);
185 // while the nickname is not the empty String and is not longer
186 // than 255 characters (the max. size of a byte)
188 // set this client's nickname to what the user put in
189 this.setNickName(tempInput);
191 // Prompt for the host (server) to connect to
192 tempInput = Library.prompt("Establish connection to: ");
193 try {
194 this.setHost(InetAddress.getByName(tempInput));
196 catch (UnknownHostException e) {
197 System.err.println(e);
200 // Prompt for the port used by the server
201 this.setPort(Library.promptInt("Connect on port no.: "));
203 // create a new DatagramPacket of type 1
204 // (login to server) to send to the server.
205 DatagramPacket outPacket = this.constructPacket(1, this.getNickName());
207 // send the packet
208 try {
209 this.getSocket().send(outPacket);
211 catch (IOException e) {
212 System.err.println(e);
217 * constructPacket is a method that builds a DatagramPacket
218 * depending on what is sent to the method as parameters.
219 * It handles length of the message and inserts it correctly
220 * into the byte array in the packet, as well as making sure
221 * the message length does not cause the packet to fill up
222 * past its defined capacity (255 characters).
224 * @param int messageType
225 * @param String message
227 public DatagramPacket constructPacket(int messageType, String message) {
228 byte[] packetData = new byte[257];
229 String localMessage = message;
231 if (localMessage == null) {
232 localMessage = "";
235 // set the message type for the packet
236 packetData[0] = (byte) messageType;
237 // set the length of the packet. (255 if localMessage >= 255)
238 packetData[1] = (localMessage.length() < 255) ? (byte) localMessage.length() : (byte) 255;
240 // put the message into the packet data
241 for (int i=0; i<packetData[1]; i++) {
242 packetData[2+i] = (byte) localMessage.charAt(i);
245 // make a new DatagramPacket and return it
246 return new DatagramPacket(
247 packetData,
248 packetData.length,
249 this.getHost(),
250 this.getPort()
255 // main. What to say... :P
256 public static void main(String[] args) throws Exception {
258 // create a new instance of Client
259 Client client = new Client();
260 // boolean keepLoopRunning helps
261 // keep track of whether or not the
262 // program should terminate once it goes
263 // back into the infinite "input-loop".
264 boolean keepLoopRunning = true;
266 // Login to the server
267 client.login();
269 // Start thread for receiving messages from the server
270 new ReceiveMessageThread(client.getSocket()).start();
273 // START HANDLING INPUT FROM THE USER AND SENDING MESSAGES
275 // Fields needed within the loop
276 byte[] sendData = null;
277 String input = null;
278 DatagramPacket outPacket = null;
279 int messageType = 0;
281 // input-loop
282 while (keepLoopRunning) {
284 // Read from keyboard
285 // while the user entered the empty String
286 input = "";
287 while (input.length() < 1) {
288 input = Library.prompt();
289 // fixes a "bad thing" where a user can
290 // clear the screen of any other user
291 // in the same chat room as this
292 // user (and the server).
293 // UPDATE: seems to only affect putty sessions?
294 if (input.contains("\f")) {
295 System.out.println("Message cannot contain a line feed (\'\\f\'/^L)");
296 input = "";
297 // start over
298 continue;
302 // If a command was invoked ('/')
303 if (input.charAt(0) == '/') {
305 // String for saving the invoked command
306 // (we will use 'input' to save any
307 // potential parameter)
308 String command = null;
310 StringTokenizer tokens = new StringTokenizer(input.substring(1, input.length()));
311 // if just "/" was typed
312 if (tokens.countTokens() == 0) {
313 // Start loop over again
314 continue;
316 // if a command was invoked that takes no parameters.
317 else if (tokens.countTokens() == 1) {
318 command = tokens.nextToken();
319 // set input to null, because when we construct the
320 // DatagramPacket later on we want the length of the
321 // packet message to be zero.
322 input = null;
324 if (command.equals("login")) {
325 messageType = 1;
326 input = client.getNickName();
327 System.out.println("Login with nickname: " + input);
329 else if (command.equals("lschans")) {
330 messageType = 3;
331 System.out.println("Channels: ");
333 else if (command.equals("lsusers")) {
334 messageType = 6;
335 System.out.println("Users: ");
337 else if (command.equals("quit")) {
338 messageType = 7;
339 System.out.println("User chose to quit.");
340 keepLoopRunning = false;
342 else if (command.equals("leave")) {
343 messageType = 7;
345 else if (command.equals("help")) {
346 // Print help about commands
347 System.out.println('\n' + client.getHelp() + '\n');
348 // start loop over (don't send a message to server)
349 continue;
351 // else if (command.equals("clear")) {
352 // // move text up ("clear screen")
353 // System.out.print('\f');
354 // continue;
355 // }
356 else {
357 System.out.println("Command syntax unknown: " +
358 command);
359 continue;
362 // if a command was invoked that takes a parameter
363 // (we do not pay attention to trailing parameters)
364 else if (tokens.countTokens() > 1) {
365 command = tokens.nextToken();
366 // set input to be the parameter to the command invoked
367 // that way it will be put as the message of the byte array
368 // in the constructed DatagramPacket later on
369 input = tokens.nextToken();
371 if (command.equals("createchannel")) {
372 messageType = 2;
374 else if (command.equals("login") && input.equals("new")) {
375 client.login();
377 else if (command.equals("join")) {
378 messageType = 4;
380 else {
381 System.out.println("Command syntax unknown: " +
382 command + ' ' + input);
383 continue;
387 // If no command invoked--regular message to channel
388 else {
389 messageType = 5;
392 // construct packet to send.
393 // Here, input will have been altered depending
394 // on whether or not a command was invoked, and
395 // which command was potentially invoked.
396 outPacket = client.constructPacket(messageType, input);
398 // try sending the packet
399 try {
400 client.getSocket().send(outPacket);
402 catch (IOException e) {
403 System.err.println(e);
406 } // end while
408 // Close socket if loop is ended (user termination)
409 client.getSocket().close();
411 } // end main
415 * ReceiveMessageThread receives messages in the
416 * background and prints them to standard output.
418 class ReceiveMessageThread extends Thread {
420 // store the packet data into this byte array
421 byte[] receivedData = null;
422 // datagram socket to use
423 DatagramSocket clientSocket = null;
424 // receive the packets with this datagram packet
425 DatagramPacket inPacket = null;
427 int messageType = 0;
428 int messageLength = 0;
429 String message = null;
432 * Constructs a new instance of a ReceiveMessageThread.
433 * The constructor takes a DatagramSocket as a parameter
434 * and uses it to receive messages.
436 * @param DatagramSocket clientSocket
438 public ReceiveMessageThread(DatagramSocket clientSocket) {
439 this.clientSocket = clientSocket;
440 this.receivedData = new byte[257];
441 this.inPacket = new DatagramPacket(receivedData, receivedData.length);
444 public void run() {
446 // infinite loop (to keep this going)
447 while (true) {
448 // try to receive a packet
449 try {
450 clientSocket.receive(inPacket);
452 // if the socket was closed, we stop the thread
453 catch (IOException e) {
454 System.err.println("Socket was closed, now exiting...");
455 // End the loop.
456 break;
459 // fetch the message type (not really used)
460 messageType = (int) receivedData[0] & 0xff;
461 // fetch the length of the message in the data
462 messageLength = (int) receivedData[1] & 0xff;
464 message = new String(receivedData).substring(2, messageLength + 2);
466 // make sure we're "not printing nothing"
467 if (message == null || message.equals("")) {
468 message = "Something went wrong here...";
470 // print message
471 System.out.println(message);
473 } // end infinite loop :-)
474 } // end run()
475 } // end class.