Monday, April 21, 2008

Multi-threaded Socket Server in Java

I have always been curious about how to make a multi-threaded server in Java, and it turns out to be pretty simple to get something basic up and running.

My goal was to create a program that would listen for incoming connections from a client, and repeat whatever is sent to that server. You can see this server work using a telnet session on port 5000.


telnet localhost 5000


Then, whatever you type will be echoed back to you. If you want to quit, just type exit.

Here's the code, consisting of 2 classes.


package sockets;
import java.io.*;
import java.net.*;

/**
* Includes main method to get things going
* Otherwise, this just prepares a new ServerSocket that
* listens for new connections
* and starts a new thread for each connection
*/
public class EchoServer {
public void go(){
try{
ServerSocket serverSocket = new ServerSocket(5000);
while(true){
System.out.print("Listening for connections on port 5000... ");
Socket client = serverSocket.accept();
Thread t = new Thread(new EchoClientHandler(client));
t.start();
System.out.println("Connected - "+client.getInetAddress());
}
}catch(Exception e){
e.printStackTrace();
}
}

public static void main(String[] args) {
EchoServer server = new EchoServer();
server.go();
}
}

/**
* The Runnable job that takes in the new Socket
*/
class EchoClientHandler implements Runnable{

private final BufferedReader reader;
private final PrintWriter output;
private final Socket socket;
private static final String MESSAGE = "ECHO... [?]\r\n";
private static final String EXIT_MESSAGE = "Sad to see you go. Goodbye.\r\n";
private static final String WELCOME_MESSAGE = "Welcome to the Echo Server. Type something to see it echoed back to you!\r\n";

public EchoClientHandler(Socket incomingSocket) throws IOException{
socket = incomingSocket;
output = new PrintWriter(incomingSocket.getOutputStream());
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
}

public void run(){
try{
output.write(WELCOME_MESSAGE);
output.flush();
String line = null;
while((line = reader.readLine()) != null){
boolean quit = false;
output.write(MESSAGE.replaceAll("\\?", line));
if(line.trim().equalsIgnoreCase("exit")){
output.write(EXIT_MESSAGE);
quit = true;
}
output.flush();
if(quit){
break;
}
}
}catch(Exception e){
System.err.println("OUCH! "+e.getMessage());
}finally{
try{socket.close();}catch(Exception ee){}
}
}
}


Here's how it works.

When the EchoServer starts up, it opens a server socket that listens for incoming connection requests. When it gets a request, it creates a new handler instance, taking the Socket as an argument. The handler is a Runnable class, and used to start a new Thread. The result is that the EchoServer can handle more than one connection at a time -- just like a good socket server should.

To expand on this, it would be a good idea to implement some means of managing the threads and connections. Using the java.util.concurrent API would be a good thing to do to implement some means of thread pooling in the server instead of using naked Threads. Thread pooling is an important thing to implement because each new persistent connection requires a new thread. Too many connections would cause some real resource problems. However, if there are limits to the amount of connections the server will allow, you need to manage connections. Therefore, some intelligence might need to be added to a Client program, so that a connection need only stay alive for a certain amount of time before becoming disconnected. Then, a well designed client might seamlessly create a new connection to the server.

1 comment:

Tony said...

Hi, I am trying combine socket and rmi together, create for distribute system. any suggestion 2 create it?