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

共享主机环境下PHP TCP Socket Server开发需求(适配ESP32)

PHP TCP Socket服务器完善方案(支持HTML页面发消息)

一、修复并优化TCP服务器代码

先修正原代码中的重复函数问题,同时调整客户端存储方式为关联数组,用客户端地址作为键、连接资源作为值,方便后续精准定位发送对象,同时实现10个客户端的连接限制:

<?php
error_reporting(E_ALL);
set_time_limit(0); // 禁用脚本超时,适配共享主机环境

$host = '0.0.0.0';
$port = 8085;
$max_clients = 10; // 限制最大连接数
$clients = []; // 关联数组:key=客户端地址,value=连接资源
$onlineUsersFile = 'online_users.json';
$messageQueueFile = 'message_queue.json'; // 消息队列文件,用于中转Web页面的发送请求

$server = stream_socket_server("tcp://$host:$port", $errno, $errstr);
if (!$server) {
    die("Error: $errstr ($errno)\n");
}
echo "Server started on $host:$port\n";

// 初始化消息队列文件
if (!file_exists($messageQueueFile)) {
    file_put_contents($messageQueueFile, json_encode([]));
}

while (true) {
    $read = array_values($clients);
    $read[] = $server;

    if (!stream_select($read, $write, $except, 1)) {
        continue;
    }

    // 处理新连接
    if (in_array($server, $read)) {
        if (count($clients) >= $max_clients) {
            $newClient = stream_socket_accept($server);
            stream_socket_sendto($newClient, "Server is full, try later.\n");
            fclose($newClient);
            echo "Rejected new client - max connections reached\n";
            continue;
        }
        $newClient = stream_socket_accept($server);
        socket_set_blocking($newClient, 0);
        $clientAddr = stream_socket_get_name($newClient, true);
        $clients[$clientAddr] = $newClient;
        
        stream_socket_sendto($newClient, "Welcome to the server!\n");
        echo "New client connected: $clientAddr\n";
        broadcastToAll($clients, "New client joined: $clientAddr\n");
        
        // 移除服务器在read数组中的条目
        $key = array_search($server, $read);
        unset($read[$key]);
    }

    // 处理客户端消息或断开
    foreach ($read as $client) {
        stream_set_timeout($client, 1);
        $data = fread($client, 1024);
        $clientAddr = stream_socket_get_name($client, true);
        
        if ($data === false || feof($client)) {
            fclose($client);
            unset($clients[$clientAddr]);
            echo "Client disconnected: $clientAddr\n";
            broadcastToAll($clients, "Client left: $clientAddr\n");
            continue;
        }
        
        if (!empty(trim($data))) {
            echo "Received from $clientAddr: $data";
            broadcast($client, "[$clientAddr] $data", $clients);
        }
    }

    // 处理Web页面发来的消息队列
    $queue = json_decode(file_get_contents($messageQueueFile), true) ?? [];
    if (!empty($queue)) {
        foreach ($queue as $index => $msgItem) {
            if (isset($clients[$msgItem['target']])) {
                stream_socket_sendto($clients[$msgItem['target']], "Admin: {$msgItem['message']}");
                echo "Sent to {$msgItem['target']}: {$msgItem['message']}";
            }
            unset($queue[$index]);
        }
        file_put_contents($messageQueueFile, json_encode(array_values($queue)));
    }

    updateOnlineUsers($clients, $onlineUsersFile);
    usleep(10000); // 降低CPU占用
}

// 向除发送者外的所有客户端广播
function broadcast($sender, $message, $clients) {
    $senderAddr = stream_socket_get_name($sender, true);
    foreach ($clients as $addr => $client) {
        if ($addr !== $senderAddr) {
            stream_socket_sendto($client, $message);
        }
    }
}

// 向所有客户端广播
function broadcastToAll($clients, $message) {
    foreach ($clients as $client) {
        stream_socket_sendto($client, $message);
    }
}

// 更新在线用户列表到JSON文件
function updateOnlineUsers($clients, $file) {
    $onlineUsers = array_keys($clients);
    file_put_contents($file, json_encode($onlineUsers));
}
?>

二、创建消息中转接口(send_message.php)

这个文件负责接收HTML页面的请求,要么返回在线客户端列表,要么将发送请求写入消息队列:

<?php
$onlineUsersFile = 'online_users.json';
$messageQueueFile = 'message_queue.json';

// 初始化队列文件
if (!file_exists($messageQueueFile)) {
    file_put_contents($messageQueueFile, json_encode([]));
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $clientAddr = $_POST['client_addr'] ?? '';
    $message = $_POST['message'] ?? '';
    
    if (empty($clientAddr) || empty($message)) {
        echo json_encode(['status' => 'error', 'msg' => 'Missing parameters']);
        exit;
    }
    
    // 将消息写入队列
    $queue = json_decode(file_get_contents($messageQueueFile), true) ?? [];
    $queue[] = [
        'target' => $clientAddr,
        'message' => $message . "\n"
    ];
    file_put_contents($messageQueueFile, json_encode($queue));
    
    echo json_encode(['status' => 'success', 'msg' => 'Message queued for sending']);
    exit;
}

// GET请求返回在线客户端列表
$onlineUsers = json_decode(file_get_contents($onlineUsersFile), true) ?? [];
echo json_encode(['online_clients' => $onlineUsers]);
?>

三、创建HTML管理页面(admin.html)

这个页面展示在线客户端列表,提供表单实现向指定客户端发送消息的功能:

<!DOCTYPE html>
<html>
<head>
    <title>TCP Socket Server Admin</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 20px auto; padding: 0 20px; }
        .client-section { margin: 20px 0; padding: 15px; border: 1px solid #eee; border-radius: 6px; }
        .client-item { padding: 8px; margin: 5px 0; background: #f8f9fa; border-radius: 4px; }
        #send-form { margin-top: 25px; }
        .form-group { margin: 12px 0; }
        label { display: block; margin-bottom: 6px; font-weight: 500; }
        select, textarea { width: 100%; padding: 8px; box-sizing: border-box; border: 1px solid #ddd; border-radius: 4px; }
        button { padding: 10px 20px; background: #0d6efd; color: #fff; border: none; border-radius: 4px; cursor: pointer; }
        button:hover { background: #0b5ed7; }
        .status { margin-top: 15px; padding: 10px; border-radius: 4px; display: none; }
        .status.success { display: block; background: #d1e7dd; color: #0f5132; }
        .status.error { display: block; background: #f8d7da; color: #842029; }
    </style>
</head>
<body>
    <h1>TCP Socket Server Admin Panel</h1>
    
    <div class="client-section">
        <h2>Online Clients</h2>
        <div id="client-list"></div>
    </div>

    <form id="send-form">
        <div class="form-group">
            <label for="client-select">Target Client:</label>
            <select id="client-select" required></select>
        </div>
        <div class="form-group">
            <label for="message-content">Message:</label>
            <textarea id="message-content" rows="3" placeholder="Enter your message..." required></textarea>
        </div>
        <button type="submit">Send Message</button>
    </form>

    <div id="status" class="status"></div>

    <script>
        // 刷新在线客户端列表
        function loadClients() {
            fetch('send_message.php')
                .then(res => res.json())
                .then(data => {
                    const clientList = document.getElementById('client-list');
                    const clientSelect = document.getElementById('client-select');
                    clientList.innerHTML = '';
                    clientSelect.innerHTML = '';

                    if (data.online_clients.length === 0) {
                        clientList.innerHTML = '<p>No clients currently online</p>';
                        clientSelect.innerHTML = '<option value="" disabled selected>No clients available</option>';
                        clientSelect.disabled = true;
                        return;
                    }

                    clientSelect.disabled = false;
                    data.online_clients.forEach(addr => {
                        // 添加到展示列表
                        const item = document.createElement('div');
                        item.className = 'client-item';
                        item.textContent = addr;
                        clientList.appendChild(item);

                        // 添加到下拉选择框
                        const option = document.createElement('option');
                        option.value = addr;
                        option.textContent = addr;
                        clientSelect.appendChild(option);
                    });
                })
                .catch(err => {
                    showStatus('Failed to load client list: ' + err.message, 'error');
                });
        }

        // 显示状态提示
        function showStatus(text, type) {
            const statusDiv = document.getElementById('status');
            statusDiv.textContent = text;
            statusDiv.className = `status ${type}`;
            setTimeout(() => {
                statusDiv.className = 'status';
            }, 3000);
        }

        // 处理表单提交
        document.getElementById('send-form').addEventListener('submit', function(e) {
            e.preventDefault();
            const clientAddr = document.getElementById('client-select').value;
            const message = document.getElementById('message-content').value;

            fetch('send_message.php', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
                body: `client_addr=${encodeURIComponent(clientAddr)}&message=${encodeURIComponent(message)}`
            })
            .then(res => res.json())
            .then(data => {
                if (data.status === 'success') {
                    showStatus(data.msg, 'success');
                    document.getElementById('message-content').value = '';
                } else {
                    showStatus(data.msg, 'error');
                }
            })
            .catch(err => {
                showStatus('Failed to send message: ' + err.message, 'error');
            });
        });

        // 页面加载时初始化,每5秒刷新一次客户端列表
        loadClients();
        setInterval(loadClients, 5000);
    </script>
</body>
</html>

四、部署注意事项

  • 确保online_users.json和message_queue.json文件有写入权限(可通过FTP设置文件权限为755或777)
  • 启动TCP服务器:由于共享主机无控制台,可创建一个启动脚本(如start_server.php),通过浏览器访问一次来启动服务器,注意主机商是否允许长期运行脚本
  • 确认端口8085在主机商处已开放,若无法使用该端口,可更换为主机允许的端口

内容的提问来源于stack exchange,提问作者MMahya RA

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 05:18:12