Lesson 4 - Java Server - Server Thread
In the previous lesson, Java Server - Google Guice, we put dependency management in the hands of the Google guice library. Today we're going to create a basic code template for our Java server thread.
Thread Features
This thread will serve as the main entrance point. This is where the connection with clients will be established.
We'll create a new core
package into which we'll put all the
important functionality of the server.
Thread Factory
We'll start as in the previous lesson by creating a factory to instantiate
the thread. In the core
package, we'll create a new
server
package in which we'll create the following classes and
interfaces:
IServerThread
- An interface providing methods to communicate with the server threadIServerThreadFactory
- An interface with a method to create anIServerThread
instanceServerThread
- AnIServerThread
interface implementationServerThreadFactory
- AnIServerThreadFactory
interface implementation
We'll put just this interface directly into the core
package:
IThreadControl
- A helper interface for working with threads
Let's define methods for the IThreadControl
interface first.
This interface will contain two methods:
start()
to start the threadshutdown()
to inform the thread that it should start the shutdown sequence
The signature of the methods will be as follows:
void start(); void shutdown();
We'll inherit the IServerThread
interface from
IThreadControl
.
The IServerThreadFactory
interface will contain one factory
method to create an IServerThread
class instance:
IServerThread getServerThread(IParameterProvider parameters) throws IOException;
Implementing the Interfaces
We've defined the interface, so let's implement them. We'll start with the
ServerThread
class, which implements the IServerThread
interface. We'll modify the class first and let it inherit from the
Thread
class. Thus, the class declaration will look like this:
class ServerThread extends Thread implements IServerThread
We'll add one class constant to the class:
private static final int SOCKET_TIMEOUT = 5000;
one instance constant:
private final int port;
and one instance variable:
private boolean running = false;
which will indicate whether the thread should run or terminate.
Next, we'll create a constructor that accepts a single parameter:
int port
. In the constructor, we'll set the thread name to
"ServerThread"
and initialize the port
instance
constant:
ServerThread(int port) throws IOException { super("ServerThread"); this.port = port; }
Finally, we'll implement the methods that the interface defines:
@Override public void shutdown() { running = false; try { join(); } catch (InterruptedException ignored) {} } @Override public void run() { try (ServerSocket serverSocket = new ServerSocket(port)) { serverSocket.setSoTimeout(SOCKET_TIMEOUT); while (running) { try { final Socket socket = serverSocket.accept(); } catch (SocketTimeoutException ignored) {} } } catch (IOException e) { e.printStackTrace(); } }
In the shutdown()
method, we set the running
variable to false
and call join()
to wait for the
thread to finish. The run()
method creates a new
ServerSocket
class instance and sets its timeout to the value of
SOCKET_TIMEOUT
, which is 5 seconds. This ensures that every 5
seconds a SocketTimeoutException
is thrown and the
running
variable is checked. Then we call accept()
on
the ServerSocket
instance. This call is blocking,
which means that the thread will freeze until the client is connected or an
exception is thrown. We'll leave the processing of the newly established
connection for the next lesson. This would be all for the
ServerThread
class for this moment. Let's start implementing the
factory.
Implementing the Factory
We'll add the @Singleton
annotation from the Google guice
library to the ServerThreadFactory
class. This annotation ensures
that whenever we ask for the factory in the future, we'll get the same instance
of it. The ServerThreadFactory
class must implement a single
getServerThread()
method. This method accepts an instance
implementing IParameterProvider
instance as a parameter to get
command line parameters. In the factory, we'll define default values for the
parameters that will be used if we don't pass some of the parameters when
starting the server:
// Default port private static final int DEFAULT_SERVER_PORT = 15378; // Default maximum number of clients private static final int DEFAULT_MAX_CLIENTS = 3; // Default waiting queue size private static final int DEFAULT_WAITING_QUEUE_SIZE = 1;
Now we can fill the getServerThread()
method's body:
@Override public IServerThread getServerThread(IParameterProvider parameters) throws IOException { final int port = parameters.getInteger(CmdParser.PORT, DEFAULT_SERVER_PORT); final int maxClients = parameters.getInteger(CmdParser.CLIENTS, DEFAULT_MAX_CLIENTS); final int waitingQueueSize = parameters.getInteger(CmdParser.MAX_WAITING_QUEUE, DEFAULT_WAITING_QUEUE_SIZE); return new ServerThread(port); }
In the method, we obtain the individual parameters and finally create and
return a new ServerThread
class instance. In the future we'll use
the remaining parameters as well.
After implementing all interfaces in concrete classes, we can register the
server thread factory in the ServerModule
class. The registration
will be the same as with the parameter factory:
bind(IServerThreadFactory.class).to(ServerThreadFactory.class);
Finally, we'll move to the Server
class, where we'll wire
everything together. First we'll add another instance constant of the
IServerThreadFactory
type:
private final IServerThreadFactory serverThreadFactory;
and we'll also add the same parameter to the constructor to initialize the factory:
@Inject public Server(IParameterFactory parameterFactory, IServerThreadFactory serverThreadFactory) { this.parameterFactory = parameterFactory; this.serverThreadFactory = serverThreadFactory; }
Now let's modify the run()
method:
private void run(String[]args) throws IOException { final IParameterProvider parameters = parameterFactory.getParameters(args); final IServerThread serverThread = serverThreadFactory.getServerThread(parameters); serverThread.start(); while(true) { final String input = scanner.nextLine(); if ("exit".equals(input)) { break; } } serverThread.shutdown(); }
In the method, we first get the parameters and pass them to the server thread
factory, which returns an IServerThread
class instance. We run the
server by the start()
method. The thread starts, but because we
haven't defined any activity, it terminates immediately. Then there's an
infinite loop awaiting user input until the user enters the word
"exit"
. When the user enters "exit"
, the server will
start shutting down. The shutdown()
method tells the server thread
to start terminating.
That would be all of today's lesson. Next time, in the lesson Java Server - Connection Manager, we'll create a class to handle incoming clients.