本文目录导读:

我来为您详细介绍如何在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通信解决方案,可以根据实际需求进行扩展和优化。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播