So I'm working on a server that handles a few commands and one small problem is trying to remove the list of active clients when a user decides to log out. Each client is handled in a thread and once the command is done this active client gets removed however its not removing.
Below is an example of removing an active client, this thread is
public class serverHandlerThread implements Runnable
{
private Socket socket;
//private BufferedWriter clientOut;
private ObjectOutputStream toClient;
private MainServer server;
private Users user;
//Constructor
serverHandlerThread(MainServer server, Socket socket)
{
this.server = server;
this.socket = socket;
}
private ObjectOutputStream getWriter()
{
return toClient;
}
private void deleteClient(serverHandlerThread obj)
{
synchronized (server.clients)
{
server.clients.remove(obj);
}
}
#Override
public void run ()
{
try
{
//Setup I/O
toClient = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream fromClient = new ObjectInputStream(socket.getInputStream());
while(!socket.isClosed())
{
//If server has received a message
if(fromClient.available() > 0)
{
//Reads message and objects from client
String input = fromClient.readUTF();
Object obj = fromClient.readObject();
//logger(input);
switch (input)
{
//Logout the user
case ".logout":
//Set the user to being logged out and print the log
user = (Users) obj;
deleteClient(this);
for (int i = 0; i < server.usersList.size(); i++)
{
if (user.getUserName().equals(server.usersList.get(i).getUserName()))
{
server.usersList.get(i).setLoggedIn(false);
logger(user.getUserName() + " has logged out");
}
}
break;
//Push message received to other clients
default:
logger("Sending message to clients");
user = (Users) obj;
deleteClient(this);
logger("clients size is: " + String.valueOf(server.clients.size()));
for (serverHandlerThread thatClient : server.getClients())
{
ObjectOutputStream thatClientOut = thatClient.getWriter();
if (thatClientOut != null)
{
thatClientOut.writeUTF(user.getUserName() + ": " + input + "\r\n");
thatClientOut.flush();
}
}
break;
}
}
}
}
catch (IOException | ClassNotFoundException e)
{
e.printStackTrace();
}
}
}
server is of type MainServer which contains the list of clients and is written as List<ServerHandlerThread> clients. MainServer calls serverHandlerThread when a new client is accepted ie. making the server multithread.
The problem is when the clients requests to logout it should delete the user from the active client list. It doesn't and so when the server tries to push messages to all clients it also tried to write a message to the client who's socket has been closed(user who logged out) and so the server spits out a broken pipe error. Any ideas?
*Edit
More information on the mainServer class, omitted a few things but this should be enough information
public class MainServer
{
//Static variables
private static final int portNumber = 4444;
//Variables
private int serverPort;
private List<serverHandlerThread> clients;
/**
* Very basic logger that prints out
* the current time and date
* #param msg used when printing the log
*/
private void logger(String msg)
{
System.out.println(LocalDate.now()+ " " +LocalTime.now() + " - " +msg);
}
private List<serverHandlerThread> getClients()
{
return clients;
}
//Starts the server and begins accepting clients
private void startServer()
{
clients = new ArrayList<>();
ServerSocket serverSocket;
try
{
serverSocket = new ServerSocket(serverPort);
acceptClients(serverSocket);
}
catch (IOException e)
{
logger("Could not listen on port: " + serverPort);
System.exit(1);
}
}
//Continuously accept clients
private void acceptClients(ServerSocket serverSocket)
{
logger("Server starts port = " + serverSocket.getLocalSocketAddress());
while (true)
{
try
{
Socket socket = serverSocket.accept();
//logger("Accepts: " + socket.getRemoteSocketAddress());
serverHandlerThread client = new serverHandlerThread(this, socket);
Thread thread = new Thread(client);
thread.setDaemon(true);
thread.start();
synchronized(clients)
{
clients.add(client);
}
}
catch (IOException e)
{
System.err.println("Accept failed on:" + serverPort);
}
}
}
public MainServer(int portNumber)
{
this.serverPort = portNumber;
}
public static void main(String[] args)
{
MainServer server = new MainServer(portNumber);
server.startServer();
}
}
*Edit 2
So I've made a little method that synchronizes the client list accross all threads and edited the mainServer to do this as-well but the problem persists
private void deleteClient(serverHandlerThread obj)
{
synchronized (server.clients)
{
server.clients.remove(obj);
}
}
You should probably refactor your code using a client manager pattern to avoid the problem you currently have:
You are managing your clients from N threads, one of which being the server.
You have access to one list with various form of synchronization which may be lead to synchronization issues because the code is all over.
Given this pattern, here is an example (I used synchronized, but other form of synchronization may work):
class ClientManager {
private final List<Client> clients;
public ClientManager() {
this.clients = new ArrayList<>();
}
public synchronized void add(Client client) {
this.clients.add(client);
}
public synchronized void remove(Client client) {
this.clients.remove(client);
}
public synchronized List<Client> list() {
return new ArrayList<>(this.clients);
}
}
Both Client (ServerHandlerThread) and Server (MainServer) will play the ClientManager: my point is that this class is doing all the synchronization work and not the Server/Client.
I use a copy of the list to minimize the lock time (otherwise, client would wait for other thread calling list()). This means that a Client logout may occurs here when you send your message: you'll need to use a flag (alive, etc) indicating if the Client is still there.
You may also check it in the sendMessage and return a status indicating if the message was sent or not.
class Server {
private final ClientManager manager = new ClientManager();
// register new client
Client newClient() {
Client client = new Client(manager);
manager.add(client);
return client;
}
void sendMessageToAll(String msg) {
for (Client client : manager.list()) {
// isAlive returns true except if the client was logged out.
// It should probably be synchronized too.
if (client.isAlive()) {
client.sendMessage(msg);
}
}
}
}
class Client {
private final ClientManager manager;
public Client(ClientManager manager) {
this.manager = manager;
}
public void logoff() {
manager.remove(this);
}
}
Edit: to answer your comment, I added an example of the Client and how server and client use the manager.
Just to clarify you discuss the client having a play in the client
manager which confuses me. My server accepts a client (socket =
serversocket.accept() ) and sends this socket to a new thread, this
thread handles all communication with the client (messaging and
commands). After the thread is started the thread is added to the
client list. The problem is in the thread when the client sends a
command the thread should run the command then delete itself from the
client list (clientlist.remove(this)). Will your solution still work
here? Cause you discussion about Client confused me
Briefly, yes.
Simple: you are using a List, which is by default not synchronized (you could use a Vector or Collections::synchronizedList for that). Because you do that in several thread, there are synchronization issues (aka "random effect" :)).
Instead of using directly the list, you should rather use a class dedicated to this usage: that's what the ClientManager is for. An object which will manage a list of Client.
This has also another advantages: instead of having several synchronized blocks across your code, all code are in one place.
Related
im making a networked game that has a server which creates a clientHandler thread every time a client joins. I want to ask the first client that joined if it wants to start the game every time a new client joins, giving it the current number of players connected. Writting through the clientHandlers printwritter gives a nullPointerException, even though ive started the thread before doing this. what could be the problem?
Here is the server code:
`public class Server implements Runnable{
private ArrayList<ClientHandler> handlers = new ArrayList<>();
private ArrayList<Player> players = new ArrayList<>();
private Game game;
private boolean start;
public static void main(String[] args) {
Server server = new Server();
Thread s = new Thread(server);
s.start();
}
public void login(String name){
//todo
for (ClientHandler c : handlers){
if (c.getName().equals(name)){
alreadyTaken(name);//todo
}
else{
players.add(new HumanPlayer(name,c));//todo
}
}
}
public void setStart(){
start = true;
}
private void alreadyTaken(String name) {
}
public void setTurn(ServerHandler sh){
//todo
}
public void updateView(){
}
public String hello() {
return "Hello"; //?
}
public void place(String s){
}
#Override
public void run() {
ServerSocket serverSocket;
try {
serverSocket = new ServerSocket(1800);
} catch (IOException e) {
throw new RuntimeException(e);
}
System.out.println("----Server----");
while (!serverSocket.isClosed()) {
try {
Socket socket = serverSocket.accept();
ClientHandler handler = new ClientHandler(socket,handlers,this);
handlers.add(handler);
Thread h = new Thread(handler);
h.start();
System.out.println("A new client has connected");
System.out.println(handlers.get(0));
handlers.get(0).out.println("START? "+ handlers.size());
if (start){
System.out.println("start request works");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
`
And here's the client handler code:
`public class ClientHandler implements Runnable{
private Socket socket;
private ArrayList<ClientHandler> handlers;
private Server server;
public PrintWriter out;
private BufferedReader in;
private String name;
public ClientHandler(Socket socket, ArrayList<ClientHandler> handlers, Server server){
this.socket = socket;
this.handlers = handlers;
this.server = server;
}
public void broadcastMessage(String msg){
System.out.println("Broadcasting");
for (ClientHandler s : this.handlers){
s.out.println("Player: " + msg);
}
}
public static String removePrefix(String s, String prefix)
{
if (s != null && s.startsWith(prefix)) {
return s.split(prefix, 2)[1];
}
return s;
}
public String getName(){
return name;
}
#Override
public void run() {
try {
out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
new Thread(() -> {
while(socket.isConnected()){
String msg;
try {
msg = in.readLine();
while(msg!=null){
switch (msg.split(" ")[0]){
case "LOGIN":
name = removePrefix(msg,"LOGIN ");
server.login(name);//todo
break;
case "HELLO":
server.hello();//todo
break;
case "PLACE":
server.place(removePrefix(msg,"PLACE "));
break;
case "QUIT":
//todo
break;
case "STOP":
//todo
break;
case "START":
server.setStart();
default:
broadcastMessage(msg);
break;
}
msg = in.readLine();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}`
I tried making a method in the client handler class which does the same thing. The server would just call that instead of writting directing through the PrintWriter, but i got the same error.
Starting a thread does not mean it is guaranteed to actually finish executing the first statement in its run() method before start() returns. In fact,
Usually it won't - starting a thread takes some time, and start() returns as soon as it can.
A JVM that runs a few statements in the thread you just started before start() returns is 'correct' - that is fine. A JVM that doesn't is also fine. Generally you don't want threads, because nothing is predictable anymore. At the very least you want to keep 'inter-thread comms' down to a minimum. Anytime a single field is used from more than one thread, things get very tricky.
What you need is synchronized or other tools to insert predictability in this code.
First, fix a bug
Your ClientHandler's run() code starts another thread for no reason. Take all that out, your run() method in ClientHandler should set up out and in and then immediately do while (socket.isConnected())
Synchronizing
At the very basic level, make a locker object and use notify/wait:
private final Object lock = new Object();
#Override public void run() {
try {
synchronized (lock) {
out = ...;
in = ...;
lock.notifyAll();
}
while (socket.isConnected()) { ... }
out definitely cannot be public here, you can't refer to a stream from multiple threads and expect things to work out!
Just 'fixing' your code involves then using something like:
public OutputStream getOutputStream() {
synchronized (lock) {
while (out == null) {
lock.wait();
}
}
return out;
}
Which will ensure that any thread that wants the out will wait for the other thread to get far enough, but, really, this is just setting you up for another 20 threading problems down the line. Instead, you want one object responsibile for all communication (both outgoing and incoming), and a concurrency-capable queue (there are various collections in the java.util.concurrent package good for this). Then:
Any other threads that want to just send data dump their message in the queue.
You have either 1 thread doing all comms, or 2 (one doing incoming, and one doing outgoing), both dedicated. The outgoing one just loops forever, grabbing objects from the queue and sending them.
If a thread wants to send a message and wait for the response, you need to use .wait() or nicer API from e.g. java.util.concurrent, or, use callback hell - you pass a closure with the code to run once the result is received.
I'm working on an academic project that consists in controlling a simulated environment created in Unity3D, from a conventional Android application. I already created the scenario in Unity and the mobile application is completely finished, my problem is in the connection. I chose to use sockets because of its simplicity. I managed to connect the application to a server written in C # through conventional Sockets, I know that information can be sent, but when I implemented it in Unity, everything failed, so I decided to use TCP Listener instead of Sockets in Unity / C # (the client Android still uses a conventional TCP connection through Sockets), and in effect, the application connects but there is no information transfer, because the Unity console throws an error that says: ObjectDisposedException: Can not access to disposed object.
Object name: 'System.Net.Sockets.TcpClient'.(The error is presented in line number 56: stream = client.tcp.GetStream ();) The main objective is to obtain the flow of information that would be represented in something like: "1: 0: 1: 0" and being able to make a Split to that string and according to that value change the state of a light bulb or other element, nevertheless I need the essential thing: to establish the connection flow. I'm not an expert in C # and much less using Unity, I really do not know much about design, but I wanted to do it to deliver an innovative work, I hope someone can guide me.
PD: C# Socket (No Unity yet) server working with Android Java client:
![1] https://imgur.com/a/HuNcDC3
//This is my Unity3D/C# Server:
public class SocketManager : MonoBehaviour
{
private List<ServerClient> connectedClients;
private List<ServerClient> disconectedClients;
private TcpListener server;
private string data;
private NetworkStream stream;
private StreamReader stremaReader;
private bool serverStarted;
public int socketPort = 7691;
private void Start()
{
connectedClients = new List<ServerClient>();
disconectedClients = new List<ServerClient>();
try
{
server = new TcpListener(IPAddress.Parse("192.168.1.64"), socketPort);
server.Start();
serverListening();
serverStarted = true;
Debug.Log("Server started. Port: " + socketPort.ToString());
}
catch (Exception ex)
{
Debug.Log("Server socket error: " + ex);
}
}
private void Update()
{
if (!serverStarted)
return;
foreach (ServerClient client in connectedClients)
{
if (IsConnected(client.tcp))
{
client.tcp.Close();
disconectedClients.Add(client);
continue;
}
else
{
stream = client.tcp.GetStream();
if (stream.DataAvailable)
{
this.stremaReader = new StreamReader(stream, true);
data = stremaReader.ReadLine();
if (data != null)
OnIcomingData(client, data);
}
}
}
}
private void OnIcomingData(ServerClient client, string data)
{
Debug.Log(client.clientName + ": " + data);
}
private bool IsConnected(TcpClient tcp)
{
try
{
if (tcp != null && tcp.Client != null && tcp.Client.Connected)
{
if (tcp.Client.Poll(0, SelectMode.SelectRead))
{
return !(tcp.Client.Receive(new byte[1], SocketFlags.Peek) == 0);
}
return true;
}
else
{
return false;
}
}
catch
{
return false;
}
}
private void serverListening()
{
server.BeginAcceptTcpClient(AcceptTcpClient, server);
}
private void AcceptTcpClient(IAsyncResult asyncResult)
{
TcpListener tcplistener = (TcpListener)asyncResult.AsyncState;
connectedClients.Add(new ServerClient(tcplistener.EndAcceptTcpClient(asyncResult)));
serverListening();
}
}
public class ServerClient
{
public TcpClient tcp;
public string clientName;
public ServerClient(TcpClient tcp)
{
this.tcp = tcp;
this.clientName = "Android";
}
}
// This is my Android/Java Client:
public class SocketThread extends Thread {
private Socket adviser;
private DataOutputStream dataOut;
#Override
public void run() {
super.run();
Log.e("Status:", "Thread started");
try {
adviser = new Socket("192.168.1.64", 7691);
dataOut = new DataOutputStream(adviser.getOutputStream());
} catch (IOException ex) {
Logger.getLogger(SocketThread.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void sendCommand(String text) {
try {
dataOut.writeUTF(text);
Log.e("Sended Text: ", text);
} catch (IOException ex) {
Logger.getLogger(SocketThread.class.getName()).log(Level.SEVERE, null, ex);
}
}
Here's a more detailed answer than my comment, for future reference.
The problem lies in the following if check if (IsConnected(client.tcp)).
Right now what is happening is that when the client is connected to the server (so the if returns true) you are Closing the connection using client.tcp.Close(); and adding the connection to your disconectedClients list (But you are not removing the client from your connectedClients list! Meaning the client is now in both lists). Then when the next iteration of the foreach loop foreach (ServerClient client in connectedClients) comes around the if (IsConnected(client.tcp)) will return false (the client is no longer connected to the server, since you called close() in the previous iteration.
You then try to call stream = client.tcp.GetStream(); on a stream that is already closed, resulting in an error telling you you cannot get the stream.
This should fix it:
if (IsConnected(client.tcp))
{
client.tcp.Close();
client.tcp.Dispose(); //Dispose of a stream to release it for GC
disconectedClients.Add(client);
connectedClients.Remove(client); //Note to also remove the disconnected client from the connectedClients list.
continue;
}
else
{ ...rest of logic }
Note that i added connectedClients.Remove(client); and client.tcp.Dispose(); to your logic closing the stream. If you don't do this it'll keep getting included in theforeachloop unnecessarily, and the programm will try to callclose()` on a connection that is already closed, resulting in more errors. Disposing of the stream will release it so the garbage collector can collect it, freeing up memory.
I'm working on a project to implement the game "Monopoly" in Java. I've currently setup a GUI on top of my client code to take text input to a chat box (or to send commands) to the server.
My Controller.java class is the code to start the GUI. When a user enters the IP and port of the game server and hits submit, the server listening to that Socket then instantiates a thread of the Controller.java inner class ClientServiceThread. This class is what should send messages between the GUI - Client - Server.
My problem is that in the outer class (Controller.java) I have an instance variable called "messageText". Later on when a user enters a message to the Chatbox, I assign that text to the "messageText" variable. However, when I attempt to grab that variable in the ClientServiceThread, it is always null. I believe this is because the server created both a new client and a new controller, which would mean the variable I'm trying to get hasn't actually been modified.
Server Code Snippet
public void run() {
try {
myServerSocket = new ServerSocket(8888);
} catch(IOException ioe) {
System.out.println("Could not create server socket on port 8888. Quitting.");
System.exit(-1);
}
System.out.println("Server successfully started");
while(ServerOn) {
try {
Socket clientSocket = myServerSocket.accept();
Controller.ClientServiceThread cliThread = new Controller().new ClientServiceThread(clientSocket, clientidnum);
cliThread.start();
//adds clients to array in order they join
connectedClients[clientidnum] = cliThread;
//increments connected ID client var
clientidnum++;
} catch(IOException ioe) {
System.out.println("Exception found on accept. Ignoring. Stack Trace :");
ioe.printStackTrace();
}
}
Controller.java Code Snippet
public class Controller extends Application implements Initializable {
Socket myClientSocket;
boolean m_bRunThread = true;
String hostname;
int hostportnumber;
private ObservableList<String> chatMessages = FXCollections.observableArrayList();
String messageText = "";
private void handleChatAction(ActionEvent event) throws IOException {
if (event.getSource() == sendchatButton){
String chat = chatInput.getText();
if(chat != null && !chat.trim().isEmpty()) {
chatMessages.add(chat);
messageText = chat;
chatmessagesList.setItems(chatMessages);
chatmessagesList.scrollTo(chatMessages.size());
}
chatInput.clear();
}
}
Inner Class ClientServiceThread Snippet
class ClientServiceThread extends Thread {
Socket myClientSocket;
boolean m_bRunThread = true;
String hostname;
int hostportnumber;
int id;
public ClientServiceThread() {
super();
}
ClientServiceThread(Socket s, int clientid) {
myClientSocket = s;
id = clientid;
}
public String getTextField(){
if(Controller.this.messageText != "") {
return Controller.this.messageText;
}
else {
return "";
}
}
public void run() {
BufferedReader in = null;
PrintWriter out = null;
System.out.println("Accepted Client Address - " + myClientSocket.getInetAddress().getHostName());
try {
in = new BufferedReader(new InputStreamReader(myClientSocket.getInputStream()));
out = new PrintWriter(new OutputStreamWriter(myClientSocket.getOutputStream()));
while(m_bRunThread) {
String clientCommand = getTextField();
System.out.println("Client Says :" + clientCommand);
I apologize for the huge walls of code. I've taken out hundreds of lines of code just to reduce the amount you need to sift through. This bug is really hindering me and I can't progress to develop the actual game unless the client can communicate with the server.
Here are links to a gist of the full files of code if you wish to have a look at them. Thanks!
https://gist.github.com/anonymous/0961189791b8127598b3b3d7ca527987
I am a Java newbie trying to learn network programming and concurrency, and I thought I'd try out writing a simple chat server where input from a client is echoed to all the clients. That's not happening. I added a couple print statements so that the program will announce that it is waiting for connections and each time it receives a connection. I am using Telnet locally to connect to the port on my machine.
The program announces success for the first and second concurrent connections but then does not announce success for subsequent connections until I close all connections. So, for example, I'll connect from five separate terminals, and the program will announce "Connection 1" and "Connection 2" but will not announce "Connection 3", 4, and 5 until I close all the terminals.
I'm looking for help figuring out where my errors lie as well as general advice for how to approach debugging a situation like this.
In a nutshell, my program has
A Main class, which starts the other three threads
A ClientListener class, which uses a SocketReader to listen for connections and stores the Sockets inputstreams and outputstreams in two Sets.
A MessageReader, which iterates over the inputstreams. If it finds a message, it puts it in a SynchronousQueue and waits for the
MessageWriter to remove it. The MessageWriter sends the message to all the outputstreams.
The code is below. Thanks for any help!
public class Main {
public static void main(String[] args) {
ClientListener clientListener = new ClientListener();
Thread clientListenerThread = new Thread(clientListener);
clientListenerThread.setPriority(Thread.MAX_PRIORITY);
clientListenerThread.start();
MessageReader messageReader = new MessageReader(clientListener);
Thread messageReaderThread = new Thread(messageReader);
messageReaderThread.setPriority(Thread.MIN_PRIORITY);
messageReaderThread.start();
MessageWriter messageWriter = new MessageWriter(messageReader, clientListener);
Thread messageWriterThread = new Thread(messageWriter);
messageWriterThread.setPriority(Thread.NORM_PRIORITY);
messageWriterThread.start();
}
}
public class ClientListener implements Runnable {
private static final int DEFAULT_PORT = 5000;
private Set<Scanner> clientIn = Collections.synchronizedSet(
new LinkedHashSet<Scanner>());
private Set<PrintWriter> clientOut = Collections.synchronizedSet(
new LinkedHashSet<PrintWriter>());
public Set<Scanner> getClientIn() {
return clientIn;
}
public Set<PrintWriter> getClientOut() {
return clientOut;
}
#Override
public void run() {
try {
ServerSocket server = new ServerSocket(DEFAULT_PORT);
System.out.println("Listening for connections...");
int connectionNum = 0;
while(true) {
Socket socket = server.accept();
connectionNum++;
System.out.format("Connection %s%n", connectionNum);
Scanner in = new Scanner(socket.getInputStream());
PrintWriter out = new PrintWriter(socket.getOutputStream());
clientIn.add(in);
clientOut.add(out);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class MessageReader implements Runnable {
private ClientListener clientListener;
private BlockingQueue<String> messages = new SynchronousQueue<String>();
public MessageReader(ClientListener clientListener) {
this.clientListener = clientListener;
}
#Override
public void run() {
while(true) {
Set<Scanner> clients = clientListener.getClientIn();
synchronized (clients) {
for(Scanner client: clients) {
if(client.hasNext()) {
try {
messages.put(client.next());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public String getMessage() throws InterruptedException {
return messages.take();
}
}
public class MessageWriter implements Runnable {
private ClientListener clientListener;
private MessageReader messageReader;
public MessageWriter(
MessageReader messageReader,
ClientListener clientListener) {
this.messageReader = messageReader;
this.clientListener = clientListener;
}
#Override
public void run() {
try {
while(true) {
String message = messageReader.getMessage();
Set<PrintWriter> clients = clientListener.getClientOut();
synchronized (clients) {
for(PrintWriter client: clients) {
client.println(message);
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
I'm not a threading expert, but in class MessageReader there is this line
if(client.hasNext())
Javadoc for Scanner.hasNext() say's "This method may block while waiting for input to scan. The scanner does not advance past any input."
If the scanner is still in wait the synchronized method never proceeds and block all other inputs. And as said in my earlier comment the line which says clientIn.add(in); in class ClientListener probably gets blocked given that its a synchronized Set, but since the print statment is written before it, it might give the impression that Connection 2 was succesfully established.
I am writing a Java multi-threaded network application and having real difficulty coming up with a way to unit test the object which sends and receives communication from network clients.
The object sends out a message to a number of clients and then waits for responses from the clients.
As each client responds, a dashboard-style GUI is updated.
In more detail...
A Message object represents a text message to be sent and contains an array of Clients which should receive the message.
The Message object is responsible for dispatching itself to all the appropriate clients.
When the dispatch() method is invoked on a Message object, the object spawns a new thread (MessageDispatcher) for each client in the Client array.
Each MessageDispatcher:
opens a new TCP socket (Socket) to the client
delivers the message to its client... PrintWriter out.println(msg text)
creates a 'Status' object which is passed to a Queue in the Message object and then on to the GUI.
Each Status object represents ONE of the following events:
Message passed to Socket (via Printwriter out.println() )
Display receipt received from client (via BufferedReader/InputStreamReader in.readline()... blocks until network input is received )
User acknowledge receipt received from client (via same method as above)
So.. I want to unit test the Message object. (using JUnit)
The unit test is called MessageTest.java (included below).
My first step has been to set up a Message object with a single recipient.
I then used JMockit to create a mock Socket object which can supply a mock OutputStream object (I am using ByteArrayOutputStream which extends OutputStream) to PrintWriter.
Then, when the MessageDispatcher calls (PrintWriter object).out, the message text will be ideally passed to my mock Socket object (via the mock OutputStream) which can check that the message text is OK.
And the sample principle for the InputStreamReader.... The mock Socket object also supplies a mock InputStreamReader object which supplies a mock BufferedReader which is called by the MessageDispatcher (as mentioned previously, MessageDispatcher blocks on in.readLine() ). At this point the mock BufferedReader should supply a fake confirmation to the MessageDispatcher...
// mock Socket
Mockit.redefineMethods(Socket.class, new Object()
{
ByteArrayOutputStream output = new ByteArrayOutputStream();
ByteArrayInputStream input = new ByteArrayInputStream();
public OutputStream getOutputStream()
{
return output;
}
public InputStream getInputStream()
{
return input;
}
});
If this wasn't multi-threaded, this should all work OK. However I have no idea how to do this with multiple threads. Can anyone give me any advice or tips?
Also if you have any input on the design (eg. Message object responsible for its own delivery rather than a separate delivery object.. "dependency injection"-style / separate thread for each client delivery) then I would be interested to hear that too.
UPDATE: here is the code:
Message.java
public class Message {
Client[] to;
String contents;
String status;
StatusListener listener;
BlockingQueue<Status> statusQ;
public Message(Client[] to, String contents, StatusListener listener)
{
this.to = to;
this.contents = contents;
this.listener = listener;
}
public void dispatch()
{
try {
// open a new thread for each client
// keep a linked list of socket references so that all threads can be closed
List<Socket> sockets = Collections.synchronizedList(new ArrayList<Socket>());
// initialise the statusQ for threads to report message status
statusQ = new ArrayBlockingQueue<Status>(to.length*3); // max 3 status objects per thread
// dispatch to each client individually and wait for confirmation
for (int i=0; i < to.length; i++) {
System.out.println("Started new thread");
(new Thread(new MessageDispatcher(to[i], contents, sockets, statusQ))).start();
}
// now, monitor queue and empty the queue as it fills up.. (consumer)
while (true) {
listener.updateStatus(statusQ.take());
}
}
catch (Exception e) { e.printStackTrace(); }
}
// one MessageDispatcher per client
private class MessageDispatcher implements Runnable
{
private Client client;
private String contents;
private List<Socket> sockets;
private BlockingQueue<Status> statusQ;
public MessageDispatcher(Client client, String contents, List<Socket> sockets, BlockingQueue<Status> statusQ) {
this.contents = contents;
this.client = client;
this.sockets = sockets;
this.statusQ = statusQ;
}
public void run() {
try {
// open socket to client
Socket sk = new Socket(client.getAddress(), CLIENTPORT);
// add reference to socket to list
synchronized(sockets) {
sockets.add(sk);
}
PrintWriter out = new PrintWriter(sk.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(sk.getInputStream()));
// send message
out.println(contents);
// confirm dispatch
statusQ.add(new Status(client, "DISPATCHED"));
// wait for display receipt
in.readLine();
statusQ.add(new Status(client, "DISPLAYED"));
// wait for read receipt
in.readLine();
statusQ.add(new Status(client, "READ"));
}
catch (Exception e) { e.printStackTrace(); }
}
}
}
.... and the corresponding unit test:
MessageTest.java
public class MessageTest extends TestCase {
Message msg;
static final String testContents = "hello there";
public void setUp() {
// mock Socket
Mockit.redefineMethods(Socket.class, new Object()
{
ByteArrayOutputStream output = new ByteArrayOutputStream();
ByteArrayInputStream input = new ByteArrayInputStream();
public OutputStream getOutputStream()
{
return output;
}
public InputStream getInputStream()
{
return input;
}
});
// NB
// some code removed here for simplicity
// which uses JMockit to overrides the Client object and give it a fake hostname and address
Client[] testClient = { new Client() };
msg = new Message(testClient, testContents, this);
}
public void tearDown() {
}
public void testDispatch() {
// dispatch to client
msg.dispatch();
}
}
Notice that the sending of multiple messages (multicast) can be achieved in a single blocking method through the NIO API (java.nio) as well, without the creation of new threads. NIO is quite complex, though.
I would start by writing the tests first, with a test-defined StatusListener implementation which stores all update events in a list. When the dispatch() method returns, the test can execute asserts on the state of the event list.
Using threads or NIO is an implementation detail for the Message class. So, unless you don't mind coupling the tests to this implementation detail, I would recommend introducing a helper class that would be responsible for sending multiple asynchronous messages and notifying the Message object upon any asynchronous replies. Then, you can mock the helper class in the unit tests, without coupling them to either threads or NIO.
I successfully implemented a test for the case of sending a message to one client. I also made some changes to the original production code, as follows:
public class Message
{
private static final int CLIENT_PORT = 8000;
// Externally provided:
private final Client[] to;
private final String contents;
private final StatusListener listener;
// Internal state:
private final List<Socket> clientConnections;
private final BlockingQueue<Status> statusQueue;
public Message(Client[] to, String contents, StatusListener listener)
{
this.to = to;
this.contents = contents;
this.listener = listener;
// Keep a list of socket references so that all threads can be closed:
clientConnections = Collections.synchronizedList(new ArrayList<Socket>());
// Initialise the statusQ for threads to report message status:
statusQueue = new ArrayBlockingQueue<Status>(to.length * 3);
}
public void dispatch()
{
// Dispatch to each client individually and wait for confirmation:
sendContentsToEachClientAsynchronously();
Status statusChangeReceived;
do {
try {
// Now, monitor queue and empty the queue as it fills up (consumer):
statusChangeReceived = statusQueue.take();
}
catch (InterruptedException ignore) {
break;
}
}
while (listener.updateStatus(statusChangeReceived));
closeRemainingClientConnections();
}
private void closeRemainingClientConnections()
{
for (Socket connection : clientConnections) {
try {
connection.close();
}
catch (IOException ignore) {
// OK
}
}
clientConnections.clear();
}
private void sendContentsToEachClientAsynchronously()
{
for (Client client : to) {
System.out.println("Started new thread");
new Thread(new MessageDispatcher(client)).start();
}
}
// One MessageDispatcher per client.
private final class MessageDispatcher implements Runnable
{
private final Client client;
MessageDispatcher(Client client) { this.client = client; }
public void run()
{
try {
communicateWithClient();
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
private void communicateWithClient() throws IOException
{
// Open connection to client:
Socket connection = new Socket(client.getAddress(), CLIENT_PORT);
try {
// Add client connection to synchronized list:
clientConnections.add(connection);
sendMessage(connection.getOutputStream());
readRequiredReceipts(connection.getInputStream());
}
finally {
connection.close();
}
}
// Send message and confirm dispatch.
private void sendMessage(OutputStream output)
{
PrintWriter out = new PrintWriter(output, true);
out.println(contents);
statusQueue.add(new Status(client, "DISPATCHED"));
}
private void readRequiredReceipts(InputStream input) throws IOException
{
BufferedReader in = new BufferedReader(new InputStreamReader(input));
// Wait for display receipt:
in.readLine();
statusQueue.add(new Status(client, "DISPLAYED"));
// Wait for read receipt:
in.readLine();
statusQueue.add(new Status(client, "READ"));
}
}
}
public final class MessageTest extends JMockitTest
{
static final String testContents = "hello there";
static final String[] expectedEvents = {"DISPATCHED", "DISPLAYED", "READ"};
#Test
public void testSendMessageToSingleClient()
{
final Client theClient = new Client("client1");
Client[] testClient = {theClient};
new MockUp<Socket>()
{
#Mock(invocations = 1)
void $init(String host, int port)
{
assertEquals(theClient.getAddress(), host);
assertTrue(port > 0);
}
#Mock(invocations = 1)
public OutputStream getOutputStream() { return new ByteArrayOutputStream(); }
#Mock(invocations = 1)
public InputStream getInputStream()
{
return new ByteArrayInputStream("reply1\nreply2\n".getBytes());
}
#Mock(minInvocations = 1) void close() {}
};
StatusListener listener = new MockUp<StatusListener>()
{
int eventIndex;
#Mock(invocations = 3)
boolean updateStatus(Status status)
{
assertSame(theClient, status.getClient());
assertEquals(expectedEvents[eventIndex++], status.getEvent());
return eventIndex < expectedEvents.length;
}
}.getMockInstance();
new Message(testClient, testContents, listener).dispatch();
}
}
The JMockit test above uses the new MockUp class, not yet available in the latest release. It can be replaced with Mockit.setUpMock(Socket.class, new Object() { ... }), though.
perhaps instead of redefining the methods getOutputStream and getInputStream, you can instead use an AbstractFactory in your Message class which creates output and input streams. In normal operation the factory will use a Socket to do that. However, for testing give it a factory which gives it streams of your choosing. That way you have more control over exactly what is happening.