信令服务端实现
<?phpuse SwooleHttpRequest;use SwooleHttpResponse;const WEBROOT = __DIR__ . '/web';$connnection_map = array();error_reporting(E_ALL);Corun(function () {$server = new SwooleCoroutineHttpServer('0.0.0.0', 9509, true);$server->set(['ssl_key_file' => __DIR__ . '/ssl/ssl.key','ssl_cert_file' => __DIR__ . '/ssl/ssl.crt',]);$server->handle('/', function (Request $req, Response $resp) {//websocketif (isset($req->header['upgrade']) and $req->header['upgrade'] == 'websocket') {$resp->upgrade();$resp->subjects = array();while (true) {$frame = $resp->recv();if (empty($frame)) {break;}$data = https://www.isolves.com/it/cxkf/yy/php/2020-08-12/json_decode($frame->data, true);switch ($data['cmd']) {case 'subscribe':subscribe($data, $resp);break;case 'publish':publish($data, $resp);break;}}free_connection($resp);return;}/tp$path = $req->server['request_uri'];if ($path == '/') {$resp->end(get_php_file(WEBROOT . '/index.html'));} else {$file = realpath(WEBROOT . $path);if (false === $file) {$resp->status(404);$resp->end('
1. 房间入口404 Not Found
');return;}if (strpos($file, WEBROOT) !== 0) {$resp->status(400);return;}if (pathinfo($file, PATHINFO_EXTENSION) === 'php') {$resp->end(get_php_file($file));return;}if (isset($req->header['if-modified-since']) and !empty($if_modified_since = $req->header['if-modified-since'])) {$info = stat($file);$modified_time = $info ? date('D, d M Y H:i:s', $info['mtime']) . ' ' . date_default_timezone_get() : '';if ($modified_time === $if_modified_since) {$resp->status(304);$resp->end();return;}}$resp->sendfile($file);}});$server->start();});function subscribe($data, $connection){global $connnection_map;$subject = $data['subject'];$connection->subjects[$subject] = $subject;$connnection_map[$subject][$connection->fd] = $connection;}function unsubscribe($subject, $current_conn){global $connnection_map;unset($connnection_map[$subject][$current_conn->fd]);}function publish($data, $current_conn){global $connnection_map;$subject = $data['subject'];$event = $data['event'];$data = $data['data'];//当前主题不存在if (empty($connnection_map[$subject])) {return;}foreach ($connnection_map[$subject] as $connection) {//不给当前连接发送数据if ($current_conn == $connection) {continue;}$connection->push(json_encode(array('cmd' => 'publish','event' => $event,'data' => $data)));}}function free_connection($connection){foreach ($connection->subjects as $subject) {unsubscribe($subject, $connection);}}function get_php_file($file){ob_start();try {include $file;} catch (Exception $e) {echo $e;}return ob_get_clean();}
下面是本地的效果图,首页可以输入房间号加入,如果为空会自动生成一个随机字符
文章插图
2. 房间内
下图我在本地使用两台笔记本实现的一个效果图,使用自签的证书,这里特意展示了两个不同的画面来区分视频同步效果 。
文章插图
请求流程分析
1. 在一台电脑上点击连接按钮,通过绑定的点击事件start()函数,我们可以发现,首先会创建一个websocket对象并发起连接,连接成功后,向信号服务器注册设备,并获取当前设备的流媒体 。获取成功后,赋值给本地元素可以展示,并且赋值给全局变量localStream 。
ws.onopen = function (e) {subscribe(subject);navigator.mediaDevices.getUserMedia({audio: true,video: true}).then(function (stream) {localVideo.srcObject = stream;localStream = stream;localVideo.addEventListener('loadedmetadata', function(){publish('call', null);})}).catch(function (e) {alert(e);});};
2. 信令服务端器在收到subscribe和publish请求后,会在内存中维护一个连接映射关系,核心逻辑是如果有其他连接进来,会进行广播通知,这里并没有实现一些细节逻辑,比如房间内连接数量限制,房间满了通知,退出连接通知等 。3. 另一个客户端点击连接会重复上一步骤,对端在收到其他客户端加入房间通知后 。
case 'call':icecandidate(localStream);//创建连接,并注册网络协商成功后给信令服务器发送信息的事件pc.createOffer({offerToReceiveAudio: 1,offerToReceiveVideo: 1}).then(function (desc) {pc.setLocalDescription(desc).then(//创建offer成功后,设置本地描述,并服务端绑定网络信息,成功后给信令服务器发送SDP offerfunction () {publish('offer', pc.localDescription);}).catch(function (e) {alert(e);});}).catch(function (e) {alert(e);});break;
4. 信令服务端收到一端offer后会转发给另一端,触发客户端的相应逻辑,同样会创建连接,并注册网络协商成功后给信令服务器发送信息的事件,同时会创建应答,成功后也会设置本地描述,并向服务端发送绑定信息 。同时向信令服务端发送answer信息,进行中转到对端 。
推荐阅读
- Mybaits中Like 的使用方式以及一些注意点
- 人马该怎样玩?
- 使用 Go 语言实现凯撒加密
- 对讲机电池原理和使用注意事项
- 使用cors完成跨域请求处理
- 超过35%的德国中小企业已使用人工智能技术
- 技术转载 || 使用java API进行zip递归压缩文件夹以及解压
- SpringBoot中使用dubbo实现RPC调用
- 正确的消毒柜使用方法
- 丰田卡罗拉双擎三种模式怎么使用?