I'm creating a multithread chat server in java.
When user u1 logs in and sends a message to user u2, if user u2 is not connected the message is sent to the server and put in an ArrayList of pending messages. When user u2 connects, he receive the message from the server and send a message to user u1 as a receipt.
This is my code:
if (pendingmsgs.size()>0) {
for(Iterator<String> itpendingmsgs = pendingmsgs.iterator(); itpendingmsgs.hasNext();) {
//....parsing of the message to get the recipient, sender and text
String pendingmsg = itpendingmsgs.next();
if (protocol.author != null && protocol.author.equals(recipient)) {
response+=msg;
protocol.sendMsg(sender, "Msg "+text+" sent to "+recipient);
itpendingmsgs.remove();
}
}
}
out.write(response.getBytes(), 0, response.length());
This is the ServerProtocol sendMsg() method:
private boolean sendMsg(String recip, String msg) throws IOException {
if (nicks.containsKey(recip)) { //if the recipient is logged in
ClientConnection c = nick.get(recipient); //get the client connection
c.sendMsg(msg); //sends the message
return true;
} else {
/* if the recipient is not logged in I save the message in the pending messages list */
pendingmsgs.add("From: "+nick+" to: "+recip+" text: "+msg);
return false;
}
}
and this is the ClientConnection sendMsg() method:
public void sendMsg(String msg) throws IOException {
out.write(msg.getBytes(), 0, msg.length());
}
where out is an OutputStream.
When user u1 logs in, sends a message to user u2 who is not logged in and then user u1 leaves, when user u2 logs in he doesn't receive the message and I get this exception:
Exception in thread "Thread-2" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
at java.util.AbstractList$Itr.remove(Unknown Source)
at ChatServer$ClientConnection.run(ChatServer.java:400)
at java.lang.Thread.run(Unknown Source)
Line 400 is
itpendingmsgs.remove();
I've tried using a CopyOnWriteArrayList but it still doesn't work.
CopyOnWriteArrayList.iterator() doesn't support remove(). You should probably use a Collections.synchronizedList(ArrayList) (properly locked during iteration as specified in the Javadoc).
That's really the simplest way to allow one thread to add to the list and the other to iterate through removing elements.
Most probably after looking at your code, issue seems to be that while you are looping through your iterator you add new content to the ArrayList in sendMsg method
protocol.sendMsg(sender, "Msg "+text+" sent to "+recipient); // this line invokes the code which adds
pendingmsgs.add("From: "+nick+" to: "+recip+" text: "+msg); // this line adds a new item
See this discussion for reason why this happened last time around.
Edit: As per comment
line 400 is itpendingmsgs.remove();
This is definitely because of addition in the list, as when you reach itpendingmsgs.remove();, you have already added a new entry in the list which makes your iterator complain.
Update:
Appraches to fix this issue:
Instead of Iterator use ListIterator and add, remove objects from the ListIterator and not underlying List.
Update Sample Code :
package com.mumz.test.listiterator;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Random;
/**
* Test Class to show case List Iterator.
*/
public class TestListIterator {
/** The pendingmsgs. */
List<String> pendingmsgs = new ArrayList<String>();
/**
* Add some content to the list and then start processing the same list.
*/
private void init() {
addContentToList();
doProcessing();
}
/**
* Add test content to list.
*/
private void addContentToList() {
for (int iDx = 0; iDx < 10; iDx++) {
pendingmsgs.add("Message " + iDx);
}
}
/**
* Randomly decide if message should be added or removed, showcase iteration using list iterator.
*/
private void doProcessing() {
if (pendingmsgs.size() > 0) {
for(ListIterator<String> listIterator = pendingmsgs.listIterator(); listIterator.hasNext();){
String currentMessage = listIterator.next();
Random random = new Random();
int nextInt = random.nextInt(100);
if((nextInt % 2) == 0){
sendMsg(currentMessage, listIterator);
} else {
listIterator.remove();
}
}
}
}
/**
* Add new content to the list using listIterator of the underlying list.
*
* #param msg
* the msg
* #param listIterator
* the list iterator
* #return true, if successful
*/
private boolean sendMsg(String msg, ListIterator<String> listIterator) {
Random random = new Random();
int nextInt = random.nextInt(10);
// Randomly add new message to list
if ((nextInt % 2) == 0) {
listIterator.add("New Messsage : " + msg);
return false;
}
return true;
}
/**
* The main method.
*
* #param args
* the arguments
*/
public static void main(String[] args) {
try {
TestListIterator testListIterator = new TestListIterator();
testListIterator.init();
System.out.println(testListIterator);
} catch (Exception e) {
e.printStackTrace();
}
}
/* (non-Javadoc)
* #see java.lang.Object#toString()
*/
#Override
public String toString() {
return String.format("TestListIterator [pendingmsgs=%s]", pendingmsgs);
}
}
Instead of using Iterator or ListIterator just use normal for or while loop, in this case you can directly modify your collection (list in this case) without getting this exception.
Use Iterator itself but dont add new elements into the list while you are looping.
Add your messages to another list say tempMessageHolder so sendMsg will add message to this list.
Once your loop is complete, add all the messages from tempMessageHolder to your main list pendingmsgs
Related
I modified an open-source project on github for a school project to fit my needs
it had a broadcast() method to send messages and it was called in the run() method in a while loop but the problem is that broadcast() sends a message to all users in a userList<>i wanted to add the ability to send a private message to one of the users by writing #username.
Here is the code for broadcast method:
private synchronized void broadcast(String msg) {
for (int i = 0; i < clientList.size(); i++) {
clientList.get(i).write(msg);
}
System.out.println("Log: Message broadcast --> " + msg);
}
and here is the run() method
public void run() {
System.out.println("Log: Got input/output streams for connected client.");
/** Get the first message from the client, attempt communication */
String clientMsg = null;
boolean accepted = false;
/** Allow client to create an account, login, or quit */
do {
clientMsg = client.read();
if (clientMsg.equals("QUIT")) {
System.out.println("Log: Client disconnected without signing in.");
client.disconnect();
return;
}
else if (clientMsg.startsWith("NEWUSER: ")) {
createUser(clientMsg);
}
else if (clientMsg.startsWith("LOGIN: ")) {
accepted = authenticate(clientMsg);
}
else
{
System.out.println("Log: Unexpected client message -> " + clientMsg);
client.disconnect();
return;
}
} while(!accepted);
/** Run main chat loop. Will read from the client, and broadcast each read
* until the client disconnects. */
while (true) {
int i=0;
String username= clientList.get(i).getUsername();
String line = client.read();
if (line == null) break;
else if(line.startsWith("#"+username)){
broadcastp(line,username);
}
else {
broadcast(line);
}
i++;
}
/** The only way for the client to exit the above loop is to disconnect.
* Therefore, call the handler's exit routine */
exit();
}
Here is the broadcastp() method that i tried to implement this feature with, but it doesn't work. It compiles and runs perfectly though just without the private chat feature.
private synchronized void broadcastp(String msg,String username) {
for (int i = 0; i < clientList.size(); i++) {
username = clientList.get(i).getUsername();
if(msg.startsWith("#"+username))
{clientList.get(i).write(msg);}
else {
continue;
}}
System.out.println("Log: Message broadcast --> " + msg);}
I do not have the full picture of how your program works, but you say the program runs perfectly, but does not do the private messaging part.
If I look at your code, in the while loop you always take the first username from the clientList (i = 0) and only call broadcastp if the line starts with that name.
First of all.. is broadcastp ever invoked? In broadcastp you have another loop, but that will always match on i == 0 given the way you invoke it (with the line and username from the while loop).
The problem seems to be be there. So something like this within the while loop might work for you (remove the i variable, and no need for broadcastp):
boolean isPrivate = false;
String line = client.read();
for (User user : clientList) {
if (line.startsWith("#" + user.getUsername())) {
user.write(line);
isPrivate = true;
break;
}
}
if (!isPrivate) {
broadcast(line);
}
I have tried a program which download files parallely using java.nio by creating a thread per file download.
package com.java.tftp.nio;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* This class is used to download files concurrently from tftp server by
* configuring the filenames, no of files.
*
* #author SHRIRAM
*
*/
public class TFTP_NIO_Client {
/**
* destination folder
* */
private String destinationFolder;
/**
* list of files names to download
* */
private List<String> fileNames;
/**
* integer indicates the number of files to download concurrently
* */
private int noOfFilesToDownload;
public TFTP_NIO_Client(List<String> fileNames, String destinationFolder,
int noOfFilesToDownload) {
this.destinationFolder = destinationFolder;
this.fileNames = fileNames;
this.noOfFilesToDownload = noOfFilesToDownload;
initializeHandlers();
}
/**
* This method creates threads to register the channel to process download
* files concurrently.
*
* #param noOfFilesToDownload
* - no of files to download
*/
private void initializeHandlers() {
for (int i = 0; i < noOfFilesToDownload; i++) {
try {
Selector aSelector = Selector.open();
SelectorHandler theSelectionHandler = new SelectorHandler(
aSelector, fileNames.get(i));
theSelectionHandler.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Setup RRQ/WRQ packet Packet : | Opcode | FileName | 0 | mode | 0 |
* Filename -> Filename in array of bytes. 0 -> indicates end of file mode
* -> string in byte array 'netascii' or 'octet'
*
* #param aOpcode
* #param aMode
* #param aFileName
* #throws IOException
*/
private void sendRequest(int aOpcode, int aMode, String aFileName,
DatagramChannel aChannel, InetSocketAddress aAddress)
throws IOException {
// Read request packet
TFTPReadRequestPacket theRequestPacket = new TFTPReadRequestPacket();
aChannel.send(
theRequestPacket.constructReadRequestPacket(aFileName, aMode),
aAddress);
}
/**
* sends TFTP ACK Packet Packet : | opcode | Block# | opcode -> 4 -> 2 bytes
* Block -> block number -> 2bytes
*
* #param aBlock
*/
private ByteBuffer sendAckPacket(int aBlockNumber) {
// acknowledge packet
TFTPAckPacket theAckPacket = new TFTPAckPacket();
return theAckPacket.getTFTPAckPacket(aBlockNumber);
}
/**
* This class is used to handle concurrent downloads from the server.
*
* */
public class SelectorHandler extends Thread {
private Selector selector;
private String fileName;
/**
* flag to indicate the file completion.
* */
private boolean isFileReadFinished = false;
public SelectorHandler(Selector aSelector, String aFileName)
throws IOException {
this.selector = aSelector;
this.fileName = aFileName;
registerChannel();
}
private void registerChannel() throws IOException {
DatagramChannel theChannel = DatagramChannel.open();
theChannel.configureBlocking(false);
selector.wakeup();
theChannel.register(selector, SelectionKey.OP_READ);
sendRequest(Constants.OP_READ, Constants.ASCII_MODE, fileName,
theChannel, new InetSocketAddress(Constants.HOST,
Constants.TFTP_PORT));
}
#Override
public void run() {
process();
}
private void process() {
System.out.println("Download started for " + fileName + " ");
File theFile = new File(destinationFolder
+ fileName.substring(fileName.lastIndexOf("/")));
FileOutputStream theFout = null;
try {
theFout = new FileOutputStream(theFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
while (!isFileReadFinished) {
try {
if (selector.select() == 0) {
try {
// sleep 2sec was introduced because selector is
// thread safe but keys are not thread safe
Thread.sleep(2000);
} catch (InterruptedException e) {
continue;
}
continue;
}
Set<SelectionKey> theSet = selector.selectedKeys();
Iterator<SelectionKey> theSelectedKeys = theSet.iterator();
synchronized (theSelectedKeys) {
while (theSelectedKeys.hasNext()) {
SelectionKey theKey = theSelectedKeys.next();
theSelectedKeys.remove();
if (theKey.isReadable()) {
isFileReadFinished = read(theKey, theFout,
fileName);
if (!isFileReadFinished) {
theKey.interestOps(SelectionKey.OP_READ);
}
} else if (theKey.isWritable()) {
// there is no implementation for file write to
// server.
theKey.interestOps(SelectionKey.OP_READ);
}
}
}
} catch (IOException ie) {
ie.printStackTrace();
}
}
System.out.println("Download finished for " + fileName);
try {
if (selector.isOpen()) {
selector.close();
}
if (theFout != null) {
theFout.close();
}
} catch (IOException ie) {
}
}
}
/**
* #param aKey
* registered key for the selector
* #param aOutStream
* - file output stream to write the file contents.
* #return boolean
* #throws IOException
*/
private boolean read(SelectionKey aKey, OutputStream aOutStream,
String aFileName) throws IOException {
DatagramChannel theChannel = (DatagramChannel) aKey.channel();
// data packet
TFTPDataPacket theDataPacket = new TFTPDataPacket();
ByteBuffer theReceivedBuffer = theDataPacket.constructTFTPDataPacket();
SocketAddress theSocketAddress = theChannel.receive(theReceivedBuffer);
theReceivedBuffer.flip();
byte[] theBuffer = theReceivedBuffer.array();
byte[] theDataBuffer = theDataPacket.getDataBlock();
if (theDataPacket.getOpCode() == Constants.OP_DATA) {
int theLimit = theDataPacket.getLimit();
// checks the limit of the buffer because a packet with data less
// than 512 bytes of content signals that it is the last packet in
// transmission for this particular file
if (theLimit != Constants.MAX_BUFFER_SIZE
&& theLimit < Constants.MAX_BUFFER_SIZE) {
byte[] theLastBlock = new byte[theLimit];
System.arraycopy(theBuffer, 0, theLastBlock, 0, theLimit);
// writes the lastblock
aOutStream.write(theLastBlock);
// sends an acknowledgment to the server using TFTP packet
// block number
theChannel
.send(sendAckPacket((((theBuffer[2] & 0xff) << 8) | (theBuffer[3] & 0xff))),
theSocketAddress);
if (theChannel.isOpen()) {
theChannel.close();
}
return true;
} else {
aOutStream.write(theDataBuffer);
// sends an acknowledgment to the server using TFTP packet
// block number
theChannel
.send(sendAckPacket((((theBuffer[2] & 0xff) << 8) | (theBuffer[3] & 0xff))),
theSocketAddress);
return false;
}
} else if (Integer.valueOf(theBuffer[1]) == Constants.OP_ERROR) {
System.out.println("File : " + aFileName + " not found ");
handleError(theReceivedBuffer);
}
return false;
}
/**
* This method handles the error packet received from Server.
*
* #param aBuffer
*/
private void handleError(ByteBuffer aBuffer) {
// Error packet
new TFTPErrorPacket(aBuffer);
}
}
Is it possible to download multiple files in parallel using java.nio by not creating a thread per file download? If yes can anybody suggest a solution to proceed further.
I would provide an approach to achieve what you are aiming for :
Let L the list of files to be downloaded.
Create a Map M which will hold the mapping of File name to be downloaded and the corresponding Selector instance.
For each file F in L
Get Selector SK from M corresponding to F
Process the state of the Selector by checking for any of the events being ready.
If processing is complete then set the Selector corresponding to F as null. This will help in identifying files
whose
processing is completed.Alternatively, you can remove F from
L; so that the next time you are looping you only process files that are not yet completely downloaded.
The above being said, I am curious to understand why you would want to attempt such a feat? If the thought process behind this requirement is to reduce the number of threads to 1 then it is not correct. Remember, you would end up really taxing the single thread running and for sure your throughput would not necessarily be optimal since the single thread would be dealing with both network as well as disk I/O. Also, consider the case of encountering an exception while writing one of the several files to the disk - you would end up aborting the transfer for all the files; something I am sure you do not want.
A better and more scalable approach would be to poll selectors on a single thread, but hand off any I/O activity to a worker thread. A better approach still would be to read the techniques presented in Doug Lea's paper and implement them. In fact Netty library already implements this pattern and is widely used in production.
I have a menu-driven program allows the user to add, remove and display the name of a person in a queue.
My program compiles and runs perfectly for me.
However, when my instructor tested it, he stated it won't compile and receives this error?:
QueueProgram.java:24: illegal start of type
MyQueue<String> queue= new MyQueue<>(15);
^
1 error
My code:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import javax.swing.JOptionPane;
public class QueueProgram {
/**
* Driver code to test class
*
* #param arguments
* Commandline arguments not used
* #throws IOException
*/
public static void main(String[] arguments) throws IOException {
//Queue Object
MyQueue<String> queue= new MyQueue<>(15);
String name;
//reading file
read(queue,arguments[0]);
String[] array = { "Offer Person", "Poll Person", "Peek person",
"Display Queue", "Exit Program"};
int choice = 0;
// display loop
while (choice != array.length-1) {
choice = JOptionPane.showOptionDialog(null, // put in center of screen
"Press a Button", // message to user
"Queue(Line) of People", // title of window
JOptionPane.YES_NO_CANCEL_OPTION, // type of option
JOptionPane.QUESTION_MESSAGE, // type of message
null, // icon
array, // array of strings
array[array.length - 1]); // default choice (last one)
if(choice==0){
//inserting the new name in queue
name=JOptionPane.showInputDialog(null,"Enter Person's name","Input");
queue.offer(name);
}
else if(choice==1){
//Display and remove the name which is at front of line
JOptionPane.showMessageDialog(null, queue.poll() + " is next in line");
}
else if(choice==2){
//Display name which is at front of line
JOptionPane.showMessageDialog(null, queue.peek() + " is front of the line");
}
else if(choice==3){
//Dispay all the list
JOptionPane.showMessageDialog(null, queue.toString());
}
//JOptionPane.showMessageDialog(null, "Your pressed button #" + choice);
}
//calling writing function
write(queue, arguments[1]);
}// end of main()
/**
* Reads a file
* #param queue
* #param file_name name of file
*/
public static void read(QueueInterface<String> queue, String file_name) throws IOException{
String name;
//creating a buffer reader to read
BufferedReader br= new BufferedReader(new FileReader(file_name));
while((name=br.readLine()) != null){
//putting in the queue
queue.offer(name);
}
//closing buffer reader
br.close();
}
/**
* Writes to file
* #param queue QueueInterface methods
* #param file_name name of file
*/
public static void write(QueueInterface<String> queue, String file_name) throws IOException{
String name;
//creating a buffer writer to write
BufferedWriter bw= new BufferedWriter(new FileWriter(file_name));
while((name=queue.poll()) != null){
//writin in file
bw.write(name);
bw.newLine();
}
//closing buffer
bw.close();
}
}// end of class
class MyQueue<T> extends ArrayQueue<T>{
/**
* Constructor
*
* #param max is the greatest number of elements in the queue
*/
public MyQueue(int max) {
super(max);
}
/**
* Returns a string representation of the object
*
* #return a name on different lines
*/
public String toString() {
// create a variable
String element = "";
int count=frontIndex;
// check to see if not empty
if (!this.empty()) {
// get the address of front element
while(count<=endIndex){
element = element +(String) array[count]+"\n";
count++;
}
}
// return either null or element
return element;
}
}
Any idea what could cause this error?
The diamond operator (<>) was introduced in Java 7 only. You should specify the type argument (new MyQueue<String>(15)) explicitly and your instructor will be able to compile it.
You can find the explanation here - Java SE 7 Features and Enhancements - Type Inference for Generic Instance Creation.
I'm not a very experienced Java programmer, so forgive me if this is a bit of a newbie question.
I'm designing a system that consists broadly of 3 modules. A client, a server and an application. The idea is the client sends a message to the server. The server triggers a use case in the application. The result of the use case is returned to the server, and the server sends the results to the client. I opted for this architecture because I'm expecting to need to support multiple clients at once, I want to be able to reuse the server module in other applications, I want to keep the code responsible for managing client connections as uncoupled from the code that implements the domain logic as possible, and because of the opportunity to learn some more advanced java.
I'm planning to tie the various modules together with queues. The client is simple enough. Issue a message and block until a response arrives (it may be oversimplifying but it's a reasonable model for now). The application is equally not a problem. It blocks on its input queue, executes a use case when it receives a valid message and pushes the results to an output queue. Having multiple clients makes things a bit more tricky but still within my grasp with my experience level. The server maintains threads for every open connection, and the server outbound/application inbound queue is synchronised, so if 2 messages arrive at once the second thread will just have to wait a moment for the first thread to deliver its payload into the queue.
The problem is the part in the middle, the server, which will have to block on two independent things. The server is watching both the client, and the application's output queue (which serves as an input queue for the server). The server needs to block until either a message comes in from the client (which it then forwards to the application), or until the application completes a task and pushes the results into the application outbound/server inbound queue.
As far as I can tell, Java can only block on one thing.
Is it possible to have the server block until either the client sends a message or the server inbound queue ceases to be empty?
UPDATE:
I've had a bit of time to work on this, and have managed to pare the problem down to the bare minimum that illustrates the problem. There's a somewhat bulky code dump to follow, even with the trimming, so apologies for that. I'll try to break it up as much as possible.
This is the code for the Server:
public class Server implements Runnable {
private int listenPort = 0;
private ServerSocket serverSocket = null;
private BlockingQueue<Message> upstreamMessaes = null;
private BlockingQueue<Message> downstreamMessages = null;
private Map<Integer, Session> sessions = new ConcurrentHashMap ();
private AtomicInteger lastId = new AtomicInteger ();
/**
* Start listening for clients to process
*
* #throws IOException
*/
#Override
public void run () {
int newSessionId;
Session newSession;
Thread newThread;
System.out.println (this.getClass () + " running");
// Client listen loop
while (true) {
newSessionId = this.lastId.incrementAndGet ();
try {
newSession = new Session (this, newSessionId);
newThread = new Thread (newSession);
newThread.setName ("ServerSession_" + newSessionId);
this.sessions.put (newSessionId, newSession);
newThread.start ();
} catch (IOException ex) {
Logger.getLogger (Server.class.getName ()).log (Level.SEVERE, null, ex);
}
}
}
/**
* Accept a connection from a new client
*
* #return The accepted Socket
* #throws IOException
*/
public Socket accept () throws IOException {
return this.getSocket().accept ();
}
/**
* Delete the specified Session
*
* #param sessionId ID of the Session to remove
*/
public void deleteSession (int sessionId) {
this.sessions.remove (sessionId);
}
/**
* Forward an incoming message from the Client to the application
*
* #param msg
* #return
* #throws InterruptedException
*/
public Server messageFromClient (Message msg) throws InterruptedException {
this.upstreamMessaes.put (msg);
return this;
}
/**
* Set the port to listen to
*
* We can only use ports in the range 1024-65535 (ports below 1024 are
* reserved for common protocols such as HTTP and ports above 65535 don't
* exist)
*
* #param listenPort
* #return Returns itself so further methods can be called
* #throws IllegalArgumentException
*/
public final Server setListenPort (int listenPort) throws IllegalArgumentException {
if ((listenPort > 1023) && (listenPort <= 65535)) {
this.listenPort = listenPort;
} else {
throw new IllegalArgumentException ("Port number " + listenPort + " not valid");
}
return this;
}
/**
* Get the server socket, initialize it if it isn't already started.
*
* #return The object's ServerSocket
* #throws IOException
*/
private ServerSocket getSocket () throws IOException {
if (null == this.serverSocket) {
this.serverSocket = new ServerSocket (this.listenPort);
}
return this.serverSocket;
}
/**
* Instantiate the server
*
* #param listenPort
* #throws IllegalArgumentException
*/
public Server ( int listenPort,
BlockingQueue<Message> incomingMessages,
BlockingQueue<Message> outgoingMessages) throws IllegalArgumentException {
this.setListenPort (listenPort);
this.upstreamMessaes = incomingMessages;
this.downstreamMessages = outgoingMessages;
System.out.println (this.getClass () + " created");
System.out.println ("Listening on port " + listenPort);
}
}
I believe the following method belongs in the Server but is currently commented out.
/**
* Notify a Session of a message for it
*
* #param sessionMessage
*/
public void notifySession () throws InterruptedException, IOException {
Message sessionMessage = this.downstreamMessages.take ();
Session targetSession = this.sessions.get (sessionMessage.getClientID ());
targetSession.waitForServer (sessionMessage);
}
This is my Session class
public class Session implements Runnable {
private Socket clientSocket = null;
private OutputStreamWriter streamWriter = null;
private StringBuffer outputBuffer = null;
private Server server = null;
private int sessionId = 0;
/**
* Session main loop
*/
#Override
public void run () {
StringBuffer inputBuffer = new StringBuffer ();
BufferedReader inReader;
try {
// Connect message
this.sendMessageToClient ("Hello, you are client " + this.getId ());
inReader = new BufferedReader (new InputStreamReader (this.clientSocket.getInputStream (), "UTF8"));
do {
// Parse whatever was in the input buffer
inputBuffer.delete (0, inputBuffer.length ());
inputBuffer.append (inReader.readLine ());
System.out.println ("Input message was: " + inputBuffer);
this.server.messageFromClient (new Message (this.sessionId, inputBuffer.toString ()));
} while (!"QUIT".equals (inputBuffer.toString ()));
// Disconnect message
this.sendMessageToClient ("Goodbye, client " + this.getId ());
} catch (IOException | InterruptedException e) {
Logger.getLogger (Session.class.getName ()).log (Level.SEVERE, null, e);
} finally {
this.terminate ();
this.server.deleteSession (this.getId ());
}
}
/**
*
* #param msg
* #return
* #throws IOException
*/
public Session waitForServer (Message msg) throws IOException {
// Generate a response for the input
String output = this.buildResponse (msg.getPayload ()).toString ();
System.out.println ("Output message will be: " + output);
// Output to client
this.sendMessageToClient (output);
return this;
}
/**
*
* #param request
* #return
*/
private StringBuffer buildResponse (CharSequence request) {
StringBuffer ob = this.outputBuffer;
ob.delete (0, this.outputBuffer.length ());
ob.append ("Server repsonded at ")
.append (new java.util.Date ().toString () )
.append (" (You said '" )
.append (request)
.append ("')");
return this.outputBuffer;
}
/**
* Send the given message to the client
*
* #param message
* #throws IOException
*/
private void sendMessageToClient (CharSequence message) throws IOException {
// Output to client
OutputStreamWriter osw = this.getStreamWriter ();
osw.write ((String) message);
osw.write ("\r\n");
osw.flush ();
}
/**
* Get an output stream writer, initialize it if it's not active
*
* #return A configured OutputStreamWriter object
* #throws IOException
*/
private OutputStreamWriter getStreamWriter () throws IOException {
if (null == this.streamWriter) {
BufferedOutputStream os = new BufferedOutputStream (this.clientSocket.getOutputStream ());
this.streamWriter = new OutputStreamWriter (os, "UTF8");
}
return this.streamWriter;
}
/**
* Terminate the client connection
*/
private void terminate () {
try {
this.streamWriter = null;
this.clientSocket.close ();
} catch (IOException e) {
Logger.getLogger (Session.class.getName ()).log (Level.SEVERE, null, e);
}
}
/**
* Get this Session's ID
*
* #return The ID of this session
*/
public int getId () {
return this.sessionId;
}
/**
* Session constructor
*
* #param owner The Server object that owns this session
* #param sessionId The unique ID this session will be given
* #throws IOException
*/
public Session (Server owner, int sessionId) throws IOException {
System.out.println ("Class " + this.getClass () + " created");
this.server = owner;
this.sessionId = sessionId;
this.clientSocket = this.server.accept ();
System.out.println ("Session ID is " + this.sessionId);
}
}
The test application is fairly basic, it just echoes a modified version of the original request message back. The real application will do work on receipt of a message and returning a meaningful response to the Server.
public class TestApp implements Runnable {
private BlockingQueue <Message> inputMessages, outputMessages;
#Override
public void run () {
Message lastMessage;
StringBuilder returnMessage = new StringBuilder ();
while (true) {
try {
lastMessage = this.inputMessages.take ();
// Construct a response
returnMessage.delete (0, returnMessage.length ());
returnMessage.append ("Server repsonded at ")
.append (new java.util.Date ().toString () )
.append (" (You said '" )
.append (lastMessage.getPayload ())
.append ("')");
// Pretend we're doing some work that takes a while
Thread.sleep (1000);
this.outputMessages.put (new Message (lastMessage.getClientID (), lastMessage.toString ()));
} catch (InterruptedException ex) {
Logger.getLogger (TestApp.class.getName ()).log (Level.SEVERE, null, ex);
}
}
}
/**
* Initialize the application
*
* #param inputMessages Where input messages come from
* #param outputMessages Where output messages go to
*/
public TestApp (BlockingQueue<Message> inputMessages, BlockingQueue<Message> outputMessages) {
this.inputMessages = inputMessages;
this.outputMessages = outputMessages;
System.out.println (this.getClass () + " created");
}
}
The Message class is extremely simple and just consists of an originating client ID and a payload string, so I've left it out.
Finally the main class looks like this.
public class Runner {
/**
*
* #param args The first argument is the port to listen on.
* #throws Exception
*/
public static void main (String[] args) throws Exception {
BlockingQueue<Message> clientBuffer = new LinkedBlockingQueue ();
BlockingQueue<Message> appBuffer = new LinkedBlockingQueue ();
TestApp appInstance = new TestApp (clientBuffer, appBuffer);
Server serverInstance = new Server (Integer.parseInt (args [0]), clientBuffer, appBuffer);
Thread appThread = new Thread (appInstance);
Thread serverThread = new Thread (serverInstance);
appThread.setName("Application");
serverThread.setName ("Server");
appThread.start ();
serverThread.start ();
appThread.join ();
serverThread.join ();
System.exit (0);
}
}
While the real application will be more complex the TestApp illustrates the basic pattern of use. It blocks on its input queue until there's something there, processes it, then pushes the result onto its output queue.
Session classes manage a live connection between a particular client and the server. It takes input from the client and converts it to Message objects, and it takes Message objects from the Server and converts them to output to send to the client.
The Server listens for new incoming connections and sets up a Session object for each incoming connection it has. When a Session passes it a Message, it puts it into its upstream queue for the application to deal with.
The difficulty I'm having is getting return messages to travel back down from the TestApp to the various clients. When a message from a client comes in, the Session generates a Message and sends it to the Server, which then puts it into its upstream queue, which is also the input queue for the TestApp. In response, the TestApp generates a response Message and puts it into the output queue, which is also the downstream queue for the Server.
This means that Sessions need to wait for two unrelated events. They should block until
Input arrives from the client (the BufferedReader on the client socket has input to process),
OR a message is sent to it by the Server (the server calls the WaitForServer () method on the session)
As for the Server itself, it also has to wait for two unrelated events.
a Session calls messageFromClient() with a message to pass to the TestApp,
OR the TestApp pushes a message onto the output/downstream queue to be passed onto a Session.
What on the face of it looked like a simple task to achieve is proving a lot more difficult than I first imagined. I expect I'm overlooking something obvious, as I'm still quite new to concurrent programming, but if you can help point out where I'm going wrong I'd appreciate instruction.
Because your implementation is using methods to pass data between client-session-server, you've actually already solved your immediate problem. However, this may not have been your intention. Here's what's happening:
Session's run method is running in its own thread, blocking on the socket. When the server calls waitForServer, this method immediately executes in the server's thread - in Java, if a thread calls a method then that method executes in that thread, and so in this case the Session did not need to unblock. In order to create the problem you are trying to solve, you would need to remove the waitForServer method and replace it with a BlockingQueue messagesFromServer queue - then the Server would place messages in this queue and Session would would need to block on it, resulting in Session needing to block on two different objects (the socket and the queue).
Assuming that you switch to the implementation where the Session will need to block on two objects, I think you can solve this with a hybrid of the two approaches I described in the comments:
Each Session's socket will need a thread to block on it - I don't see any way around this, unless you're willing to replace this with a fixed thread pool (say, 4 threads) that poll the sockets and sleep for a few dozen milliseconds if there's nothing to read from them.
You can manage all Server -> Sessions traffic with a single queue and a single thread that blocks on it - the Server includes the Session "address" in its payload so that the thread blocking on it knows what to do with the message. If you find that this doesn't scale when you have a lot of sessions, then you can always increase the thread/queue count, e.g. with 32 sessions you can have 4 threads/queues, 8 sessions per thread/queue.
I may have misunderstood but it seems that where you have the code "listening" for a message, you should be able to use a simple OR statement to solve this.
One other thing that might be useful is to add a unique id to every client so you can tell which client the message is intended for.
I'm trying to figure out how to continuously read a file and once there is a new line added, output the line. I'm doing this using a sleep thread however it just seems to blow through the whole file and exit the program.
Any suggestions what I'm doing wrong?
Here is my code:
import java.io.*;
import java.lang.*;
import java.util.*;
class jtail {
public static void main (String args[])
throws InterruptedException, IOException{
BufferedReader br = new BufferedReader(
new FileReader("\\\\server01\\data\\CommissionPlanLog.txt"));
String line = null;
while (br.nextLine ) {
line = br.readLine();
if (line == null) {
//wait until there is more of the file for us to read
Thread.sleep(1000);
}
else {
System.out.println(line);
}
}
} //end main
} //end class jtail
thanks in advance
UPDATE: I've since changed the line "while (br.nextLine ) {" to just "while (TRUE) {"
This in somewhat old, but I have used the mechanism and it works pretty well.
edit: link no longer works, but I found it in the internet archive
https://web.archive.org/web/20160510001134/http://www.informit.com/guides/content.aspx?g=java&seqNum=226
The trick is to use a java.io.RandomAccessFile, and periodically check if the file length is greater that your current file position. If it is, then you read the data. When you hit the length, you wait. wash, rinse, repeat.
I copied the code, just in case that new link stops working
package com.javasrc.tuning.agent.logfile;
import java.io.*;
import java.util.*;
/**
* A log file tailer is designed to monitor a log file and send notifications
* when new lines are added to the log file. This class has a notification
* strategy similar to a SAX parser: implement the LogFileTailerListener interface,
* create a LogFileTailer to tail your log file, add yourself as a listener, and
* start the LogFileTailer. It is your job to interpret the results, build meaningful
* sets of data, etc. This tailer simply fires notifications containing new log file lines,
* one at a time.
*/
public class LogFileTailer extends Thread
{
/**
* How frequently to check for file changes; defaults to 5 seconds
*/
private long sampleInterval = 5000;
/**
* The log file to tail
*/
private File logfile;
/**
* Defines whether the log file tailer should include the entire contents
* of the exising log file or tail from the end of the file when the tailer starts
*/
private boolean startAtBeginning = false;
/**
* Is the tailer currently tailing?
*/
private boolean tailing = false;
/**
* Set of listeners
*/
private Set listeners = new HashSet();
/**
* Creates a new log file tailer that tails an existing file and checks the file for
* updates every 5000ms
*/
public LogFileTailer( File file )
{
this.logfile = file;
}
/**
* Creates a new log file tailer
*
* #param file The file to tail
* #param sampleInterval How often to check for updates to the log file (default = 5000ms)
* #param startAtBeginning Should the tailer simply tail or should it process the entire
* file and continue tailing (true) or simply start tailing from the
* end of the file
*/
public LogFileTailer( File file, long sampleInterval, boolean startAtBeginning )
{
this.logfile = file;
this.sampleInterval = sampleInterval;
}
public void addLogFileTailerListener( LogFileTailerListener l )
{
this.listeners.add( l );
}
public void removeLogFileTailerListener( LogFileTailerListener l )
{
this.listeners.remove( l );
}
protected void fireNewLogFileLine( String line )
{
for( Iterator i=this.listeners.iterator(); i.hasNext(); )
{
LogFileTailerListener l = ( LogFileTailerListener )i.next();
l.newLogFileLine( line );
}
}
public void stopTailing()
{
this.tailing = false;
}
public void run()
{
// The file pointer keeps track of where we are in the file
long filePointer = 0;
// Determine start point
if( this.startAtBeginning )
{
filePointer = 0;
}
else
{
filePointer = this.logfile.length();
}
try
{
// Start tailing
this.tailing = true;
RandomAccessFile file = new RandomAccessFile( logfile, "r" );
while( this.tailing )
{
try
{
// Compare the length of the file to the file pointer
long fileLength = this.logfile.length();
if( fileLength < filePointer )
{
// Log file must have been rotated or deleted;
// reopen the file and reset the file pointer
file = new RandomAccessFile( logfile, "r" );
filePointer = 0;
}
if( fileLength > filePointer )
{
// There is data to read
file.seek( filePointer );
String line = file.readLine();
while( line != null )
{
this.fireNewLogFileLine( line );
line = file.readLine();
}
filePointer = file.getFilePointer();
}
// Sleep for the specified interval
sleep( this.sampleInterval );
}
catch( Exception e )
{
}
}
// Close the file that we are tailing
file.close();
}
catch( Exception e )
{
e.printStackTrace();
}
}
}
If you're planning to implement this on a reasonable sized application where multiple objects might be interested in processing the new lines coming to the file, you might want to consider the Observer pattern.
The object reading from the file will notify each object subscribed to it as soon as a line has been processed.
This will allow you to keep logic well separated on the class where it's needed.
Also consider org.apache.commons.io.input.Tailer if you do not have the requirement of writing this from scratch.
The way your code is written now, you will not go through your while loop when your 'line==null' because you are checking to see that it has a next line before you even get into the loop.
Instead, try doing a while(true){ } loop. That way, you will always be looping through it, catching your pause cases, until you hit a condition that would cause the program to end.