Navigation

Php

PHP Socket Programming: Real-time Communication Servers (Complete Guide 2025)

Master PHP socket programming for real-time applications. Learn TCP/UDP sockets, WebSocket servers, and build scalable communication systems with practical examples.

Table Of Contents

Introduction

Real-time communication has become essential for modern web applications, from chat systems and live notifications to multiplayer games and collaborative tools. While many developers rely on third-party services like Pusher or Firebase, PHP's built-in socket capabilities offer a powerful, cost-effective alternative for building custom real-time communication servers.

In this comprehensive guide, you'll learn how to harness PHP's socket programming features to create robust, scalable real-time applications. We'll cover everything from basic TCP/UDP socket operations to advanced WebSocket servers and multi-client communication systems.

Understanding Socket Programming Fundamentals

What Are Sockets?

Sockets provide a communication endpoint that allows programs to exchange data across networks or within the same machine. Think of them as telephone connections - one program "calls" another by connecting to its socket, enabling bidirectional communication.

PHP supports several socket types:

  • TCP Sockets: Reliable, connection-oriented communication
  • UDP Sockets: Fast, connectionless communication
  • Unix Domain Sockets: Local inter-process communication
  • WebSockets: Real-time browser communication

Socket Communication Flow

<?php
// Server workflow
// 1. Create socket
// 2. Bind to address/port
// 3. Listen for connections
// 4. Accept connections
// 5. Read/Write data
// 6. Close connection

// Client workflow
// 1. Create socket
// 2. Connect to server
// 3. Read/Write data
// 4. Close connection
?>

Setting Up Your PHP Socket Environment

Prerequisites and Configuration

Before diving into socket programming, ensure your PHP environment supports socket operations:

<?php
// Check socket extension
if (!extension_loaded('sockets')) {
    die('Socket extension not loaded');
}

// Check available functions
$required_functions = [
    'socket_create',
    'socket_bind',
    'socket_listen',
    'socket_accept',
    'socket_read',
    'socket_write',
    'socket_close'
];

foreach ($required_functions as $function) {
    if (!function_exists($function)) {
        die("Required function $function not available");
    }
}

echo "Socket environment ready!\n";
?>

Basic Socket Configuration

<?php
class SocketConfig {
    public const DEFAULT_HOST = '127.0.0.1';
    public const DEFAULT_PORT = 8080;
    public const BUFFER_SIZE = 8192;
    public const SOCKET_TIMEOUT = 30;
    
    public static function getServerDefaults(): array {
        return [
            'host' => self::DEFAULT_HOST,
            'port' => self::DEFAULT_PORT,
            'buffer_size' => self::BUFFER_SIZE,
            'timeout' => self::SOCKET_TIMEOUT,
            'max_connections' => 100,
            'protocol' => SOL_TCP
        ];
    }
    
    public static function validatePort(int $port): bool {
        return $port >= 1024 && $port <= 65535;
    }
    
    public static function validateHost(string $host): bool {
        return filter_var($host, FILTER_VALIDATE_IP) !== false;
    }
}
?>

Building TCP Socket Servers

Basic TCP Server Implementation

<?php
class SimpleTcpServer {
    private $socket;
    private string $host;
    private int $port;
    private bool $running = false;
    private array $clients = [];
    
    public function __construct(string $host = '127.0.0.1', int $port = 8080) {
        $this->host = $host;
        $this->port = $port;
    }
    
    public function start(): void {
        // Create socket
        $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        if ($this->socket === false) {
            throw new RuntimeException('Failed to create socket: ' . socket_strerror(socket_last_error()));
        }
        
        // Set socket options
        socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1);
        socket_set_option($this->socket, SOL_SOCKET, SO_KEEPALIVE, 1);
        
        // Bind to address and port
        if (!socket_bind($this->socket, $this->host, $this->port)) {
            throw new RuntimeException('Failed to bind socket: ' . socket_strerror(socket_last_error($this->socket)));
        }
        
        // Listen for connections
        if (!socket_listen($this->socket, 10)) {
            throw new RuntimeException('Failed to listen on socket: ' . socket_strerror(socket_last_error($this->socket)));
        }
        
        echo "Server started on {$this->host}:{$this->port}\n";
        $this->running = true;
        $this->acceptConnections();
    }
    
    private function acceptConnections(): void {
        while ($this->running) {
            $clientSocket = socket_accept($this->socket);
            
            if ($clientSocket === false) {
                continue;
            }
            
            $clientId = uniqid('client_');
            $this->clients[$clientId] = $clientSocket;
            
            echo "Client connected: $clientId\n";
            
            // Handle client in separate process or thread
            $this->handleClient($clientSocket, $clientId);
        }
    }
    
    private function handleClient($clientSocket, string $clientId): void {
        while (true) {
            $data = socket_read($clientSocket, 1024, PHP_NORMAL_READ);
            
            if ($data === false || $data === '') {
                break;
            }
            
            $message = trim($data);
            echo "Received from $clientId: $message\n";
            
            // Echo message back to client
            $response = "Server received: $message\n";
            socket_write($clientSocket, $response, strlen($response));
            
            // Handle special commands
            if ($message === 'quit') {
                break;
            }
        }
        
        $this->disconnectClient($clientSocket, $clientId);
    }
    
    private function disconnectClient($clientSocket, string $clientId): void {
        socket_close($clientSocket);
        unset($this->clients[$clientId]);
        echo "Client disconnected: $clientId\n";
    }
    
    public function stop(): void {
        $this->running = false;
        
        // Close all client connections
        foreach ($this->clients as $clientSocket) {
            socket_close($clientSocket);
        }
        
        // Close server socket
        if ($this->socket) {
            socket_close($this->socket);
        }
        
        echo "Server stopped\n";
    }
    
    public function __destruct() {
        $this->stop();
    }
}

// Usage
$server = new SimpleTcpServer('127.0.0.1', 8080);
$server->start();
?>

Multi-Client TCP Server with Select

<?php
class MultiClientTcpServer {
    private $masterSocket;
    private array $clients = [];
    private string $host;
    private int $port;
    private bool $running = false;
    private int $maxClients;
    
    public function __construct(string $host = '127.0.0.1', int $port = 8080, int $maxClients = 100) {
        $this->host = $host;
        $this->port = $port;
        $this->maxClients = $maxClients;
    }
    
    public function start(): void {
        $this->masterSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        
        socket_set_option($this->masterSocket, SOL_SOCKET, SO_REUSEADDR, 1);
        socket_bind($this->masterSocket, $this->host, $this->port);
        socket_listen($this->masterSocket, 10);
        
        echo "Multi-client server started on {$this->host}:{$this->port}\n";
        $this->running = true;
        
        $this->eventLoop();
    }
    
    private function eventLoop(): void {
        while ($this->running) {
            $readSockets = [$this->masterSocket];
            $readSockets = array_merge($readSockets, $this->clients);
            
            $writeSockets = [];
            $exceptSockets = [];
            
            // Use select to monitor socket activity
            $activity = socket_select($readSockets, $writeSockets, $exceptSockets, 1);
            
            if ($activity === false) {
                break;
            }
            
            if ($activity === 0) {
                continue; // Timeout, no activity
            }
            
            // Check for new connections
            if (in_array($this->masterSocket, $readSockets)) {
                $this->handleNewConnection();
                $key = array_search($this->masterSocket, $readSockets);
                unset($readSockets[$key]);
            }
            
            // Handle client activity
            foreach ($readSockets as $clientSocket) {
                $this->handleClientActivity($clientSocket);
            }
        }
    }
    
    private function handleNewConnection(): void {
        if (count($this->clients) >= $this->maxClients) {
            $newSocket = socket_accept($this->masterSocket);
            socket_write($newSocket, "Server full. Try again later.\n");
            socket_close($newSocket);
            return;
        }
        
        $newSocket = socket_accept($this->masterSocket);
        if ($newSocket === false) {
            return;
        }
        
        $clientId = $this->getClientId($newSocket);
        $this->clients[$clientId] = $newSocket;
        
        $welcomeMessage = "Welcome! You are client $clientId\n";
        socket_write($newSocket, $welcomeMessage);
        
        $this->broadcastMessage("Client $clientId joined the server\n", $clientId);
        
        echo "New client connected: $clientId (Total: " . count($this->clients) . ")\n";
    }
    
    private function handleClientActivity($clientSocket): void {
        $data = socket_read($clientSocket, 1024, PHP_NORMAL_READ);
        
        if ($data === false || $data === '') {
            $this->handleClientDisconnect($clientSocket);
            return;
        }
        
        $clientId = $this->getClientId($clientSocket);
        $message = trim($data);
        
        echo "Message from $clientId: $message\n";
        
        // Handle commands
        if (strpos($message, '/') === 0) {
            $this->handleCommand($clientSocket, $clientId, $message);
        } else {
            // Broadcast message to all clients
            $broadcastMessage = "[$clientId]: $message\n";
            $this->broadcastMessage($broadcastMessage, $clientId);
        }
    }
    
    private function handleCommand($clientSocket, string $clientId, string $command): void {
        $parts = explode(' ', $command, 2);
        $cmd = $parts[0];
        $args = $parts[1] ?? '';
        
        switch ($cmd) {
            case '/list':
                $clientList = "Connected clients: " . implode(', ', array_keys($this->clients)) . "\n";
                socket_write($clientSocket, $clientList);
                break;
                
            case '/whisper':
                $this->handleWhisper($clientSocket, $clientId, $args);
                break;
                
            case '/quit':
                socket_write($clientSocket, "Goodbye!\n");
                $this->handleClientDisconnect($clientSocket);
                break;
                
            default:
                socket_write($clientSocket, "Unknown command: $cmd\n");
        }
    }
    
    private function handleWhisper($clientSocket, string $senderId, string $args): void {
        $parts = explode(' ', $args, 2);
        if (count($parts) < 2) {
            socket_write($clientSocket, "Usage: /whisper <client_id> <message>\n");
            return;
        }
        
        $targetId = $parts[0];
        $message = $parts[1];
        
        if (!isset($this->clients[$targetId])) {
            socket_write($clientSocket, "Client '$targetId' not found\n");
            return;
        }
        
        $whisperMessage = "[Whisper from $senderId]: $message\n";
        socket_write($this->clients[$targetId], $whisperMessage);
        socket_write($clientSocket, "Whisper sent to $targetId\n");
    }
    
    private function handleClientDisconnect($clientSocket): void {
        $clientId = $this->getClientId($clientSocket);
        
        socket_close($clientSocket);
        unset($this->clients[$clientId]);
        
        $this->broadcastMessage("Client $clientId left the server\n");
        echo "Client disconnected: $clientId (Remaining: " . count($this->clients) . ")\n";
    }
    
    private function broadcastMessage(string $message, string $excludeClientId = ''): void {
        foreach ($this->clients as $clientId => $clientSocket) {
            if ($clientId !== $excludeClientId) {
                socket_write($clientSocket, $message);
            }
        }
    }
    
    private function getClientId($socket): string {
        foreach ($this->clients as $id => $clientSocket) {
            if ($clientSocket === $socket) {
                return $id;
            }
        }
        
        // Generate new ID for new connections
        return 'client_' . uniqid();
    }
    
    public function stop(): void {
        $this->running = false;
        
        foreach ($this->clients as $clientSocket) {
            socket_close($clientSocket);
        }
        
        socket_close($this->masterSocket);
        echo "Server stopped\n";
    }
}
?>

UDP Socket Programming

UDP Server Implementation

<?php
class UdpServer {
    private $socket;
    private string $host;
    private int $port;
    private bool $running = false;
    private array $clients = [];
    
    public function __construct(string $host = '127.0.0.1', int $port = 8081) {
        $this->host = $host;
        $this->port = $port;
    }
    
    public function start(): void {
        $this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
        
        if (!socket_bind($this->socket, $this->host, $this->port)) {
            throw new RuntimeException('Failed to bind UDP socket');
        }
        
        echo "UDP Server started on {$this->host}:{$this->port}\n";
        $this->running = true;
        
        $this->listenForMessages();
    }
    
    private function listenForMessages(): void {
        while ($this->running) {
            $buffer = '';
            $clientHost = '';
            $clientPort = 0;
            
            $bytesReceived = socket_recvfrom(
                $this->socket,
                $buffer,
                1024,
                0,
                $clientHost,
                $clientPort
            );
            
            if ($bytesReceived === false) {
                continue;
            }
            
            $clientKey = "$clientHost:$clientPort";
            $message = trim($buffer);
            
            echo "UDP message from $clientKey: $message\n";
            
            // Register client
            if (!isset($this->clients[$clientKey])) {
                $this->clients[$clientKey] = [
                    'host' => $clientHost,
                    'port' => $clientPort,
                    'last_seen' => time()
                ];
                echo "New UDP client: $clientKey\n";
            } else {
                $this->clients[$clientKey]['last_seen'] = time();
            }
            
            // Process message
            $response = $this->processMessage($message, $clientKey);
            
            // Send response
            socket_sendto(
                $this->socket,
                $response,
                strlen($response),
                0,
                $clientHost,
                $clientPort
            );
        }
    }
    
    private function processMessage(string $message, string $clientKey): string {
        $parts = explode(' ', $message, 2);
        $command = $parts[0];
        $data = $parts[1] ?? '';
        
        switch ($command) {
            case 'PING':
                return 'PONG';
                
            case 'TIME':
                return date('Y-m-d H:i:s');
                
            case 'ECHO':
                return "ECHO: $data";
                
            case 'CLIENTS':
                return 'CLIENTS: ' . count($this->clients);
                
            case 'BROADCAST':
                $this->broadcastMessage("[$clientKey]: $data", $clientKey);
                return 'MESSAGE_BROADCASTED';
                
            default:
                return "UNKNOWN_COMMAND: $command";
        }
    }
    
    private function broadcastMessage(string $message, string $excludeClient = ''): void {
        foreach ($this->clients as $clientKey => $client) {
            if ($clientKey !== $excludeClient) {
                socket_sendto(
                    $this->socket,
                    $message,
                    strlen($message),
                    0,
                    $client['host'],
                    $client['port']
                );
            }
        }
    }
    
    public function getClientStats(): array {
        $activeClients = 0;
        $staleClients = 0;
        $now = time();
        
        foreach ($this->clients as $client) {
            if ($now - $client['last_seen'] < 300) { // 5 minutes
                $activeClients++;
            } else {
                $staleClients++;
            }
        }
        
        return [
            'total' => count($this->clients),
            'active' => $activeClients,
            'stale' => $staleClients
        ];
    }
    
    public function stop(): void {
        $this->running = false;
        socket_close($this->socket);
        echo "UDP Server stopped\n";
    }
}

// Simple UDP Client for testing
class UdpClient {
    private $socket;
    private string $serverHost;
    private int $serverPort;
    
    public function __construct(string $serverHost = '127.0.0.1', int $serverPort = 8081) {
        $this->serverHost = $serverHost;
        $this->serverPort = $serverPort;
        $this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
    }
    
    public function sendMessage(string $message): string {
        socket_sendto(
            $this->socket,
            $message,
            strlen($message),
            0,
            $this->serverHost,
            $this->serverPort
        );
        
        $response = '';
        socket_recvfrom($this->socket, $response, 1024, 0, $serverHost, $serverPort);
        
        return trim($response);
    }
    
    public function close(): void {
        socket_close($this->socket);
    }
}
?>

WebSocket Server Implementation

WebSocket Protocol Handler

<?php
class WebSocketServer {
    private $socket;
    private array $clients = [];
    private string $host;
    private int $port;
    private bool $running = false;
    
    public function __construct(string $host = '127.0.0.1', int $port = 8082) {
        $this->host = $host;
        $this->port = $port;
    }
    
    public function start(): void {
        $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1);
        socket_bind($this->socket, $this->host, $this->port);
        socket_listen($this->socket, 10);
        
        echo "WebSocket server started on ws://{$this->host}:{$this->port}\n";
        $this->running = true;
        
        $this->eventLoop();
    }
    
    private function eventLoop(): void {
        while ($this->running) {
            $readSockets = [$this->socket];
            $readSockets = array_merge($readSockets, array_values($this->clients));
            
            $writeSockets = [];
            $exceptSockets = [];
            
            if (socket_select($readSockets, $writeSockets, $exceptSockets, 1) < 1) {
                continue;
            }
            
            if (in_array($this->socket, $readSockets)) {
                $this->handleNewConnection();
                $key = array_search($this->socket, $readSockets);
                unset($readSockets[$key]);
            }
            
            foreach ($readSockets as $clientSocket) {
                $this->handleClientData($clientSocket);
            }
        }
    }
    
    private function handleNewConnection(): void {
        $newSocket = socket_accept($this->socket);
        if ($newSocket === false) {
            return;
        }
        
        // Read HTTP headers
        $headers = $this->readHeaders($newSocket);
        
        if ($this->performHandshake($newSocket, $headers)) {
            $clientId = uniqid('ws_client_');
            $this->clients[$clientId] = $newSocket;
            echo "WebSocket client connected: $clientId\n";
            
            // Send welcome message
            $welcomeData = json_encode([
                'type' => 'welcome',
                'client_id' => $clientId,
                'message' => 'Connected to WebSocket server'
            ]);
            $this->sendFrame($newSocket, $welcomeData);
        } else {
            socket_close($newSocket);
        }
    }
    
    private function readHeaders($socket): array {
        $headers = [];
        $buffer = '';
        
        while (strpos($buffer, "\r\n\r\n") === false) {
            $byte = socket_read($socket, 1);
            if ($byte === false || $byte === '') {
                break;
            }
            $buffer .= $byte;
        }
        
        $lines = explode("\r\n", $buffer);
        foreach ($lines as $line) {
            if (strpos($line, ':') !== false) {
                [$key, $value] = explode(':', $line, 2);
                $headers[trim($key)] = trim($value);
            }
        }
        
        return $headers;
    }
    
    private function performHandshake($socket, array $headers): bool {
        if (!isset($headers['Sec-WebSocket-Key'])) {
            return false;
        }
        
        $key = $headers['Sec-WebSocket-Key'];
        $acceptKey = base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
        
        $response = "HTTP/1.1 101 Switching Protocols\r\n" .
                   "Upgrade: websocket\r\n" .
                   "Connection: Upgrade\r\n" .
                   "Sec-WebSocket-Accept: $acceptKey\r\n\r\n";
        
        socket_write($socket, $response);
        
        return true;
    }
    
    private function handleClientData($clientSocket): void {
        $data = socket_read($clientSocket, 2048);
        
        if ($data === false || $data === '') {
            $this->disconnectClient($clientSocket);
            return;
        }
        
        $frame = $this->parseFrame($data);
        
        if ($frame === false) {
            return;
        }
        
        $clientId = $this->getClientId($clientSocket);
        
        switch ($frame['opcode']) {
            case 0x1: // Text frame
                $this->handleTextMessage($clientSocket, $clientId, $frame['payload']);
                break;
                
            case 0x8: // Close frame
                $this->disconnectClient($clientSocket);
                break;
                
            case 0x9: // Ping frame
                $this->sendPong($clientSocket, $frame['payload']);
                break;
                
            case 0xa: // Pong frame
                echo "Received pong from $clientId\n";
                break;
        }
    }
    
    private function parseFrame(string $data): array|false {
        if (strlen($data) < 2) {
            return false;
        }
        
        $firstByte = ord($data[0]);
        $secondByte = ord($data[1]);
        
        $fin = ($firstByte >> 7) & 1;
        $opcode = $firstByte & 0xF;
        $masked = ($secondByte >> 7) & 1;
        $payloadLength = $secondByte & 0x7F;
        
        $offset = 2;
        
        if ($payloadLength === 126) {
            $payloadLength = unpack('n', substr($data, $offset, 2))[1];
            $offset += 2;
        } elseif ($payloadLength === 127) {
            $payloadLength = unpack('J', substr($data, $offset, 8))[1];
            $offset += 8;
        }
        
        if ($masked) {
            $maskingKey = substr($data, $offset, 4);
            $offset += 4;
        }
        
        $payload = substr($data, $offset, $payloadLength);
        
        if ($masked) {
            for ($i = 0; $i < strlen($payload); $i++) {
                $payload[$i] = chr(ord($payload[$i]) ^ ord($maskingKey[$i % 4]));
            }
        }
        
        return [
            'fin' => $fin,
            'opcode' => $opcode,
            'payload' => $payload
        ];
    }
    
    private function handleTextMessage($clientSocket, string $clientId, string $message): void {
        echo "Message from $clientId: $message\n";
        
        $data = json_decode($message, true);
        if ($data === null) {
            return;
        }
        
        switch ($data['type'] ?? '') {
            case 'chat':
                $this->broadcastMessage([
                    'type' => 'chat',
                    'client_id' => $clientId,
                    'message' => $data['message'] ?? '',
                    'timestamp' => time()
                ], $clientId);
                break;
                
            case 'ping':
                $this->sendFrame($clientSocket, json_encode([
                    'type' => 'pong',
                    'timestamp' => time()
                ]));
                break;
                
            case 'get_clients':
                $this->sendFrame($clientSocket, json_encode([
                    'type' => 'client_list',
                    'clients' => array_keys($this->clients),
                    'count' => count($this->clients)
                ]));
                break;
        }
    }
    
    private function sendFrame($socket, string $data, int $opcode = 0x1): void {
        $dataLength = strlen($data);
        $frame = '';
        
        // First byte: FIN (1) + RSV (000) + Opcode (4 bits)
        $frame .= chr(0x80 | $opcode);
        
        // Payload length
        if ($dataLength < 126) {
            $frame .= chr($dataLength);
        } elseif ($dataLength < 65536) {
            $frame .= chr(126) . pack('n', $dataLength);
        } else {
            $frame .= chr(127) . pack('J', $dataLength);
        }
        
        $frame .= $data;
        
        socket_write($socket, $frame);
    }
    
    private function sendPong($socket, string $data): void {
        $this->sendFrame($socket, $data, 0xa);
    }
    
    private function broadcastMessage(array $data, string $excludeClientId = ''): void {
        $message = json_encode($data);
        
        foreach ($this->clients as $clientId => $clientSocket) {
            if ($clientId !== $excludeClientId) {
                $this->sendFrame($clientSocket, $message);
            }
        }
    }
    
    private function disconnectClient($clientSocket): void {
        $clientId = $this->getClientId($clientSocket);
        
        socket_close($clientSocket);
        unset($this->clients[$clientId]);
        
        echo "WebSocket client disconnected: $clientId\n";
        
        // Notify other clients
        $this->broadcastMessage([
            'type' => 'client_disconnected',
            'client_id' => $clientId
        ]);
    }
    
    private function getClientId($socket): string {
        foreach ($this->clients as $id => $clientSocket) {
            if ($clientSocket === $socket) {
                return $id;
            }
        }
        return 'unknown';
    }
    
    public function stop(): void {
        $this->running = false;
        
        foreach ($this->clients as $clientSocket) {
            socket_close($clientSocket);
        }
        
        socket_close($this->socket);
        echo "WebSocket server stopped\n";
    }
}
?>

Advanced Socket Features

SSL/TLS Socket Implementation

<?php
class SecureSocketServer {
    private $context;
    private $socket;
    private array $clients = [];
    private string $host;
    private int $port;
    
    public function __construct(string $host = '0.0.0.0', int $port = 8443) {
        $this->host = $host;
        $this->port = $port;
        $this->setupSslContext();
    }
    
    private function setupSslContext(): void {
        $this->context = stream_context_create([
            'ssl' => [
                'local_cert' => '/path/to/server.pem',
                'local_pk' => '/path/to/server.key',
                'allow_self_signed' => true,
                'verify_peer' => false,
                'verify_peer_name' => false,
                'security_level' => 1
            ]
        ]);
    }
    
    public function start(): void {
        $this->socket = stream_socket_server(
            "ssl://{$this->host}:{$this->port}",
            $errno,
            $errstr,
            STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
            $this->context
        );
        
        if (!$this->socket) {
            throw new RuntimeException("Failed to create SSL server: $errstr ($errno)");
        }
        
        echo "Secure server started on ssl://{$this->host}:{$this->port}\n";
        
        while (true) {
            $client = stream_socket_accept($this->socket, -1);
            
            if ($client) {
                $clientId = uniqid('ssl_client_');
                $this->clients[$clientId] = $client;
                echo "Secure client connected: $clientId\n";
                
                $this->handleSecureClient($client, $clientId);
            }
        }
    }
    
    private function handleSecureClient($client, string $clientId): void {
        while (!feof($client)) {
            $data = fread($client, 1024);
            
            if ($data === false || $data === '') {
                break;
            }
            
            $message = trim($data);
            echo "Secure message from $clientId: $message\n";
            
            // Echo back with encryption confirmation
            $response = "SECURE: $message\n";
            fwrite($client, $response);
        }
        
        fclose($client);
        unset($this->clients[$clientId]);
        echo "Secure client disconnected: $clientId\n";
    }
}
?>

Performance Monitoring and Statistics

<?php
class SocketServerMonitor {
    private array $stats = [
        'connections_total' => 0,
        'connections_active' => 0,
        'messages_sent' => 0,
        'messages_received' => 0,
        'bytes_sent' => 0,
        'bytes_received' => 0,
        'errors' => 0,
        'start_time' => 0
    ];
    
    private array $clientStats = [];
    
    public function __construct() {
        $this->stats['start_time'] = time();
    }
    
    public function recordConnection(string $clientId): void {
        $this->stats['connections_total']++;
        $this->stats['connections_active']++;
        
        $this->clientStats[$clientId] = [
            'connected_at' => time(),
            'messages_sent' => 0,
            'messages_received' => 0,
            'bytes_sent' => 0,
            'bytes_received' => 0
        ];
    }
    
    public function recordDisconnection(string $clientId): void {
        $this->stats['connections_active']--;
        
        if (isset($this->clientStats[$clientId])) {
            $this->clientStats[$clientId]['disconnected_at'] = time();
        }
    }
    
    public function recordMessageSent(string $clientId, int $bytes): void {
        $this->stats['messages_sent']++;
        $this->stats['bytes_sent'] += $bytes;
        
        if (isset($this->clientStats[$clientId])) {
            $this->clientStats[$clientId]['messages_sent']++;
            $this->clientStats[$clientId]['bytes_sent'] += $bytes;
        }
    }
    
    public function recordMessageReceived(string $clientId, int $bytes): void {
        $this->stats['messages_received']++;
        $this->stats['bytes_received'] += $bytes;
        
        if (isset($this->clientStats[$clientId])) {
            $this->clientStats[$clientId]['messages_received']++;
            $this->clientStats[$clientId]['bytes_received'] += $bytes;
        }
    }
    
    public function recordError(): void {
        $this->stats['errors']++;
    }
    
    public function getStats(): array {
        $uptime = time() - $this->stats['start_time'];
        
        return array_merge($this->stats, [
            'uptime_seconds' => $uptime,
            'uptime_formatted' => $this->formatUptime($uptime),
            'avg_messages_per_second' => $uptime > 0 ? round(($this->stats['messages_sent'] + $this->stats['messages_received']) / $uptime, 2) : 0,
            'avg_bytes_per_second' => $uptime > 0 ? round(($this->stats['bytes_sent'] + $this->stats['bytes_received']) / $uptime, 2) : 0
        ]);
    }
    
    public function getClientStats(string $clientId): ?array {
        return $this->clientStats[$clientId] ?? null;
    }
    
    public function getAllClientStats(): array {
        return $this->clientStats;
    }
    
    public function getTopClients(int $limit = 10): array {
        $clients = $this->clientStats;
        
        uasort($clients, function($a, $b) {
            return ($b['messages_sent'] + $b['messages_received']) - ($a['messages_sent'] + $a['messages_received']);
        });
        
        return array_slice($clients, 0, $limit, true);
    }
    
    private function formatUptime(int $seconds): string {
        $days = floor($seconds / 86400);
        $hours = floor(($seconds % 86400) / 3600);
        $minutes = floor(($seconds % 3600) / 60);
        $seconds = $seconds % 60;
        
        return sprintf('%dd %02d:%02d:%02d', $days, $hours, $minutes, $seconds);
    }
    
    public function exportStats(string $format = 'json'): string {
        $data = [
            'server_stats' => $this->getStats(),
            'client_stats' => $this->clientStats,
            'export_time' => date('Y-m-d H:i:s')
        ];
        
        switch ($format) {
            case 'json':
                return json_encode($data, JSON_PRETTY_PRINT);
                
            case 'csv':
                return $this->exportToCsv($data);
                
            default:
                throw new InvalidArgumentException("Unsupported format: $format");
        }
    }
    
    private function exportToCsv(array $data): string {
        $output = "Server Statistics\n";
        $output .= "Metric,Value\n";
        
        foreach ($data['server_stats'] as $key => $value) {
            $output .= "$key,$value\n";
        }
        
        $output .= "\nClient Statistics\n";
        $output .= "Client ID,Connected At,Messages Sent,Messages Received,Bytes Sent,Bytes Received\n";
        
        foreach ($data['client_stats'] as $clientId => $stats) {
            $output .= sprintf(
                "%s,%s,%d,%d,%d,%d\n",
                $clientId,
                date('Y-m-d H:i:s', $stats['connected_at']),
                $stats['messages_sent'],
                $stats['messages_received'],
                $stats['bytes_sent'],
                $stats['bytes_received']
            );
        }
        
        return $output;
    }
}
?>

Real-World Applications

Chat Server Implementation

<?php
class ChatServer extends MultiClientTcpServer {
    private array $rooms = [];
    private array $clientRooms = [];
    private SocketServerMonitor $monitor;
    private array $bannedIps = [];
    private array $rateLimits = [];
    
    public function __construct(string $host = '127.0.0.1', int $port = 8080) {
        parent::__construct($host, $port);
        $this->monitor = new SocketServerMonitor();
        $this->rooms['general'] = [];
    }
    
    protected function handleNewConnection(): void {
        $clientIp = $this->getClientIp();
        
        if ($this->isIpBanned($clientIp)) {
            $newSocket = socket_accept($this->masterSocket);
            socket_write($newSocket, "You are banned from this server.\n");
            socket_close($newSocket);
            return;
        }
        
        if ($this->isRateLimited($clientIp)) {
            $newSocket = socket_accept($this->masterSocket);
            socket_write($newSocket, "Rate limit exceeded. Please try again later.\n");
            socket_close($newSocket);
            return;
        }
        
        parent::handleNewConnection();
        
        // Auto-join general room
        $clientId = $this->getLastConnectedClientId();
        $this->joinRoom($clientId, 'general');
    }
    
    private function joinRoom(string $clientId, string $roomName): void {
        if (!isset($this->rooms[$roomName])) {
            $this->rooms[$roomName] = [];
        }
        
        // Leave current room
        if (isset($this->clientRooms[$clientId])) {
            $oldRoom = $this->clientRooms[$clientId];
            unset($this->rooms[$oldRoom][$clientId]);
            
            $this->broadcastToRoom($oldRoom, "$clientId left the room\n", $clientId);
        }
        
        // Join new room
        $this->rooms[$roomName][$clientId] = true;
        $this->clientRooms[$clientId] = $roomName;
        
        $this->broadcastToRoom($roomName, "$clientId joined the room\n", $clientId);
        
        $clientSocket = $this->clients[$clientId];
        socket_write($clientSocket, "Joined room: $roomName\n");
        
        // Send room info
        $roomUsers = array_keys($this->rooms[$roomName]);
        $userList = "Users in $roomName: " . implode(', ', $roomUsers) . "\n";
        socket_write($clientSocket, $userList);
    }
    
    private function broadcastToRoom(string $roomName, string $message, string $excludeClientId = ''): void {
        if (!isset($this->rooms[$roomName])) {
            return;
        }
        
        foreach (array_keys($this->rooms[$roomName]) as $clientId) {
            if ($clientId !== $excludeClientId && isset($this->clients[$clientId])) {
                socket_write($this->clients[$clientId], $message);
                $this->monitor->recordMessageSent($clientId, strlen($message));
            }
        }
    }
    
    protected function handleCommand($clientSocket, string $clientId, string $command): void {
        $parts = explode(' ', $command, 2);
        $cmd = $parts[0];
        $args = $parts[1] ?? '';
        
        switch ($cmd) {
            case '/join':
                if (empty($args)) {
                    socket_write($clientSocket, "Usage: /join <room_name>\n");
                } else {
                    $this->joinRoom($clientId, $args);
                }
                break;
                
            case '/rooms':
                $roomList = "Available rooms:\n";
                foreach ($this->rooms as $roomName => $users) {
                    $userCount = count($users);
                    $roomList .= "  $roomName ($userCount users)\n";
                }
                socket_write($clientSocket, $roomList);
                break;
                
            case '/stats':
                $stats = $this->monitor->getStats();
                $statsMessage = "Server Statistics:\n";
                $statsMessage .= "  Active connections: {$stats['connections_active']}\n";
                $statsMessage .= "  Total messages: {$stats['messages_sent']}\n";
                $statsMessage .= "  Uptime: {$stats['uptime_formatted']}\n";
                socket_write($clientSocket, $statsMessage);
                break;
                
            case '/kick':
                if ($this->isAdmin($clientId)) {
                    $this->kickUser($args, $clientId);
                } else {
                    socket_write($clientSocket, "Permission denied\n");
                }
                break;
                
            case '/ban':
                if ($this->isAdmin($clientId)) {
                    $this->banUser($args, $clientId);
                } else {
                    socket_write($clientSocket, "Permission denied\n");
                }
                break;
                
            default:
                parent::handleCommand($clientSocket, $clientId, $command);
        }
    }
    
    protected function handleClientActivity($clientSocket): void {
        $clientId = $this->getClientId($clientSocket);
        
        if (!$this->checkRateLimit($clientId)) {
            socket_write($clientSocket, "Slow down! You're sending messages too fast.\n");
            return;
        }
        
        parent::handleClientActivity($clientSocket);
    }
    
    private function checkRateLimit(string $clientId): bool {
        $now = time();
        $window = 60; // 1 minute window
        $maxMessages = 30; // Max 30 messages per minute
        
        if (!isset($this->rateLimits[$clientId])) {
            $this->rateLimits[$clientId] = [];
        }
        
        // Clean old entries
        $this->rateLimits[$clientId] = array_filter(
            $this->rateLimits[$clientId],
            function($timestamp) use ($now, $window) {
                return $now - $timestamp < $window;
            }
        );
        
        if (count($this->rateLimits[$clientId]) >= $maxMessages) {
            return false;
        }
        
        $this->rateLimits[$clientId][] = $now;
        return true;
    }
    
    private function isAdmin(string $clientId): bool {
        // Implement admin check logic
        return strpos($clientId, 'admin_') === 0;
    }
    
    private function kickUser(string $targetClientId, string $adminId): void {
        if (!isset($this->clients[$targetClientId])) {
            socket_write($this->clients[$adminId], "User $targetClientId not found\n");
            return;
        }
        
        $targetSocket = $this->clients[$targetClientId];
        socket_write($targetSocket, "You have been kicked by an administrator\n");
        $this->handleClientDisconnect($targetSocket);
        
        $this->broadcastMessage("$targetClientId was kicked by $adminId\n");
    }
    
    private function banUser(string $targetClientId, string $adminId): void {
        $clientIp = $this->getClientIpById($targetClientId);
        if ($clientIp) {
            $this->bannedIps[] = $clientIp;
            $this->kickUser($targetClientId, $adminId);
            socket_write($this->clients[$adminId], "User $targetClientId (IP: $clientIp) has been banned\n");
        }
    }
    
    private function isIpBanned(string $ip): bool {
        return in_array($ip, $this->bannedIps);
    }
    
    private function isRateLimited(string $ip): bool {
        // Implement IP-based rate limiting
        return false;
    }
    
    private function getClientIp(): string {
        // Get client IP from socket
        return '127.0.0.1'; // Placeholder
    }
    
    private function getClientIpById(string $clientId): ?string {
        // Get client IP by client ID
        return '127.0.0.1'; // Placeholder
    }
    
    private function getLastConnectedClientId(): string {
        return array_key_last($this->clients);
    }
}
?>

Frequently Asked Questions (FAQ)

What's the difference between TCP and UDP sockets in PHP?

TCP (Transmission Control Protocol) provides reliable, ordered delivery of data with error checking and flow control. It's connection-oriented, meaning a connection must be established before data transfer. UDP (User Datagram Protocol) is connectionless and faster but doesn't guarantee delivery or order. Use TCP for applications requiring reliability (chat, file transfer) and UDP for real-time applications where speed matters more than reliability (gaming, live streaming).

How do I handle multiple clients efficiently in PHP socket servers?

Use the socket_select() function to monitor multiple sockets simultaneously without blocking. This allows your server to handle many concurrent connections in a single thread. For even better performance, consider using the ReactPHP library or implementing a multi-process architecture with pcntl_fork() for CPU-intensive operations.

Can PHP socket servers handle thousands of concurrent connections?

While PHP can handle hundreds of concurrent connections with proper optimization, it's not ideal for thousands of connections due to memory usage and the lack of native async I/O. For high-concurrency scenarios, consider using ReactPHP, Swoole, or dedicated solutions like Node.js or Go. However, for moderate loads (under 500 concurrent connections), PHP sockets work well.

How do I implement SSL/TLS encryption for socket connections?

Use PHP's stream context with SSL options for encrypted connections. Create a stream context with SSL certificates and use stream_socket_server() with the ssl:// protocol. Ensure you have valid SSL certificates and configure the context properly for production use. For WebSocket over SSL (WSS), implement the WebSocket protocol over an SSL stream.

Conclusion

PHP socket programming opens up a world of possibilities for building real-time, interactive applications without relying on external services. From simple TCP servers to complex WebSocket implementations, you now have the tools to create scalable communication systems that can handle everything from chat applications to live data feeds.

The key to successful socket programming lies in understanding your application's requirements: choose TCP for reliability, UDP for speed, and WebSockets for browser-based real-time communication. Always implement proper error handling, rate limiting, and security measures to ensure your socket servers are production-ready.

Start with simple implementations and gradually add features like authentication, rate limiting, and monitoring. With practice and the examples provided in this guide, you'll be building robust real-time applications that can compete with commercial solutions.

Ready to build your first real-time PHP application? Start with the basic TCP server example and expand from there. Share your socket programming projects and experiences in the comments below!

Share this article

Add Comment

No comments yet. Be the first to comment!

More from Php