Socket Programming Overview
Sproogy's socket module provides a high-level abstraction for building secure, scalable TCP/SSL socket applications.
Architecture
Sproogy's socket architecture consists of three main layers:
Key Components
1. Request/Response Model
All socket communication uses structured JSON messages:
Request:
{
"id": "req-12345",
"endpoint": "/users/42",
"headers": {
"Authorization": "Bearer token",
"X-Client-Version": "1.0"
},
"data": {
"includeDetails": true
}
}
Response:
{
"id": "req-12345",
"status": 200,
"headers": {
"X-Server-Version": "1.0"
},
"data": {
"id": 42,
"username": "alice",
"email": "alice@example.com"
}
}
2. SSL/TLS Security
Sproogy enforces SSL/TLS by default:
- Server side: Implement
SSLFactoryLoaderto provide certificates - Client side: Implement
SSLSecuritySocketLoaderto trust server certificates - Protocols: Supports TLSv1.2, TLSv1.3
- Cipher suites: Configurable via
SSLContext
3. Routing System
Controllers use annotations for endpoint mapping:
@Controller
public class UserController {
@AppMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable("id") Long id) {
// Automatic routing based on endpoint pattern
}
@AppMapping("/users")
public ResponseEntity<List<User>> listUsers() {
// Different endpoint, different method
}
}
4. Filter Chain
Cross-cutting concerns (auth, logging, encryption) are handled via filters:
@Component
public class AuthFilter implements Filter {
@Override
public void doFilter(Request request, Response response, FilterChain chain) {
// Pre-processing: Check authentication
if (!isAuthenticated(request)) {
response.setStatus(401);
return; // Block request
}
chain.doFilter(request, response); // Continue to next filter/controller
// Post-processing: Add security headers
response.getHeaders().put("X-Frame-Options", "DENY");
}
}
5. Transformers
Transformers allow custom encoding/decoding:
- InputTransformer: Transform raw input before deserialization (e.g., decrypt)
- OutputTransformer: Transform output before sending (e.g., encrypt, compress)
@Component
public class EncryptionTransformer implements InputTransformer, OutputTransformer {
@Override
public String transform(String input) {
return decrypt(input); // Decrypt incoming data
}
@Override
public String transformOutput(String output) {
return encrypt(output); // Encrypt outgoing data
}
}
Complete Request Flow
Let's trace a complete request from client to server and back, showing every step:
Steps:
- Client Request: Client sends request as String
- Socket Connection: Request received by Server Socket
- Thread Creation: Server spawns new Thread to handle connection
- Request Handler: Thread delegates to Request Handler
- Input Transformation: Optional custom transformation (decrypt, decompress)
- Deserialization: JSON string →
Requestobject - Filter Chain Forward: Filters process request in order (PRE-processing)
- Filter 1 (e.g., Authentication)
- Filter 2 (e.g., Logging)
- Controller Routing: ControllerHandler uses Route Matcher to find endpoint
- Controller Execution: Matched controller method executes business logic
- Filter Chain Unstack: Filters process response in reverse order (POST-processing)
- Filter 2 (e.g., Log response)
- Filter 1 (e.g., Add security headers)
- Serialization:
Responseobject → JSON string - Output Transformation: Optional custom transformation (encrypt, compress)
- Send Response: Response String sent back to Client via Socket
Server vs Client
Server Responsibilities
- Listen on configured port
- Accept incoming SSL connections
- Process requests through filter chain
- Route to appropriate controllers
- Return responses
Modules needed:
core(DI container)socket-common(Request/Response models)socket-server(Server, filters, routing)
Client Responsibilities
- Connect to remote SSL server
- Send requests with proper format
- Receive and deserialize responses
- Handle connection errors
Modules needed:
socket-common(Request/Response models)socket-client(SocketTemplate, SSL loaders)
Communication Patterns
1. Request-Response (Synchronous)
Client sends request, waits for response:
// Client
ResponseEntity<User> response = socketTemplate.sendRequest("/users/1", User.class);
User user = response.getData();
2. Fire-and-Forget (Asynchronous)
Client sends request without waiting for response:
// Use ResponseListener for async handling
socketTemplate.addResponseListener(response -> {
System.out.println("Async response: " + response.getData());
});
socketTemplate.sendRequestAsync("/notify", notificationData);
3. Long-Lived Connections
Clients can maintain persistent connections for real-time communication:
// Server pushes updates to connected clients
@Component
public class NotificationService {
private final List<Socket> connectedClients = new CopyOnWriteArrayList<>();
public void broadcastUpdate(String message) {
for (Socket client : connectedClients) {
sendToClient(client, message);
}
}
}
Performance Considerations
Thread Pool
Sproogy uses a thread pool for handling concurrent clients:
// DefaultServer uses ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors() * 2
);
Tuning:
- Small thread pool = fewer concurrent clients
- Large thread pool = more memory usage
- Consider virtual threads (Java 21+) for massive concurrency
Connection Limits
OS limits (file descriptors) affect max connections:
# Check limits (Linux)
ulimit -n
# Increase for production
ulimit -n 65536
Message Size
Large messages impact performance:
- Keep messages small (< 1MB recommended)
- Use compression for large payloads
- Stream large files separately (don't send via socket protocol)
Security Best Practices
-
Always use TLS 1.2+: Disable older protocols
sslContext.init(keyManagers, trustManagers, null);
SSLParameters params = new SSLParameters();
params.setProtocols(new String[]{"TLSv1.2", "TLSv1.3"}); -
Validate client certificates (mutual TLS):
sslSocket.setNeedClientAuth(true); -
Implement authentication filters:
@Component
public class AuthFilter implements Filter {
public void doFilter(Request req, Response res, FilterChain chain) {
String token = req.getHeaders().get("Authorization");
if (!tokenService.validate(token)) {
res.setStatus(401);
return;
}
chain.doFilter(req, res);
}
} -
Rate limiting:
@Component
public class RateLimitFilter implements Filter {
private final Map<String, RateLimiter> limiters = new ConcurrentHashMap<>();
public void doFilter(Request req, Response res, FilterChain chain) {
String clientId = extractClientId(req);
if (!limiters.computeIfAbsent(clientId, k -> createLimiter()).tryAcquire()) {
res.setStatus(429); // Too Many Requests
return;
}
chain.doFilter(req, res);
}
}
Next Steps
Dive deeper into specific topics:
👉 Server Setup - Configure and customize your server
👉 Client Setup - Build robust socket clients
👉 Routing - Advanced endpoint mapping
👉 Filters - Build powerful interceptors