com.hazelcast.nio.PacketIOHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hazelcast-all Show documentation
Show all versions of hazelcast-all Show documentation
Kubernetes Service Discovery for Hazelcast Discovery SPI
/*
* Copyright (c) 2008-2019, Hazelcast, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hazelcast.nio;
import com.hazelcast.spi.annotation.PrivateApi;
import java.nio.ByteBuffer;
import static com.hazelcast.nio.Bits.BYTE_SIZE_IN_BYTES;
import static com.hazelcast.nio.Bits.INT_SIZE_IN_BYTES;
import static com.hazelcast.nio.Bits.SHORT_SIZE_IN_BYTES;
import static com.hazelcast.nio.Packet.VERSION;
/**
* Responsible for writing or reading an Packet. Originally the logic was placed in the Packet. The problem with this approach
* is that a single Packet instance can't be shared between multiple connections and this leads to increased memory usage since
* the packet needs to be copied for every connection.
*
* The {@link PacketIOHelper} is stateful because it tracks where the packet reading from ByteBuffer or writing to ByteBuffer.
*
* A {@link PacketIOHelper} instance should only be used for reading, or only be used for writing. So if you need to read and
* write at the same time, you need to have 2 instances.
*
* A {@link PacketIOHelper} is designed to be reused.
*/
@PrivateApi
public class PacketIOHelper {
static final int HEADER_SIZE = BYTE_SIZE_IN_BYTES + SHORT_SIZE_IN_BYTES + INT_SIZE_IN_BYTES + INT_SIZE_IN_BYTES;
private int valueOffset;
private int size;
private boolean headerComplete;
private char flags;
private int partitionId;
private byte[] payload;
/**
* Writes the packet data to the supplied {@code ByteBuffer}, up to the buffer's limit. If it returns {@code false},
* it should be called again to write the remaining data.
*
* @param dst the destination byte buffer
* @return {@code true} if all the packet's data is now written out; {@code false} otherwise.
*/
public boolean writeTo(Packet packet, ByteBuffer dst) {
if (!headerComplete) {
if (dst.remaining() < HEADER_SIZE) {
return false;
}
dst.put(VERSION);
dst.putChar(packet.getFlags());
dst.putInt(packet.getPartitionId());
size = packet.totalSize();
dst.putInt(size);
headerComplete = true;
}
if (writeValue(packet, dst)) {
reset();
return true;
} else {
return false;
}
}
private boolean writeValue(Packet packet, ByteBuffer dst) {
if (size > 0) {
// the number of bytes that can be written to the bb.
int bytesWritable = dst.remaining();
// the number of bytes that need to be written.
int bytesNeeded = size - valueOffset;
int bytesWrite;
boolean done;
if (bytesWritable >= bytesNeeded) {
// All bytes for the value are available.
bytesWrite = bytesNeeded;
done = true;
} else {
// Not all bytes for the value are available. So lets write as much as is available.
bytesWrite = bytesWritable;
done = false;
}
byte[] byteArray = packet.toByteArray();
dst.put(byteArray, valueOffset, bytesWrite);
valueOffset += bytesWrite;
if (!done) {
return false;
}
}
return true;
}
/**
* Reads the packet data from the supplied {@code ByteBuffer}. The buffer may not contain the complete packet.
* If this method returns {@code false}, it should be called again to read more packet data.
*
* @param src the source byte buffer
* @return the read Packet if all the packet's data is now read; {@code null} otherwise.
*/
public Packet readFrom(ByteBuffer src) {
if (!headerComplete) {
if (src.remaining() < HEADER_SIZE) {
return null;
}
byte version = src.get();
if (VERSION != version) {
throw new IllegalArgumentException("Packet versions are not matching! Expected -> "
+ VERSION + ", Incoming -> " + version);
}
flags = src.getChar();
partitionId = src.getInt();
size = src.getInt();
headerComplete = true;
}
if (readValue(src)) {
Packet packet = new Packet(payload, partitionId).resetFlagsTo(flags);
reset();
return packet;
} else {
return null;
}
}
private void reset() {
headerComplete = false;
payload = null;
valueOffset = 0;
}
private boolean readValue(ByteBuffer src) {
if (payload == null) {
payload = new byte[size];
}
if (size > 0) {
int bytesReadable = src.remaining();
int bytesNeeded = size - valueOffset;
boolean done;
int bytesRead;
if (bytesReadable >= bytesNeeded) {
bytesRead = bytesNeeded;
done = true;
} else {
bytesRead = bytesReadable;
done = false;
}
// read the data from the byte-buffer into the bytes-array.
src.get(payload, valueOffset, bytesRead);
valueOffset += bytesRead;
if (!done) {
return false;
}
}
return true;
}
}