Protocol Design Basics in smart-socket: Handling Half‑Packet and Sticky‑Packet Issues
This article explains the design of the Protocol interface in the smart-socket framework, covering data integrity, half‑packet and sticky‑packet handling, performance considerations, and provides concrete IntegerProtocol and StringProtocol implementations with best‑practice guidelines.
Protocol Interface Overview
In the smart-socket framework, the Protocol<T> interface is the core component for parsing network byte streams into business messages. It defines a single method decode(ByteBuffer readBuffer, AioSession session) that receives a ByteBuffer containing raw bytes and the current session object. Returning a non‑null value indicates a complete message has been decoded; returning null signals that more data is needed.
Design Principles
Data Integrity – the protocol must reliably identify message boundaries, using fixed‑length messages, length‑field prefixes, or special delimiters.
Half‑Packet and Sticky‑Packet Handling – the design must correctly process fragmented messages (half‑packet) and combined messages (sticky‑packet).
Performance Optimization – parsing is a high‑frequency operation, so object creation and memory copying should be minimized.
Simple Protocol Example: IntegerProtocol
public class IntegerProtocol implements Protocol<Integer> {
@Override
public Integer decode(ByteBuffer data, AioSession session) {
// Check if there are enough bytes for an integer
if (data.remaining() < Integer.BYTES)
return null;
// Read and return the integer
return data.getInt();
}
}Working Process
Check that the ByteBuffer contains at least Integer.BYTES (4) bytes.
If fewer bytes are available, return null to wait for more data.
When enough bytes exist, call getInt() to read the integer and return it.
Usage Example
// Server side
MessageProcessor<Integer> serverProcessor = (session, msg) -> {
int respMsg = msg + 1;
System.out.println("Received from client: " + msg + ", response: " + respMsg);
session.writeBuffer().writeInt(respMsg);
session.writeBuffer().flush();
};
AioQuickServer<Integer> server = new AioQuickServer<>(8888, new IntegerProtocol(), serverProcessor);
// Client side
MessageProcessor<Integer> clientProcessor = (session, msg) ->
System.out.println("Received from server: " + msg);
AioQuickClient<Integer> client = new AioQuickClient<>("localhost", 8888, new IntegerProtocol(), clientProcessor);
AioSession session = client.start();
session.writeBuffer().writeInt(1);
session.writeBuffer().flush();Complex Protocol Example: StringProtocol
The string protocol handles variable‑length messages using a length‑field prefix.
Design Idea
Message format: [length (4 bytes)][string bytes] Read the length field first to determine the actual string length.
Read the specified number of bytes and convert them to a UTF‑8 string.
Core Implementation
public class StringProtocol implements Protocol<String> {
private final Map<AioSession, FixedLengthFrameDecoder> decoderMap = new ConcurrentHashMap<>();
@Override
public String decode(ByteBuffer readBuffer, AioSession session) {
FixedLengthFrameDecoder decoder = decoderMap.get(session);
if (decoder != null) {
String content = bigContent(readBuffer, decoder);
if (content != null) {
decoderMap.remove(session);
}
return content;
}
if (readBuffer.remaining() < Integer.BYTES) {
return null;
}
readBuffer.mark();
int length = readBuffer.getInt();
// Handle messages larger than the buffer capacity
if (length + Integer.BYTES > readBuffer.capacity()) {
FixedLengthFrameDecoder fixedLengthFrameDecoder = new FixedLengthFrameDecoder(length);
decoderMap.put(session, fixedLengthFrameDecoder);
return null;
}
// Half‑packet: not enough bytes for the whole message
if (length > readBuffer.remaining()) {
readBuffer.reset();
return null;
}
return convert(readBuffer, length);
}
private String convert(ByteBuffer byteBuffer, int length) {
byte[] b = new byte[length];
byteBuffer.get(b);
return new String(b, StandardCharsets.UTF_8);
}
}Half‑Packet and Sticky‑Packet Handling
Half‑packet handling: When the buffer lacks enough bytes for a full message, mark() and reset() preserve the read position and wait for more data.
Long‑message handling: For messages exceeding the buffer capacity, a FixedLengthFrameDecoder is created and stored in decoderMap to accumulate data across reads.
Sticky‑packet handling: If multiple complete messages are present, the framework repeatedly invokes decode until it returns null.
Protocol Design Best Practices
Avoid storing session‑specific state inside a Protocol implementation because a single instance is shared across sessions.
Use auxiliary decoders such as FixedLengthFrameDecoder or DelimiterFrameDecoder to simplify complex protocol implementations.
Release temporary resources (e.g., decoders created for a specific session) promptly to prevent memory leaks.
Chapter Summary
The chapter detailed the design principles and usage of the Protocol interface in smart-socket, demonstrating both a simple integer protocol and a more complex string protocol. Key points include proper handling of half‑packet and sticky‑packet scenarios, performance‑aware parsing, and best practices such as avoiding stateful protocol instances and leveraging auxiliary decoders.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Three Knives
Every line of code you contribute to open source could help make the future better.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
