You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 03:30:05