1

I'm implementing a client server communication via socketChannel. During load tests with larger objets I run into problems on the client side. So I've implemented a test program to verify my problem an to illustrate it here.

First a small explaination: I don't know on client side the size of the object that will be send through the socket. So I split my sending into two parts: 1.: serializition of the object into a byteBuffer on server side (in the example illustrated with the byte-array. 2.: sending the soze of the object through the socket 3.: sending the object

On client side, I first read the object size in a 4-byte ByteBuffer. As second I create a new ByteBuffer with the readed size and then read the data from the socketchannel into the Buffer.

If you have a look a the code, than you can see (In the client class), that my expectation is, that the socketChannel.read method will return the same byte count as the readed object size before.

But after increasing the sendet byte array, there will bee a lot of missmatches and zero sizes. Why this happends?? the socketchannel is nonblocking. so it should be able to read everything in the configured byte buffer. the size of the byte buffer is big enough. so why are there sometimes bytes missing?

Thank you very much! Here is the example code:

Main Class

package socketChannelMaxTest;

import java.io.IOException;

public class MaxTest {

    public static void main(String[] args) throws IOException {
        Sender.startServer();
        new Client();
    }

}

Server Sender

package socketChannelMaxTest;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class Sender {

    private SocketChannel sc;

    public Sender(SocketChannel sc) {
        this.sc = sc;
        startThreads();
    }

    public static void startServer() throws IOException {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ServerSocketChannel socket = ssc.bind(new InetSocketAddress(9999));
        new Thread(() -> {
            System.out.println("Server: Listening");
            SocketChannel sc;
            try {
                sc = socket.accept();
                sc.configureBlocking(true);
                new Sender(sc);
            } catch (IOException e) {
                e.printStackTrace();
            }

        }).start();
    }

    private void startThreads() {
        new Thread(() -> {
            System.out.println("Sender: start sending");
            ByteBuffer headerBuffer = ByteBuffer.allocateDirect(4);

            int maxBufferSize = 10*1024;
            for (int i = 1; i < maxBufferSize; i++) {
                byte[] randomByteArray = new byte[i];
                ByteBuffer dataBuffer = ByteBuffer.wrap(randomByteArray);
                int objectSize = randomByteArray.length;
                headerBuffer.putInt(objectSize);
                headerBuffer.flip();
                try {
                    sc.write(headerBuffer);
                    System.out.println("Sender: " + objectSize + " " + sc.write(dataBuffer));
                } catch (IOException e) {
                    e.printStackTrace();
                }
                headerBuffer.compact();

            }
            System.out.println("Sender: finished");
        }, "Receiver Thread").start();
    }
}

Client Receiver

package socketChannelMaxTest;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class Client {
    public Client() throws IOException {
        startThreads();
    }

    private void startThreads() throws IOException {
        System.out.println("Client: start client");
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 9999));
        socketChannel.configureBlocking(true);
        System.out.println("Client: connected");

        new Thread(() -> {
            System.out.println("Client: start listening");
            ByteBuffer headerBuffer = ByteBuffer.allocate(4);
            int readedObjectSize = 0;
            while (socketChannel.isConnected()) {
                try {
                    int read = socketChannel.read(headerBuffer);
                    headerBuffer.flip();
                    readedObjectSize = headerBuffer.getInt();
                    headerBuffer.compact();

                    ByteBuffer dataBuffer = ByteBuffer.allocateDirect(readedObjectSize);
                    int readedDataBufferSize = socketChannel.read(dataBuffer);
                    // should be 0
                    int remainginBytes = dataBuffer.remaining();
                    dataBuffer.flip();
                    System.out.println("Client:" + readedObjectSize + " " + readedDataBufferSize + " " + remainginBytes);

                    if (readedObjectSize != readedDataBufferSize)
                        System.out.println("Missmatch");

                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        }, "Receiver Thread").start();
    }

}
Jürgen
  • 146
  • 12

1 Answers1

0

There is a problem with the fact that you assume your remaining data will always be 0 at any time

// should be 0
int remainginBytes = dataBuffer.remaining();

You can not be sure that this will be the case at any time. If the data gets too big, you will need to read again from your SocketChannel to retrieve the remaining information.

You should have a loop that reads from the socketChannel until there is no remaining bytes

int read = socketChannel.read(headerBuffer);
headerBuffer.flip();
readedObjectSize = headerBuffer.getInt();
headerBuffer.compact();

int remainginBytes = -1;
int readedDataBufferSize = 0;
ByteBuffer dataBuffer = ByteBuffer.allocateDirect(readedObjectSize);

while(remainginBytes != 0){

    readedDataBufferSize = socketChannel.read(dataBuffer);
    remainginBytes = dataBuffer.remaining();
}

dataBuffer.flip();

System.out.println("Client:" + readedObjectSize + " " + readedDataBufferSize + " " + remainginBytes);
if (readedObjectSize != readedDataBufferSize)
   System.out.println("Missmatch");

Remember that configuring a socket to non-blocking mode will not ensure you that it will not return before all the data is retrieved

Feel free to comment if this still does not work for you !

Arthur Attout
  • 2,701
  • 2
  • 26
  • 49
  • You are right. this works. My expectation was, that socketChannel reads until the buffer is full or the data was transfered. Can you tell me where I can read something about this behaviour of socket channel read? Thanks – Jürgen Mar 14 '18 at 07:44
  • [The javadoc for the read method](https://docs.oracle.com/javase/7/docs/api/java/nio/channels/SocketChannel.html#readjava.nio.ByteBuffer) states "A read operation might not fill the buffer, and in fact it might not read any bytes at all. Whether or not it does so depends upon the nature and state of the channel" – Arthur Attout Mar 14 '18 at 08:22
  • can be simplified by removing the 'remainingBytes' and 'readedDataBufferSize' variables and just testing `dataBuffer.remaining() != 0` – pstanton Oct 15 '18 at 06:53