https://github.com/utfunderscore/hermes
TCP communication library using kryo and netty or nio
https://github.com/utfunderscore/hermes
netty netty-client netty-server
Last synced: 21 days ago
JSON representation
TCP communication library using kryo and netty or nio
- Host: GitHub
- URL: https://github.com/utfunderscore/hermes
- Owner: utfunderscore
- Created: 2024-07-12T16:49:07.000Z (almost 2 years ago)
- Default Branch: main
- Last Pushed: 2025-08-31T16:05:39.000Z (9 months ago)
- Last Synced: 2025-08-31T17:40:01.668Z (9 months ago)
- Topics: netty, netty-client, netty-server
- Language: Java
- Homepage:
- Size: 241 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Hermes
A multi-module Java framework for packet-based communication with support for different transport implementations (Netty) and serialization codecs (Kryo).
## Overview
Hermes provides a clean abstraction for sending and receiving typed packets over network connections. It handles serialization, event dispatching, and request-response patterns out of the box.
## Modules
| Module | Description |
|--------|-------------|
| `core` | Core framework: Hermes, Packet, Event system, Response handling |
| `kryo` | Kryo-based serialization codec |
| `netty` | Netty server and client implementations |
## Key Components
### Packet
All packets implement the `Packet` interface:
```java
public interface Packet {
boolean expectsResponse();
int getId();
}
```
- `expectsResponse()` - Whether the packet expects a response from the handler
- `getId()` - Unique identifier for the packet type
### PacketCodec
Encodes packets to bytes and decodes bytes back to packets:
```java
public interface PacketCodec {
Packet> decode(byte[] data);
byte[] encode(Packet> packet);
}
```
### Hermes
The main abstract class that handles:
- Sending packets
- Registering listeners
- Handling incoming packets
- Managing responses
## Usage Examples
### 1. Creating a Custom Packet
```java
public class ChatMessagePacket implements Packet {
private final String message;
private final UUID senderId;
public ChatMessagePacket(String message, UUID senderId) {
this.message = message;
this.senderId = senderId;
}
@Override
public boolean expectsResponse() {
return true; // Server should acknowledge
}
@Override
public int getId() {
return 1;
}
public String getMessage() { return message; }
public UUID getSenderId() { return senderId; }
}
// Response is just a regular class - no need to implement Packet
public class ChatResponse {
private final boolean success;
private final String serverMessage;
public ChatResponse(boolean success, String serverMessage) {
this.success = success;
this.serverMessage = serverMessage;
}
public boolean isSuccess() { return success; }
public String getServerMessage() { return serverMessage; }
}
```
### 2. Setting Up a Server
```java
// Create Kryo codec with custom configuration
Supplier kryoSupplier = () -> {
Kryo kryo = new Kryo();
kryo.register(ChatMessagePacket.class);
kryo.register(ChatResponse.class);
return kryo;
};
PacketCodec codec = new KryoPacketCodec(kryoSupplier);
// Create and start server
NettyServerPlatform server = new NettyServerPlatform(codec);
server.start(new InetSocketAddress(8080));
// Register packet listener
server.listen(ChatMessagePacket.class, (channel, packet) -> {
System.out.println("Received: " + packet.getMessage());
return new ChatResponse(true, "Message received!");
});
```
### 3. Connecting as a Client
```java
// Create client with same codec
NettyClientPlatform client = new NettyClientPlatform(codec);
// Connect to server
client.connect(new InetSocketAddress("localhost", 8080));
// Send packet without expecting response
client.sendPacket(new ChatMessagePacket("Hello!", UUID.randomUUID()));
// Send packet with response (async)
CompletableFuture response = client.sendResponsePacket(
new ChatMessagePacket("Hello!", UUID.randomUUID()),
ChatResponse.class
);
ChatResponse result = response.get(5, TimeUnit.SECONDS);
```
### 4. Listening for Packets (Server-Side)
```java
// Using listenIgnore for packets that don't return responses
server.listenIgnore(TestPacket.class, (channel, packet) -> {
System.out.println("Received from " + channel.getId() + ": " + packet.message);
});
// Using full listener for packets that expect responses
server.listen(ChatMessagePacket.class, (channel, packet) -> {
// Process packet and return response
return new ChatResponse(true, "Processed: " + packet.getMessage());
});
```
### 5. Broadcasting to All Connected Clients
```java
// Access all registered channels from the server
for (Map.Entry entry : server.getHermesToNettyChannel().entrySet()) {
HermesChannel channel = entry.getKey();
server.sendPacket(channel, new BroadcastPacket("Hello everyone!"));
}
```
### 6. Handling Client Disconnection
```java
server.listenIgnore(ChannelClosePacket.class, (channel, packet) -> {
System.out.println("Client disconnected: " + channel.getId());
// Clean up resources associated with this client
});
```
## Common Patterns
### Request-Response Pattern
```java
// On client side - send request and wait for response
CompletableFuture future = client.sendResponsePacket(
new MyRequestPacket(data),
ServerResponse.class
);
ServerResponse response = future.get(10, TimeUnit.SECONDS);
// On server side - handle request and return response
hermes.listen(MyRequestPacket.class, (channel, packet) -> {
// Process request
return new ServerResponse(result);
});
```
### Event-Driven Architecture
```java
// Register multiple listeners for the same packet type
hermes.listen(PlayerJoinPacket.class, (channel, packet) -> {
// First listener: notify admins
notifyAdmins(packet.getPlayerName());
return null;
});
hermes.listen(PlayerJoinPacket.class, (channel, packet) -> {
// Second listener: update player list
updatePlayerList(packet.getPlayerName());
return null;
});
```
### Custom Codec Implementation
```java
public class JsonPacketCodec implements PacketCodec {
private final ObjectMapper mapper = new ObjectMapper();
@Override
public Packet> decode(byte[] data) {
return mapper.readValue(data, Packet.class);
}
@Override
public byte[] encode(Packet> packet) {
return mapper.writeValueAsBytes(packet);
}
}
```
## Dependencies
### Core Module (Minimal)
- SLF4J API
- JetBrains Annotations
### Kryo Module
- Kryo (serialization)
### Netty Module
- Netty (transport)
- Depends on: core, kryo
## Building
```bash
./gradlew build
```
## Testing
```bash
./gradlew test
```
## Thread Safety
- `PacketEventManager` uses concurrent collections for thread-safe listener registration
- `ResponseManager` uses concurrent maps for thread-safe response handling
- `NettyPlatform` uses concurrent maps for channel management
## Best Practices
1. **Register all packet types** in your Kryo configuration before use
2. **Use meaningful packet IDs** to avoid conflicts
3. **Handle timeouts** when waiting for responses
4. **Clean up resources** by calling `shutdown()` on platforms
5. **Use response packets** for bidirectional communication