Skip to content

sk8erboi17/fast-socket

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FastSocket

High-Performance Socket Library for Java 25

FastSocket is a lightweight networking library built on Java 25's Panama FFM API. It provides a simple, fluent API for building scalable client-server applications with built-in support for unicast, multicast, and broadcast messaging.

Table of Contents


Installation

Requirements

  • Java 25 or later with preview features enabled
  • Maven 3.8+

Maven Dependency

<dependency>
    <groupId>io.github.sk8erboi17</groupId>
    <artifactId>fast-socket-api</artifactId>
    <version>2.0.0</version>
</dependency>

Running with Preview Features

java --enable-preview -jar your-application.jar

Quick Start

Minimal Echo Server

import io.github.sk8erboi17.api.FastSocket;

public class EchoServer {
    public static void main(String[] args) {
        FastSocket.create()
            .onMessage((conn, msg) -> conn.send("Echo: " + msg))
            .start(8080);
    }
}

Minimal Client

import io.github.sk8erboi17.api.FastSocketClient;

public class EchoClient {
    public static void main(String[] args) throws Exception {
        var client = FastSocketClient.create()
            .onMessage(msg -> System.out.println("Received: " + msg))
            .connect("localhost", 8080);

        client.send("Hello!");
        Thread.sleep(1000);
        client.close();
    }
}

Architecture

FastSocket uses a multi-reactor pattern for high concurrency:

                    ┌─────────────────┐
                    │  AcceptorLoop   │  ← Single thread accepts connections
                    │ (ServerSocket)  │
                    └────────┬────────┘
                             │ round-robin distribution
           ┌─────────────────┼─────────────────┐
           ▼                 ▼                 ▼
   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐
   │  EventLoop-0 │  │  EventLoop-1 │  │  EventLoop-N │  ← Worker threads
   │  (Selector)  │  │  (Selector)  │  │  (Selector)  │    handle I/O
   └──────────────┘  └──────────────┘  └──────────────┘
           │                 │                 │
           ▼                 ▼                 ▼
   ┌──────────────────────────────────────────────────┐
   │            NativeBufferPool (Panama FFM)         │  ← Off-heap memory
   │         Thread-local caches + lock-free queues   │
   └──────────────────────────────────────────────────┘

Key components:

Component Description
AcceptorLoop Single thread that accepts incoming connections and distributes them to workers
EventLoop Worker thread with its own Selector for non-blocking I/O
NativeBufferPool Off-heap memory pool using Panama FFM's MemorySegment
ConnectionRegistry Tracks all connections for unicast/multicast/broadcast
ConnectionGroup Named group of connections for multicast messaging

Server API

Creating a Server

There are two ways to create a server:

1. With Default Configuration

FastSocket server = FastSocket.create()
    .onMessage((conn, msg) -> conn.send(msg))
    .start(8080);

2. With Custom Configuration

FastSocket server = FastSocket.create(config -> config
    .workerThreads(8)
    .bufferPoolSize(256)
    .maxFrameSize(1024 * 1024)
    .tcpNoDelay(true)
    .keepAlive(true)
)
.onMessage((conn, msg) -> conn.send(msg))
.start(8080);

Starting on a Specific Host

// Listen on all interfaces
server.start(8080);

// Listen on specific interface
server.start("192.168.1.100", 8080);

// Using InetSocketAddress
server.start(new InetSocketAddress("localhost", 8080));

Event Handlers

FastSocket provides four event handlers that you can register:

onConnect

Called when a new client connects. Use this to initialize connection state.

server.onConnect(conn -> {
    System.out.println("New connection: " + conn.id());
    System.out.println("Remote address: " + conn.remoteAddress());

    // Initialize connection attributes
    conn.attr("connectedAt", System.currentTimeMillis());
    conn.attr("messageCount", 0);

    // Add to default group
    server.group("lobby").add(conn);

    // Send welcome message
    conn.send("Welcome! Your ID is " + conn.id());
});

onMessage

Called when a message is received from a client. The message is automatically deserialized based on its type marker.

server.onMessage((conn, msg) -> {
    // msg can be: String, Integer, Long, Float, Double, Boolean, or byte[]

    if (msg instanceof String text) {
        System.out.println("String message: " + text);
        conn.send("Got your text!");

    } else if (msg instanceof Integer number) {
        System.out.println("Integer message: " + number);
        conn.send(number * 2);

    } else if (msg instanceof byte[] data) {
        System.out.println("Binary data: " + data.length + " bytes");
        conn.sendBytes(data); // Echo back
    }
});

Type-Safe Message Handlers

You can register handlers for specific types:

// Handle only String messages
server.onMessage(String.class, (conn, text) -> {
    System.out.println("Text: " + text);
});

// Handle only binary messages
server.onBinaryMessage((conn, bytes) -> {
    System.out.println("Binary: " + bytes.length + " bytes");
});

onDisconnect

Called when a client disconnects (either gracefully or due to error).

server.onDisconnect(conn -> {
    System.out.println("Disconnected: " + conn.id());

    // Connection is automatically removed from all groups
    // and unregistered from the registry

    // Clean up any external resources
    String username = conn.attr("username");
    if (username != null) {
        userDatabase.setOffline(username);
    }
});

onError

Called when an error occurs during message processing.

server.onError((conn, error) -> {
    System.err.println("Error on connection " + conn.id() + ": " + error.getMessage());
    error.printStackTrace();

    // Optionally close the connection
    // conn.close();
});

Configuration

Parameter Type Default Description
workerThreads int Available CPUs Number of EventLoop worker threads
bufferPoolSize int 128 Number of buffers per size tier
maxFrameSize int 65536 (64KB) Maximum allowed frame size in bytes
tcpNoDelay boolean true Disable Nagle's algorithm for lower latency
keepAlive boolean true Enable TCP keep-alive probes
FastSocket.create(config -> config
    .workerThreads(16)           // More workers for high concurrency
    .bufferPoolSize(512)         // More buffers for many connections
    .maxFrameSize(10 * 1024 * 1024)  // 10MB max frame
    .tcpNoDelay(true)            // Low latency
    .keepAlive(true)             // Detect dead connections
);

Server Lifecycle

FastSocket server = FastSocket.create()
    .onMessage((conn, msg) -> conn.send(msg))
    .start(8080);

// Check if running
if (server.isRunning()) {
    System.out.println("Server is running");
}

// Get statistics
System.out.println("Connected clients: " + server.connectionCount());
System.out.println("Buffer stats: " + server.bufferStats());

// Block until shutdown
server.awaitTermination();

// Or stop explicitly
server.stop();

// Can also use try-with-resources
try (FastSocket s = FastSocket.create().onMessage((c, m) -> {}).start(8080)) {
    // Server runs here
} // Automatically stopped

Connection API

The Connection object represents a client connection and provides methods for sending data and storing state.

Sending Messages

send(Object data)

Automatically detects the type and sends with appropriate encoding:

conn.send("Hello");           // Sends as String
conn.send(42);                // Sends as Integer
conn.send(123456789L);        // Sends as Long
conn.send(3.14f);             // Sends as Float
conn.send(Math.PI);           // Sends as Double
conn.send(true);              // Sends as Boolean
conn.send(new byte[]{1,2,3}); // Sends as byte[]

sendBytes(byte[] data)

Sends raw binary data:

byte[] imageData = Files.readAllBytes(Path.of("image.png"));
conn.sendBytes(imageData);

sendHeartbeat()

Sends a keep-alive ping:

conn.sendHeartbeat();

Fluent Chaining

All send methods return the Connection for chaining:

conn.send("Hello")
    .send("World")
    .send(42)
    .sendHeartbeat();

Connection Attributes

Store per-connection state without using external maps. There are two ways to use attributes:

Index-Based Attributes (Fastest)

Use integer indices 0-7. This is O(1) with no hashing overhead:

// Define constants for clarity
static final int ATTR_USER_ID = 0;
static final int ATTR_SESSION = 1;
static final int ATTR_ROLE = 2;

// Set attributes
conn.attr(ATTR_USER_ID, "user-123");
conn.attr(ATTR_SESSION, sessionToken);
conn.attr(ATTR_ROLE, "admin");

// Get attributes (type is inferred)
String userId = conn.attr(ATTR_USER_ID);
String session = conn.attr(ATTR_SESSION);
String role = conn.attr(ATTR_ROLE);

String-Keyed Attributes (Convenient)

Use string keys for readability. Slightly slower due to linear scan:

// Set attributes
conn.attr("userId", "user-123");
conn.attr("username", "john_doe");
conn.attr("role", "admin");

// Get attributes
String userId = conn.attr("userId");
String username = conn.attr("username");

// Check if attribute exists
if (conn.hasAttr("role")) {
    String role = conn.attr("role");
}

Note: String-keyed attributes use pairs of slots internally (key, value), so you can store up to 4 string-keyed attributes.

Connection Lifecycle

// Get connection ID (unique across server lifetime)
long id = conn.id();

// Get remote address
InetSocketAddress remote = conn.remoteAddress();
String ip = remote.getAddress().getHostAddress();
int port = remote.getPort();

// Check if connection is still open
if (conn.isOpen()) {
    conn.send("Still connected!");
}

// Close the connection
conn.close();

Messaging Patterns

FastSocket supports three messaging patterns out of the box.

Unicast (One-to-One)

Send a message to a specific connection by its ID.

// From server to specific connection
server.send(connectionId, "Private message");
server.sendBytes(connectionId, binaryData);

// Check if send was successful (connection exists and is open)
boolean sent = server.send(connectionId, "Hello");
if (!sent) {
    System.out.println("Connection not found or closed");
}

// Get connection by ID and send directly
Connection conn = server.connection(connectionId);
if (conn != null && conn.isOpen()) {
    conn.send("Direct message");
}

Use Cases for Unicast

  • Private messages between users
  • Targeted notifications
  • Request-response patterns
  • Game state updates for specific player

Multicast (One-to-Group)

Send a message to a named group of connections.

// Create/get a group and add connections
server.group("lobby").add(conn);

// Send to all group members
server.group("lobby").broadcast("Welcome to the lobby!");

// Or use the shorthand
server.multicast("lobby", "Hello everyone!");

// Send to group except one connection
server.group("lobby").broadcastExcept("User typing...", senderConn);
server.multicastExcept("lobby", "User joined", joiningConn);

// Conditional multicast
server.group("admins").broadcastIf(
    "Admin alert!",
    c -> c.attr("level") != null && (int) c.attr("level") >= 5
);

Use Cases for Multicast

  • Chat rooms
  • Game lobbies
  • Topic subscriptions (pub/sub)
  • Team notifications

Broadcast (One-to-All)

Send a message to ALL connected clients.

// Broadcast to everyone
server.broadcast("Server maintenance in 5 minutes");

// Broadcast binary data
server.broadcastBytes(systemUpdatePayload);

// Broadcast except one connection
server.broadcastExcept("New user joined!", joiningConnection);
server.broadcastExcept("User left", leavingConnectionId);

// Conditional broadcast
server.broadcastIf(
    "EU server update",
    conn -> "EU".equals(conn.attr("region"))
);

Use Cases for Broadcast

  • System announcements
  • Server status updates
  • Global events
  • Heartbeat/ping all clients

Groups API

Groups are named collections of connections for multicast messaging.

Creating and Managing Groups

// Get or create a group (lazy creation)
ConnectionGroup lobby = server.group("lobby");

// Add connection to group
lobby.add(conn);

// Remove connection from group
lobby.remove(conn);

// Check membership
if (lobby.contains(conn)) {
    System.out.println("Connection is in lobby");
}

// Get group size
int memberCount = lobby.size();

// Check if group is empty
if (lobby.isEmpty()) {
    System.out.println("Lobby is empty");
}

// Get all members
Set<Connection> members = lobby.members();
for (Connection member : members) {
    System.out.println("Member: " + member.id());
}

Multiple Group Membership

A connection can belong to multiple groups:

server.onConnect(conn -> {
    // Add to multiple groups
    server.group("all-users").add(conn);
    server.group("lobby").add(conn);
});

server.onMessage((conn, msg) -> {
    String text = (String) msg;

    if (text.startsWith("/join ")) {
        String room = text.substring(6);
        server.group("lobby").remove(conn);
        server.group(room).add(conn);
        conn.attr("currentRoom", room);
        conn.send("Joined " + room);
    }
});

Group Metadata

Store custom data on groups:

ConnectionGroup room = server.group("game-123");

// Set metadata
room.metadata(new GameState(/* ... */));

// Get metadata
GameState state = room.metadata();

Group Messaging

ConnectionGroup group = server.group("chat");

// Broadcast to all members
group.broadcast("Hello everyone!");

// Broadcast except one
group.broadcastExcept("User is typing...", typingUser);

// Conditional broadcast
group.broadcastIf(
    "Moderator message",
    conn -> "mod".equals(conn.attr("role"))
);

// Binary broadcast
group.broadcastBytes(binaryPayload);

Group Maintenance

// Remove closed connections from a group
int removed = server.group("lobby").prune();
System.out.println("Pruned " + removed + " closed connections");

// Close all connections in a group
server.group("old-session").closeAll();

// Remove empty groups
int emptyGroups = server.registry().pruneEmptyGroups();

// Check if group exists
if (server.registry().hasGroup("lobby")) {
    // ...
}

// Remove a group
server.registry().removeGroup("temporary-room");

// Get all group names
Set<String> groupNames = server.registry().groupNames();

Automatic Cleanup

When a connection disconnects, it is automatically removed from all groups:

server.onDisconnect(conn -> {
    // No need to manually remove from groups!
    // FastSocket handles this automatically

    // Just handle your business logic
    System.out.println("User left: " + conn.attr("username"));
});

Client API

Creating a Client

FastSocketClient client = FastSocketClient.create()
    .onConnect(() -> System.out.println("Connected!"))
    .onMessage(msg -> System.out.println("Received: " + msg))
    .onDisconnect(() -> System.out.println("Disconnected"))
    .onError(err -> System.err.println("Error: " + err.getMessage()))
    .connect("localhost", 8080);

Client Configuration

FastSocketClient client = FastSocketClient.create(config -> config
    .bufferPoolSize(64)
    .maxFrameSize(1024 * 1024)
    .connectTimeout(5000)
    .tcpNoDelay(true)
);

Sending Messages

// Send various types
client.send("Hello Server!");
client.send(42);
client.send(3.14);
client.send(true);
client.send(new byte[]{1, 2, 3, 4, 5});

// Check connection state
if (client.isConnected()) {
    client.send("Still here!");
}

Client Lifecycle

// Connect
client.connect("localhost", 8080);

// Check connection
boolean connected = client.isConnected();

// Disconnect
client.close();

// Or use try-with-resources
try (var c = FastSocketClient.create().onMessage(m -> {}).connect("localhost", 8080)) {
    c.send("Hello!");
}

SSL/TLS Encryption

FastSocket supports encrypted connections using SSL/TLS. The implementation uses Java's SSLEngine for non-blocking encryption with a dedicated buffer pool optimized for SSL's memory access patterns.

Server SSL Configuration

Basic SSL Server

import io.github.sk8erboi17.api.FastSocket;
import io.github.sk8erboi17.ssl.SslConfig;
import java.io.FileInputStream;

public class SecureServer {
    public static void main(String[] args) throws Exception {
        // Load keystore containing server certificate and private key
        var keystoreStream = new FileInputStream("server-keystore.p12");

        SslConfig sslConfig = SslConfig.forServer(keystoreStream, "keystorePassword")
            .build();

        FastSocket server = FastSocket.create(config -> config
            .workerThreads(4)
            .ssl(sslConfig))
            .onConnect(conn -> {
                System.out.println("Secure connection from: " + conn.remoteAddress());
                System.out.println("Protocol: " + conn.sslProtocol());     // e.g., "TLSv1.3"
                System.out.println("Cipher: " + conn.sslCipherSuite());    // e.g., "TLS_AES_256_GCM_SHA384"
            })
            .onMessage((conn, msg) -> {
                conn.send("Encrypted echo: " + msg);
            })
            .start(8443);

        System.out.println("Secure server started on port 8443");
    }
}

Advanced SSL Configuration

SslConfig sslConfig = SslConfig.forServer(keystoreStream, "password")
    .protocols("TLSv1.3", "TLSv1.2")          // Allowed protocols
    .ciphers(                                   // Allowed cipher suites
        "TLS_AES_256_GCM_SHA384",
        "TLS_AES_128_GCM_SHA256",
        "TLS_CHACHA20_POLY1305_SHA256"
    )
    .clientAuth(true)                           // Require client certificates
    .trustStore(truststoreStream, "trustpass")  // For client cert validation
    .build();

Client SSL Configuration

Basic SSL Client

import io.github.sk8erboi17.api.FastSocketClient;
import io.github.sk8erboi17.ssl.SslConfig;
import java.io.FileInputStream;

public class SecureClient {
    public static void main(String[] args) throws Exception {
        // For production: use system trust store or custom truststore
        // For testing with self-signed certs: trust the server's certificate
        var truststoreStream = new FileInputStream("truststore.p12");

        SslConfig sslConfig = SslConfig.forClient()
            .trustStore(truststoreStream, "truststorePassword")
            .build();

        FastSocketClient client = FastSocketClient.create()
            .ssl(sslConfig)
            .onConnect(() -> System.out.println("Secure connection established"))
            .onMessage(msg -> System.out.println("Received: " + msg))
            .connect("localhost", 8443);

        // Check SSL info
        System.out.println("SSL enabled: " + client.isSsl());
        System.out.println("Protocol: " + client.sslProtocol());

        client.send("Hello over TLS!");
        Thread.sleep(1000);
        client.close();
    }
}

Trust All Certificates (Testing Only!)

// WARNING: Never use in production! Disables certificate validation.
SslConfig sslConfig = SslConfig.forClient()
    .trustAll()  // Accepts any certificate - INSECURE
    .build();

Client Certificate Authentication

// Client with its own certificate (mutual TLS / mTLS)
var clientKeystore = new FileInputStream("client-keystore.p12");
var truststore = new FileInputStream("truststore.p12");

SslConfig sslConfig = SslConfig.forClient()
    .keyStore(clientKeystore, "clientpass")      // Client's certificate
    .trustStore(truststore, "trustpass")          // Server's CA
    .build();

SSL Buffer Pool

SSL connections use a dedicated buffer pool (SslBufferPool) optimized for TLS operations. Each SSL connection requires 3 buffers (20KB each) for network input, network output, and application data.

Why a Dedicated Pool?

SSL buffers have different characteristics than regular I/O buffers:

  1. Larger size: 16-17KB per buffer (TLS packet size) vs 1-32KB for regular I/O
  2. Persistent: Buffers persist for connection lifetime, not short-lived
  3. Fixed count: Each connection needs exactly 3 buffers

Pool Sizing

The pool is automatically sized for 1000 concurrent SSL connections (3000 buffers = ~60MB). For different loads:

// Memory calculation:
// 100 connections  × 3 buffers × 20KB = 6MB
// 1,000 connections × 3 buffers × 20KB = 60MB
// 10,000 connections × 3 buffers × 20KB = 600MB

Pool Monitoring

The pool includes built-in leak detection and statistics:

// Access via SslBufferPool.stats() if you have a reference
// Stats include: poolSize, available, acquired, hitRate, missCount

Certificate Generation

Self-Signed Certificate (Development)

# Generate a self-signed certificate in PKCS12 format
keytool -genkeypair \
    -alias server \
    -keyalg RSA \
    -keysize 2048 \
    -validity 365 \
    -keystore server-keystore.p12 \
    -storetype PKCS12 \
    -storepass changeit \
    -keypass changeit \
    -dname "CN=localhost,OU=Dev,O=MyCompany,L=City,ST=State,C=US"

Export Certificate for Client Trust

# Export the certificate (no private key)
keytool -exportcert \
    -alias server \
    -keystore server-keystore.p12 \
    -file server.crt \
    -storepass changeit

# Import into a truststore for clients
keytool -importcert \
    -alias server \
    -file server.crt \
    -keystore client-truststore.p12 \
    -storetype PKCS12 \
    -storepass changeit \
    -noprompt

Using the Same Keystore as Truststore (Testing)

For testing with self-signed certificates, you can use the server's keystore as the client's truststore:

// Server
SslConfig serverSsl = SslConfig.forServer(
    new FileInputStream("test-keystore.p12"), "testpass"
).build();

// Client (trusts the same keystore)
SslConfig clientSsl = SslConfig.forClient()
    .trustStore(new FileInputStream("test-keystore.p12"), "testpass")
    .build();

SSL Connection Lifecycle

Client                              Server
   │                                   │
   │──────── TCP Connect ─────────────►│
   │                                   │
   │◄─────── TCP Accept ──────────────│
   │                                   │
   │◄═══════ SSL Handshake ═══════════►│  ← Negotiates protocol, cipher,
   │         (TLS 1.3: 1-RTT)          │    exchanges certificates
   │                                   │
   │         onConnect() called        │  ← After handshake completes
   │                                   │
   │◄══════ Encrypted Messages ═══════►│  ← All data encrypted
   │                                   │
   │──────── SSL close_notify ────────►│  ← Graceful shutdown
   │◄─────── SSL close_notify ─────────│
   │                                   │
   │         onDisconnect() called     │
   │                                   │

SSL Performance Considerations

  1. Handshake cost: TLS 1.3 requires 1 round-trip; TLS 1.2 requires 2. Use connection pooling for short-lived connections.

  2. Encryption overhead: ~2-5% CPU overhead for modern ciphers (AES-GCM with hardware acceleration).

  3. Buffer memory: Each SSL connection uses ~60KB for buffers. Plan accordingly for high connection counts.

  4. Session resumption: TLS 1.3 supports 0-RTT resumption for returning clients (not yet implemented in FastSocket).


Binary Protocol

FastSocket uses a compact binary protocol for efficient communication.

Frame Format

┌──────────────┬───────────────┬──────────────┬─────────────┐
│ START_MARKER │ FRAME_LENGTH  │  DATA_TYPE   │   PAYLOAD   │
│   (1 byte)   │   (4 bytes)   │   (1 byte)   │  (N bytes)  │
└──────────────┴───────────────┴──────────────┴─────────────┘
Field Size Description
START_MARKER 1 byte Always 0x01 - marks frame start
FRAME_LENGTH 4 bytes Total frame length (big-endian int)
DATA_TYPE 1 byte Type marker for deserialization
PAYLOAD N bytes Actual data

Type Markers

Type Marker Encoding
String 0x01 UTF-8 bytes
Integer 0x02 4 bytes, big-endian
Long 0x03 8 bytes, big-endian
Float 0x04 4 bytes, IEEE 754
Double 0x05 8 bytes, IEEE 754
Boolean 0x06 1 byte (0 or 1)
Bytes 0x07 Raw bytes
Heartbeat 0x10 No payload

Frame Size Limits

The maxFrameSize configuration limits the maximum frame size to prevent memory exhaustion attacks:

// Default: 64KB
// Increase for large payloads:
FastSocket.create(config -> config.maxFrameSize(10 * 1024 * 1024)); // 10MB

Frames exceeding this limit cause the connection to be closed.


Buffer Pool

FastSocket uses off-heap native memory via Panama FFM for efficient I/O.

Size Tiers

Tier Size Use Case
SMALL 256 bytes Headers, small messages
MEDIUM 4096 bytes Typical messages
LARGE 65536 bytes Large payloads

Monitoring

NativeBufferPool.PoolStats stats = server.bufferStats();

System.out.println("Small buffers - total: " + stats.smallTotal() +
                   ", available: " + stats.smallAvailable());
System.out.println("Medium buffers - total: " + stats.mediumTotal() +
                   ", available: " + stats.mediumAvailable());
System.out.println("Large buffers - total: " + stats.largeTotal() +
                   ", available: " + stats.largeAvailable());

Tuning

// More buffers for high connection count
FastSocket.create(config -> config.bufferPoolSize(512));

Complete Examples

Chat Room Server

import io.github.sk8erboi17.api.FastSocket;

public class ChatServer {
    public static void main(String[] args) {
        FastSocket server = FastSocket.create()
            .onConnect(conn -> {
                // Set default username
                conn.attr("username", "Anonymous-" + conn.id());

                // Add to lobby
                server.group("lobby").add(conn);
                conn.attr("room", "lobby");

                // Announce join
                server.group("lobby").broadcastExcept(
                    "[SERVER] " + conn.attr("username") + " joined the lobby",
                    conn
                );

                // Send welcome
                conn.send("[SERVER] Welcome! Commands: /name <name>, /join <room>, /whisper <id> <msg>, /list");
            })
            .onMessage((conn, msg) -> {
                String text = (String) msg;
                String username = conn.attr("username");
                String currentRoom = conn.attr("room");

                if (text.startsWith("/name ")) {
                    // Change username
                    String newName = text.substring(6).trim();
                    String oldName = username;
                    conn.attr("username", newName);
                    server.group(currentRoom).broadcast(
                        "[SERVER] " + oldName + " is now known as " + newName
                    );

                } else if (text.startsWith("/join ")) {
                    // Join a different room
                    String newRoom = text.substring(6).trim();

                    // Leave current room
                    server.group(currentRoom).remove(conn);
                    server.group(currentRoom).broadcast(
                        "[SERVER] " + username + " left the room"
                    );

                    // Join new room
                    server.group(newRoom).add(conn);
                    conn.attr("room", newRoom);
                    server.group(newRoom).broadcastExcept(
                        "[SERVER] " + username + " joined the room",
                        conn
                    );
                    conn.send("[SERVER] You joined " + newRoom);

                } else if (text.startsWith("/whisper ")) {
                    // Private message: /whisper <id> <message>
                    String[] parts = text.substring(9).split(" ", 2);
                    if (parts.length == 2) {
                        try {
                            long targetId = Long.parseLong(parts[0]);
                            String message = parts[1];

                            boolean sent = server.send(targetId,
                                "[WHISPER from " + username + "] " + message);

                            if (sent) {
                                conn.send("[WHISPER to " + targetId + "] " + message);
                            } else {
                                conn.send("[SERVER] User not found");
                            }
                        } catch (NumberFormatException e) {
                            conn.send("[SERVER] Invalid user ID");
                        }
                    }

                } else if (text.equals("/list")) {
                    // List users in current room
                    StringBuilder sb = new StringBuilder("[SERVER] Users in " + currentRoom + ": ");
                    for (var member : server.group(currentRoom).members()) {
                        sb.append(member.attr("username")).append(" (").append(member.id()).append("), ");
                    }
                    conn.send(sb.toString());

                } else {
                    // Regular message - broadcast to room
                    server.group(currentRoom).broadcast(
                        "[" + username + "] " + text
                    );
                }
            })
            .onDisconnect(conn -> {
                String username = conn.attr("username");
                String room = conn.attr("room");

                // Announce departure (connection already removed from groups automatically)
                server.group(room).broadcast("[SERVER] " + username + " disconnected");
            })
            .onError((conn, err) -> {
                System.err.println("Error on " + conn.id() + ": " + err.getMessage());
            })
            .start(8080);

        System.out.println("Chat server started on port 8080");
        server.awaitTermination();
    }
}

Game Lobby Server

import io.github.sk8erboi17.api.FastSocket;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

public class GameLobbyServer {
    private static final AtomicLong gameIdCounter = new AtomicLong(1);

    public static void main(String[] args) {
        FastSocket server = FastSocket.create(config -> config
            .workerThreads(4)
            .bufferPoolSize(256)
        )
        .onConnect(conn -> {
            conn.attr("state", "lobby");
            conn.attr("ready", false);
            server.group("lobby").add(conn);

            conn.send("WELCOME:" + conn.id());
            broadcastLobbyUpdate(server);
        })
        .onMessage((conn, msg) -> {
            String[] parts = ((String) msg).split(":", 2);
            String cmd = parts[0];
            String arg = parts.length > 1 ? parts[1] : "";

            switch (cmd) {
                case "SET_NAME" -> {
                    conn.attr("name", arg);
                    broadcastLobbyUpdate(server);
                }

                case "CREATE_GAME" -> {
                    String gameId = "game-" + gameIdCounter.getAndIncrement();
                    server.group("lobby").remove(conn);
                    server.group(gameId).add(conn);
                    conn.attr("state", "waiting");
                    conn.attr("gameId", gameId);
                    conn.attr("isHost", true);

                    conn.send("GAME_CREATED:" + gameId);
                    broadcastLobbyUpdate(server);
                }

                case "JOIN_GAME" -> {
                    String gameId = arg;
                    var gameGroup = server.group(gameId);

                    if (gameGroup.size() < 4) {
                        server.group("lobby").remove(conn);
                        gameGroup.add(conn);
                        conn.attr("state", "waiting");
                        conn.attr("gameId", gameId);
                        conn.attr("isHost", false);

                        conn.send("JOINED:" + gameId);
                        gameGroup.broadcast("PLAYER_JOINED:" + conn.attr("name"));
                        broadcastLobbyUpdate(server);
                    } else {
                        conn.send("ERROR:Game is full");
                    }
                }

                case "READY" -> {
                    conn.attr("ready", true);
                    String gameId = conn.attr("gameId");
                    server.group(gameId).broadcast("PLAYER_READY:" + conn.attr("name"));

                    // Check if all ready
                    boolean allReady = true;
                    for (var player : server.group(gameId).members()) {
                        if (!(boolean) player.attr("ready")) {
                            allReady = false;
                            break;
                        }
                    }

                    if (allReady && server.group(gameId).size() >= 2) {
                        for (var player : server.group(gameId).members()) {
                            player.attr("state", "playing");
                        }
                        server.group(gameId).broadcast("GAME_START");
                    }
                }

                case "GAME_ACTION" -> {
                    String gameId = conn.attr("gameId");
                    server.group(gameId).broadcastExcept(
                        "ACTION:" + conn.attr("name") + ":" + arg,
                        conn
                    );
                }

                case "LEAVE_GAME" -> {
                    String gameId = conn.attr("gameId");
                    server.group(gameId).remove(conn);
                    server.group(gameId).broadcast("PLAYER_LEFT:" + conn.attr("name"));

                    server.group("lobby").add(conn);
                    conn.attr("state", "lobby");
                    conn.attr("gameId", null);
                    conn.attr("ready", false);

                    conn.send("LEFT_GAME");
                    broadcastLobbyUpdate(server);
                }
            }
        })
        .onDisconnect(conn -> {
            String gameId = conn.attr("gameId");
            if (gameId != null) {
                server.group(gameId).broadcast("PLAYER_DISCONNECTED:" + conn.attr("name"));
            }
            broadcastLobbyUpdate(server);
        })
        .start(9000);

        System.out.println("Game lobby server started on port 9000");
    }

    private static void broadcastLobbyUpdate(FastSocket server) {
        int lobbyCount = server.group("lobby").size();
        server.group("lobby").broadcast("LOBBY_UPDATE:" + lobbyCount);
    }
}

Pub/Sub Server

import io.github.sk8erboi17.api.FastSocket;
import java.util.*;

public class PubSubServer {
    public static void main(String[] args) {
        FastSocket server = FastSocket.create()
            .onConnect(conn -> {
                conn.attr("subscriptions", new HashSet<String>());
                conn.send("READY");
            })
            .onMessage((conn, msg) -> {
                String[] parts = ((String) msg).split(" ", 3);
                String cmd = parts[0];

                switch (cmd) {
                    case "SUBSCRIBE" -> {
                        if (parts.length >= 2) {
                            String topic = parts[1];
                            Set<String> subs = conn.attr("subscriptions");
                            subs.add(topic);
                            server.group("topic:" + topic).add(conn);
                            conn.send("SUBSCRIBED " + topic);
                        }
                    }

                    case "UNSUBSCRIBE" -> {
                        if (parts.length >= 2) {
                            String topic = parts[1];
                            Set<String> subs = conn.attr("subscriptions");
                            subs.remove(topic);
                            server.group("topic:" + topic).remove(conn);
                            conn.send("UNSUBSCRIBED " + topic);
                        }
                    }

                    case "PUBLISH" -> {
                        if (parts.length >= 3) {
                            String topic = parts[1];
                            String message = parts[2];
                            server.group("topic:" + topic).broadcast(
                                "MESSAGE " + topic + " " + message
                            );
                        }
                    }

                    case "LIST" -> {
                        Set<String> subs = conn.attr("subscriptions");
                        conn.send("SUBSCRIPTIONS " + String.join(",", subs));
                    }
                }
            })
            .start(7000);

        System.out.println("Pub/Sub server started on port 7000");
    }
}

API Reference

FastSocket

// Creation
static FastSocket create()
static FastSocket create(Consumer<FastSocketConfig> configurer)

// Event Handlers
FastSocket onConnect(Consumer<Connection> handler)
FastSocket onMessage(BiConsumer<Connection, Object> handler)
FastSocket onMessage(Class<String> type, BiConsumer<Connection, String> handler)
FastSocket onBinaryMessage(BiConsumer<Connection, byte[]> handler)
FastSocket onDisconnect(Consumer<Connection> handler)
FastSocket onError(BiConsumer<Connection, Throwable> handler)

// Lifecycle
FastSocket start(int port)
FastSocket start(String host, int port)
FastSocket start(InetSocketAddress address)
void stop()
void close()  // AutoCloseable
void awaitTermination()
boolean isRunning()
boolean isSsl()  // Returns true if SSL is enabled

// Messaging
boolean send(long connectionId, Object message)
boolean sendBytes(long connectionId, byte[] data)
void broadcast(Object message)
void broadcastBytes(byte[] data)
void broadcastExcept(Object message, Connection except)
void broadcastExcept(Object message, long exceptId)
void broadcastIf(Object message, Predicate<Connection> filter)
boolean multicast(String groupName, Object message)
boolean multicastExcept(String groupName, Object message, Connection except)

// Registry & Groups
ConnectionRegistry registry()
ConnectionGroup group(String name)
Connection connection(long id)
int connectionCount()

// Configuration & Stats
FastSocketConfig config()
NativeBufferPool.PoolStats bufferStats()

Connection

// Identity
long id()
InetSocketAddress remoteAddress()
boolean isOpen()

// SSL Info
boolean isSsl()              // True if connection uses SSL/TLS
String sslProtocol()         // e.g., "TLSv1.3" or "NONE"
String sslCipherSuite()      // e.g., "TLS_AES_256_GCM_SHA384" or null

// Sending
Connection send(Object data)
Connection sendBytes(byte[] data)
Connection sendHeartbeat()

// Attributes (index-based)
<T> T attr(int index)
Connection attr(int index, Object value)

// Attributes (string-keyed)
<T> T attr(String key)
Connection attr(String key, Object value)
boolean hasAttr(String key)

// Lifecycle
void close()

ConnectionGroup

// Membership
ConnectionGroup add(Connection connection)
ConnectionGroup remove(Connection connection)
boolean contains(Connection connection)
int size()
boolean isEmpty()
Set<Connection> members()

// Metadata
<T> T metadata()
ConnectionGroup metadata(Object metadata)

// Messaging
ConnectionGroup broadcast(Object message)
ConnectionGroup broadcastExcept(Object message, Connection except)
ConnectionGroup broadcastIf(Object message, Predicate<Connection> filter)
ConnectionGroup broadcastBytes(byte[] data)

// Maintenance
int prune()
void closeAll()

ConnectionRegistry

// Connection Management
void register(Connection connection)
void unregister(Connection connection)
Connection get(long connectionId)
boolean contains(long connectionId)
int connectionCount()
Collection<Connection> connections()

// Group Management
ConnectionGroup group(String name)
ConnectionGroup getGroup(String name)
ConnectionGroup removeGroup(String name)
boolean hasGroup(String name)
int groupCount()
Set<String> groupNames()
int pruneEmptyGroups()

// Unicast
boolean send(long connectionId, Object message)
boolean sendBytes(long connectionId, byte[] data)

// Broadcast
void broadcast(Object message)
void broadcastExcept(Object message, Connection except)
void broadcastExcept(Object message, long exceptId)
void broadcastIf(Object message, Predicate<Connection> filter)
void broadcastBytes(byte[] data)

// Multicast
boolean multicast(String groupName, Object message)
boolean multicastExcept(String groupName, Object message, Connection except)
boolean multicastBytes(String groupName, byte[] data)

// Utility
List<Connection> findByAttribute(String key, Object value)
Connection findFirstByAttribute(String key, Object value)
int pruneClosedConnections()
void closeAll()

FastSocketClient

// Creation
static FastSocketClient create()
static FastSocketClient create(Consumer<FastSocketClientConfig> configurer)

// Event Handlers
FastSocketClient onConnect(Runnable handler)
FastSocketClient onMessage(Consumer<Object> handler)
FastSocketClient onDisconnect(Runnable handler)
FastSocketClient onError(Consumer<Throwable> handler)

// SSL Configuration
FastSocketClient ssl(SslConfig config)   // Enable SSL with given config

// Connection
FastSocketClient connect(String host, int port)
boolean isConnected()
void close()

// SSL Info
boolean isSsl()          // True if SSL is enabled
String sslProtocol()     // e.g., "TLSv1.3" or "NONE"

// Sending
void send(Object data)
void send(byte[] data)

License

MIT License

About

An implementation of an asynchronous socket in Java with an easy-to-use API

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages