本文目录导读:

我来为您详细介绍如何在PHP项目中集成WebRTC实现P2P通信。
WebRTC基础架构
核心组件
- 信令服务器:PHP实现,负责交换元数据
- STUN/TURN服务器:处理NAT穿透
- 浏览器端:JavaScript实现WebRTC连接
PHP信令服务器实现
1 WebSocket信令服务器(使用Ratchet)
// composer.json
{
"require": {
"cboden/ratchet": "^0.4",
"react/socket": "^1.12"
}
}
// SignalingServer.php
<?php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class SignalingServer implements MessageComponentInterface {
protected $clients;
protected $users = [];
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, true);
switch ($data['type']) {
case 'register':
$this->users[$data['userId']] = $from;
echo "用户注册: {$data['userId']}\n";
break;
case 'offer':
case 'answer':
case 'ice-candidate':
$this->routeMessage($data);
break;
}
}
public function onClose(ConnectionInterface $conn) {
$this->clients->detach($conn);
// 移除用户
foreach ($this->users as $userId => $userConn) {
if ($userConn === $conn) {
unset($this->users[$userId]);
break;
}
}
}
public function onError(ConnectionInterface $conn, \Exception $e) {
echo "错误: {$e->getMessage()}\n";
$conn->close();
}
private function routeMessage($data) {
$targetUserId = $data['targetUserId'];
if (isset($this->users[$targetUserId])) {
$targetConn = $this->users[$targetUserId];
$targetConn->send(json_encode([
'type' => $data['type'],
'senderId' => $data['senderId'],
'data' => $data['data']
]));
}
}
}
// server.php - 启动信令服务器
<?php
require 'vendor/autoload.php';
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
$server = IoServer::factory(
new HttpServer(
new WsServer(
new SignalingServer()
)
),
8080
);
echo "WebSocket信令服务器启动在端口 8080\n";
$server->run();
2 RESTful信令API(传统方式)
// SignalingController.php
<?php
namespace App\Controllers;
use App\Models\User;
use App\Models\Room;
use App\Services\SignalingService;
class SignalingController {
private $signalingService;
public function __construct() {
$this->signalingService = new SignalingService();
}
// 创建房间
public function createRoom($userId) {
$roomId = uniqid('room_', true);
$room = new Room();
$room->id = $roomId;
$room->hostId = $userId;
$room->participants = [$userId];
$room->createdAt = time();
$room->save();
return json_encode(['roomId' => $roomId]);
}
// 加入房间
public function joinRoom($roomId, $userId) {
$room = Room::find($roomId);
if (!$room) {
return json_encode(['error' => '房间不存在']);
}
if (count($room->participants) >= 2) {
return json_encode(['error' => '房间已满']);
}
$room->participants[] = $userId;
$room->save();
return json_encode([
'roomId' => $roomId,
'participants' => $room->participants,
'hostId' => $room->hostId
]);
}
// 交换信令信息
public function exchangeSignal($roomId, $senderId, $signalType, $data) {
$this->signalingService->addSignal($roomId, $senderId, $signalType, $data);
$pendingSignals = $this->signalingService->getPendingSignals($roomId, $senderId);
return json_encode(['signals' => $pendingSignals]);
}
}
前端JavaScript实现
1 完整的WebRTC客户端
// webrtc-client.js
class WebRTCPeer {
constructor(userId, signalingServer) {
this.userId = userId;
this.signalingServer = signalingServer;
this.peerConnection = null;
this.localStream = null;
this.config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turn:your-turn-server.com:3478',
username: 'username',
credential: 'password'
}
]
};
}
// 初始化本地媒体
async initLocalMedia(constraints = { audio: true, video: true }) {
try {
this.localStream = await navigator.mediaDevices.getUserMedia(constraints);
return this.localStream;
} catch (error) {
console.error('获取媒体失败:', error);
throw error;
}
}
// WebSocket信令连接
connectWebSocket(serverUrl) {
this.ws = new WebSocket(serverUrl);
this.ws.onopen = () => {
console.log('WebSocket连接成功');
this.ws.send(JSON.stringify({
type: 'register',
userId: this.userId
}));
};
this.ws.onmessage = async (event) => {
const data = JSON.parse(event.data);
await this.handleSignalingMessage(data);
};
this.ws.onerror = (error) => {
console.error('WebSocket错误:', error);
};
}
// 处理信令消息
async handleSignalingMessage(data) {
switch (data.type) {
case 'offer':
await this.handleOffer(data);
break;
case 'answer':
await this.handleAnswer(data);
break;
case 'ice-candidate':
await this.handleIceCandidate(data);
break;
}
}
// 创建offer(发起呼叫)
async createOffer(targetUserId) {
this.targetUserId = targetUserId;
this.peerConnection = new RTCPeerConnection(this.config);
this.setupPeerConnectionListeners();
// 添加本地流
if (this.localStream) {
this.localStream.getTracks().forEach(track => {
this.peerConnection.addTrack(track, this.localStream);
});
}
const offer = await this.peerConnection.createOffer();
await this.peerConnection.setLocalDescription(offer);
// 发送offer
this.ws.send(JSON.stringify({
type: 'offer',
senderId: this.userId,
targetUserId: targetUserId,
data: offer
}));
}
// 处理收到的offer
async handleOffer(data) {
this.targetUserId = data.senderId;
this.peerConnection = new RTCPeerConnection(this.config);
this.setupPeerConnectionListeners();
// 添加本地流
if (this.localStream) {
this.localStream.getTracks().forEach(track => {
this.peerConnection.addTrack(track, this.localStream);
});
}
await this.peerConnection.setRemoteDescription(new RTCSessionDescription(data.data));
const answer = await this.peerConnection.createAnswer();
await this.peerConnection.setLocalDescription(answer);
// 发送answer
this.ws.send(JSON.stringify({
type: 'answer',
senderId: this.userId,
targetUserId: data.senderId,
data: answer
}));
}
// 设置peerConnection监听器
setupPeerConnectionListeners() {
this.peerConnection.onicecandidate = (event) => {
if (event.candidate) {
this.ws.send(JSON.stringify({
type: 'ice-candidate',
senderId: this.userId,
targetUserId: this.targetUserId,
data: event.candidate
}));
}
};
this.peerConnection.ontrack = (event) => {
const remoteVideo = document.getElementById('remoteVideo');
if (remoteVideo) {
remoteVideo.srcObject = event.streams[0];
}
};
this.peerConnection.onconnectionstatechange = (event) => {
console.log('连接状态:', this.peerConnection.connectionState);
};
}
// 处理ICE候选
async handleIceCandidate(data) {
if (data.data) {
try {
await this.peerConnection.addIceCandidate(new RTCIceCandidate(data.data));
} catch (error) {
console.error('添加ICE候选失败:', error);
}
}
}
// 处理answer
async handleAnswer(data) {
await this.peerConnection.setRemoteDescription(new RTCSessionDescription(data.data));
}
// 关闭连接
close() {
if (this.peerConnection) {
this.peerConnection.close();
}
if (this.localStream) {
this.localStream.getTracks().forEach(track => track.stop());
}
if (this.ws) {
this.ws.close();
}
}
}
// 使用示例
const peer = new WebRTCPeer('user123', 'ws://localhost:8080');
await peer.initLocalMedia({ audio: true, video: true });
// 显示本地视频
const localVideo = document.getElementById('localVideo');
localVideo.srcObject = peer.localStream;
// 连接信令服务器
peer.connectWebSocket('ws://localhost:8080');
// 发起呼叫
document.getElementById('callButton').onclick = () => {
peer.createOffer('user456');
};
2 数据通道通信
// 添加数据通道
class DataChannelPeer extends WebRTCPeer {
createDataChannel(label = 'chat') {
this.dataChannel = this.peerConnection.createDataChannel(label);
this.setupDataChannelListeners();
return this.dataChannel;
}
setupDataChannelListeners() {
this.dataChannel.onopen = () => {
console.log('数据通道已打开');
this.sendMessage('Hello!');
};
this.dataChannel.onmessage = (event) => {
console.log('收到消息:', event.data);
};
this.dataChannel.onclose = () => {
console.log('数据通道已关闭');
};
}
sendMessage(message) {
if (this.dataChannel && this.dataChannel.readyState === 'open') {
this.dataChannel.send(message);
}
}
}
PHP后端辅助功能
1 房间管理
// RoomManager.php
<?php
namespace App\Services;
class RoomManager {
private $rooms = [];
public function createRoom($hostId, $maxParticipants = 2) {
$roomId = bin2hex(random_bytes(8));
$this->rooms[$roomId] = [
'id' => $roomId,
'hostId' => $hostId,
'participants' => [$hostId],
'maxParticipants' => $maxParticipants,
'status' => 'waiting',
'createdAt' => time()
];
return $roomId;
}
public function joinRoom($roomId, $userId) {
if (!isset($this->rooms[$roomId])) {
throw new \Exception('房间不存在');
}
$room = &$this->rooms[$roomId];
if (count($room['participants']) >= $room['maxParticipants']) {
throw new \Exception('房间已满');
}
$room['participants'][] = $userId;
$room['status'] = 'connected';
return $room;
}
public function getRoomInfo($roomId) {
return $this->rooms[$roomId] ?? null;
}
public function leaveRoom($roomId, $userId) {
if (isset($this->rooms[$roomId])) {
$room = &$this->rooms[$roomId];
$room['participants'] = array_filter(
$room['participants'],
function($p) use ($userId) {
return $p !== $userId;
}
);
if (empty($room['participants'])) {
unset($this->rooms[$roomId]);
} else {
$room['status'] = 'waiting';
}
}
}
}
2 完整PHP控制器
// WebRTCController.php
<?php
namespace App\Controllers;
use App\Services\RoomManager;
use App\Services\TURNServer;
class WebRTCController {
private $roomManager;
private $turnServer;
public function __construct() {
$this->roomManager = new RoomManager();
$this->turnServer = new TURNServer();
}
// 获取ICE服务器配置
public function getIceServers() {
return json_encode([
'iceServers' => [
[
'urls' => 'stun:stun.l.google.com:19302'
],
[
'urls' => 'turn:relay1.expressturn.com:3478',
'username' => 'your-username',
'credential' => 'your-password'
]
]
]);
}
// 创建房间
public function createRoom($request) {
$userId = $request->get('userId');
$roomId = $this->roomManager->createRoom($userId);
return json_encode([
'success' => true,
'roomId' => $roomId,
'userId' => $userId
]);
}
// 加入房间
public function joinRoom($request) {
$roomId = $request->get('roomId');
$userId = $request->get('userId');
try {
$room = $this->roomManager->joinRoom($roomId, $userId);
return json_encode([
'success' => true,
'room' => $room
]);
} catch (\Exception $e) {
return json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
}
// 获取TURN服务器凭据
public function getTurnCredentials() {
$credentials = $this->turnServer->generateCredentials();
return json_encode($credentials);
}
}
部署和优化建议
1 Docker部署
# docker-compose.yml
version: '3'
services:
signaling-server:
build: .
ports:
- "8080:8080"
environment:
- APP_ENV=production
volumes:
- ./:/var/www/html
turn-server:
image: coturn/coturn
ports:
- "3478:3478"
- "5349:5349"
command: -n --lt-cred-mech --fingerprint --realm=yourdomain.com --user=username:password
2 安全考虑
// SecurityMiddleware.php
class WebRTCSecurity {
public function validateToken($token) {
// 验证用户Token
$decoded = JWT::decode($token, $this->secretKey, ['HS256']);
return $decoded;
}
public function sanitizeSignalData($data) {
// 清理信令数据
$allowedFields = ['type', 'senderId', 'targetUserId', 'data', 'roomId'];
return array_intersect_key($data, array_flip($allowedFields));
}
}
3 错误处理和日志
// Logger.php
class WebRTCLogger {
public function logSignaling($type, $fromUserId, $toUserId, $status) {
$logEntry = [
'timestamp' => date('Y-m-d H:i:s'),
'type' => $type,
'from' => $fromUserId,
'to' => $toUserId,
'status' => $status
];
file_put_contents(
'logs/signaling.log',
json_encode($logEntry) . PHP_EOL,
FILE_APPEND
);
}
}
性能监控
// MonitorController.php
class WebRTCMonitor {
public function getRoomStats() {
global $rooms;
return json_encode([
'activeRooms' => count($rooms),
'totalUsers' => array_sum(array_map(function($room) {
return count($room['participants']);
}, $rooms))
]);
}
}
这个架构提供了完整的PHP WebRTC P2P通信解决方案,可以根据实际需求进行扩展和优化。