PHP项目WebRTC与P2P通信

wen PHP项目 4

本文目录导读:

PHP项目WebRTC与P2P通信

  1. WebRTC基础架构
  2. PHP信令服务器实现
  3. 前端JavaScript实现
  4. PHP后端辅助功能
  5. 部署和优化建议
  6. 性能监控

我来为您详细介绍如何在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通信解决方案,可以根据实际需求进行扩展和优化。

抱歉,评论功能暂时关闭!