-
Notifications
You must be signed in to change notification settings - Fork 0
ConnectionServer
ConnectionServer is the Transport Layer (Layer 2) of JSI's architecture. It provides TCP socket management and thread-per-client concurrency on top of the abstract Server foundation.
┌─────────────────────────────────────┐
│ HttpServer / DatabaseServer │ ← Protocol implementations
│ (Your custom protocol) │
├─────────────────────────────────────┤
│ ConnectionServer │ ← YOU ARE HERE
│ (TCP socket handling) │
├─────────────────────────────────────┤
│ Server (abstract base) │ ← Foundation layer
└─────────────────────────────────────┘
File: connection/ConnectionServer.java
package jsi.connection;
import java.io.*;
import java.net.ServerSocket;
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import jsi.Request;
import jsi.Server;
public abstract class ConnectionServer extends Server {
private final int port;
public ConnectionServer(int port) {
this.port = port;
}
@Override
public void start() {
System.out.println("Server is starting...");
onBeforeStart();
try (ServerSocket socket = new ServerSocket(port)) {
System.out.println("Server started on port " + port);
onServerStarted();
while (true) {
Socket clientSocket = socket.accept();
// Spawn new thread for each client
new Thread(() -> {
try (
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream(),
StandardCharsets.UTF_8));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream())
) {
String request = in.readLine();
Response response = handleRequest(parseRequest(request));
out.println(response.serialize());
out.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Parse the incoming request string into a Request object.
* Subclasses implement protocol-specific parsing.
*/
protected abstract Request parseRequest(String input);
/**
* Read a file from the filesystem.
* Utility method available to all ConnectionServer subclasses.
*/
protected String readFile(String filePath) throws IOException {
Path path = Paths.get(filePath);
return Files.readString(path, StandardCharsets.UTF_8);
}
}private final int port;
public ConnectionServer(int port) {
this.port = port;
}Unlike the base Server class, ConnectionServer assumes TCP networking, so it requires a port number.
try (ServerSocket socket = new ServerSocket(port)) {
while (true) {
Socket clientSocket = socket.accept(); // Block until client connects
// ... handle client ...
}
}Key operations:
-
Bind:
new ServerSocket(port)binds to the specified port - Listen: Automatically begins listening for connections
-
Accept:
socket.accept()blocks until a client connects, then returns aSocket
Socket clientSocket = socket.accept();
new Thread(() -> {
// This code runs in a separate thread for this client
handleClientRequest();
}).start();
// Main thread continues, ready to accept next clientThreading model:
Main Thread Client Thread 1 Client Thread 2
│ │ │
├──accept()────────────────►│ │
│ (spawns thread) ├─ parse request │
│ ├─ handle request │
├──accept()──────────────────────────────────────►│
│ (spawns thread) ├─ send response ├─ parse request
│ └─ close socket ├─ handle request
├──accept() ├─ send response
. (blocked) └─ close socket
. .
Benefits:
- Simple to understand and implement
- Each client is isolated (crash doesn't affect others)
- Natural request/response pairing
Limitations:
- One thread per connection (doesn't scale to thousands of connections)
- Thread creation overhead
- No connection pooling or reuse
// 1. Read raw data from socket
String requestString = in.readLine();
// 2. Parse into Request object (protocol-specific)
Request request = parseRequest(requestString);
// 3. Process request (business logic)
Response response = handleRequest(request);
// 4. Serialize response
String responseString = response.serialize();
// 5. Write to socket
out.println(responseString);
out.flush();This pipeline abstracts the wire protocol from the application protocol.
try (
BufferedReader in = new BufferedReader(...);
PrintWriter out = new PrintWriter(...)
) {
// Use streams
} finally {
clientSocket.close(); // Always close socket
}Try-with-resources ensures streams are closed even if exceptions occur.
Simplest possible protocol - echo back what client sends:
import jsi.connection.ConnectionServer;
import jsi.Request;
import jsi.Response;
public class EchoServer extends ConnectionServer {
public EchoServer(int port) {
super(port);
}
@Override
protected Request parseRequest(String input) {
// Wrap string in a Request object
return new SimpleRequest(input);
}
@Override
public Response handleRequest(Request request) {
// Echo back the same content
String message = ((SimpleRequest) request).getMessage();
return new SimpleResponse("ECHO: " + message);
}
public static void main(String[] args) {
new EchoServer(9000).start();
}
}
// Simple request/response implementations
class SimpleRequest implements Request {
private String message;
public SimpleRequest(String message) { this.message = message; }
public String getMessage() { return message; }
@Override
public String serialize() { return message; }
}
class SimpleResponse implements Response {
private String message;
public SimpleResponse(String message) { this.message = message; }
@Override
public String serialize() { return message; }
}Test with telnet:
$ telnet localhost 9000
> Hello, server!
< ECHO: Hello, server!Custom protocol where each line is a command:
public class CommandServer extends ConnectionServer {
public CommandServer(int port) {
super(port);
}
@Override
protected Request parseRequest(String input) {
// Parse: "COMMAND arg1 arg2"
String[] parts = input.split(" ", 2);
String command = parts[0];
String args = parts.length > 1 ? parts[1] : "";
return new CommandRequest(command, args);
}
@Override
public Response handleRequest(Request request) {
CommandRequest cmd = (CommandRequest) request;
switch (cmd.getCommand()) {
case "PING":
return new CommandResponse("PONG");
case "TIME":
return new CommandResponse(LocalDateTime.now().toString());
case "ECHO":
return new CommandResponse(cmd.getArgs());
default:
return new CommandResponse("ERROR: Unknown command");
}
}
}Test:
$ telnet localhost 9000
> PING
< PONG
> TIME
< 2025-12-10T14:30:00
> ECHO Hello World
< Hello WorldServerSocket is Java's TCP server socket implementation.
Creation:
ServerSocket serverSocket = new ServerSocket(port);Behind the scenes:
- Allocates a socket file descriptor
- Binds to
0.0.0.0:port(all network interfaces) - Calls OS
listen()syscall with default backlog (50)
Accepting connections:
Socket clientSocket = serverSocket.accept(); // BlocksThis method blocks until a client connects, then returns a new Socket representing that connection.
Each accepted connection gets its own Socket object.
Reading data:
InputStream in = clientSocket.getInputStream();
BufferedReader reader = new BufferedReader(
new InputStreamReader(in, StandardCharsets.UTF_8)
);
String line = reader.readLine(); // Blocks until newlineWriting data:
OutputStream out = clientSocket.getOutputStream();
PrintWriter writer = new PrintWriter(out);
writer.println("Hello, client!");
writer.flush(); // Important! Forces data to be sent immediatelyJSI consistently uses UTF-8:
new InputStreamReader(clientSocket.getInputStream(), StandardCharsets.UTF_8);This ensures international characters are handled correctly.
new Thread(() -> {
// Client handling code
}).start();Each Thread object:
- Creates a new OS-level thread
- Has its own stack (typically 1 MB on 64-bit JVM)
- Scheduled by OS thread scheduler
Cost analysis:
- Memory: ~1 MB per thread (stack space)
- Creation time: ~0.2-0.5 ms
- Context switching: Adds overhead when switching between threads
Practical limits:
- Thousands of threads: Possible but slow
- Tens of thousands: System will struggle
- Hundreds of thousands: Not feasible
For production systems, use thread pools:
private ExecutorService threadPool = Executors.newFixedThreadPool(100);
@Override
public void start() {
try (ServerSocket socket = new ServerSocket(port)) {
while (true) {
Socket clientSocket = socket.accept();
// Submit to thread pool instead of creating new thread
threadPool.submit(() -> handleClient(clientSocket));
}
}
}Benefits:
- Limits concurrent threads
- Reuses threads (no creation overhead)
- Bounded resource usage
Client connects
↓
Main thread: accept() returns
↓
New thread spawned
↓
Thread reads request
↓
Thread processes request
↓
Thread writes response
↓
Thread closes socket
↓
Thread terminates
Note: Each connection is completely independent. No state is shared between threads (unless you explicitly share it).
ConnectionServer provides a utility method for reading files:
protected String readFile(String filePath) throws IOException {
Path path = Paths.get(filePath);
return Files.readString(path, StandardCharsets.UTF_8);
}public class FileServer extends ConnectionServer {
@Override
public Response handleRequest(Request request) {
String filename = ((FileRequest) request).getFilename();
try {
String content = readFile("files/" + filename);
return new FileResponse(200, content);
} catch (IOException e) {
return new FileResponse(404, "File not found");
}
}
}Paths.get("static/index.html")This resolves relative to the current working directory (where the JVM was started).
Best practice: Use absolute paths or configure a base directory:
private String baseDir = "/var/www/html";
protected String readFile(String relativePath) throws IOException {
Path fullPath = Paths.get(baseDir, relativePath);
return Files.readString(fullPath, StandardCharsets.UTF_8);
}Implement parseRequest() for your protocol:
@Override
protected Request parseRequest(String input) {
// JSON-RPC parsing
JsonObject json = JsonParser.parse(input);
String method = json.get("method").getAsString();
JsonArray params = json.get("params").getAsJsonArray();
return new JsonRpcRequest(method, params);
}Override lifecycle methods:
@Override
protected void onBeforeStart() {
System.out.println("Loading configuration...");
loadConfig();
connectToDatabase();
}
@Override
protected void onServerStarted() {
System.out.println("Server ready!");
registerWithServiceDiscovery();
}Override start() for complete control:
@Override
public void start() {
ExecutorService pool = Executors.newFixedThreadPool(50);
onBeforeStart();
try (ServerSocket socket = new ServerSocket(port)) {
onServerStarted();
while (true) {
Socket client = socket.accept();
pool.submit(() -> handleClient(client));
}
} catch (IOException e) {
e.printStackTrace();
}
}Add request/response interceptors:
public abstract class MiddlewareConnectionServer extends ConnectionServer {
private List<Middleware> middlewares = new ArrayList<>();
public void use(Middleware middleware) {
middlewares.add(middleware);
}
@Override
public Response handleRequest(Request request) {
// Pre-processing
for (Middleware m : middlewares) {
request = m.before(request);
}
// Actual handling
Response response = doHandleRequest(request);
// Post-processing
for (Middleware m : middlewares) {
response = m.after(response);
}
return response;
}
protected abstract Response doHandleRequest(Request request);
}With thread-per-client model:
- Short-lived connections: 1000-5000 req/sec (depends on request processing time)
- Long-lived connections: Limited by thread count (typically < 10,000 concurrent)
- Thread creation: 0.2-0.5 ms per connection
- Context switching: Adds 1-10 µs per switch
- Socket I/O: Depends on network and data size
Connections Memory (1 MB/thread) Context Switching
─────────────────────────────────────────────────────────
100 100 MB Negligible
1,000 1 GB Noticeable
10,000 10 GB Significant
100,000 100 GB Unusable
For high-concurrency scenarios, consider:
- Thread pools (limit concurrent threads)
- Async I/O (NIO, Netty)
- Connection multiplexing (HTTP/2)
-
Core Abstractions -
Server,Request,Responseinterfaces - Architecture Overview - How ConnectionServer fits in the layers
- HTTP Server - Protocol implementation on top of ConnectionServer
- Database Server - Another protocol implementation
Next: Explore HTTP Server to see how HTTP protocol is built on ConnectionServer, or Database Server for database query handling.
JSI - Java Server Interface | Educational Server Framework | Zero Dependencies
Home • Getting Started • Architecture • Source Code
Made for learning | Report Issues • Discussions
Last updated: December 2025 | JSI v1.0
HTTP Development
Database Development
Custom Protocols