0

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:

https://github.com/ico77/chat-server-client/blob/master/src/main/java/hr/ivica/chat/server/ChatServer.java

which can take advantage as running the server as a threaded pool server.

However the sendToAll function in this case writes directly to the PrintWriters 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?

  • You can't 'call a method in a thread', unless you are that thread. You can however call a method in an object. Your question remains obscure. – user207421 Aug 20 '15 at 22:37

2 Answers2

1

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:

  • One queue for the server to receive messages
  • Depending on your implementation, one queue for each of the client threads to receive messages from the server, or one queue that's shared (if it's designed so that any thread can handle any message).

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);
  }
}
durron597
  • 31,968
  • 17
  • 99
  • 158
  • Thanks for the time to reply to my query. I know I am being a tad difficult as I am a tad new to Java and OOP in general. The threads don't communicate between the client handler and server per-se. The Server launches the client handler as a thread. The thread consults with Class 3 - the protocol handler, but the client handler itself deals with all incoming and outgoing data. My code is very much based on https://github.com/fusion2004/MultiThreadChat-Server/blob/master/src/org/fusion2004/multithreadchat/server/MultiThreadChatServer.java (Not that I am saying this is good code)... – Aniseedwolf Aug 20 '15 at 22:33
  • specifically, while I understand the gist of how messages can travel from the socket/client handlers using your method, I do not necessary want all clients to be able to see all messages. For example, if the chatters are in different chat rooms. "implements Runnable" seems to suggest the thread should work very independently, while I would like a thread to target and talk to other threads (If possible) in a more teamwork way. I.E "I have a message from this thread. Are you other threads in the same room? If so here is the message I got." – Aniseedwolf Aug 20 '15 at 22:41
  • @Aniseedwolf so create a queue for each room – durron597 Aug 20 '15 at 22:43
  • Well users may want to talk directly and privately to each other too. Maybe I could create a counter when each thread is started and ended, and pass the number to each new runnable instance. (eg http://stackoverflow.com/questions/9123272/is-there-a-way-to-pass-parameters-to-a-runnable) Then use "queue[i]=new LinkedBlockingQueue();", where 'i' is the thread to receive the message? The queue array I guess should be somewhere that all the threads can read and write to and the thread will only read the instance intended it... but then you have a bunch of threads all polling the array? – Aniseedwolf Aug 20 '15 at 23:12
0

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.

Krle
  • 70
  • 7