In this SO answer Sorrow states the following:
I recommend using java.nio.channels.SocketChannel connected with Selector and SelectionKey. This solution is somewhat event-based, but is more complicated than just plain sockets.
If you decide for that solution you will find the code examples in the linked answer.
But, if you are talking about java.net.Socket
then, no, there are no events. I like JTeagle's answer on a similar question:
This is often done by spawning a separate thread for the client that continuously makes blocking calls to read() from the stream - that way, as soon as data becomes available the read() call unblocks and can act on what it received ('the event fires'), then it goes back to blocking waiting for the next event.
And in my experience also, that's mostly how sockets are handled in Java. I wrote an implementation of event based socket. Since reading is blockable, a thread is most probably needed not to block your main program:
public class ObservableSocket extends Thread {
private final Socket socket;
private final ArrayList<ObservableSocketListener> listeners;
private volatile boolean isReading;
private int BUFFER_SIZE = 1024;
public ObservableSocket(String host, int port) throws UnknownHostException, IOException {
this.socket = new Socket(host, port);
this.listeners = new ArrayList<ObservableSocketListener>(1);
isReading = true;
this.start();
}
public void addListener(ObservableSocketListener l) {
if (!listeners.contains(l)) {
listeners.add(l);
}
}
public void removeListener(ObservableSocketListener l) {
if (!listeners.contains(l)) {
listeners.remove(l);
}
}
public void die() {
isReading = false;
try {
this.join();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
public void write(byte[] data) throws IOException {
socket.getOutputStream().write(data);
socket.getOutputStream().flush();
}
private byte[] getData(byte[] buffer, int red) {
byte[] redData = new byte[red];
System.arraycopy(buffer, 0, redData, 0, red);
return redData;
}
@Override
public void run() {
byte[] buffer = new byte[BUFFER_SIZE];
int red;
ObservableSocketEvent event;
try {
while (isReading && (red = socket.getInputStream().read(buffer)) > -1) {
event = new ObservableSocketEvent(this, getData(buffer, red));
for (ObservableSocketListener l : listeners) {
l.dataAvailable(event);
}
}
}
catch (Exception exception) {
event = new ObservableSocketEvent(this, exception);
for (ObservableSocketListener l : listeners) {
l.errorOccured(event);
}
}
finally {
if (socket != null) {
try {
socket.close();
for (ObservableSocketListener l : listeners) {
l.closed(new ObservableSocketEvent(this));
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
}
This is the listener class you'll need to implement:
public interface ObservableSocketListener extends EventListener {
public void dataAvailable(ObservableSocketEvent event);
public void errorOccured(ObservableSocketEvent event);
public void closed(ObservableSocketEvent event);
}
And the event class:
public class ObservableSocketEvent extends EventObject {
private final byte[] data;
private final Exception exception;
public ObservableSocketEvent(Object source) {
super(source);
this.data = null;
this.exception = null;
}
public ObservableSocketEvent(Object source, byte[] data) {
super(source);
this.data = data;
this.exception = null;
}
public ObservableSocketEvent(Object source, Exception exception) {
super(source);
this.data = null;
this.exception = exception;
}
public byte[] getData() {
return data;
}
public Exception getException() {
return exception;
}
}
I made a server generating some random data for testing this code, this is how I used it form my client's class main method:
ObservableSocket observableSocket = new ObservableSocket("localhost", 3339);
observableSocket.addListener(new ObservableSocketListener() {
@Override
public void dataAvailable(ObservableSocketEvent event) {
System.out.println("data received: "+new String(event.getData()));
}
@Override
public void closed(ObservableSocketEvent event) {
System.out.println("closing socket");
}
@Override
public void errorOccured(ObservableSocketEvent event) {
System.out.println("error occured");
event.getException().printStackTrace();
}
});
Thread.currentThread().sleep(10000);
observableSocket.die();
And it outputs:
data received: data 0
data received: data 1
data received: data 2
data received: data 3
data received: data 4
closing socket // thread is alive here
BUILD SUCCESSFUL (total time: 10 seconds) // thread dies here
In terms of my test, the sleep
in client is needed because the die
method:
- exits the reading loop (via a flag set to false)
- and waits for the thread to die (Thread.join)
Without the sleep, the test client finishes immediatley (the die method works). Without the die method, the ObservableSocket thread lives after the test is over.
When using this code you should be aware of two things:
- upon instantiating the
ObservableSocket
the Socket
is immiediatly connected and a Thread
is started.
- you must call the
die
method from a thread which is not the ObservableSocket
thread (e.g. don't call that method from within that class)