求助:XAMPP环境下JS+PHP WebSocket双人聊天连接失败
问题描述
从未使用过WebSocket,尝试用JavaScript+PHP开发仅限两人的网页聊天功能,但客户端与服务器无法建立连接。PHP端频繁报错:Warning: socket_bind(): Unable to bind address [xxx],更换端口号也无法解决,开发环境为XAMPP。
客户端代码(script.js)
// WebSocket连接 const socket = new WebSocket('ws://localhost:1234'); // WebSocket事件处理 socket.onopen = function() { console.log('WebSocket连接已建立。'); }; socket.onmessage = function(event) { const message = JSON.parse(event.data); displayMessage(message); }; socket.onclose = function() { console.log('Socket连接已关闭。'); }; // 发送消息 document.getElementById('chat-form').addEventListener('submit', function(event) { event.preventDefault(); const messageInput = document.getElementById('message-input'); const message = { text: messageInput.value }; socket.send(JSON.stringify(message)); messageInput.value = ''; }); // 显示消息 function displayMessage(message) { const chatMessages = document.getElementById('chat-messages'); const messageElement = document.createElement('div'); messageElement.textContent = message.text; chatMessages.appendChild(messageElement); chatMessages.scrollTop = chatMessages.scrollHeight; }
服务器端代码(server.php)
<?php // WebSocket初始化 $host = 'localhost'; $port = 1234; // 创建socket $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); if ($socket === false) { echo "无法创建socket: " . socket_strerror(socket_last_error()); exit; } // 绑定socket $result = socket_bind($socket, $host, $port); if ($result === false) { echo "无法绑定socket: " . socket_strerror(socket_last_error($socket)); exit; } // 监听socket $result = socket_listen($socket); if ($result === false) { echo "无法监听socket: " . socket_strerror(socket_last_error($socket)); exit; } // 接受WebSocket连接 $clientSocket = socket_accept($socket); if ($clientSocket === false) { echo "客户端未连接: " . socket_strerror(socket_last_error($socket)); exit; } $message = array('text' => '欢迎加入聊天!'); socket_write($clientSocket, json_encode($message)); // 消息处理 while (true) { // 读取客户端消息 $clientMessage = socket_read($clientSocket, 1024); if ($clientMessage === false) { echo "无法读取socket: " . socket_strerror(socket_last_error($clientSocket)); break; } // 发送消息给客户端 socket_write($clientSocket, $clientMessage); } // 关闭socket socket_close($clientSocket); socket_close($socket); ?>
问题解决步骤
1. 修复socket_bind绑定失败
- 检查端口占用:Windows执行
netstat -ano | findstr :1234,Mac/Linux执行lsof -i :1234,确认端口是否被其他进程占用,更换未被占用的端口(如8080、9000),同步修改客户端和服务器代码的端口号。 - 提升运行权限:Windows以管理员身份打开命令提示符,执行
php server.php;Mac/Linux执行sudo php server.php——1024以下端口需要管理员权限才能绑定。 - 修改监听地址:将服务器代码中的
$host = 'localhost';改为$host = '0.0.0.0';,让socket监听所有网络接口,避免localhost解析问题。
2. 补充WebSocket协议握手逻辑
你的PHP代码仅创建了普通TCP socket,但WebSocket连接需要完成握手协议才能建立,这是客户端无法连接的核心原因。替换为以下支持握手的服务器代码:
<?php $host = '0.0.0.0'; $port = 1234; // 创建socket并设置复用选项 $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1); if ($socket === false) { echo "无法创建socket: " . socket_strerror(socket_last_error()); exit; } // 绑定并监听(最多2个连接) $result = socket_bind($socket, $host, $port); if ($result === false) { echo "无法绑定socket: " . socket_strerror(socket_last_error($socket)); exit; } $result = socket_listen($socket, 2); if ($result === false) { echo "无法监听socket: " . socket_strerror(socket_last_error($socket)); exit; } // 存储客户端连接 $clients = []; $clients[] = $socket; while (true) { $read = $clients; $write = null; $except = null; // 多路复用监听socket活动 socket_select($read, $write, $except, null); foreach ($read as $socketItem) { // 处理新连接 if ($socketItem === $socket) { $client = socket_accept($socket); if ($client === false) continue; // 读取握手请求并完成握手 $handshake = socket_read($client, 1024); if (!$handshake) continue; $headers = []; $lines = preg_split("/\r\n/", $handshake); foreach ($lines as $line) { $line = trim($line); if (strpos($line, ':') !== false) { list($key, $value) = explode(':', $line, 2); $headers[strtolower(trim($key))] = trim($value); } } $secKey = $headers['sec-websocket-key']; $secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))); $response = "HTTP/1.1 101 Switching Protocols\r\n"; $response .= "Upgrade: websocket\r\n"; $response .= "Connection: Upgrade\r\n"; $response .= "Sec-WebSocket-Accept: $secAccept\r\n\r\n"; socket_write($client, $response, strlen($response)); $clients[] = $client; echo "新客户端连接\n"; // 发送欢迎消息 $welcome = json_encode(['text' => '欢迎加入聊天!']); sendWebSocketMessage($client, $welcome); } else { // 处理客户端消息 $data = socket_read($socketItem, 1024); if (!$data) { // 客户端断开连接 $index = array_search($socketItem, $clients); socket_close($socketItem); unset($clients[$index]); echo "客户端断开连接\n"; continue; } $decodedMessage = decodeWebSocketMessage($data); if (!$decodedMessage) continue; // 转发消息给另一个客户端 foreach ($clients as $client) { if ($client !== $socket && $client !== $socketItem) { sendWebSocketMessage($client, $decodedMessage); } } } } } // WebSocket消息编码 function sendWebSocketMessage($socket, $message) { $length = strlen($message); $frame = chr(0x81); if ($length <= 125) { $frame .= chr($length); } elseif ($length > 125 && $length < 65536) { $frame .= chr(126); $frame .= pack('n', $length); } else { $frame .= chr(127); $frame .= pack('N', $length); } $frame .= $message; socket_write($socket, $frame, strlen($frame)); } // WebSocket消息解码 function decodeWebSocketMessage($data) { $firstByte = ord($data[0]); $opcode = $firstByte & 0x0F; if ($opcode !== 1) return null; $secondByte = ord($data[1]); $masked = ($secondByte & 0x80) !== 0; $payloadLength = $secondByte & 0x7F; $offset = 2; $mask = []; if ($masked) { $mask = [ ord($data[2]), ord($data[3]), ord($data[4]), ord($data[5]) ]; $offset += 4; } 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; } $payload = substr($data, $offset, $payloadLength); if ($masked) { for ($i = 0; $i < $payloadLength; $i++) { $payload[$i] = chr(ord($payload[$i]) ^ $mask[$i % 4]); } } return $payload; } socket_close($socket); ?>
3. 运行注意事项
- 不要通过浏览器访问
server.php,必须在命令行执行php server.php启动WebSocket服务器。 - 客户端页面需通过XAMPP的Apache服务访问(如
http://localhost/chat.html),不能直接打开本地HTML文件,否则会触发跨域或协议错误。 - 临时关闭防火墙测试,确认端口未被拦截。
内容的提问来源于stack exchange,提问作者Elecim91
相关产品推荐
相关产品推荐

