Reputation: 1
I have a working Java Server (Although a tad rough around the edges) which contains 3 main classes.
The first class runs the server and gets the socket to listen on a port and passes new connections to a client handler.
The second class is a threaded client handler
The third is a protocol class which is called from the client handler and processes information. Once the information is processed, the protocol class returns a processed or formatted response back to the client handler to pass to the client.
The advantage is that the second class just needs to be loaded with what is acceptable data to accept from the socket. The data can be passed to the protocol handler, and the protocol handler can be loaded with whatever protocol you want the server to use to talk to the client.
In this instance I have loaded in a telnet-based chat class.
If, for example, someone leave the chat the client handler class may execute code such as:
for (i = 0; i < currentClientsConnected; i++) {
if(threads[i] != null && threads[i] != this) {
outputLine = threads[i].serverprotocol.processInput("** " + username + " has left the room **");
threads[i].out.printf(outputLine);
}
}
This passes "** [username] has left the room **"
to the serverprotocol
class, which then returns the data in the best best way to transmit the message to the clients. In this case the serverprotocol
class formats the message with telnet control code which tells the client to re-draw the screen, add the new message and scroll up the existing current messages in the buffer.
I may also only want the client handler class to send message to sockets where the users are in certain chat rooms for example, so I will not want to always send to all the sockets.
In my code, this is Class 1 - the server class which accepts sockets with:
while (true) {
int i;
// Try and accept the connection
try {
clientSocket = serverSocket.accept();
// System.out.printf("Remote IP:");
// System.out.printf(clientSocket.getRemoteSocketAddress().toString());
// Find an unused socket if one is available
for (i = 0; i < maxClientsAllowed; i++) {
// If found create thread
if (threads[i] == null) {
(threads[i] = new clientThread(clientSocket, threads)).start();
break;
}
}
// If all sockets are taken
if (i == maxClientsAllowed) {
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
out.printf("Server too busy. Try later.\n");
out.close();
clientSocket.close();
}
} catch(IOException e) {
System.out.println(e);}
}
and Class 2 is a class which extends thread:
class clientThread extends Thread {
private String clientName = null;
private DataInputStream in;
private PrintWriter out;
private Socket clientSocket = null;
private final clientThread[] threads;
private int currentClientsConnected;
private serverprotocol serverprotocol;
public clientThread(Socket clientSocket, clientThread[] threads) {
this.clientSocket = clientSocket;
this.threads = threads;
currentClientsConnected = threads.length;
}
public void run() {
//stuff
}
}
I have been trying desperately to see if I can get this working using implements Runnable
instead, but I have had no luck in calling a thread's processInput
(or maybe that should read dataToBeProcessed
) method based on the instance number of the thread (Simply called i
in the code here).
The closest I have seen in:
which can take advantage as running the server as a threaded pool server.
However the sendToAll
function in this case writes directly to the PrintWriter
s associated to the socket via a HashMap
. The server does not let you send to individual protocol handler classes, or even to the individual ChatServerWorker
class instances. This means I can't, for example, send a message to socket 1 and 3 only and then a separate message to socket 2.
I can't find a single example online where an instance of a socket handler can be called without using extends Thread
.
Specifically,I want to keep the ability to use lines like:
threads[i].out.printf(outputLine);
or
if(threads[i].[class].[var] == 'something') {
// stuff
}
Where an integer can be used to reference the thread instance, or any class vars or methods used by that thread.
Am I missing something?
Upvotes: 0
Views: 2389
Reputation: 70
I am not sure if I understood your question. The short answer: if you want to make clientThread a Runnable, just do it and then change the line
(threads[i] = new clientThread(clientSocket, threads)).start();
into
(threads[i] = new Thread(new clientThread(clientSocket, threads))).start();
If you look at the documentation: http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#Thread(java.lang.Runnable)
Threads accept objects with Runnable supertype.
The long answer: you should not store threads directly but make an abstraction that represents a Client on the server side. This abstraction should encapsulate functionality for communication. That way, if you want to implement a different communication library you can easily subclass it and avoid breaking the open-close principle.
https://en.wikipedia.org/wiki/Open/closed_principle
Good luck.
Upvotes: 0
Reputation: 32343
Your big problem is that you are using the Threads themselves directly as the communication layer between the Server
and the Client
threads, something you should not do.
Instead, create your own interface Message
objects that communicate the different information between the threads, and use LinkedBlockingQueue
to process them.
You should probably have:
So you might do something like:
Message:
public interface Message {
accept(Server server);
}
Disconnection Message (I'm just going to do one):
public class DisconnectionMessage implements Message {
String username;
public void accept(Server server) {
server.handleMessage(this);
}
}
Server Runnable:
public void run() {
while(isServerOnline()) {
Message clientMessage = queue.poll();
clientMessage.accept(this);
}
}
public void handleMessage(DisconnectionMessage msg) {
// code
}
public void handleMessage(ConnectionMessage msg) {
// code
}
etc.
Client Runnable:
private final Socket socket;
private final BlockingQueue<Message> queue;
public Client(BlockingQueue<Message> queue, Socket socket) {
this.queue = queue;
this.socket = socket;
}
public void run() {
while(true) {
Message msg = receiveMessage();
queue.offer(msg);
}
}
Upvotes: 1