Beruflich Dokumente
Kultur Dokumente
Echo Server
One of the simplest types of servers that you can build is an echo server. An echo server
simply returns exactly what you send it. For example, if you send it the string "abc" it
will echo "abc" back to you. There is hardly a demand for a high-performance echo
server, but because of its simplicity it lends itself well to exercises like this one, to
illustrate how to build a server from the framework we built.
Listing 5. EchoServer.java
package com.javasrc.server.echo;
import java.net.*;
import com.javasrc.server.*;
/**
* An echo server echos, or repeats, all data that it receives
*/
public class EchoServer extends AbstractServer
{
/**
* Creates a new EchoServer
*/
public EchoServer()
{
// Initialize our super class
super( 8899, 50, "com.javasrc.server.echo.EchoRequestHandler",
2000, 5, 10 );
}
/**
* Launch an echo server
*/
public static void main( String[] args )
{
EchoServer es = new EchoServer();
es.startServer();
}
}
The core functionality for the EchoServer is the single call in its constructor to its super
class's constructor. The options create the following behavior:
The AbstractServer class then handles creating the ServerSocket, listening on port
8899, and the request handling infrastructure to handle up to ten simultaneous echo
clients. As each RequestThread processes its connection, it delegates the actual
"conversation" with the client to the EchoRequestHandler class, shown in Listing 6.
Listing 6. EchoRequestHandler.java
package com.javasrc.server.echo;
/**
* An EchoRequestHandler handles incoming requests sent to an Echo
Server
*/
public class EchoRequestHandler implements RequestHandler
{
/**
* Echo Server request handling logic
*/
public void handleRequest( Socket socket )
{
try
{
// Obtain input and output streams to the client
BufferedReader in = new BufferedReader( new
InputStreamReader( socket.getInputStream() ) );
PrintStream out = new PrintStream( socket.getOutputStream() );
// Read lines from the client and echo them back; close the
connection as soon
// as the Echo receives a blank line
String line = in.readLine();
while( line != null "" line.length() > 0 )
{
// Echo the line back to the client
out.println( line );
out.flush();
We always need a condition in a server to terminate the conversation. In this case, two
conditions end the communication:
Although an echo server is very simplistic, the fact that we were able to create a very
robust one (in terms of quickly handling several simultaneous connections) in under 200
lines of well-documented code is impressive.
Chat Server
A chat server is an interesting server. I was first involved in constructing one in 1998,
while working for an online video game company. The purpose of a chat server is to
enable clients to connect and chat with one another. A basic chat server provides the
following features:
To implement this functionality in the JavaSRC server framework, we build three classes:
ChatServer, ChatRequestHandler, and DuplicateLoginException. While the echo
server completely disconnected the server from the request handler, the chat server needs
a much tighter coupling between the two.
The request handler represents a chat client. It needs to tell the chat server to send
messages to one or more other chat clients. Plus, the chat server needs to know about all
clients to facilitate this communication and to report management functionality (such as
current users and user joining and exiting events). To facilitate this, the ChatServer
provides a static reference to its instance that request handlers can use to register
themselves with.
Listing 7. ChatServer.java
package com.javasrc.server.chat;
/**
* A chat server enables users to connect and chat
*/
public class ChatServer extends AbstractServer
{
/**
* A static reference to the ChatServer
*/
public static ChatServer theServer = null;
/**
* A Map of usernames to chat clients
*/
private Map chatClients = new TreeMap();
/**
* Creates a new chat server listening on port 9988
*/
public ChatServer()
{
super( 9988, 50, "com.javasrc.server.chat.ChatRequestHandler",
2000, 5, 50 );
theServer = this;
}
/**
* Adds a new chat client
*/
public void addChatClient( String name, ChatRequestHandler client )
throws DuplicateLoginException
{
if( this.chatClients.containsKey( name ) )
{
throw new DuplicateLoginException( name );
}
for( Iterator i=this.chatClients.keySet().iterator(); i.hasNext(); )
{
String username = ( String )i.next();
System.out.println( "notifying user: " + username + " that a new
user was added: " + name );
ChatRequestHandler user = ( ChatRequestHandler )
this.chatClients.get( username );
user.newUser( name );
}
this.chatClients.put( name, client );
}
/**
* Sends a message to all users
*/
public void sendMessage( String user, String message )
{
for( Iterator i=this.chatClients.keySet().iterator(); i.hasNext(); )
{
String username = ( String )i.next();
if( !username.equalsIgnoreCase( user ) )
{
ChatRequestHandler client = ( ChatRequestHandler )
this.chatClients.get( username );
client.sendMessage( user, message );
}
}
}
/**
* Sends an emotion to all users
*/
public void sendEmotion( String user, String message )
{
for( Iterator i=this.chatClients.keySet().iterator(); i.hasNext(); )
{
String username = ( String )i.next();
if( !username.equalsIgnoreCase( user ) )
{
ChatRequestHandler client = ( ChatRequestHandler )
this.chatClients.get( username );
client.sendEmotion( user, message );
}
}
}
/**
* Sends a message to a specific user
*/
public void sendMessage( String from, String to, String message )
{
for( Iterator i=this.chatClients.keySet().iterator(); i.hasNext(); )
{
String username = ( String )i.next();
if( username.equalsIgnoreCase( to ) )
{
ChatRequestHandler client = ( ChatRequestHandler )
this.chatClients.get( username );
client.privateMessage( from, message );
}
}
}
/**
* Returns a Set containing the currently logged in users
*/
public Set getUsers()
{
return this.chatClients.keySet();
}
/**
* Removes the specified chat client from the chat
*/
public void removeChatClient( String name )
{
if( name != null "" this.chatClients.containsKey( name ) )
{
this.chatClients.remove( name );
for( Iterator i=this.chatClients.keySet().iterator();
i.hasNext(); )
{
String username = ( String )i.next();
ChatRequestHandler user = ( ChatRequestHandler )
this.chatClients.get( username );
user.removeUser( name );
}
}
}
/**
* Creates a new stand-alone chat server
*/
public static void main( String[] args )
{
ChatServer cs = new ChatServer();
cs.startServer();
}
}
The constructor creates a server listening on port 9988 with a socket backlog of 50
connections, a request queue that can grow up to 2000 requests, 5 initial and minimum
request threads, and 50 maximum request threads. Request handling is delegated to the
com.javasrc.server.chat.ChatRequestHandler class.
Because the ChatServer is the main conduit between chat clients, it maintains references
to them in a private chatClients map and exposes the following methods for them to
use:
• addChatClient(): Adds the specified chat client to the chat server and notify all
existing clients that this new client has arrived; duplicate logins are forbidden.
• sendMessage(): Sends a message from a client to all other chat clients.
• sendMessage() (from and to names): Sends a private message from a client to
another client.
• sendEmotion(): Sends an emotion from a client to all other clients.
• getUsers(): Returns a Set containing the names of all currently logged on users.
• removeChatClient(): Removes the specified client from the chat server and
notifies all other clients that this user has exited.
Socket communications with chat clients are handled in the ChatRequestHandler class,
shown in listing 8.
Listing 8. ChatRequestHandler.java
package com.javasrc.server.chat;
import com.javasrc.server.*;
/**
* A ChatRequestHandler manages the communications for a single chat
user
*/
public class ChatRequestHandler implements RequestHandler
{
/**
* The user’s username once he logs in
*/
private String username;
/**
* Has the user logged in yet?
*/
private boolean loggedIn = false;
/**
* Once a client has connected, this is the interface to read from
the client
*/
private BufferedReader in;
/**
* Once a client has connected, this is the interface to write out to
the client
*/
private PrintWriter out;
/**
* Handles the incoming request
*
* @param socket The socket communication back to the client
*/
public void handleRequest(java.net.Socket socket)
{
try
{
// Get input and output writers that we can use to communicate
with the client through
this.in = new BufferedReader( new
InputStreamReader( socket.getInputStream() ) );
this.out = new PrintWriter( socket.getOutputStream() );
// Say "hi"
System.out.println( "New user connected" );
out.println( "Welcome to the JavaSRC ChatServer!" );
out.flush();
// Read the next line from the client if they are still
connected
if( connected )
{
line = in.readLine();
}
}
// Say goodbye
System.out.println( "User exited: " + this.username );
this.loggedIn = false;
this.username = null;
out.println( "Goodbye" );
out.flush();
}
catch( Exception e )
{
e.printStackTrace();
}
}
/**
* Handles command processing
*/
private boolean handleCommand( String command, String line )
{
if( command.equalsIgnoreCase( "user" ) )
{
if( !this.loggedIn )
{
String argument = line.substring( 5 );
System.out.println( "Received login command for: " + argument );
if( argument.length() == 0 )
{
out.println( "ERROR Invalid username" );
}
else
{
try
{
ChatServer.theServer.addChatClient( argument, this );
this.username = argument;
this.loggedIn = true;
out.println( "SUCCESS User " + this.username + " logged in"
);
}
catch( DuplicateLoginException dpe )
{
out.println( "ERROR Username " + argument + " is already in
use" );
}
}
}
else
{
out.println( "ERROR You are already logged in" );
}
}
else if( command.equalsIgnoreCase( "help" ) )
{
showHelp();
}
else if( command.equalsIgnoreCase( "exit" ) )
{
System.out.println( "Received exit command for user: " +
this.username );
ChatServer.theServer.removeChatClient( this.username );
return false;
}
else
{
// All of these commands require the user to first be logged in
if( !this.loggedIn )
{
out.println( "ERROR You need to logon to send commands to the
ChatServer" );
out.flush();
}
else if( command.equalsIgnoreCase( "send" ) )
{
String argument = line.substring( 5 );
ChatServer.theServer.sendMessage( this.username, argument );
}
else if( command.equalsIgnoreCase( "priv" ) )
{
String argument = line.substring( 5 );
if( argument.indexOf( ’:’ ) == -1 )
{
out.println( "ERROR: Private messages need a recipient!" );
}
else
{
String recipient = argument.substring( 0,
argument.indexOf( ’:’ ) );
String message = argument.substring( argument.indexOf( ’:’ )
+ 1 );
ChatServer.theServer.sendMessage( this.username, recipient,
message );
}
}
else if( command.equalsIgnoreCase( "list" ) )
{
Set users = ChatServer.theServer.getUsers();
StringBuffer sb = new StringBuffer();
for( Iterator i=users.iterator(); i.hasNext(); )
{
String user = ( String )i.next();
if( !user.equalsIgnoreCase( this.username ) )
{
sb.append( user + "," );
}
}
if( sb.length() > 1 )
{
out.println( "LIST " + sb.substring( 0, sb.length() - 1 ) );
}
else
{
out.println( "LIST You are the only user online" );
}
}
else if( command.equalsIgnoreCase( "emot" ) )
{
String argument = line.substring( 5 );
ChatServer.theServer.sendEmotion( this.username, argument );
}
}
/**
* A user sent, or broadcasted, the specified message to the group
*/
public void sendMessage( String from, String message )
{
out.println( "MESG " + from + ":" + message );
out.flush();
}
/**
* A user sent an emotion to the chat server
*/
public void sendEmotion( String from, String message )
{
out.println( "EMOT " + from + ":" + message );
out.flush();
}
/**
* This user has received a private message from the specified user
*/
public void privateMessage( String from, String message )
{
out.println( "PRIV " + from + ":" + message );
out.flush();
}
/**
* Notification that the specified user has entered the chat
*/
public void newUser( String otherUser )
{
out.println( "USER " + otherUser );
out.flush();
}
/**
* Notification that the specified user has left the chat
*/
public void removeUser( String otherUser )
{
out.println( "RUSR " + otherUser );
out.flush();
}
/**
* Displays help info
*/
private void showHelp()
{
StringBuffer sb = new StringBuffer();
sb.append( "COMMAND FORMAT: <four-letter-command> <argument>\r\n" );
sb.append( " CHAT COMMAND SUMMARY:\r\n" );
sb.append( " USER <username>\r\n" );
sb.append( " HELP\r\n" );
sb.append( " EXIT\r\n" );
sb.append( " SEND <message>\r\n" );
sb.append( " PRIV <recipient>:<message>\r\n" );
sb.append( " EMOT <emotion message>\r\n" );
sb.append( " MANAGEMENT COMMAND SUMMARY:\r\n" );
sb.append( " LIST\r\n" );
sb.append( " NOTIFICATION SUMMARY\r\n" );
sb.append( " MESG <user>:<message>\r\n" );
sb.append( " PRIV <user>:<message>\r\n" );
sb.append( " EMOT <user>:<message>\r\n" );
sb.append( " USER <user>\r\n" );
sb.append( " RUSR <user>\r\n" );
out.println( sb.toString() );
out.flush();
}
}
The handleRequest() method is the entry-point into this class from the RequestThread.
It operates as follows:
The exit condition for this server, as we'll see later, is a boolean condition set by the
result of the handleCommand() method. The exit condition is satisfied when the user
enters the command EXIT.
The handleCommand() method implements the logic of the chat server. The following
summarizes the commands that it supports:
• USER: logs in a new user by asking the ChatServer to add a new client; this
validates that the user's name is not already in use.
• HELP: displays the help menu (writes directly back to the socket).
• EXIT: asks the ChatServer to remove this user from its chat client list and then
returns false to cause the handleRequest() loop to cease.
• SEND: sends a message to all users in the chat server by calling the ChatServer's
sendMessage() method.
• PRIV: sends a private message to a specific user by calling an alternate version of
the ChatServer's sendMessage() method.
• LIST: retrieves a list of the current users in the chat server by calling the
ChatServer's getUsers() method.
• EMOT: sends an emotion to all users in the chat server by calling the
ChatServer's sendEmotion() method.
And then the ChatRequestHandler also handles notifications of these messages from the
ChatServer, generated by other ChatRequestHandler instances. The following
summarizes these notifications:
For completeness, listing 9 shows the code for the DuplicateLoginException class.
Listing 9. DuplicateLoginException.java
package com.javasrc.server.chat.exception;
Because we do not yet have a chat client, the best way to test the chat server is using a
telnet program. The following is a sample telnet session with the Chat Server. Note that
you have to specify the port that you want to connect to using telnet; otherwise it will
connect on the default port (25).
In this example, the bold commands are the ones that I sent; the non-bold ones are
responses and notifications. In this conversation, "userOne" logged on, listed the current
users, sent a message, received a message, sent a private message, received a private
message, sent an emotion, saw that "userTwo" left, and then exited. It’s a simple
conversation that could be presented very well inside of a robust chat client, but it
suffices for demonstration purposes.
Summary
Thus far we have looked at creating the infrastructure for a robust Java server, which
inclues the introduction of a request queue that is serviced by a set of request threads, and
then built two examples on top of this infrastructure: a simple echo server and a more
complex chat server. These examples illustrate that the JavaSRC server framework
allows you to focus more on business logic and less on the implementation details behind
building a robust server.