基础设想

信令服务器主要负责转发 SDP ,当然,我也可以选择将我的业务逻辑写在里面(生产环境不推荐这样做)。选择基于Web Sockets主要是因为这块比较熟悉,并且双向连接,我们能够基于此做的事非常多。我相信信令服务器必须要有的一项功能便是服务端主动向客户端推送消息,同样的道理,具有以上功能的都可以考虑用来做信令服务器,如何更好的融入系统,这才是影响我们选择的重要因素。

这里需要引入一个房间的概念来将多人规定在一个域内,并且我能够通过房间号获取到所有人的信息(包括web sockets的连接信息)。

基础架构如下:

服务端设置了多种消息格式,用于处理不同的消息(消息格式与客户端是对应的),具体消息格式为:

USER_IDENTITY("USER_IDENTITY","用户身份认证"),SDP_OFFER("SDP_OFFER","type 为 offer 的 desc"),SDP_ANSWER("SDP_ANSWER","type 为 answer 的 desc"),SDP_CANDIDATE("SDP_CANDIDATE","type 为 offer 的 desc"),CLIENT_MULTIPLY_CONNECTION_CREATEQUE("CLIENT_MULTIPLY_CONNECTION_CREATEQUE","多个连接创建请求"),SERVER_CALLEESRESP("SERVER_CALLEESRESP","成员信息响应");

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

Web sockets

服务端的web sockets 需要保存用户与服务端的session以及组装便于 web scokets 接收消息时,进行消息处理的数据结构。

Web sockets 收发消息处理

服务端web socket 主要负责消息的处理以及转发还有相关业务逻辑的保存,这里抽象一个MsgCenter类来负责处理各类消息。抽象一个SignalingChannel来保存user、对等点(多个),房间号信息。通过SignalingChannel能够获取当前用户的对等点用户,以便转发 SDP。

// signalingChannel 类
/*** 请求房间号*/private String roomId;/*** 调用者*/private User caller;/*** 远程对等用户名称集合*/private Set<String> callees;public SignalingChannel(User user,String roomId) {this.caller = user;this.roomId = roomId;this.callees = new HashSet<>();}/*** 新增远程对象* @author DJZ-HXF* @date 2019/6/12 15:58* @param userName*/public void addCallee(String userName){callees.add(userName);}public void addCallees(Set<String> userNames){callees.addAll(userNames);}// MessageCenter 类
/*** 消息对应的处理器*/private static Map<String, BiConsumer<? extends Map<String,String>,Session>> handlers = new HashMap<>();public static void handleMsg(String msg, Session session) {MessageWrapper messageWrapper = JSONObject.parseObject(msg, MessageWrapper.class);BiConsumer consumer = handlers.get(messageWrapper.getMsgType());if (consumer != null) {if(messageWrapper.getMsgBody() instanceof JSONArray){JSONArray temp = (JSONArray) messageWrapper.getMsgBody();// 组装name 和 sdp 的映射Map<String,String> result = new HashMap<>();temp.forEach(jsonObject->{if(jsonObject instanceof JSONObject){Map tempMap = (Map) jsonObject;result.put(tempMap.get("userName").toString(),tempMap.get("sdp").toString());}});consumer.accept(result,session);}else{consumer.accept(messageWrapper.getMsgBody(), session);}}}/*** 新增消息处理器** @param msgType    消息类型* @param biConsumer 消息处理者* @author DJZ-HXF* @date 2019/6/12 16:30*/public static <T> void addHandler(String msgType, BiConsumer<? extends Map<String,String>, Session> biConsumer) {handlers.put(msgType, biConsumer);}

1.接收到认证消息时,保存用户信息。

// 处理用户认证消息MessageCenter.addHandler(MsgConsts.USER_IDENTITY.getValue(), (Map<String, String> map, Session session) -> {User user = new User();user.setName(map.get("name"));user.setRoomId(map.get("roomId"));sessionIdUserMap.put(session.getId(), user);userNameSessionMap.put(user.getName(), session);});

2.接收到多个连接创建请求(即开始请求多人通话)消息时,,建立SignalingChannel


// 处理多个连接建立请求 并响应成员信息MessageCenter.addHandler(MsgConsts.CLIENT_MULTIPLY_CONNECTION_CREATEQUE.getValue(), (Map<String, String> map, Session session) -> {User user = new User();user.setName(map.get("name"));user.setRoomId(map.get("roomId"));Set<String> waitCalleeNames = new HashSet<>();synchronized (SdpWebSocket.class){// 获取房间里获取尚未建立信道的成员Set<String> hasSignalingChannleUsers = Optional.ofNullable(roomIdSignalingChannelsMap.get(user.getRoomId())).orElseGet(HashSet::new).stream().map(u -> u.getCaller().getName()).collect(Collectors.toSet());sessionIdUserMap.forEach((id, u) -> {if (user.getRoomId().equals(u.getRoomId()) && !hasSignalingChannleUsers.contains(u.getName()) && !user.getName().equals(u.getName())) {waitCalleeNames.add(u.getName());}});SignalingChannel signalingChannel = new SignalingChannel(user, user.getRoomId());signalingChannel.addCallees(waitCalleeNames);sessionIdSignalingChannelMap.put(session.getId(), signalingChannel);if (!roomIdSignalingChannelsMap.containsKey(user.getRoomId())) {roomIdSignalingChannelsMap.put(user.getRoomId(), new HashSet<>());}roomIdSignalingChannelsMap.get(user.getRoomId()).add(signalingChannel);}if(waitCalleeNames.size()==0){return ;}// 响应成员信息MessageWrapper<Set<String>> result = new MessageWrapper();result.setMsgType(MsgConsts.SERVER_CALLEESRESP.getValue());result.setMsgBody(waitCalleeNames);try {session.getBasicRemote().sendText(JSONObject.toJSONString(result));} catch (IOException e) {e.printStackTrace();}});

远程对等用户名称集合为房间内成员并且尚未建立`signalchannel`的成员。

并且信令服务器响应将与之建立连接的成员信息,以便客户端创建对应数量的连接。

3.接收到多个 type 为 offer 的 desc和成员名称的消息时, 获取其SignalingChannel , 然后向其匹配的成员发送该desc和自己的名称。

// 处理type为offer的desc,MessageCenter.addHandler(MsgConsts.SDP_OFFER.getValue(), (Map<String, String> userNameSdp, Session session) -> {SignalingChannel signalingChannel = sessionIdSignalingChannelMap.get(session.getId());// 向远程发送offersignalingChannel.getCallees().forEach(userName -> {if(userNameSdp.containsKey(userName)){Session remoteSession = userNameSessionMap.get(userName);if (remoteSession != null && remoteSession.isOpen()) {try {SdpWrapper sdpWrapper = new SdpWrapper();sdpWrapper.setSdp(userNameSdp.get(userName));sdpWrapper.setUserName(signalingChannel.getCaller().getName());MessageWrapper<SdpWrapper> result = new MessageWrapper();result.setMsgType(MsgConsts.SDP_OFFER.getValue());result.setMsgBody(sdpWrapper);remoteSession.getBasicRemote().sendText(JSONObject.toJSONString(result));} catch (IOException e) {e.printStackTrace();}}}});});

4.接收到单个 type 为 answer 的desc和成员名称的消息时,检测房间里的signalingChannel集合,如果在信道的远程对等点集合中包含了该成员名称,那么向该调用者发送answer和自己的名称。

// 处理type为answer的desc,MessageCenter.addHandler(MsgConsts.SDP_ANSWER.getValue(), (Map<String, String> map, Session session) -> {SignalingChannel signalingChannel = sessionIdSignalingChannelMap.get(session.getId());SdpWrapper sdpWrapper = new SdpWrapper();sdpWrapper.setUserName(signalingChannel.getCaller().getName());sdpWrapper.setSdp(JSONObject.toJSONString(map.get("sdp")));Set<SignalingChannel> signalingChannels = roomIdSignalingChannelsMap.get(signalingChannel.getRoomId());// 向其调用者发送 answersignalingChannels.forEach(sc -> {if(sc.getCaller().getName().equals(map.get("userName"))){Session remoteSession = userNameSessionMap.get(sc.getCaller().getName());if (remoteSession != null && remoteSession.isOpen()) {MessageWrapper<SdpWrapper> result = new MessageWrapper();result.setMsgType(MsgConsts.SDP_ANSWER.getValue());result.setMsgBody(sdpWrapper);try {remoteSession.getBasicRemote().sendText(JSONObject.toJSONString(result));} catch (IOException e) {e.printStackTrace();}}}});});

5.接收到单个 candidate和成员名称 的消息时,获取其SingalingChannel,然后向其对等点中的该成员发送该 candidate和自己的名称,并且还需要检测房间里的signalingChannel集合,如果该信道的调用者为该成员名称,那么向该调用者发送answer和自己的名称。

// 处理 candidateMessageCenter.addHandler(MsgConsts.SDP_CANDIDATE.getValue(), (Map<String, String> map, Session session) - {SignalingChannel signalingChannel = sessionIdSignalingChannelMap.get(session.getId());SdpWrapper sdpWrapper = new SdpWrapper();sdpWrapper.setUserName(signalingChannel.getCaller().getName());sdpWrapper.setSdp(JSONObject.toJSONString(map.get("sdp")));MessageWrapper<SdpWrapper> result = new MessageWrapper();result.setMsgType(MsgConsts.SDP_CANDIDATE.getValue());result.setMsgBody(sdpWrapper);// 向对等体发送 candidatesignalingChannel.getCallees().forEach(userName -> {if(userName.equals(map.get("userName"))){Session remoteSession = userNameSessionMap.get(userName);if (remoteSession != null && remoteSession.isOpen()) {try {remoteSession.getBasicRemote().sendText(JSONObject.toJSONString(result));} catch (IOException e) {e.printStackTrace();}}}});Set<SignalingChannel> signalingChannels = roomIdSignalingChannelsMap.get(signalingChannel.getRoomId());signalingChannels.forEach(sc -> {if (sc.getCaller().getName().equals(map.get("userName"))) {Session remoteSession = userNameSessionMap.get(sc.getCaller().getName());if (remoteSession != null && remoteSession.isOpen()) {try {remoteSession.getBasicRemote().sendText(JSONObject.toJSONString(result));} catch (IOException e) {e.printStackTrace();}}}});});

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

WebRTC实现多人视频聊天之信令服务器设计相关推荐

  1. WebRTC实现多人视频聊天

    写在前面 实现房间内人员的视频聊天,由于并未很完善,所以需要严格按照步骤来,当然基于此完善,就是时间的问题了. 架构 整个设计架构如下: 图片来自于参考博文.我使用的是第一种Mesh 架构,无需任何流 ...

  2. WebRTC实现多人视频聊天之客户端设计

    写在前面 在开始之前,需要对如何建立点对点连接有一个了解,参考我的另一篇博文:WebRTC之点对点连接.下图是来自参考博文中的一张图片. 基础想法 基础架构如下: 客户端主要在于处理从WebSocke ...

  3. 教你用WebRTC撸一个多人视频聊天

    之前公司准备用 webRTC 来实现视频聊天,研究了几天,撸了个 demo 出来,(虽然最后并没有采用这项技术,囧),但是还是写一个出来吧! WebRTC简单介绍 WebRTC (Web Real-T ...

  4. 实践:《从头到脚撸一个多人视频聊天 — 前端 WebRTC 实战(一)》

    2019独角兽企业重金招聘Python工程师标准>>> 请先阅读原文,链接:从头到脚撸一个多人视频聊天 - 前端 WebRTC 实战(一),本文只涉及实践过程中的问题 1.video ...

  5. 使用WebRTC搭建前端视频聊天室——信令篇

    转载自:使用WebRTC搭建前端视频聊天室--信令篇 建议看这篇之前先看一下使用WebRTC搭建前端视频聊天室--入门篇 如果需要搭建实例的话可以参照SkyRTC-demo:github地址 其中使用 ...

  6. Android 集成 Agora SDK 快速体验 RTC 版多人视频聊天|掘金技术征文

    RTC (Real-Time Communication) 作为实时通讯领域的"新贵",在互动直播.远程控制.多人视频会议.屏幕共享等领域广受好评,如果你还不了解 RTC ,Tak ...

  7. 基于webRTC的1V1在线视频聊天(网页版DEMO)

    给大家分享一个基于webRTC的1V1在线视频聊天DEMO: https://webrtc.jiuhoutech.com/ DEMO效果如下: 说明: 1. 可以显示本地画面以及对方画面 2. 可以发 ...

  8. 【游戏开发实战】Unity从零开发多人视频聊天功能,无聊了就和自己视频聊天(附源码 | Mirror | 多人视频 | 详细教程)

    文章目录 一.前言 二.思考问题与解决方案 1.思考问题 2.解决方案 2.1.Unity中如何开启摄像头并对图像进行采样 2.2.图像如何中转给其他客户端 2.3.如何实现清晰度切换 2.4.客户端 ...

  9. (Agora声网)多人视频聊天应用的开发(三)多人聊天

    转载于:Android多人视频聊天应用的开发(三)多人聊天-玖哥的书房-51CTO博客 http://blog.51cto.com/dongfeng9ge/2104587 本系列文章结合声网官方在Gi ...

最新文章

  1. Python enumerate() 函数的使用
  2. 在Ubuntu 16.04.1 LTS上测试Linux AIO功能实录
  3. sql exists 的用法
  4. first() mysql_EF6配合MySQL或MSSQL(CodeFirst模式)配置指引
  5. php不用于输出的函数,PHP常用函数和常见疑难问题解答
  6. 用c语言实现相机坐标的过滤,华为2014年机试题【字符串过滤】-【C语言/C++】
  7. 一起谈.NET技术,Silverlight 游戏开发小技巧:实现街霸4的选人界面
  8. 控件readonly之后的取值
  9. C语言(二)C语言程序结构及简单的C程序举例
  10. C语言实现通讯录制作-链表学习案例(C/C++)
  11. java毕业设计德云社票务系统Mybatis+系统+数据库+调试部署
  12. 网友盘点大陆十大“缺德”行业
  13. 从fastq生成vcf文件
  14. 迪拜政府和当地银行合作推出基于区块链的贷款平台
  15. Delphi中三种延时方法
  16. html网易云音乐图片轮播效果,Vue之网易云音乐PC版轮播图的实现
  17. 中式装修之美,呈现出东方的诗意与唯美
  18. IDEA提示方法参数的快捷键
  19. java excel 设置行高 jxi_win7系统下excel2007批量设置行高、列宽、行列间距的方法...
  20. 技术VS管理,哪个更重要?

热门文章

  1. SqlServer 中的触发器
  2. Linux之汇编语言
  3. 基于飞凌RK3588核心板的无人机主控方案
  4. Android 应用强制更新策略
  5. aes加密算法 java实现,AES加密算法的java实现
  6. 2022天梯赛个人代码留存
  7. Guitar Pro8优秀的自动扒谱软件
  8. 进阶面试的必看的ORM架构之 ORM简介
  9. ORACLE UGA与CGA
  10. python Matplotlib库基础