Onur Öçalan
Onur Öçalan

Reputation: 89

Java Socket InputStream reads data but return in the wrong order

I developed an application using Java socket. I am exchanging messages with this application with the help of byte arrays. I have a message named M1, 1979 bytes long. My socket buffer length is 512 bytes. I read this message in 4 parts, each with 512 bytes, but the last one is of course 443 bytes. I will name these parts like A, B, C, and D. So ABCD is a valid message of mine respectively.

I have a loop with a thread which is like below.

BlockingQueue<Chunk> queue = new LinkedBlockingQueue<>();
InputStream in = socket.getInputStream()
byte[] buffer = new byte[512];

while(true) {
  int readResult = in.read(buffer);
  if(readResult != -1) {
    byte[] arr = Arrays.copyOf(buffer, readResult);
    Chunk c = new Chunk(arr);
    queue.put(c);
  }
}
    

I'm filling the queue with the code above. When the message sending starts, I see the queue fill up in ABCD form but sometimes I put the data in the queue as a BACD. But I know that this is impossible because the TCP connection guarantees the order.

I looked at the dumps with Wireshark. This message comes correctly with a single tcp package. So there is no problem on the sender side. I am 100% sure that the message has arrived correctly but the read method does not seem to read in the correct order and this situation doesn't always happen. I could not find a valid reason for what caused this.

When I tried the same code on two different computers I noticed that the problem was in only one. The jdk versions on these computers are different. I looked at the version differences between the two jdk versions. When the Jdk version is "JDK 8u202", I am getting the situation where it works incorrectly. When I tried it with jdk 8u271, there was no problem. Maybe it is related to that but I wasn't sure. Because I have no valid evidence.

I am open to all kinds of ideas and suggestions. It's really on its way to being the most interesting problem I've ever encountered. Thank you for your help.

EDIT: I found similar question. Blocking Queue Take out of Order

EDIT: Ok, I have read all the answers given below. Thank you for providing different perspectives for me. I will try to supplement some missing information.

Actually I have 2 threads. Thread 1(SocketReader) is responsible for reading socket. It wraps the information it reads with a Chunk class and puts it on the queue in the other Thread 2. So queue is in Thread 2. Thread 2(MessageDecoder) is consuming the blocking queue. There are no threads other than these. Actually this is a simple example of a "producer consumer design patter".

And yes, other messages are sent, but other messages take up less than 512 bytes. Therefore, I can read in one go. I do not encounter any sort problem.

MessageDecoder.java

public class MessageDecoder implements Runnable{

    private BlockingQueue<Chunk> queue = new LinkedBlockingQueue<>();

    public MessageDecoder() {
    }

    public void run() {
        while(true) {
            Chunk c;
            try {
                c = queue.take();
                System.out.println(c.toString());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
           
            decodeMessageChunk(c);
        }
    }

    public void put(Chunk c) {
        try {
            queue.put(c);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

SocketReader.java

public class SocketReader implements Runnable{

    private final MessageDecoder msgDec;
    private final InputStream in;
    byte[] buffer = new byte[512];

    public SocketReader(InputStream in, MessageDecoder msgDec) {
        this.in = in;
        this.msgDec = msgDec;
    }

    public void run() {
        while(true) {
            int readResult = in.read(buffer);
            if(readResult != -1) {
                byte[] arr = Arrays.copyOf(buffer, readResult);
                Chunk c = new Chunk(arr);
                msgDec.put(c);
            }
        }
    }
}

Upvotes: 1

Views: 328

Answers (1)

aran
aran

Reputation: 11860

Even if it's a FIFO queue, the locking of the LinkedBloquingQueue is unfair, so you can't guarantee the ordering of elements. More info regarding this here

I'd suggest using an ArrayBlockingQueue instead. Like the LinkedBloquingQueue, the order is not guaranteed but offers a slightly different locking mechanism.

This class supports an optional fairness policy for ordering waiting producer and consumer threads. By default, this ordering is not guaranteed. However, a queue constructed with fairness set to true grants threads access in FIFO order. Fairness generally decreases throughput but reduces variability and avoids starvation.

In order to set fairness, you must initialize it using this constructor:

enter image description here

So, for example:

   ArrayBlockingQueue<Chunk> fairQueue = new ArrayBlockingQueue<>(1000, true);
   /*.....*/
   Chunk c = new Chunk(arr);
   fairQueue.add(c);

As the docs state, this should grant thread access in FIFO order, guaranteeing the retrievement of the elements to be consistent while avoiding possible locking robbery happening in LinkedBloquingQueue's lock mechanism.

Upvotes: 1

Related Questions