1

A couple of weeks ago, I posted the following question because I had problems with reading objects from an ObjectInputStream using readObject:

Continuously read objects from an ObjectInputStream in Java

With the responds I got, I think I was able to understand what is going wrong -> I am calling readObject in a loop, even if no data has been send en therefore I receive an EOFException.

However, because I really want a mechanism where I am continuesly reading from the input stream I am looking for a solution for this problem.

I tried to use the following to create a mechanism where I only call readObject when there is data available:

if(mObjectIn.available() > 0)
    mObjectIn.readObject()

But unfornately, mObjectIn.available() always returns 0.

Can anyone get me in the good direction. Is it possible at all to implement what I want??

Community
  • 1
  • 1
Marvin Lasut
  • 21
  • 2
  • 6
  • You shouldn't really be opening a new question. It's the same question as before. You should add new information to the old question. However, I can tell you that your conclusion is wrong - it's not because no more data is being sent, it's because the client code uses `close()` to close the connection. – RealSkeptic May 28 '15 at 15:14
  • Apologies....I will keep this in mind in the future. But I don't see how the client closes the connection. I don't close it manually... – Marvin Lasut May 28 '15 at 17:42
  • Well, you haven't shared your full client code neither here nor in the original question. – RealSkeptic May 28 '15 at 17:48

2 Answers2

2

You can send an int through the ObjectOutputStream to let the other side know when you will stop sending objects.

For example:

public static void main(String[] args) {
    //SERVER
    new Thread(new Runnable() {
        @Override
        public void run() {
            try (ServerSocket ss = new ServerSocket(1234)) {
                try (Socket s = ss.accept()) {
                    try (ObjectInputStream ois = new ObjectInputStream(
                            s.getInputStream())) {
                        while (ois.readInt() != -1) {//Read objects until the other side sends -1.
                            System.out.println(ois.readObject());
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }).start();

    //CLIENT
    try (Socket s = new Socket(InetAddress.getByName("localhost"), 1234)) {
        try (ObjectOutputStream oos = new ObjectOutputStream(
                s.getOutputStream())) {
            for (int i = 0; i < 10; i++) {
                oos.writeInt(1);//Specify that you are still sending objects.
                oos.writeObject("Object" + i);
                oos.flush();
            }
            oos.writeInt(-1);//Let the other side know that you've stopped sending object.
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

}

Or you can write a null object at the end to let the other side know you won't be sending any more objects. This will work only if you are sure that none of the objects you need to send are null.

new Thread(new Runnable() {
        @Override
        public void run() {
            try (ServerSocket ss = new ServerSocket(1234)) {
                try (Socket s = ss.accept()) {
                    try (ObjectInputStream ois = new ObjectInputStream(
                            s.getInputStream())) {
                        String obj;
                        while ((obj = (String) ois.readObject()) != null) {
                            System.out.println(obj);
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }).start();

    try (Socket s = new Socket(InetAddress.getByName("localhost"), 1234)) {
        try (ObjectOutputStream oos = new ObjectOutputStream(
                s.getOutputStream())) {
            for (int i = 0; i < 10; i++) {
                oos.writeObject("Object" + i);
                oos.flush();
            }
            oos.writeObject(null);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
Titus
  • 22,031
  • 1
  • 23
  • 33
0

I have written client/server tcp code in Java before. Streams can be tricky. When you work with streams you cannot assume all the data is there when you do a read. Instead you have to pull bytes out of the stream and if some msg delimiter is reached, then you take all those acquired bytes and process them. In your case you will turn them into an object.

Below are three classes I use to extract msg's from a socket.

First the MsgExtractor that get as input bytes that would essentially come from a socket. It checks to see if a msg has arrived (via the delimiter).

import org.apache.log4j.Logger;

//~--- JDK imports ------------------------------------------------------------

import java.text.ParseException;

import java.util.Arrays;
import java.util.MissingResourceException;
import java.util.concurrent.BlockingQueue;

/**
 * This class parses the data retrieved from the TCP socket streams for new messages.  All new messages found are inserted into the shared message queue.
 *
 * @author jmartinez
 */
public class MsgExtractor {

    //////////////////////////////////////////////////////////////////////////
    // STATIC VARIBLES
    private static final Logger logger = Logger.getLogger(MsgExtractor.class);

    ///////////////////////////////////////////////////////////////////////
    // CONSTANTS
    private final int INIT_BUFFER_SIZE = 10000;

    // <buffer variables>
    private byte[] bufferedMsg  = new byte[INIT_BUFFER_SIZE];
    private int    bufferSize   = INIT_BUFFER_SIZE;
    private int    curBufferPos = 0;    // ...current position on the buffered message
    private int    curMsgSize   = 0;    // ...current amount of buffered chars

    ///////////////////////////////////////////////////////////////////////
    // VARIABLES
    private final byte[]        delimiter;
    private final int           delimiterSize;
    private final BlockingQueue msgQueue;
    private final int           maxMsgSize;

    // </>
    ////////////////////////////////////////////////////////////////////////
    // FUNCTIONS

    /**
     * Creates a new MsgExtractor.
     *
     * @param msgService
     */
    public MsgExtractor(MessageService msgService) {
        ServerProperties properties = ServerProperties.getInstance();

        if (properties == null) {
            throw new MissingResourceException("unable to obtain properties", MsgExtractor.class.getName(),
                                               "ServerProperties");
        }

        this.maxMsgSize = Integer.parseInt(properties.getProperty(ServerProperties.MAX_MESSAGE_SIZE));
        this.delimiter  = Arrays.copyOf(msgService.getMsgHandler().getMessageDelmiter(),
                                        msgService.getMsgHandler().getMessageDelmiter().length);
        this.delimiterSize = delimiter.length;
        this.msgQueue      = msgService.getSharedMsgQueue();
    }

    /**
     * Inserts new chars into the message buffer.  It then extracts any messages found in the buffer by checking for any occurrences of the message delimiter.  Extracted messages are removed from the buffer, converted to String, and inserted into the ManagedQueue.
     *
     * @param cbuf - An array containing the new chars that need to be added to the message buffer.
     * @param offset - Array offset from where on the array to start collecting the new chars.
     * @param length - The number of chars that need to be collected.
     * @throws java.lang.InterruptedException
     * @throws java.text.ParseException
     */
    public void insertNewChars(byte[] cbuf, int offset, int length) throws InterruptedException, ParseException {

        // ...check if the message buffer has enough room to add the new chars... if not, increase the buffer size.
        if (bufferSize < curMsgSize + length) {
            increaseBufferSize();
        }

        // ...add the new chars to the buffer one at a time
        for (int i = 0; i < length; i++) {
            bufferedMsg[curMsgSize++] = cbuf[i + offset];
        }

        // ...keep checking for new messages as long as they are being found
        boolean rv;

        do {
            rv = checkForNewMsg();
        } while (rv == true);

        if (curMsgSize > maxMsgSize) {
            throw new ParseException("max message size reached and still not found delimiter", curMsgSize);
        }
    }

    /**
     * Doubles the message buffer size.
     */
    private void increaseBufferSize() {
        bufferSize *= 2;

        byte[] temp = new byte[bufferSize];

        System.arraycopy(bufferedMsg, 0, temp, 0, curMsgSize);
        bufferedMsg = temp;
    }

    /**
     * Checks if the delimiter is found in the currently buffered message.
     * checkForNewMsg starts its search where it last left off at.
     *
     * Performance can be improved if this method checks for all occurrences of the message delimiter, instead of one.
     *
     * @return true if delimiter was found in buffer, else false
     */
    private boolean checkForNewMsg() throws InterruptedException {
        while (curBufferPos <= curMsgSize - delimiterSize) {
            boolean delimitterFound = true;

            for (int i = 0; i < delimiterSize; i++) {
                if (delimiter[i] != bufferedMsg[i + curBufferPos]) {
                    delimitterFound = false;
                    break;
                }
            }

            if (delimitterFound) {
                extractNewMsg(curBufferPos);

                return true;
            } else {
                curBufferPos++;
            }
        }

        return false;
    }

    /**
     * A new message is located at index = 0 through delimiterPos - 1.  the method extracts that message and inserts it into a local String array.
     *
     * Performance can be improved if this method extracted a messages for all occurrences of the message delimiter, instead of one.
     *
     * @param delimiterPos - The position where the delimiter was found.
     */
    private void extractNewMsg(int delimiterPos) throws InterruptedException {
        try {
            msgQueue.put(new String(bufferedMsg, 0, delimiterPos - 1));
        } catch (InterruptedException ie) {
            logger.error("Interrupted while putting message to ManagedQueue", ie);

            throw ie;
        } catch (Exception e) {
            logger.error("Unable to put message to ManagedQueue", e);
        }

        // <reset the message buffer and corresponding variables>
        byte[] tmpBuffer  = new byte[this.bufferSize];
        int    tmpMsgSize = 0;

        for (int i = delimiterPos + this.delimiterSize; i < curMsgSize; i++) {
            tmpBuffer[tmpMsgSize++] = bufferedMsg[i];
        }

        curBufferPos = 0;
        bufferedMsg  = tmpBuffer;
        curMsgSize   = tmpMsgSize;

        // </>
    }
}

Here is the ConnectionHandler that manages the connection and feeds the MsgExtractor with bytes:

import org.apache.log4j.Logger;

//~--- JDK imports ------------------------------------------------------------

import java.io.IOException;
import java.io.InputStream;

import java.net.Socket;

import java.text.ParseException;

import java.util.concurrent.atomic.AtomicBoolean;

/**
 * This class handles all new connections. It reads data from the socket stream
 * and sends it to a MsgExtractor for further processing.
 *
 * This class in Runnable. use the run method to start it up and interrupt it to
 * shut it down.
 *
 * @author Jose
 */
public class ConnectionHandler implements Runnable {

    //////////////////////////////////////////////////////////////////////////
    // STATIC VARIBLES
    // ...log4j's Logger is thread safe
    private static final Logger        logger    = Logger.getLogger(ConnectionHandler.class);
    private final static AtomicBoolean isRunning = new AtomicBoolean(false);

    /////////////////////////////////////////////////////////////////////////
    // Constants
    private final int    BUFFER_SIZE = 8000;
    private final byte[] rcvdChars   = new byte[BUFFER_SIZE];

    ////////////////////////////////////////////////////////////////////////
    // INSTANCE VARIABLES
    private final Socket       socket;
    private final MsgExtractor msgExtractor;

    /////////////////////////////////////////////////////////////////////////
    // FUNCTIONS

    /**
     * Creates a new ConnectionHandler.
     *
     * @param socket - The socket that this object is to read from.
     * @param msgService - The MessageService that is used to create the
     * MsgExtractor object that this object uses.
     */
    public ConnectionHandler(Socket socket, MessageService msgService) {
        this.socket = socket;
        logger.info("ConnectionHandler thread ID:" + Thread.currentThread().getId() + " instanctiated for listen port "
                    + socket.getLocalPort());
        msgExtractor = new MsgExtractor(msgService);
    }

    /**
     * Starts the ConnectionHandler. Creates an input stream from this objects
     * socket to read data from. all read data is sent to a MsgExtractor. The
     * MSgExtractor will extract messages from the read data and will add any
     * messages to this objects ManagedQueue. This method continues operating
     * till the thread is interrupted or the socket is no longer available for
     * providing input. Returns right away if validation of this object fails.
     */
    public void run() {

        // ...if validation fails, return
        if (isValid() == false) {
            return;
        }

        // ...if already running, return
        if (!isRunning.compareAndSet(false, true)) {
            logger.warn("ConnectionHandler thead ID:" + Thread.currentThread().getId()
                        + " is already running, not going to run again.");

            return;
        }

        logger.info("ConnectionHandler thead ID:" + Thread.currentThread().getId() + " is starting up.");

        // <get input reader from socket>
        InputStream inputReader;

        try {
            inputReader = socket.getInputStream();
        } catch (IOException ex) {
            logger.error("ConnectionHandler thread ID:" + Thread.currentThread().getId()
                         + ", failed to get socket input stream in ParserThread.run", ex);

            return;
        }

        // </>
        // ...bytes read from the socket
        int bytesRead;

        try {

            // ...stops when the thread is interrupted or the socket no longer provides input
            while ((socket.isInputShutdown() == false) || (Thread.interrupted() == false)) {
                try {

                    // ...get data from socket stream
                    bytesRead = inputReader.read(rcvdChars, 0, BUFFER_SIZE);
                } catch (IOException e) {    // ... catch any exception and call it a day for this thread
                    logger.error("ConnectionHandler thread ID:" + Thread.currentThread().getId()
                                 + ", encountered error reading from socket, could be a closed connection.", e);

                    break;
                }

                try {
                    msgExtractor.insertNewChars(rcvdChars, 0, bytesRead);
                } catch (ParseException pe) {
                    logger.error("ConnectionHandler thread ID:" + Thread.currentThread().getId()
                                 + ", encountered parsing error, closing connection.", pe);

                    break;
                } catch (InterruptedException ex) {
                    break;
                }
            }
        } finally {

            // ...close the socket if it is still open
            if (socket.isClosed() == false) {
                try {
                    socket.close();
                } catch (IOException ex) {
                    logger.error("ConnectionHandler thread ID:" + Thread.currentThread().getId()
                                 + ", failed to close socket.", ex);
                }
            }

            isRunning.set(false);
        }    // end of: finally

        logger.info("ConnectionHandler thead ID:" + Thread.currentThread().getId() + " is shutting down.");
    }

    /**
     * Used by the run() method to validate this object. If validation fails,
     * the run() method returns right away.
     *
     * @return - Returns true is this object is valid, else false.
     */
    private boolean isValid() {
        if (socket == null) {
            logger.error("ConnectionHandler thread ID:" + Thread.currentThread().getId()
                         + ", validation failed, Socket is null");

            return false;
        }

        return true;
    }
}

And for completion here is the MsgService that pops up in the above code:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * The Model Object, or DTO, of the CMS server.  It contains information that is needed by just about every component of the CMS server.
 *
 * There is a separate instance of this class for each messaging service that the CMS server is configured for.
 *
 * @author Jose
 */
public class MessageService {

    /**
     * the shared message queue where new messages are inserted into by the MsgExtractor, and taken by the MessageQueueWorker.
     */
    private final BlockingQueue<byte[]> sharedMsgQueue = new LinkedBlockingQueue<byte[]>();

    /**
     * the TCP listen port
     */
    private final int port;

    /**
     * the MessageHandler that will process new messages
     */
    private final MessageHandler msgHandler;

    /**
     * optional.  max number of TCP connections
     */
    private final int maxConnections;

    /**
     * optional.  max number of worker threads that will be used to process new messages
     */
    private final int maxWorkerThreads;

    /**
     * Creates new message service object.  Sets max connections and max worker threads to 0.
     *
     * @param port - TCP port this message service will listen on.
     * @param msgHandler - the MessageHandler that will be called to process messages received for this message service.
     */
    public MessageService(int port, MessageHandler msgHandler) {
        this.port             = port;
        this.msgHandler       = msgHandler.getNewInstance();
        this.maxConnections   = 0;
        this.maxWorkerThreads = 0;
    }

    /**
     * Creates new message service object.  Sets max worker threads to 0.
     *
     * @param port - TCP port this message service will listen on.
     * @param msgHandler - the MessageHandler that will be called to process messages received for this message service.
     * @param connections - max concurrent connections available for this service.
     */
    public MessageService(int port, MessageHandler msgHandler, int connections) {
        this.port             = port;
        this.msgHandler       = msgHandler.getNewInstance();
        this.maxConnections   = connections;
        this.maxWorkerThreads = 0;
    }

    /**
     * Creates new message service object.
     *
     * @param port - TCP port this message service will listen on.
     * @param msgHandler - the MessageHandler that will be called to process messages received for this message service.
     * @param connections - max concurrent connections available for this service.
     * @param workerThreads - max worker threads that will process messages for this message service.
     */
    public MessageService(int port, MessageHandler msgHandler, int connections, int workerThreads) {
        this.port             = port;
        this.msgHandler       = msgHandler.getNewInstance();
        this.maxConnections   = connections;
        this.maxWorkerThreads = workerThreads;
    }

    /**
     *
     * @return this object's MessageHandler
     */
    public MessageHandler getMsgHandler() {
        return msgHandler.getNewInstance();
    }

    /**
     *
     * @return the TCP port this MessageService will listen on
     */
    public int getPort() {
        return port;
    }

    /**
     *
     * @return the BlockingQueue used to store new messages.
     */
    public BlockingQueue<byte[]> getSharedMsgQueue() {
        return sharedMsgQueue;
    }

    /**
     *
     * @return max concurrent connections available for this service
     */
    public int getMaxConnections() {
        return maxConnections;
    }

    /**
     *
     * @return max worker threads that will process messages for this message service
     */
    public int getMaxWorkerThreads() {
        return this.maxWorkerThreads;
    }
}
Jose Martinez
  • 11,452
  • 7
  • 53
  • 68
  • the problem is.... when you use a delimiter you can forget about streaming binary data :/ bcuz you'll never be sure whether the delimiter was a delimiter _per se_ or just another piece of data.. – netikras Oct 15 '17 at 05:59