Search code examples
javanetwork-programmingnio

Java NIO - problems with receiving data using SocketChannels



I'm writing simple instant messenger using Java NIO. It's working fine exept that client doesn't get messages with contacts that have been already logged in.
This is the main server process method:

public void process() throws IOException, ClassNotFoundException{
    while (true){
        selector.select();
        Set keys = selector.selectedKeys();
        Iterator it = keys.iterator();
        while (it.hasNext()){
            SelectionKey key = (SelectionKey)it.next();
            it.remove();
            if (key.isAcceptable()){
                /*configuring incoming channel to be non-blocking 
                and adding it to the clients set*/
            }
            if (key.isReadable()) {
                SocketChannel client = (SocketChannel)key.channel();
                /*Message class contains its type, source user name and data.
                getMessage() method reads message from SocketChannel*/
                Message m = getMessage(client);    
                switch (m.getType()) {
                    case LOGIN_REQUESTED:
                        /*Accept or reject the client log in name
                        in case of accepting add user with its status
                        to Map users*/
                        break;
                    case CONTACTS_REQUESTED:
                        /*Here is the problem, client gets only one message
                        sendMessage() writes the buffer with serialized 
                        message to clients channels*/
                        for (String name : users.keySet()) {
                            sendMessage(client, MessageType.CONTACTS_REQUESTED, 
                                name, users.get(name).toString());
                        }
                        break;
                   //Other messages are processing
                }
            }
        }
    }
}

The client method, which handles incoming messages:

private void processIncomingMessages() 
    throws IOException, ClassNotFoundException {
    ByteArrayInputStream bais;
    ObjectInputStream ois;
    ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
    while (true){
        selector.select();
        Set keys = selector.selectedKeys();
        Iterator it = keys.iterator();
        while (it.hasNext()){
            SelectionKey key = (SelectionKey)it.next();
            it.remove();
            if (key.isReadable()){
                SocketChannel sc = (SocketChannel)key.channel();
                buffer.clear();
                if (sc.read(buffer) != -1){
                    buffer.flip();
                    bais = new ByteArrayInputStream(buffer.array());
                    ois = new ObjectInputStream(bais);
                    Message m = (Message)ois.readObject();
                    /*My castom event serves to notify GUI to update 
                    its contact list. In case of sending CONTACTS_REQUESTED 
                    messages, it gets only one (first?) of them*/
                    fireNetworkEvent(m);
                }
            }
        }
    }
}

Other messages, which are sent separately comes to clients without any problems. If there any questions, please ask. Any help will be appreciated


Solution

  • My guess is that in the client the...

     if (sc.read(buffer) != -1){
    

    Is not guaranteed to read your entire message, it may read nothing, some of it or multiple messages.

    Sockets are streams of bytes and if you want to read a packet (as you do) you need to packetize the stream.

    One way to do that is prepend the length of the packet, and make sure you read that entire packet before trying to process.

    Another way is to write the packet in a way that it has a unique terminator that you read until you get the terminator.