A lightweight abstract TCP/IP socket & packet library.
This project uses the following dependencies:
- JBCodec: Java Byte Codec >= v1.0
A Packet<T>
is an interface that represents a packet containing data of type T
. The generic parameter T
specifies the type of values that the packet encode and decode.
When working with packets, the T parameter allows you to define the specific type of data that the packet encapsulates. It provides a way to make the packet interface more flexible and reusable, as you can use different types for different packets.
The packet construction pattern consists of several elements that are used to construct a packet:
TYPE | LENGTH | NAME | DESCRIPTION |
---|---|---|---|
int | 4B | LENGTH | The total Packet length, excluding the 4 bytes used to store the length itself. |
int | 4B | ID | The id of the Packet. |
x | Variable | DATA | The data block(s) included in the packet. |
The interface Packet implements 2 subinterfaces: S2CPacket<T>
and C2SPacket<T>
, to separate Server→Client
and Client→Server
communication.
The S2CPacket
and C2SPacket
could be registered with the same id as long as there is an outgoing (serverside) and an ingoing (clientside) Packet that take the same type of Object as input.
The server cannot send a S2CPacket<Object[]>
with id 0, when the clients awaits a S2CPacket<String>
as id 0. However, C2SPacket<String>
(serverside) with id 0 would be a valid as long as the C2SPacket
(clientside) also awaits a <String>
with id 0.
This table represents a valid packet configuration:
(S/C : Server/Client where the packet was registered)
S/C | ID | TYPE | OBJECT |
---|---|---|---|
S | 0 | S2C | send T1 |
C | 0 | S2C | read T1 |
C | 0 | C2S | send T2 |
S | 0 | C2S | read T2 |
This table represents a wrong packet configuration:
S/C | ID | TYPE | OBJECT |
---|---|---|---|
S | 0 | S2C | send T2 |
C | 0 | S2C | read T1 |
C | 0 | C2S | send T2 |
S | 0 | C2S | read T3 |
Implementing S2CPacket
and C2SPacket
in the same subclass for the client and the server ensures that the awaited Object is the same for the two.
The construction of a data block varies depending of the generic argument passed T
, basic data blocks are defined as follows:
TYPE | LENGTH | NAME | DESCRIPTION |
---|---|---|---|
short | 2B | HEADER | The header used to decode the following data, see CodecManager. |
x | variable | DATA | The data of the block. |
This definition is valable for generic types such as: Byte, Short, Integer, Double, Float, Long and Character, because their size is known. The header can be omitted in some cases, such as in arrays or maps where multiple following elements use the same header which is specified in the parent data block
Data blocks for types with variable size such as String, Arrays, Lists and Maps have a different construction:
TYPE | LENGTH | NAME | DESCRIPTION |
---|---|---|---|
short | 2B | HEADER | The header used to decode the following data, see CodecManager. |
int | 4B | LENGTH | The length of the following data, see CodecManager. |
x | variable | DATA | The data of the block. |
Data blocks can be concatonated inside each other, for example a String[] (String array) is described this way:
TYPE | LENGTH | NAME | DESCRIPTION |
---|---|---|---|
short | 2B | HEADER | The Array header. |
int | 4B | LENGTH | The count of elements contained int the array. |
short | 2B | HEADER | The String header. |
x | variable | DATA | The data of the block. |
This specific data is constructed this way:
TYPE | LENGTH | NAME | DESCRIPTION |
---|---|---|---|
int | 4B | LENGTH | The length of the following String. |
x | LENGTH | DATA | The bytes of the String. |
Note that the Header is dropped because it is specified after the Array's length; the same decoder is used for all of the elements contained in the array.
Data Blocks generated by the CodecManager, are handed to the EncryptionManager and the CompressionManager.
These operations follow a specific order for writing the packet:
NAME | RETURNS | DESCRIPTION |
---|---|---|
new Packet<T>() |
Packet | New packet instance created. |
Packet#xWrite(x) |
Object | The write method of the packet is called. |
CodecManager#encode(Object) |
ByteBuffer | The packet's content gets encoded by the corresponding Encoder<T> . |
EncryptionManager#encoder(ByteBuffer) |
ByteBuffer | The ByteBuffer gets encrypted using the registered Encryptor . |
CompressionManager#compress(ByteBuffer) |
ByteBuffer | The ByteBuffer gets compressed using the registered Compressor . |
ADD LENGTH, ID | ByteBuffer | The length of the packet and it's ID is added. |
write(ByteBuffer) |
boolean | Writes the ByteBuffer to the OutputStream . Returns true if succeeded. |
To read an incoming packet, the same operations are done in reverse.
Data Blocks are handed to the CompressionManager and the EncryptionManager:
NAME | RETURNS | DESCRIPTION |
---|---|---|
read(ByteBuffer (4)) |
int | Reads the input for 4 bytes. This represents the LENGTH of the following packet. |
read(ByteBuffer (LENGTH)) |
int | Reads the input for LENGTH bytes. |
PARSE ID | ByteBuffer | The 4 first bytes of the data represent the ID of the packet. |
CompressionManager#decompress(ByteBuffer) |
ByteBuffer | The ByteBuffer gets decompressed using the registered Decompressor . |
EncryptionManager#decrypt(ByteBuffer) |
ByteBuffer | The ByteBuffer gets decrypted using the registered Decryptor . |
CodecManager#decode(Packet) |
ByteBuffer | The packet's content gets decoded by the corresponding Decoder<T> . |
PacketManager#packetInstance(ID) |
Packet | New packet instance is created using it's id. |
Packet#xRead(x, obj) |
void | The packet is read by the receiver. |
The EncryptionManager
class is responsible for managing the encryption and decryption of the input and output ByteBuffers
.
Encryptor getEncryptor()
,Decryptor getDecryptor()
: Getters for Encryptor/Decryptor.void setEncryptor(Encryptor e)
,void setDecryptor(Decryptor d)
: Setters for Encryptor/Decryptor.static EncryptionManager raw()
: This static factory method creates and initializes aEncryptionManager
instance withRawDecryptor
andRawEncryptor
, this does not encrypt.static EncryptionManager aes(byte[] key)
: This static factory method creates and initializes aEncryptionManager
instance using the AES symmetric key algorithm.
The CompressionManager
class is responsible for managing the compressing and decompressing the input and output ByteBuffers
.
Compressor getCompressor()
,Decompressor getDecompressor()
: Getters for Compressor/Decompressor.void setCompressor(Compressor e)
,void setDecompressor(Decompressor d)
: Setters for Compressor/Decompressor.static CompressionManager raw()
: This static factory method creates and initializes aCompressionManager
instance withRawCompressor
andRawDecompressor
, this does not compresses.
There are 2 default EventManagers:
AsyncEventManager
handles events asyncronously, in a separate thread pool.EventManager
handles events syncronously, default EventManager.
Changing the default EventManager:
The default is SyncEventManager
P4JServer.setEventManager(<consumer>)
P4JCient.setEventManager(<consumer>)
Adding an EventListener:
P4JServer.getEventManager().register(<listener>)
P4JClient.getEventManager().register(<listener>)
EventListener usage:
- The
@ListenerPriority
(optional) annotation is used to change the listener's priority (higher = executed earlier, default is 0) - The
@EventHandler
annotation is used to mark methods to handle the event specified by the first parameter. Example:
@ListenerPriority(priority = 10)
public class MyEventListener implements EventListener {
@EventHandler
public void onClientConnect(ClientConnectedEvent event) {
// do something
}
// the name doesn't matter
@EventHandler
public void kjwdsfkjhsf(P4JEvent event) {
// do something
}
}
There are multiple events (implementing P4JEvent
):
ClientConnectedEvent(P4JClientInstance, P4JServerInstance)
: When a client connects to a server.
Server side: ServerClient -> P4JServer
Client side: P4JClient -> ClientServerS2CReadPacketEvent(P4JInstance, Packet, Class<Packet>, int, Throwable, boolean)
: When a client reads an incoming packet from the server.S2CWritePacketEvent(P4JClientInstance, Packet, Class<Packet>, int, Throwable, boolean)
: When a server writes an outgoing packet to the client.C2SReadPacketEvent(P4JClientInstance, Packet, Class<Packet>, int, Throwable, boolean)
: When a server reads an incoming packet from the client.C2SWritePacketEvent(P4JClientInstance, Packet, Class<Packet>, int, Throwable, boolean)
: When a client writes an outgoing packet to the server.ClientDisconnectedEvent(ClosedChannelException, P4JClientInstance)
: When a client connection gets closed.
To compile use maven, implemented options are: test
, package
, install
& defaults