Created
April 23, 2012 07:54
-
-
Save colin-haber/2469422 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * CHAT - CHAT Has Acronym Troubles - The stupid messaging protocol. | |
| * Copyright (C) 2012 Jon Stevens and Colin Haber | |
| * | |
| * This program is free software: you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License as published by | |
| * the Free Software Foundation, either version 3 of the License, or | |
| * (at your option) any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| */ | |
| package com.limyc.chat; | |
| import com.n1nja.semver.Version; | |
| /** | |
| * @author Colin Haber | |
| * @version 0.2.0 | |
| * @since 0.2.0 | |
| */ | |
| public class CHAT { | |
| public static final Version VERSION = new Version(0, 2, 0); | |
| public static final String PROTOCOL = "CHAT"; | |
| public static enum Command { | |
| REGISTER("REG"), | |
| MESSAGE("MSG"), | |
| DISCONNECT("DSC"); | |
| private String text; | |
| private Command(String text) { | |
| this.text = text; | |
| } | |
| public String getCommand() { | |
| return this.text; | |
| } | |
| @Override | |
| public String toString() { | |
| return this.getCommand(); | |
| } | |
| } | |
| public static enum Status { | |
| GENERIC_SUCCESS("000"), | |
| GENERIC_FAILURE("010"), | |
| GENERIC_INVALID_ID("011"), | |
| GENERIC_ERROR("020"), | |
| REGISTER_SUCCESS("100"), | |
| REGISTER_FAILURE("110"), | |
| REGISTER_NAME_TAKEN("111"), | |
| REGISTER_ERROR("120"), | |
| MESSAGE_SUCCESS("200"), | |
| MESSAGE_FAILURE("210"), | |
| MESSAGE_INVALID_ID("211"), | |
| MESSAGE_ERROR("220"), | |
| DISCONNECT_SUCCESS("300"), | |
| DISCONNECT_FAILURE("310"), | |
| DISCONNECT_INVALID_ID("311"), | |
| DISCONNECT_ERROR("320"); | |
| private String text; | |
| private Status(String text) { | |
| this.text = text; | |
| } | |
| public String getStatus() { | |
| return this.text; | |
| } | |
| @Override | |
| public String toString() { | |
| return this.getStatus(); | |
| } | |
| } | |
| public static String getHeader(Command c) { | |
| return CHAT.PROTOCOL + ":" + c.getCommand() + ":"; | |
| } | |
| public static String getHeader(Command c, Status s) { | |
| return CHAT.getHeader(c) + s.getStatus() + ":"; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.n1nja.networking; | |
| import java.io.*; | |
| import java.net.*; | |
| /** | |
| * A simple network client framework for single-line communication | |
| * client-server pairs. | |
| * @author Colin Haber | |
| */ | |
| public abstract class Client extends ConsoleApplication { | |
| private final Socket socket; | |
| private BufferedReader netReader; | |
| private BufferedWriter netWriter; | |
| /** | |
| * Creates a new Client. | |
| */ | |
| public Client() { | |
| socket = new Socket(); | |
| } | |
| /** | |
| * Creates a new Client, and connects it to the provided host. | |
| * Equivalent to {@code new Client().connect(hostAddress, port)}. | |
| * @param hostAddress | |
| * @param port | |
| */ | |
| public Client(InetAddress hostAddress, int port) { | |
| this(); | |
| this.connect(hostAddress, port); | |
| } | |
| /** | |
| * Connects to the provided host. | |
| * @param hostAddress | |
| * @param port | |
| */ | |
| public void connect(InetAddress hostAddress, int port) { | |
| try { | |
| this.socket.connect(new InetSocketAddress(hostAddress, port)); | |
| this.netReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); | |
| this.netWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); | |
| this.printLine("Connected to " + this.socket.getInetAddress().getCanonicalHostName() + ":" + this.socket.getPort()); | |
| } catch (IOException e) { | |
| e.printStackTrace(); | |
| } | |
| } | |
| /** | |
| * Tests whether the Client has ever been connected to a server. | |
| * @return | |
| */ | |
| public boolean isConnected() { | |
| return this.socket.isConnected(); | |
| } | |
| @Override | |
| public void start() { | |
| super.start(); | |
| final Client client = this; | |
| new Thread( | |
| new Runnable() { | |
| @Override | |
| public void run() { | |
| while (!client.socket.isInputShutdown()) { | |
| try { | |
| if (client.netReader.ready()) { | |
| client.handle(client.readLine()); | |
| } | |
| } catch (IOException e) { | |
| e.printStackTrace(); | |
| } | |
| } | |
| } | |
| } | |
| ).start(); | |
| } | |
| /** | |
| * Called when input is read from the Server. | |
| * @param s | |
| */ | |
| public abstract void handle(String s); | |
| /** | |
| * Closes any open sockets and exits the program. | |
| */ | |
| public void stop() { | |
| if (!this.socket.isClosed()) { | |
| try { | |
| this.socket.close(); | |
| } catch (IOException e) { | |
| e.printStackTrace(); | |
| } | |
| } | |
| System.exit(0); | |
| } | |
| /** | |
| * Writes a single char to the Server. | |
| * @param c | |
| */ | |
| public void write(char c) { | |
| this.write((int) c); | |
| } | |
| /** | |
| * Writes a single int to the Server. | |
| * @param i | |
| */ | |
| public void write(int i) { | |
| try { | |
| this.netWriter.write(i); | |
| this.netWriter.flush(); | |
| } catch (IOException e) { | |
| e.printStackTrace(); | |
| } | |
| } | |
| /** | |
| * Writes a line of text to the Server. | |
| * @param s | |
| */ | |
| public void writeLine(String s) { | |
| try { | |
| this.netWriter.write(s + System.getProperty("line.separator")); | |
| this.netWriter.flush(); | |
| } catch (IOException e) { | |
| e.printStackTrace(); | |
| } | |
| } | |
| /** | |
| * Reads a single int from the Server. | |
| * @return | |
| */ | |
| public int read() { | |
| int read = -1; | |
| try { | |
| read = this.netReader.read(); | |
| } catch (IOException e) { | |
| e.printStackTrace(); | |
| } | |
| return read; | |
| } | |
| /** | |
| * Reads a line of text from the Server. | |
| * @return | |
| */ | |
| public String readLine() { | |
| String readLine = null; | |
| try { | |
| readLine = this.netReader.readLine(); | |
| } catch (IOException e) { | |
| e.printStackTrace(); | |
| } | |
| return readLine; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.limyc.limychat.server; | |
| import java.net.Socket; | |
| import java.util.Arrays; | |
| import com.limyc.chat.CHAT; | |
| import com.limyc.limychat.User; | |
| public class Connection extends com.n1nja.networking.Connection{ | |
| private User user; | |
| private Server server; | |
| public Connection(Server server, Socket socket) { | |
| super(server, socket); | |
| this.user = new User(); | |
| this.server = server; | |
| } | |
| @Override | |
| public void handle(String s) { | |
| this.printLine(s); | |
| if (s.startsWith(CHAT.PROTOCOL)) { | |
| String[] args = s.split(":"); | |
| this.command(args[1], Arrays.copyOfRange(args, 2, args.length)); | |
| } | |
| } | |
| @Override | |
| public void stop() { | |
| this.writeLine(CHAT.getHeader(CHAT.Command.DISCONNECT, CHAT.Status.DISCONNECT_SUCCESS)); | |
| this.printLine("Disconnected."); | |
| this.server.removeUser(this.getUser()); | |
| super.stop(); | |
| } | |
| public void command(String command, String[] params) { | |
| if (command.equals(CHAT.Command.REGISTER.getCommand()) && params.length >= 1) { | |
| String name = params[0]; | |
| long ID = System.nanoTime(); | |
| this.getUser().setName(name); | |
| this.getUser().setID(ID); | |
| if (this.server.addUser(this.getUser(), this)) { | |
| this.writeLine(CHAT.getHeader(CHAT.Command.REGISTER, CHAT.Status.REGISTER_SUCCESS) + this.getUser().getID()); | |
| this.printLine("Registered as " + this.getUser().getName()); | |
| } else { | |
| this.writeLine(CHAT.getHeader(CHAT.Command.REGISTER, CHAT.Status.REGISTER_NAME_TAKEN)); | |
| } | |
| } else if (command.equals(CHAT.Command.MESSAGE.getCommand()) && params.length >= 2) { | |
| String ID = params[0]; | |
| if (this.getUser().getID() == Long.valueOf(ID)) { | |
| String message = params[1]; | |
| this.server.writeLine(CHAT.getHeader(CHAT.Command.MESSAGE) + this.getUser().getName() + ":" + message); | |
| this.printLine(this.getUser().getName() + ": " + message); | |
| } else { | |
| this.writeLine(CHAT.getHeader(CHAT.Command.MESSAGE, CHAT.Status.MESSAGE_INVALID_ID)); | |
| } | |
| } else if (command.endsWith(CHAT.Command.DISCONNECT.getCommand()) && params.length >= 1) { | |
| String ID = params[0]; | |
| if (this.getUser().getID() == Long.valueOf(ID)) { | |
| this.stop(); | |
| } else { | |
| this.writeLine(CHAT.getHeader(CHAT.Command.DISCONNECT, CHAT.Status.DISCONNECT_INVALID_ID)); | |
| } | |
| } else { | |
| this.writeLine(CHAT.getHeader(CHAT.Command.GENERIC, CHAT.Status.GENERIC_FAILURE)); | |
| } | |
| } | |
| public User getUser() { | |
| return this.user; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.n1nja.networking; | |
| import java.io.BufferedReader; | |
| import java.io.IOException; | |
| import java.io.InputStreamReader; | |
| import java.text.SimpleDateFormat; | |
| import java.util.Arrays; | |
| import java.util.Date; | |
| public abstract class ConsoleApplication { | |
| private SimpleDateFormat timestampFormat; | |
| public ConsoleApplication() { | |
| this.timestampFormat = new SimpleDateFormat("'['yyyy-MM-dd HH:mm:ss']'"); | |
| } | |
| public synchronized void printLine(String s) { | |
| System.out.println(this.timestampFormat.format(new Date()) + " " + s); | |
| } | |
| public void start() { | |
| new Thread( | |
| new Runnable() { | |
| @Override | |
| public void run() { | |
| monitor(); | |
| } | |
| } | |
| ).start(); | |
| } | |
| public void monitor() { | |
| BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in)); | |
| while (true) { | |
| try { | |
| if (consoleReader.ready()) { | |
| String[] args = consoleReader.readLine().split(" "); | |
| command(args[0].toUpperCase(), Arrays.copyOfRange(args, 0, args.length)); | |
| } | |
| } catch (IOException e) { | |
| e.printStackTrace(); | |
| } | |
| } | |
| } | |
| public abstract void command(String command, String[] params); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.limyc.limychat.server; | |
| import java.util.HashMap; | |
| import com.limyc.limychat.LimyChat; | |
| import com.limyc.limychat.User; | |
| public class Server extends com.n1nja.networking.Server<Connection> { | |
| public static void main(String[] s) { | |
| new Server(LimyChat.DEFAULT_PORT).start(); | |
| } | |
| private HashMap<User, Connection> registeredUsers; | |
| public Server(int port) { | |
| super(port); | |
| registeredUsers = new HashMap<User, Connection>(); | |
| } | |
| @Override | |
| public void command(String command, String[] params) { | |
| if (command.equals("STOP") && params.length >= 0) { | |
| this.stop(); | |
| } | |
| } | |
| @Override | |
| public void listen() { | |
| final Connection c = new Connection(this, this.accept()); | |
| new Thread( | |
| new Runnable() { | |
| @Override | |
| public void run() { | |
| c.start(); | |
| } | |
| } | |
| ).start(); | |
| this.addConnection(c); | |
| } | |
| @Override | |
| public void writeLine(String s) { | |
| for (Connection c : this.registeredUsers.values()) { | |
| c.writeLine(s); | |
| } | |
| } | |
| public synchronized boolean addUser(User u, Connection c) { | |
| boolean nameIsTaken = false; | |
| checkNames:for (User r : this.registeredUsers.keySet()) { | |
| if (u.getName().equals(r.getName())) { | |
| nameIsTaken = true; | |
| break checkNames; | |
| } | |
| } | |
| if (!nameIsTaken) { | |
| this.registeredUsers.put(u, c); | |
| } | |
| return !nameIsTaken; | |
| } | |
| public synchronized void removeUser(User u) { | |
| this.registeredUsers.remove(u); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.n1nja.networking; | |
| import java.net.*; | |
| import java.io.*; | |
| public abstract class Connection { | |
| private final BufferedWriter netWrite; | |
| private final BufferedReader netRead; | |
| private final Socket socket; | |
| private final Server<? extends Connection> server; | |
| public Connection(Server<? extends Connection> server, Socket socket) { | |
| this.server = server; | |
| this.socket = socket; | |
| BufferedWriter netWrite = null; | |
| BufferedReader netRead = null; | |
| try { | |
| netWrite = new BufferedWriter(new OutputStreamWriter(this.socket.getOutputStream())); | |
| netRead = new BufferedReader(new InputStreamReader(this.socket.getInputStream())); | |
| } catch (IOException e) { | |
| e.printStackTrace(); | |
| } | |
| this.netWrite = netWrite; | |
| this.netRead = netRead; | |
| this.printLine("Connected."); | |
| } | |
| public void start() { | |
| while (!this.socket.isClosed()) { | |
| try { | |
| if (this.netRead.ready()) { | |
| this.handle(this.netRead.readLine()); | |
| } | |
| } catch (IOException e) { | |
| e.printStackTrace(); | |
| } | |
| } | |
| } | |
| public abstract void handle(String s); | |
| public void stop() { | |
| try { | |
| this.socket.close(); | |
| } catch (IOException e) { | |
| e.printStackTrace(); | |
| } finally { | |
| this.server.removeConnection(this); | |
| } | |
| } | |
| public void write(char c) { | |
| this.write((int) c); | |
| } | |
| public void write(int i) { | |
| try { | |
| this.netWrite.write(i); | |
| this.netWrite.flush(); | |
| } catch (IOException e) { | |
| e.printStackTrace(); | |
| } | |
| } | |
| public int read() { | |
| int read = -1; | |
| try { | |
| read = this.netRead.read(); | |
| } catch (IOException e) { | |
| e.printStackTrace(); | |
| } | |
| return read; | |
| } | |
| public void writeLine(String s) { | |
| try { | |
| this.netWrite.write(s + System.getProperty("line.separator")); | |
| this.netWrite.flush(); | |
| } catch (IOException e) { | |
| e.printStackTrace(); | |
| } | |
| } | |
| public String readLine() { | |
| String readLine = null; | |
| try { | |
| readLine = this.netRead.readLine(); | |
| } catch (IOException e) { | |
| e.printStackTrace(); | |
| } | |
| return readLine; | |
| } | |
| public void printLine(String s) { | |
| this.server.printLine("[" + this.socket.getInetAddress().getCanonicalHostName() + "] " + s); | |
| } | |
| public Server<? extends Connection> getServer() { | |
| return this.server; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.n1nja.networking; | |
| import java.net.*; | |
| import java.io.*; | |
| import java.util.*; | |
| public abstract class Server<C extends Connection> extends ConsoleApplication { | |
| private ServerSocket listener; | |
| private HashSet<Connection> connections; | |
| public Server(int port) { | |
| try { | |
| this.listener = new ServerSocket(port); | |
| } catch (IOException e) { | |
| e.printStackTrace(); | |
| } | |
| this.connections = new HashSet<Connection>(); | |
| } | |
| @Override | |
| public void start() { | |
| super.start(); | |
| final Server<C> server = this; | |
| new Thread( | |
| new Runnable() { | |
| @Override | |
| public void run() { | |
| server.listen(); | |
| } | |
| } | |
| ).start(); | |
| try { | |
| this.printLine("Server started at " + InetAddress.getLocalHost().getCanonicalHostName() + ":" + this.listener.getLocalPort()); | |
| } catch (UnknownHostException e) { | |
| e.printStackTrace(); | |
| } | |
| } | |
| public abstract void listen(); | |
| public synchronized void writeLine(String s) { | |
| for (Connection c: this.connections) { | |
| c.writeLine(s); | |
| } | |
| } | |
| public void stop() { | |
| try { | |
| this.listener.close(); | |
| } catch (IOException e) { | |
| e.printStackTrace(); | |
| } | |
| for (Connection c : this.connections) { | |
| c.stop(); | |
| } | |
| System.exit(0); | |
| } | |
| public synchronized void addConnection(Connection c) { | |
| this.connections.add(c); | |
| } | |
| public synchronized void removeConnection(Connection c) { | |
| this.connections.remove(c); | |
| } | |
| public Socket accept() { | |
| Socket accept = null; | |
| try { | |
| accept = this.listener.accept(); | |
| } catch (IOException e) { | |
| e.printStackTrace(); | |
| } | |
| return accept; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.limyc.limychat; | |
| public class User { | |
| private String name; | |
| private long ID; | |
| public User() { | |
| this.name = null; | |
| this.ID = 0L; | |
| } | |
| public void setName(String name) { | |
| this.name = name; | |
| } | |
| public void setID(long ID) { | |
| this.ID = ID; | |
| } | |
| public String getName() { | |
| return this.name; | |
| } | |
| public long getID() { | |
| return this.ID; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment