Program must accept requests to add and remove tasks from the list through the server. After starting, server accepts connections in an infinite loop and reads from them a line containing json of the form:
{ "type": "ADD", "task": "Название задачи" }
where type is the type of operation (ADD or REMOVE) and task is the task itself. After processing the request, a list of all tasks should be displayed in the console. After connecting, my console gives null. What can be wrong?
Server class:
public class TodoServer {
public TodoServer(int port, Todos todos) {
while (true) {
try (ServerSocket serverSocket = new ServerSocket(port);
Socket clientSocket = serverSocket.accept();
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()))) {
System.out.println("New connection accepted");
final String json = in.readLine();
Gson gson = new Gson();
String type = gson.fromJson("\"type\"", String.class);
String task = gson.fromJson("\"task\"", String.class);
if (type.equals("ADD")) {
todos.addTask(task);
} else if (type.equals("REMOVE")) {
todos.removeTask(task);
}
System.out.println(todos.getAllTasks());
} catch (IOException e) {
System.out.println("Соединение разорвано");
}
}
}
public void start() throws IOException {
int port = 8989;
System.out.println("Starting server at " + port + "...");
}
}
Task class:
public class Todos {
static ArrayList <String> tasks = new ArrayList<>();
public void addTask(String task) {
tasks.add(task);
Collections.sort(tasks);
}
public void removeTask(String task) {
tasks.remove(task);//...
}
public String getAllTasks() {
return tasks.toString();
}
public ArrayList<String> getListTask() {
return tasks;
}
}
The Main class which the server starts:
public class Main {
public static void main(String[] args) throws IOException {
Todos todos = new Todos();
TodoServer server = new TodoServer(8989, todos);
server.start();
}
}
From what you've shown here, your parsing and use of JSON is the issue. As a starting point, you read a String json but then do nothing with it.
You'll want to parse that value into an object, and then access values out of it (like you would a dictionary or map). How to do that with GSON should have plenty of documentation and examples readily available.
If you are using an IDE for development, I also recommend using this as a great opportunity for trying the debugger out - setting breakpoints, inspecting values, etc!
It would be better to define a simple POJO to represent a task:
#Data
class MyTask {
private String type;
private String task;
}
Here #Data is a Lombok annotation which provides the boilerplate code of getters/setters/default constructor/toString/hashCode/equals.
Then the instance of such POJO is deserialized from JSON abd processed as needed:
final String json = in.readLine();
MyTask task = new Gson().fromJson(json, MyTask.class);
if ("ADD".equals(task.getType())) {
todos.addTask(task.getTask());
} else if ("REMOVE".equals(task.getType())) {
todos.removeTask(task.getTask());
}
System.out.println(todos.getAllTasks());
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 writing a Connection class that sends and receives data going both ways via Commands and CommandResults. However, when multiple requests are sent quickly over the Connection, some do not make it through properly.
It sound like a race condition of sorts, but I feel like I've prepared for that by:
Locking and unlocking writes to the socket,
having a table of sent Commands whose CommandResults haven't been received,
and locking and unlocking changes to said table.
Commands are received and processed on a single thread, so that shouldn't be the issue.
I've looked over the code enough times that I feel like the problem has to be elsewhere, but my team is very confident that Connection is the culprit.
This sample is a little long, but this was as small as I feel I could make a complete example. I did make sure it was well documented though. The important things to know are:
AwaitWrappers are just a Future. Getting the resource will block until it is actually filled in,
Messages just wrap requests and responses,
a Serializer is basically a gson wrapper,
Commands and CommandResults are tracked with a common UUID,
and ICommandHandlers take in a Command and output a CommandResult. The contents of Commands and CommandResults shouldn't matter for this.
Connection.java:
public class Connection {
private Socket socket;
private ICommandHandler handler;
private Serializer ser;
private Lock resultsLock;
private Lock socketWriteLock;
private Map<UUID,AwaitWrapper<CommandResult>> reservations;
public Connection(Socket socket) {
ser = new Serializer();
reservations = new TreeMap<UUID,AwaitWrapper<CommandResult>>();
handler = null;
this.socket = socket;
// Set up locks
resultsLock = new ReentrantLock();
socketWriteLock = new ReentrantLock();
}
public Connection(String host, int port) throws UnknownHostException, IOException {
socket = new Socket(host, port);
ser = new Serializer();
reservations = new TreeMap<UUID,AwaitWrapper<CommandResult>>();
handler = null;
// Set up locks
resultsLock = new ReentrantLock(true);
socketWriteLock = new ReentrantLock(true);
}
/* Sends a command on the socket, and waits for the response
*
* #param com The command to be sent
* #return The Result of the command operation.
*/
public CommandResult sendCommand(Command com) {
try {
AwaitWrapper<CommandResult> delayedResult = reserveResult(com);
write(new Message(com));
CommandResult res = delayedResult.waitOnResource();
removeReservation(com);
return res;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/* Sets handler for incoming Commands. Also starts listening to the socket
*
* #param handler The handler for incoming Commands
*/
public void setCommandHandler(ICommandHandler handler) {
if (handler == null) return;
this.handler = handler;
startListening();
}
/* Starts a thread that listens to the socket
*
* Note: don't call this until handler has been set!
*/
private void startListening() {
Thread listener = new Thread() {
#Override
public void run() {
while (receiveMessage());
handler.close();
}
};
listener.start();
}
/* Recives all messages (responses _and_ results) on a socket
*
* Note: don't call this until handler has been set!
*
* #return true if successful, false if error
*/
private boolean receiveMessage() {
InputStream in = null;
try {
in = socket.getInputStream();
Message message = (Message)ser.deserialize(in, Message.class);
if (message == null) return false;
if (message.containsCommand()) {
// Handle receiving a command
Command com = message.getCommand();
CommandResult res = handler.handle(com);
write(new Message(res));
} else if (message.containsResult()) {
// Handle receiving a result
CommandResult res = message.getResult();
fulfilReservation(res);
} else {
// Neither command or result...?
return false;
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
//--------------------------
// Thread safe IO operations
private void write(Message mes) throws IOException {
OutputStream out = socket.getOutputStream();
socketWriteLock.lock();
ser.serialize(out, mes);
socketWriteLock.unlock();
}
//----------------------------------
//Thread safe reservation operations
private AwaitWrapper<CommandResult> reserveResult(Command com) {
AwaitWrapper<CommandResult> delayedResult = new AwaitWrapper<CommandResult>();
resultsLock.lock();
reservations.put(com.getUUID(), delayedResult);
resultsLock.unlock();
return delayedResult;
}
private void fulfilReservation(CommandResult res) {
resultsLock.lock();
reservations.get(res.getUUID()).setResource(res);
resultsLock.unlock();
}
private void removeReservation(Command com) {
resultsLock.lock();
reservations.remove(com.getUUID());
resultsLock.unlock();
}
//-------------------------------------------------------------------
// A Message wraps both commands and results for easy deserialization
private class Message {
...
}
}
When monitoring the receiving side of the Connection, the handler never gets triggered for some of the Commands sent. It should be triggered by and process every incoming Command.
I'm considering ditching the reservation table and locking writes to the socket until the the response has been received, but I'm expecting that that won't come without significant performance penalties.
Am I missing some crucial step that would prevent race conditions?
EDIT: Adding the Serializer and ICommandHandler classes for those who are curious.
Serializer.java:
public class Serializer {
private Gson gson;
public Serializer() {
gson = new Gson();
}
public Object deserialize(InputStream is, Class type) throws IOException {
JsonReader reader = new JsonReader(new InputStreamReader(is, StandardCharsets.UTF_8));
reader.setLenient(true);
if (reader.hasNext()) {
Object res = gson.fromJson(reader, type);
return res;
}
return null;
}
public void serialize(OutputStream os, Object obj) throws IOException {
JsonWriter writer = new JsonWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));
gson.toJson(obj, obj.getClass(), writer);
writer.flush();
}
}
ICommandHandler:
public interface ICommandHandler {
public CommandResult handle(Command com);
public void close();
}
The locks do nothing in your case, there is no race-condition there, if the same handler is used for multiple sockets then the locks need to be inside the handler, you're using a single-threaded client so locks do nothing there, Note: when using locks use a try-finally.
If you're just starting with Sockets you probably don't know this but SocketChannel is a lot more efficient than the Socket class which is extremely old.
I can't help you more than that without seeing Serializer and ICommandHandler.
It is most likely a problem in the Serializer.
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
Im facing one problem in streaming data capture for reading the broadcast data during multithreading, pls help or suggest,
Actually there is one class which is reading data from one of the udp socket. Another class accepts the tcp connection from every client request, creates a thread for every client and request the same udp class for data. The thing is working with 1st thread which gets created. But when i request with another client from another pc/ip the packets get losted to the 2nd client/thread
I have made a workaround by creating a list where im storing the Threads outputstream object
and looping it to send the data to all the client. But this is just temporary as it ll delay the packets if clients/connections gets increased.
code for reading UDP Data
public class EventNotifier
{
private InterestingEvent ie;
public DatagramSocket clientSocket;
public String[] split_str;
byte[] receiveData;
HashMap<String, String> secMap = new HashMap<String, String>();
public EventNotifier(InterestingEvent event)
{
ie = event;
clientSocket = new DatagramSocket(9050);
receiveData = new byte[500];
}
public String getDataFeed(String client_id)
{
try
{
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
clientSocket.receive(receivePacket);
String s = new String(receivePacket.getData());
String split_str = s.split(",");
if(secMap.containsValue(split_str[0]))
return s;
else
return "";
} catch(Exception e3) {}
}
}// end of eventNotifier class
code for multithreading handling client requests
public class multiServer
{
static protected List<PrintWriter> writers = new ArrayList<PrintWriter>();
static String client_id = "";
public static void main(String[] args)
{
try
{
ServerSocket servsock = new ServerSocket(8858);
Socket incoming;
while(true)
{
incoming = servsock.accept();
multiServerThread connection = new multiServerThread(incoming);
Thread t1 = new Thread(connection);
t1.start();
}
}
catch(IOException e)
{
System.out.println("couldnt make socket");
}
}
}
class multiServerThread extends Thread implements InterestingEvent
{
Socket incoming;
PrintWriter out=null;
PrintWriter broad=null;
BufferedReader in = null;
String cliString=null;
private EventNotifier en;
int id;
public static String udp_data;
public void interestingEvent(String str1)
{
this.udp_data = str1;
}
public String getUdpData()
{
String _udp_data = this.udp_data;
return _udp_data;
}
multiServerThread(Socket incoming)
{
this.incoming=incoming;
en = new EventNotifier(this);
}
public void run()
{
try
{
out = new PrintWriter(incoming.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(incoming.getInputStream()));
cliString = in.readLine();
multiServer.writers.add(out);
while(true)
{
try
{
udp_data = en.getDataFeed(cliString);
if(udp_data!=null && udp_data.length()>0)
{
//workaround for serving the data to all cleints who are connected
for (int i=0; i<multiServer.writers.size();i++)
{
broad=multiServer.writers.get(i);
broad.println(udp_data.trim());
}
//else will directly write to the outputstream object for every thread which is connected
// out.println(udp_data.trim());
}
}
catch (Exception e)
{
System.out.println("exception "+e);
}
Thread.sleep(1);
}
} catch(IOException e)
{
System.out.print("IO Exception :: "+ e);
}
catch(InterruptedException e)
{
System.out.print("exception "+ e);
}
}
}
You need mutual exclusion (or a different design).
For example, what will happen if two threads call multiServer.writers.add(out); concurrently?
From the ArrayList Javadocs
Note that this implementation is not synchronized. If multiple threads access an ArrayList instance concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more elements, or [...])
Another problem is two calling udp_data = en.getDataFeed(cliString); concurrently. The second thread might overwrite the result of the first. You'll loose data!
What happens if one thread calls for (int i=0; i<multiServer.writers.size();i++) while another thread is busy doing multiServer.writers.add(out);? The size may have increased, before out has actually been added to the list!
public class multiServer
{
private List<PrintWriter> writers = new ArrayList<PrintWriter>();
public synchronized void addWriter(PrintWrite out) {
writers.add(out);
}
public synchronized void serveAllWriters(String data) {
for (int i=0; i<multiServer.writers.size();i++)
{
broad=multiServer.writers.get(i);
broad.println(data);
}
}
}
Now when a thread tries to add a writer, the synchronizeds will make sure no other thread is adding or printing. So multiServerThread should be fixed to use the new methods:
class multiServerThread extends Thread implements InterestingEvent
{
//...
private String udp_data;
//...
myMultiServer.addWriter(out);
//...
udp_data = en.getDataFeed(cliString);
if(udp_data!=null && udp_data.length()>0)
myMultiServer.serveAllWriters(udp_data.trim());
//...
}
There might be more problems, not sure I don't fully understand your code. The question you must ask yourself is, can another thread read and/or write the same data or object? Yes? Then you'll need proper synchronization.
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.