文章目录
1 .webrtc服务
1.1 webrtc是什么?
旨在建立一个互联网浏览器间的实时通信的平台,让WebRTC技术成为 H5标准之一,可以在浏览器和移动端上运行开发.
1.2 WebRTC通话原理
两个不同网络环境的客户端/浏览器(例:两个浏览器在不同的局域网中如何进行通信),要实现点对点的音视频对话,难点在哪里?
比如:PeerA端可支持VP8、H264多种编码格式,而PeerB端支持VP9、H264,要保证二端都正确的编解码,最简 单的办法就是取它们的交集H264 注:有一个专门的协议 ,称为Session Description Protocol (SDP),可用于描述上述这类信息, 在WebRTC中,参与 视频通讯的双方必须先交换SDP信息,这样双方才能知根知底 ,而交换SDP的过程,也称为"媒体协商"。
理想的网络情况是每个浏览器的电脑都是私有公网IP,可以直接进行点对点连接。例如:.通话手机之间可以直接用地址进行webrtc.这是一个理想的网络情况
实际情况可能是客户端用了wifi或者在局域网中,需要nat地址转换(网络穿透)
为了解决实际情况的问题,需要用到stun和turn,
什么是stun?
是一种网络协议,它允许位于NAT后的客户端找出自己的公网地址,查出自己位于哪种类型的NAT之后以及NAT为某一个本地端口所绑定的 Internet端端口.这些信息被用来在两个同时处于NAT路由器之后的主机之间创建UDP通信.该协议由RFC 5389定义.stun主要是给无法在公网环境下的 视频通话设备分配公网IP用的。这样两台电脑就可以在公网IP中进行通话. 注意的是,即使有了公网ip和端口,通话质量的好坏往往需要根据使用者本地的带宽确定
什么是turn?
是STUN/RFC5389的一个拓展,主要添加了Relay功能。如果 终端在NAT之后, 那么在特定的情景下,有可能使得终端无法和其对等端(peer)进行直接的通信,这时就需要公网 的服务器作为一个中继, 对来往的数据进行转发。这个转发的协议就被定义为TURN。
在STUN分配公网IP失败后,可以通过TURN服务器请求公网IP地址作为中继地址。这种方式的带宽由服务器端承 担,在多人视频聊天的时候,本地带宽压力较小,并且,根据Google的说明,TURN协议可以使用在所有的环境中。 (单向数据200kbps 一对一通话).
那么turn比我stun的好在哪里?
明显的是stun使用了多个终端的本地带宽,当本地的终端带宽很差时,有可能无法有效通话,那么turn就可以在stun的基础上加了一个中继,使用turn协议,就会使用turn的公网带宽.终端的本地带宽影响就没那么大了.
1.3 coturn
1.3.1 什么是ice和coturn?
ICE跟STUN和TURN不一样,ICE不是一种协议,而是一个框架(Framework),它整合了STUN和TURN。 coturn开源项目集成了STUN和TURN的功能。在WebRTC中用来描述 网络信息的术语叫candidate。 媒体协商 sdp(sdp ) ,网络协商 candidate(ice) 在webrtc中是很重要的东西 . 以下是完整的webrtc的流程图
1.3.2 安装coturn
https://www.freesion.com/article/98861196286/
https://blog.csdn.net/weixin_44517656/article/details/120731407
1.3.3 查看安装是否完成
Trickle ICE (webrtc.github.io) ,提前开放配置文件中的3478端口
建议使用火狐浏览器测试,
1.3.4 测试stun
1.3.5 测试turn
到现在coturn已经可以正常运行并开放了端口3478了,下面是信令服务器
1.4 信令服务器
实际情况是使用了websocket建立了通信通道,在一个app后台中(Java),在springboot中,我们如果使用了spring-boot-starter-websocket而不去导入集成的websocket,那么可能使用的就是tomcat的内置websocket,最好是使用spring-boot版本的websocket.
1.4.1 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
1.4.2 集成AbstractWebSocketHandler类
package com.luke.api.newwebsocket;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.luke.api.service.CloudChannelService;
import com.luke.api.utils.FileUtil;
import com.luke.api.vo.ChannelVo;
import com.luke.common.pojo.CloudChannel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.BinaryMessage;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
/**
* ws消息处理类
*/
@Component
@Slf4j
public class MyWsHandler extends AbstractWebSocketHandler {
@Resource
private CloudChannelService channelService;
// 注册
private static final int code_login = 0;
// 注册成功
private static final int code_login_success = 1;
// 对方是否在线
private static final int code_isOnline = 2;
// 对方离线
private static final int code_offline = 6;
// 重复登陆
private static final int code_repeatLogin = 5;
// 心跳发送(客户端发送心跳,每2分钟发送一次)
private static final int code_ping = 3;
// 心跳返回(服务端返回心跳)
private static final int code_pong = 4;
@Override
public void afterConnectionEstablished(WebSocketSession session) throws IOException {
String devId = FileUtil.getDevId(String.valueOf(session.getUri()));
if (StringUtils.isBlank(devId)) {
log.info("连接失败: [{}], id不能为空", session.getId());
session.sendMessage(new TextMessage("连接失败,设备id不能为空"));
session.close();
return;
}
log.info("建立ws连接sid: [{}]", session.getId());
}