本文目录导读:

在PHP中实现“双人操作”通常有几种常见的应用场景,我这里针对实时双人协作(如双人编辑文档、双人游戏对战等)给出几种主流实现方案:
最基础方案:轮询 + 数据库(适合学习/小规模)
// 用户A写入操作
// save_action.php
<?php
session_start();
$action = $_POST['action'];
$user_id = $_SESSION['user_id'];
// 存入数据库
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$stmt = $pdo->prepare("INSERT INTO actions (user_id, action, created_at) VALUES (?, ?, NOW())");
$stmt->execute([$user_id, $action]);
// 返回给对方的最新状态
$stmt = $pdo->query("SELECT * FROM actions WHERE user_id != $user_id ORDER BY id DESC LIMIT 1");
$other_action = $stmt->fetch();
echo json_encode($other_action);
?>
<!-- 前端定时轮询 -->
<script>
function checkOtherAction() {
fetch('check_actions.php')
.then(res => res.json())
.then(data => {
if(data.action) {
// 执行对方操作
applyAction(data.action);
}
});
}
setInterval(checkOtherAction, 1000); // 每秒检查
</script>
推荐方案:WebSocket + Ratchet(实时双向)
安装依赖
composer require cboden/ratchet
服务端代码 (chat_server.php)
<?php
require 'vendor/autoload.php';
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class DualPlayer implements MessageComponentInterface {
protected $clients;
protected $rooms = [];
public function __construct() {
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn) {
$this->clients->attach($conn);
echo "新连接: {$conn->resourceId}\n";
}
public function onMessage(ConnectionInterface $from, $msg) {
$data = json_decode($msg);
switch($data->type) {
case 'join_room':
// 加入房间
$room = $data->room;
$this->rooms[$room][] = $from;
$from->room = $room;
// 如果有两人,通知开始
if(count($this->rooms[$room]) == 2) {
foreach($this->rooms[$room] as $client) {
$client->send(json_encode([
'type' => 'game_start',
'message' => '双方已就绪!'
]));
}
}
break;
case 'action':
// 转发动作给房间内另一个人
$room = $from->room;
foreach($this->rooms[$room] as $client) {
if($client !== $from) {
$client->send(json_encode([
'type' => 'opponent_action',
'action' => $data->action
]));
}
}
break;
}
}
public function onClose(ConnectionInterface $conn) {
$this->clients->detach($conn);
if(isset($conn->room)) {
unset($this->rooms[$conn->room]);
}
}
public function onError(ConnectionInterface $conn, \Exception $e) {
$conn->close();
}
}
// 启动WebSocket服务器
$server = IoServer::factory(
new HttpServer(
new WsServer(
new DualPlayer()
)
),
8080
);
echo "WebSocket服务器运行在 ws://localhost:8080\n";
$server->run();
前端HTML (dual_game.html)
<!DOCTYPE html>
<html>
<head>双人协作</title>
</head>
<body>
<div id="status">等待对手加入...</div>
<div id="gameArea">
<button onclick="sendAction('left')">←</button>
<button onclick="sendAction('right')">→</button>
<button onclick="sendAction('jump')">↑</button>
</div>
<div id="opponentAction"></div>
<script>
const ws = new WebSocket('ws://localhost:8080');
const roomId = 'room_' + Math.floor(Math.random() * 1000);
ws.onopen = () => {
ws.send(JSON.stringify({
type: 'join_room',
room: roomId
}));
};
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
if(data.type === 'game_start') {
document.getElementById('status').textContent = data.message;
} else if(data.type === 'opponent_action') {
document.getElementById('opponentAction').textContent =
'对手执行: ' + data.action;
// 这里可以写对手动作的视觉反馈
}
};
function sendAction(action) {
ws.send(JSON.stringify({
type: 'action',
action: action
}));
}
</script>
</body>
</html>
启动服务端
php chat_server.php
然后用两个浏览器打开 dual_game.html,它们会配对到同一个房间。
文件锁方案(最简单的双人文件编辑)
<?php
// 双人编辑同一个文件
$filename = 'shared_data.json';
$fp = fopen($filename, 'r+');
// 获取独占锁
if (flock($fp, LOCK_EX)) {
// 读取当前数据
$data = json_decode(file_get_contents($filename), true);
// 用户A修改
$data['user1_score'] = $_POST['score'];
// 写入回文件
ftruncate($fp, 0);
fwrite($fp, json_encode($data));
fflush($fp);
flock($fp, LOCK_UN);
}
fclose($fp);
推荐的生产级方案
- Pusher (第三方WebSocket服务)
- Socket.IO + PHP (通过Node.js中转)
- Laravel Echo + Laravel WebSockets
总结对比
| 方案 | 延迟 | 实现难度 | 适合场景 |
|---|---|---|---|
| 轮询 | 高 | 简单 | 学习演示 |
| WebSocket | 低 | 中等 | 实时游戏/协作 |
| 文件锁 | 高 | 简单 | 双人编辑 |
| Pusher | 低 | 中等 | 生产环境 |
对于真正的实时双人操作,强烈建议使用WebSocket方案,如果只是简单的双人轮流操作(类似回合制),用HTTP请求+数据库就足够了。