1

I'm writing a application that has a connection to a measuring device. This device can be connected by serial and networkconnection. The serial side I'm done with and catches the data that is send by using a SerialPortEvent.

Now I'm trying to accomplish the same thing with a socket. I have the connection working, and I can send/and receive data from the device. Problem with this is that I force a Thread.sleep to wait for all data to be ready. But now I want to automatically catch the receiving data like it did with the SerialPortEvent.

This is a 2 part question:

  1. Is there a similar Event for a socket? Or is a custom solution preferable in this situation? If so, please add explanation.
  2. How to accomplish DATA_AVAILABLE for a socket

Below here is a snippet of the code (only the neccesary catching part) for the SerialEventPort, as a reference to what I also want to accomplish with a socket:

@Override
public void serialEvent(SerialPortEvent event)
{

if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE)
{
  try
  {
    int available = inputStream.available();
    byte[] readBuffer = new byte[available];

    if (available > 0)
    {
      inputStream.read(readBuffer);
    }

  }

}

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Skillcoil
  • 184
  • 2
  • 16

1 Answers1

1

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)
Community
  • 1
  • 1
linski
  • 5,046
  • 3
  • 22
  • 35