Simple console-mode (command-line) chat client
/*
* Copyright (c) Ian F. Darwin, http://www.darwinsys.com/, 1996-2002.
* All rights reserved. Software written by Ian F. Darwin and others.
* $Id: LICENSE,v 1.8 2004/02/09 03:33:38 ian Exp $
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* Java, the Duke mascot, and all variants of Sun's Java "steaming coffee
* cup" logo are trademarks of Sun Microsystems. Sun's, and James Gosling's,
* pioneering role in inventing and promulgating (and standardizing) the Java
* language and environment is gratefully acknowledged.
*
* The pioneering role of Dennis Ritchie and Bjarne Stroustrup, of AT&T, for
* inventing predecessor languages C and C++ is also gratefully acknowledged.
*/
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
import java.net.*;
import java.util.*;
/** Simple console-mode (command-line) chat client.
* @author Ian Darwin, http://www.darwinsys.com/
* @version $Id: ConsChat.java,v 1.6 2004/02/16 02:44:43 ian Exp $
*/
public class ConsChat {
public static void main(String[] args) throws IOException {
new ConsChat().chat();
}
protected Socket sock;
protected BufferedReader is;
protected PrintWriter pw;
protected BufferedReader cons;
protected ConsChat() throws IOException {
sock = new Socket("localhost", Chat.PORTNUM);
is = new BufferedReader(new InputStreamReader(sock.getInputStream()));
pw = new PrintWriter(sock.getOutputStream(), true);
cons = new BufferedReader(new InputStreamReader(System.in));
// Construct and start the reader: from server to stdout.
// Make a Thread to avoid lockups.
new Thread() {
public void run() {
setName("socket reader thread");
System.out.println("Starting " + getName());
System.out.flush();
String line;
try {
// reader thread blocks here
while ((line = is.readLine()) != null) {
System.out.println(line);
System.out.flush();
}
} catch (IOException ex) {
System.err.println("Read error on socket: " + ex);
return;
}
}
}.start();
}
protected void chat() throws IOException {
String text;
System.out.print("Login name: "); System.out.flush();
text = cons.readLine();
send(Chat.CMD_LOGIN + text);
// Main thread blocks here
while ((text = cons.readLine()) != null) {
if (text.length() == 0 || text.charAt(0) == '#')
continue; // ignore null lines and comments
if (text.charAt(0) == '/')
send(text.substring(1));
else send("B"+text);
}
}
protected void send(String s) {
pw.println(s);
pw.flush();
}
}
/** Constants and Class Methods for Java Chat Clients and Server.
*
* The protocol:
* --> Lusername
* --> Btext_to_broadcast
* --> Musername\Message
* --> Q
* <-- any text to be displayed.
*
* @author Ian Darwin
* @version $Id: Chat.java,v 1.3 2004/02/16 02:44:43 ian Exp $
*/
class Chat {
// These are the first character of messages from client to server
public static final int PORTNUM = 9999;
public static final int MAX_LOGIN_LENGTH = 20;
public static final char SEPARATOR = '\\';
public static final char COMMAND = '\\';
public static final char CMD_LOGIN = 'L';
public static final char CMD_QUIT = 'Q';
public static final char CMD_MESG = 'M';
public static final char CMD_BCAST = 'B';
// These are the first character of messages from server to client
public static final char RESP_PUBLIC = 'P';
public static final char RESP_PRIVATE = 'M';
public static final char RESP_SYSTEM = 'S';
// TODO in main loop:
// if (text.charAt(0) == '/')
// send(text);
// else send("B"+text);
public static boolean isValidLoginName(String login) {
// check length
if (login.length() > MAX_LOGIN_LENGTH)
return false;
// check for bad chars
// if (contains bad chars)
// return false
// Passed above tests, is OK
return true;
}
}
/** Trivial Chat Server to go with our Trivial Chat Client.
*
* WARNING -- this code is believed thread-safe but has NOT been 100% vetted
* by a team of world-class experts for Thread-safeness.
* DO NOT BUILD ANYTHING CRITICAL BASED ON THIS until you have done so.
* See the various books on Threaded Java for design issues.
*
* @author Ian F. Darwin, http://www.darwinsys.com/
* @version $Id: ChatServer.java,v 1.10 2004/03/13 21:56:32 ian Exp $
*/
class ChatServer {
/** What I call myself in system messages */
protected final static String CHATMASTER_ID = "ChatMaster";
/** What goes between any handle and the message */
protected final static String SEP = ": ";
/** The Server Socket */
protected ServerSocket servSock;
/** The list of my current clients */
protected ArrayList clients;
/** Debugging state */
private static boolean DEBUG = false;
/** Main just constructs a ChatServer, which should never return */
public static void main(String[] argv) {
System.out.println("DarwinSys Chat Server 0.1 starting...");
if (argv.length == 1 && argv[0].equals("-debug"))
DEBUG = true;
ChatServer w = new ChatServer();
w.runServer(); // should never return.
System.out.println("**ERROR* Chat Server 0.1 quitting");
}
/** Construct (and run!) a Chat Service */
ChatServer() {
clients = new ArrayList();
try {
servSock = new ServerSocket(Chat.PORTNUM);
System.out.println("DarwinSys Chat Server Listening on port " +
Chat.PORTNUM);
} catch(IOException e) {
log("IO Exception in ChatServer.<init>" + e);
System.exit(0);
}
}
public void runServer() {
try {
while (true) {
Socket us = servSock.accept();
String hostName = us.getInetAddress().getHostName();
System.out.println("Accepted from " + hostName);
ChatHandler cl = new ChatHandler(us, hostName);
synchronized (clients) {
clients.add(cl);
cl.start();
if (clients.size() == 1)
cl.send(CHATMASTER_ID, "Welcome! you're the first one here");
else {
cl.send(CHATMASTER_ID, "Welcome! you're the latest of " +
clients.size() + " users.");
}
}
}
} catch(IOException e) {
log("IO Exception in runServer: " + e);
System.exit(0);
}
}
protected void log(String s) {
System.out.println(s);
}
/** Inner class to handle one conversation */
protected class ChatHandler extends Thread {
/** The client socket */
protected Socket clientSock;
/** BufferedReader for reading from socket */
protected BufferedReader is;
/** PrintWriter for sending lines on socket */
protected PrintWriter pw;
/** The client's host */
protected String clientIP;
/** String form of user's handle (name) */
protected String login;
/* Construct a Chat Handler */
public ChatHandler(Socket sock, String clnt) throws IOException {
clientSock = sock;
clientIP = clnt;
is = new BufferedReader(
new InputStreamReader(sock.getInputStream()));
pw = new PrintWriter(sock.getOutputStream(), true);
}
/** Each ChatHandler is a Thread, so here's the run() method,
* which handles this conversation.
*/
public void run() {
String line;
try {
while ((line = is.readLine()) != null) {
char c = line.charAt(0);
line = line.substring(1);
switch (c) {
case Chat.CMD_LOGIN:
if (!Chat.isValidLoginName(line)) {
send(CHATMASTER_ID, "LOGIN " + line + " invalid");
log("LOGIN INVALID from " + clientIP);
continue;
}
login = line;
broadcast(CHATMASTER_ID, login +
" joins us, for a total of " +
clients.size() + " users");
break;
case Chat.CMD_MESG:
if (login == null) {
send(CHATMASTER_ID, "please login first");
continue;
}
int where = line.indexOf(Chat.SEPARATOR);
String recip = line.substring(0, where);
String mesg = line.substring(where+1);
log("MESG: " + login + "-->" + recip + ": "+ mesg);
ChatHandler cl = lookup(recip);
if (cl == null)
psend(CHATMASTER_ID, recip + " not logged in.");
else
cl.psend(login, mesg);
break;
case Chat.CMD_QUIT:
broadcast(CHATMASTER_ID,
"Goodbye to " + login + "@" + clientIP);
close();
return; // The end of this ChatHandler
case Chat.CMD_BCAST:
if (login != null)
broadcast(login, line);
else
log("B<L FROM " + clientIP);
break;
default:
log("Unknown cmd " + c + " from " + login + "@" + clientIP);
}
}
} catch (IOException e) {
log("IO Exception: " + e);
} finally {
// the sock ended, so we're done, bye now
System.out.println(login + SEP + "All Done");
synchronized(clients) {
clients.remove(this);
if (clients.size() == 0) {
System.out.println(CHATMASTER_ID + SEP +
"I'm so lonely I could cry...");
} else if (clients.size() == 1) {
ChatHandler last = (ChatHandler)clients.get(0);
last.send(CHATMASTER_ID,
"Hey, you're talking to yourself again");
} else {
broadcast(CHATMASTER_ID,
"There are now " + clients.size() + " users");
}
}
}
}
protected void close() {
if (clientSock == null) {
log("close when not open");
return;
}
try {
clientSock.close();
clientSock = null;
} catch (IOException e) {
log("Failure during close to " + clientIP);
}
}
/** Send one message to this user */
public void send(String sender, String mesg) {
pw.println(sender + SEP + mesg);
}
/** Send a private message */
protected void psend(String sender, String msg) {
send("<*" + sender + "*>", msg);
}
/** Send one message to all users */
public void broadcast(String sender, String mesg) {
System.out.println("Broadcasting " + sender + SEP + mesg);
for (int i=0; i<clients.size(); i++) {
ChatHandler sib = (ChatHandler)clients.get(i);
if (DEBUG)
System.out.println("Sending to " + sib);
sib.send(sender, mesg);
}
if (DEBUG) System.out.println("Done broadcast");
}
protected ChatHandler lookup(String nick) {
synchronized(clients) {
for (int i=0; i<clients.size(); i++) {
ChatHandler cl = (ChatHandler)clients.get(i);
if (cl.login.equals(nick))
return cl;
}
}
return null;
}
/** Present this ChatHandler as a String */
public String toString() {
return "ChatHandler[" + login + "]";
}
}
}
/**
* <p>
* Simple Chat Room Applet.
* Writing a Chat Room seems to be one of many obligatory rites (or wrongs)
* of passage for Java experts these days.</p>
* <p>
* This one is a toy because it doesn't have much of a protocol, which
* means we can't query the server as to * who's logged in,
* or anything fancy like that. However, it works OK for small groups.</p>
* <p>
* Uses client socket w/ two Threads (main and one constructed),
* one for reading and one for writing.</p>
* <p>
* Server multiplexes messages back to all clients.</p>
* @author Ian Darwin
* @version $Id: ChatRoom.java,v 1.8 2004/03/09 03:59:37 ian Exp $
*/
class ChatRoom extends Applet {
/** Whether we are being run as an Applet or an Application */
protected boolean inAnApplet = true;
/** The state of logged-in-ness */
protected boolean loggedIn;
/* The Frame, for a pop-up, durable Chat Room. */
protected Frame cp;
/** The default port number */
protected static int PORTNUM = Chat.PORTNUM;
/** The actual port number */
protected int port;
/** The network socket */
protected Socket sock;
/** BufferedReader for reading from socket */
protected BufferedReader is;
/** PrintWriter for sending lines on socket */
protected PrintWriter pw;
/** TextField for input */
protected TextField tf;
/** TextArea to display conversations */
protected TextArea ta;
/** The Login button */
protected Button lib;
/** The LogOUT button */
protected Button lob;
/** The TitleBar title */
final static String TITLE = "Chat: Ian Darwin's Toy Chat Room Client";
/** The message that we paint */
protected String paintMessage;
/** init, overriding the version inherited from Applet */
public void init() {
paintMessage = "Creating Window for Chat";
repaint();
cp = new Frame(TITLE);
cp.setLayout(new BorderLayout());
String portNum = null;
if (inAnApplet)
portNum = getParameter("port");
port = PORTNUM;
if (portNum != null)
port = Integer.parseInt(portNum);
// The GUI
ta = new TextArea(14, 80);
ta.setEditable(false); // readonly
ta.setFont(new Font("Monospaced", Font.PLAIN, 11));
cp.add(BorderLayout.NORTH, ta);
Panel p = new Panel();
Button b;
// The login button
p.add(lib = new Button("Login"));
lib.setEnabled(true);
lib.requestFocus();
lib.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
login();
lib.setEnabled(false);
lob.setEnabled(true);
tf.requestFocus(); // set keyboard focus in right place!
}
});
// The logout button
p.add(lob = new Button("Logout"));
lob.setEnabled(false);
lob.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
logout();
lib.setEnabled(true);
lob.setEnabled(false);
lib.requestFocus();
}
});
p.add(new Label("Message here:"));
tf = new TextField(40);
tf.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (loggedIn) {
pw.println(Chat.CMD_BCAST+tf.getText());
tf.setText("");
}
}
});
p.add(tf);
cp.add(BorderLayout.SOUTH, p);
cp.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
// If we do setVisible and dispose, then the Close completes
ChatRoom.this.cp.setVisible(false);
ChatRoom.this.cp.dispose();
logout();
}
});
cp.pack();
// After packing the Frame, centre it on the screen.
Dimension us = cp.getSize(),
them = Toolkit.getDefaultToolkit().getScreenSize();
int newX = (them.width - us.width) / 2;
int newY = (them.height- us.height)/ 2;
cp.setLocation(newX, newY);
cp.setVisible(true);
paintMessage = "Window should now be visible";
repaint();
}
protected String serverHost = "localhost";
/** LOG ME IN TO THE CHAT */
public void login() {
showStatus("In login!");
if (loggedIn)
return;
if (inAnApplet)
serverHost = getCodeBase().getHost();
try {
sock = new Socket(serverHost, port);
is = new BufferedReader(new InputStreamReader(sock.getInputStream()));
pw = new PrintWriter(sock.getOutputStream(), true);
} catch(IOException e) {
showStatus("Can't get socket to " +
serverHost + "/" + port + ": " + e);
cp.add(new Label("Can't get socket: " + e));
return;
}
showStatus("Got socket");
// Construct and start the reader: from server to textarea.
// Make a Thread to avoid lockups.
new Thread(new Runnable() {
public void run() {
String line;
try {
while (loggedIn && ((line = is.readLine()) != null))
ta.append(line + "\n");
} catch(IOException e) {
showStatus("GAA! LOST THE LINK!!");
return;
}
}
}).start();
// FAKE LOGIN FOR NOW
pw.println(Chat.CMD_LOGIN + "AppletUser");
loggedIn = true;
}
/** Log me out, Scotty, there's no intelligent life here! */
public void logout() {
if (!loggedIn)
return;
loggedIn = false;
try {
if (sock != null)
sock.close();
} catch (IOException ign) {
// so what?
}
}
// It is deliberate that there is no STOP method - we want to keep
// going even if the user moves the browser to another page.
// Anti-social? Maybe, but you can use the CLOSE button to kill
// the Frame, or you can exit the Browser.
/** Paint paints the small window that appears in the HTML,
* telling the user to look elsewhere!
*/
public void paint(Graphics g) {
Dimension d = getSize();
int h = d.height;
int w = d.width;
g.fillRect(0, 0, w, 0);
g.setColor(Color.black);
g.drawString(paintMessage, 10, (h/2)-5);
}
/** a showStatus that works for Applets or non-Applets alike */
public void showStatus(String mesg) {
if (inAnApplet)
super.showStatus(mesg);
System.out.println(mesg);
}
/** A main method to allow the client to be run as an Application */
public static void main(String[] args) {
ChatRoom room101 = new ChatRoom();
room101.inAnApplet = false;
room101.init();
room101.start();
}
}
Related examples in the same category