webRTC:流程和socket搭建信令服务器

上一篇文章 webRTC初探:如何实现音视频的录制 实现了电脑上的摄像头、麦克风、屏幕分享桌面的操控,本文更进一步介绍流程。

简单提一句PeerConnection

既然是两个浏览器(端)之间通信,那自然需要一个"用于维护双方关系的载体",PeerConnection就是WebRTC中的这个载体,也是webrtc的核心。

复制代码
let PeerConnection = window.RTCPeerConnection ||
        window.mozRTCPeerConnection ||
        window.webkitRTCPeerConnection;

PeerConnection是一个对象,它里面有一些核心方法,后面实现完整流程的时候会解释,这里为了下面的流程图不至于难懂先提一嘴:

  • addTrack() :添加音频或者视频轨道
  • addIceCandidate(): 保存 ICE 候选信息,即双方协商信息,持续整个建立通信过程,直到没有更多候选信息
  • createAnswer() :创建应答信令
  • createDataChannel(): 创建消息通道,建立WebRTC通信之后,就可以 p2p 的直接发送文本消息,无需中转服务器
  • createOffer(): 创建初始信令
  • setLocalDescription() :保存自己端创建的信令
  • setLocalDescription() :保存自己端创建的信令

WebRTC的会话流程

下图就是通过载体的核心方法和事件完成两个浏览器的关联的过程:

其中"圈1"是websocket通道的搭建和双方初始化PeerConnection,紧跟着有关信令(SDP)的交换和监听就是通过socket完成的。

信令服务器的搭建

所谓信令服务器,其实完全可以说是通过websocket交换信令的即时通讯服务器。

在实现之前,需要思考一下所需的数据和数据结构:既然是会议,那就需要"用户userId"和"房间roomId",存储的话就通过Redis就好了,Redis中有一种数据结构Hash,大概结构是这样的:

具体代码如下:

javascript 复制代码
const httpServer = require('http').createServer();
const io = require('socket.io')(httpServer);
 
var redis = require('redis')
const roomKey = "meeting-room::"
var redisClient = redis.createClient(6379, '127.0.0.1')
redisClient.on('error', function (err) {
  console.log('redisClient connect Error ' ,err);
});
 
const userMap = new Map()
io.on('connection', async (socket) => {
  await onListener(socket)
});
 
httpServer.listen(18080, async() => {
  console.log('服务器启动成功:18080');
  await redisClient.connect();
});
 
function getMsg(type,msg,status=200,data=null){
  return {"type":type,"msg":msg,"status":status,"data":data}
}
 
function getParams(url,queryName){
  let query = decodeURI(url.split('?')[1]);
  let vars = query.split("&");
  for (var i = 0; i <= vars.length; i++) {
    var pair = vars[i].split("=");
    if (pair[0] === queryName) {
      return pair[1];
    }
  }
  return null;
}
 
/**
 * DB data
 * @param {Object} userId
 * @param {Object} roomId
 */
async function getUserDetailByUid(userId,roomId){
  let res = JSON.stringify(({"userId":userId,"roomId":roomId}))
  return res
}
 
/**
 * 监听
 */
async function onListener(s){
  let url = s.client.request.url
  let userId = getParams(url,'userId')
  let roomId = getParams(url,'roomId')
  console.log("client uid:"+userId+" roomId: "+roomId+" online ")
  //user map
  userMap.set(userId,s)
  //room cache
  if(roomId){
    await redisClient.hSet(roomKey+roomId,userId, await getUserDetailByUid(userId,roomId))
    oneToRoomMany(roomId,getMsg('join',userId+ ' join then room'))
  }
  s.on('msg', async (data) => {
    console.log("msg",data)
    await oneToRoomMany(roomId,data)
  });
  s.on('disconnect', () => { 
    console.log("client uid:"+userId+" roomId: "+roomId+" offline ")
    userMap.delete(userId)
    if(roomId){
      redisClient.hDel(roomKey+roomId,userId)
      oneToRoomMany(roomId,getMsg('leave',userId+' leave the room '))
    }
  });    
  s.on('roomUserList', async (data) => {
    // console.log("roomUserList msg",data)
    s.emit('roomUserList',await getRoomUser(data['roomId']))
  })
  s.on('call',(data) => {
    let targetUid = data['targetUid']
    if(userMap.get(targetUid)){
      oneToOne(targetUid,getMsg('call',"远程呼叫",200,data))
    }else{
      console.log(targetUid+ "不在线")
    }
  })
  s.on('candidate',(data) => {
    let targetUid = data['targetUid']
    if(userMap.get(targetUid)){
      oneToOne(targetUid,getMsg('candidate',"ice candidate",200,data))
    }else{
      console.log(targetUid+ "不在线")
    }
  })
  s.on('offer',(data) => {
    let targetUid = data['targetUid']
    if(userMap.get(targetUid)){
      oneToOne(targetUid,getMsg('offer',"rtc offer",200,data))
    }else{
      console.log(targetUid+ "不在线")
    }
  })
  s.on('answer',(data) => {
    let targetUid = data['targetUid']
    if(userMap.get(targetUid)){
      oneToOne(targetUid,getMsg('answer',"rtc answer",200,data))
    }else{
      console.log(targetUid+ "不在线")
    }
  })
}
 
/**
 * 单独用户提示
 * @param {Object} uid
 * @param {Object} msg
 */
function oneToOne(uid,msg){
  let s = userMap.get(uid)
  if(s){
    s.emit('msg',msg)
  }else{
    console.log(uid+"用户不在线")
  }
}
 
/**
 * 获取房间用户列表
 * @param {Object} roomId
 */
async function getRoomUser(roomId){
  return await redisClient.hGetAll(roomKey+roomId)
}
 
/**
 * 广播
 * @param {Object} roomId
 * @param {Object} msg
 */
async function oneToRoomMany(roomId,msg){
  let ulist = await redisClient.hGetAll(roomKey+roomId)
  for(const uid in ulist){
    oneToOne(uid,msg)
  }
}
相关推荐
对象存储与RustFS17 小时前
网盘限速逼你开会员?用 RustFS 自己搭一个不限速的“私人网盘
服务器·后端·docker
众壹新能源科技18 小时前
逆变器自带无功不够用,加装5MVar SVG后功率因数从0.85回到0.99
运维·人工智能·自动化
..Dauntless..18 小时前
【Linux】一切皆文件和缓冲区
linux·运维·服务器
裕晟资质规划19 小时前
三类资质变更报告时限差在哪?军工 15 日、集成印制 10 日
大数据·服务器·网络
QMCY_jason19 小时前
x86 ubuntu2204 无gui 安装 openeuler 2203 aarch64版本
linux·运维·服务器
重生之小比特19 小时前
【Linux系统】12 进程程序替换 自定义Shell 命令行解释器
linux·运维·服务器
轻舟技术A19 小时前
Provider 不属于 Agent 核心:rpi 的分层理由与收益
服务器·网络
考拉讲支付19 小时前
母婴亲子服务平台分账架构设计:多场景多方分账与合规清结算实践
大数据·运维·人工智能·微信小程序·架构·微信公众平台
连山齐名19 小时前
ai应用开发的学习笔记
服务器·笔记·学习
LateFrames19 小时前
“场控软件” 如何容错与运维
运维·容错·led大屏·播控系统·场控软件