《从零开始学 JSSIP:JavaScript 实时通信开发实战》
一、基础知识准备
1.1 什么是 SIP 协议
SIP(Session Initiation Protocol,会话初始协议)是一种用于创建、修改和终止多媒体会话的应用层协议,广泛应用于 IP 电话、视频会议、即时消息等实时通信场景。它是由 IETF(Internet Engineering Task Force)制定的标准协议,在 RFC 3261 中详细定义。
SIP 协议采用 C/S(客户端 / 服务器)架构,基于文本格式进行消息交换,具有简单、灵活、可扩展的特点。与 HTTP 协议类似,SIP 也是一种请求 - 响应式协议,但专为实时通信设计,支持多种媒体类型和复杂的会话控制功能。
SIP 协议的主要功能包括:
-
用户定位:确定参与会话的用户位置
-
会话建立:创建多媒体会话,协商媒体参数
-
会话修改:动态调整会话参数,如添加视频流
-
会话终止:优雅地结束会话
-
会话管理:包括用户可用性检查、会话转移等功能
SIP 消息主要分为两类:请求(Request)和响应(Response)。常见的 SIP 请求方法包括:
-
INVITE:用于发起会话
-
ACK:用于确认会话建立
-
BYE:用于终止会话
-
CANCEL:用于取消未完成的请求
-
OPTIONS:用于查询服务器能力
-
MESSAGE:用于发送即时消息
-
INFO:用于发送带外信息
SIP 响应则包含一个状态码,表示请求处理结果。常见的状态码分类与 HTTP 类似,如 1xx(临时响应)、2xx(成功响应)、3xx(重定向)、4xx(客户端错误)、5xx(服务器错误)等。
1.2 为什么选择 JSSIP
在 Web 浏览器中实现 SIP 功能曾经是一项复杂的任务,需要处理各种底层协议细节和浏览器兼容性问题。JSSIP(JavaScript SIP Library)的出现极大简化了这一过程,它是一个专为浏览器和 Node.js 环境设计的开源 JavaScript 库,提供了简单易用的 API 来实现 SIP 客户端功能。
JSSIP 的主要特点包括:
-
纯 JavaScript 实现:100% 用 JavaScript 编写,无需任何外部依赖(除了 WebRTC 支持),可在现代浏览器和 Node.js 环境中直接使用(5)
-
轻量级:体积小巧,不会显著增加页面加载时间,适合在各种 Web 应用中集成
-
易于使用:提供简洁直观的 API,即使没有 SIP 协议背景的开发者也能快速上手
-
全面的 SIP 支持:支持完整的 SIP 协议栈,包括会话建立、修改、终止,即时消息,状态通知等功能
-
WebRTC 集成:无缝集成 WebRTC 技术,支持音频 / 视频通话和数据通道(5)
-
WebSocket 传输:支持通过 WebSocket 传输 SIP 消息,适应现代 Web 环境
-
多平台兼容:同时支持浏览器和 Node.js 环境,可用于开发全栈实时通信应用
JSSIP 由撰写 RFC 7118(WebSocket 作为 SIP 传输协议)的团队开发,具有很高的权威性和可靠性。它被广泛应用于各种 WebRTC 通信解决方案中,如 Web 电话、视频会议系统、即时通讯工具等(24)。
1.3 开发环境搭建
在开始编写 JSSIP 应用之前,需要搭建合适的开发环境。本节将指导你完成环境设置,并介绍必要的工具和资源。
1.3.1 所需工具
开发 JSSIP 应用需要以下基本工具:
-
现代浏览器:JSSIP 支持最新版本的 Chrome、Firefox、Edge 等主流浏览器。由于使用了 WebRTC 技术,不支持 IE 浏览器。
-
文本编辑器:选择一款适合 JavaScript 开发的编辑器,如 Visual Studio Code、Sublime Text、Atom 等。
-
Web 服务器:由于浏览器安全限制,JSSIP 应用需要在 Web 服务器环境中运行,不能直接通过文件协议(file://)打开。可以使用以下任意一种:
-
内置 Web 服务器的编辑器(如 VS Code 的 Live Server 扩展)
-
Node.js 的 http-server 模块
-
Apache 或 Nginx 等传统 Web 服务器
-
简单的 Python 服务器:
python -m http.server
(Python 3.x)
-
版本控制系统(可选):如 Git,用于管理项目代码
-
调试工具:浏览器自带的开发者工具(F12)是必备的调试工具,用于查看控制台输出、网络请求和 JavaScript 错误。
1.3.2 获取 JSSIP 库
有多种方式获取 JSSIP 库文件:
- 从 CDN 引入:可以直接从 cdnjs 等 CDN 服务引入 JSSIP 库,无需下载到本地。
\<script src="https://cdnjs.cloudflare.com/libraries/jssip/3.9.0/jssip.min.js">\</script>
-
从官网下载:访问 JSSIP 官方网站(http://www.jssip.net/)下载最新版本的库文件(6)。
-
使用 npm 安装(适用于 Node.js 环境或构建工具):
npm install jssip
- 从 GitHub 获取:JSSIP 的源代码托管在 GitHub 上,可以克隆整个仓库:
git clone https://github.com/versatica/JsSIP.git
1.3.3 基本 HTML 模板
创建一个基本的 HTML 文件,作为 JSSIP 应用的基础结构:
\<!DOCTYPE html>\<html>\<head>  \<title>JSSIP Demo\</title>  \<meta charset="UTF-8">  \<!-- 引入JSSIP库 -->  \<script src="jssip-3.9.0.min.js">\</script>\</head>\<body>  \<!-- 在这里添加应用界面元素 -->     \<script>  // 在这里编写JSSIP代码  \</script>\</body>\</html>
1.3.4 启用调试日志
JSSIP 提供了强大的调试日志功能,帮助开发者诊断问题。默认情况下,JSSIP 不向浏览器控制台记录任何内容。要启用调试日志,可以在浏览器控制台中运行以下命令并重新加载页面:
JsSIP.debug.enable('JsSIP:\*');
这将启用所有模块的调试输出。你也可以指定特定模块的日志,例如只启用 UA 模块的日志:
JsSIP.debug.enable('JsSIP:UA');
要禁用调试日志,运行:
JsSIP.debug.disable('JsSIP:\*');
日志设置会存储在浏览器的 LocalStorage 中,下次访问时仍然有效(5)。
1.4 WebRTC 基础
JSSIP 与 WebRTC(Web Real-Time Communication)技术紧密结合,实现浏览器端的实时音视频通信。因此,了解 WebRTC 的基础知识对掌握 JSSIP 至关重要。
1.4.1 WebRTC 概述
WebRTC 是一项由 W3C 和 IETF 共同制定的开放标准,允许浏览器之间进行实时数据传输,无需安装第三方插件。它提供了一组 API,使浏览器能够访问本地媒体设备(如摄像头、麦克风),并通过点对点连接直接交换数据。
WebRTC 的核心组件包括:
-
MediaStream API:用于访问和操作本地媒体设备,如摄像头和麦克风
-
RTCPeerConnection API:用于建立点对点连接,管理媒体流传输
-
RTCDataChannel API:用于在浏览器之间传输任意数据
WebRTC 的主要特点包括:
-
支持多种媒体类型:音频、视频、任意数据
-
直接点对点连接,减少服务器负载
-
支持 NAT 穿透和防火墙穿越
-
内置加密,确保通信安全
1.4.2 获取本地媒体流
使用navigator.mediaDevices.getUserMedia()
方法可以获取本地媒体设备的流:
navigator.mediaDevices.getUserMedia({ audio: true, video: true })  .then(function(stream) {  // 成功获取媒体流  videoElement.srcObject = stream;  })  .catch(function(error) {  // 处理错误  console.error('获取媒体设备失败:', error);  });
1.4.3 RTCPeerConnection
RTCPeerConnection
是 WebRTC 的核心 API,用于建立点对点连接。它管理着:
-
媒体流的传输
-
ICE(Interactive Connectivity Establishment)过程,用于 NAT 穿透
-
DTLS(Datagram Transport Layer Security)安全层
-
SDP(Session Description Protocol)协商
创建RTCPeerConnection
实例:
const configuration = {  iceServers: \[  { urls: 'stun:stun.l.google.com:19302' },  { urls: 'turn:turn.example.com', username: 'user', credential: 'password' }  ]};const peerConnection = new RTCPeerConnection(configuration);
1.4.4 SDP 协商
SDP(Session Description Protocol)是一种用于描述多媒体会话的格式。在 WebRTC 中,SDP 用于协商媒体格式、编解码器、传输地址等参数。
SDP 协商过程包括:
-
本地创建 SDP offer
-
将 offer 发送给远程对等方
-
远程对等方创建 SDP answer 并返回
-
双方交换 ICE 候选地址,建立连接
// 创建offerpeerConnection.createOffer()  .then(function(offer) {  return peerConnection.setLocalDescription(offer);  })  .then(function() {  // 将offer发送给远程对等方  sendSdpToRemotePeer(peerConnection.localDescription);  })  .catch(function(error) {  console.error('创建offer失败:', error);  });// 处理远程offerfunction handleRemoteOffer(offer) {  peerConnection.setRemoteDescription(new RTCSessionDescription(offer))  .then(function() {  return peerConnection.createAnswer();  })  .then(function(answer) {  return peerConnection.setLocalDescription(answer);  })  .then(function() {  // 将answer发送给远程对等方  sendSdpToRemotePeer(peerConnection.localDescription);  })  .catch(function(error) {  console.error('处理远程offer失败:', error);  });}
1.4.5 ICE 候选收集
ICE(Interactive Connectivity Establishment)是一种用于在 NAT 设备和防火墙之间建立连接的机制。RTCPeerConnection
会自动收集本地设备的 ICE 候选地址,并通过信令服务器交换这些候选地址。
// 监听ICE候选收集事件peerConnection.onicecandidate = function(event) {  if (event.candidate) {  // 将ICE候选发送给远程对等方  sendIceCandidateToRemotePeer(event.candidate);  }};// 处理远程ICE候选function handleRemoteIceCandidate(candidate) {  peerConnection.addIceCandidate(new RTCIceCandidate(candidate))  .catch(function(error) {  console.error('添加远程ICE候选失败:', error);  });}
1.5 本书约定和学习路径
本书旨在帮助 JavaScript 开发者快速掌握 JSSIP 库,从零开始构建实时通信应用。在开始深入学习之前,需要了解一些约定和学习路径。
1.5.1 本书约定
-
代码示例:本书中的代码示例均为纯 JavaScript,不使用任何第三方框架(如 React、Vue 等),以确保兼容性和学习的专注性。
-
版本约定:本书基于 JSSIP 3.9.x 版本编写,所有示例和说明均以此版本为基础。由于 JSSIP 可能会更新,建议在实际开发中参考最新文档。
-
日志输出:为了便于理解代码执行流程,很多示例中会包含
console.log()
输出。在实际生产环境中,应根据需要调整日志级别或禁用日志。 -
注释规范:代码注释采用 JSDoc 风格,详细说明函数参数、返回值和功能。
-
安全提示:涉及安全敏感信息(如密码、密钥)的示例,均使用占位符(如
'your_password'
),实际使用时请替换为真实值。
1.5.2 学习路径建议
本书按照由浅入深的顺序组织内容,建议按照以下路径学习:
- 基础篇(第 1-3 章):
-
第 1 章:了解 SIP 协议基础和 JSSIP 库
-
第 2 章:学习 JSSIP 核心组件和基本使用方法
-
第 3 章:掌握 SIP 注册和基本会话管理
- 进阶篇(第 4-6 章):
-
第 4 章:深入学习 JSSIP 的 API 细节
-
第 5 章:实现完整的音视频通话功能
-
第 6 章:了解即时消息和高级会话管理
- 高级篇(第 7-9 章):
-
第 7 章:学习安全机制和最佳实践
-
第 8 章:优化 JSSIP 应用的性能
-
第 9 章:构建完整的实时通信应用
- 附录:
-
附录 A:SIP 消息参考
-
附录 B:API 快速参考
-
附录 C:常见问题解答
1.5.3 实践建议
学习 JSSIP 最好的方法是通过实践。建议读者:
-
动手编写代码:不要只是阅读,要实际编写和测试每一个示例。
-
搭建测试环境:设置一个 SIP 服务器(如 FreeSWITCH、Asterisk)进行实际测试,体验真实的通信场景。
-
尝试修改代码:在示例基础上进行修改和扩展,观察结果变化,加深理解。
-
参与开源社区:访问 JSSIP 的 GitHub 仓库和社区论坛,学习他人的经验,分享自己的成果。
-
阅读官方文档:JSSIP 的官方文档(http://www.jssip.net/documentation/)是最权威的参考资料,应经常查阅。
通过以上方法,你将能够系统地掌握 JSSIP,并能够开发出功能丰富的实时通信应用。
二、JSSIP 核心组件
2.1 JSSIP 体系结构
JSSIP 采用模块化设计,由多个核心组件构成,每个组件负责特定的功能。理解这些组件的结构和关系,对于有效使用 JSSIP 至关重要。
2.1.1 主要组件概述
JSSIP 的核心组件包括:
-
JsSIP.UA(User Agent):表示 SIP 用户代理,是 JSSIP 的核心类,负责与 SIP 服务器通信,管理注册状态和会话。
-
JsSIP.Socket:表示传输层接口,负责与 SIP 服务器建立连接,发送和接收 SIP 消息。JSSIP 提供了 WebSocket 和 Node.js 专用的 Socket 实现。
-
JsSIP.Message:表示 SIP 消息,包含请求或响应的所有信息,如方法、状态码、头部和内容。
-
JsSIP.RTCSession:表示基于 WebRTC 的多媒体会话,管理音视频流和数据通道(5)。
-
JsSIP.Registrator:管理 SIP 注册过程,处理注册请求和响应(5)。
-
JsSIP.Options:用于发送 OPTIONS 请求,查询服务器能力。
-
JsSIP.DTMF:处理 DTMF(双音多频)信号,用于电话拨号和交互式语音响应系统。
这些组件协同工作,提供了完整的 SIP 客户端功能,从底层网络通信到高层会话管理,一应俱全。
2.1.2 组件关系图
+----------------+\| JsSIP |+----------------+\| - UA |\| - Socket |\| - Message |\| - RTCSession |\| - Registrator|+----------------+  \| | |  v v v+---------+ +---------+ +------------+\| Socket | | Message| | RTCSession|+---------+ +---------+ +------------+  \| |+------v--------+ +-----v------+\| WebSocket | | Node.js |\| Interface | | Interface|+-------------+ +-----------+
2.1.3 工作流程概述
JSSIP 的典型工作流程如下:
-
初始化:创建
JsSIP.UA
实例,配置 SIP 服务器地址、用户凭证等参数。 -
连接:UA 通过 Socket 与 SIP 服务器建立 WebSocket 连接。
-
注册:UA 向 SIP 服务器发送 REGISTER 请求,注册用户身份。
-
会话建立:通过 INVITE 请求发起会话,协商媒体参数,建立 RTCPeerConnection。
-
数据传输:通过 RTCPeerConnection 交换音视频数据或任意应用数据。
-
会话管理:处理会话中的各种事件(如来电、挂断、媒体流变化)。
-
会话终止:通过 BYE 请求结束会话。
-
注销:发送 UNREGISTER 请求,取消注册状态。
-
断开连接:关闭 WebSocket 连接,释放资源。
2.2 JsSIP.UA 类详解
JsSIP.UA
是 JSSIP 的核心类,代表 SIP 用户代理,负责与 SIP 服务器通信,管理注册状态和会话。本节将详细介绍JsSIP.UA
的创建、配置和基本用法。
2.2.1 创建 UA 实例
要创建JsSIP.UA
实例,需要提供一个配置对象。配置对象包含了连接到 SIP 服务器所需的基本信息。
// 创建WebSocket接口const socket = new JsSIP.WebSocketInterface('wss://sip.example.com');// 配置对象const configuration = {  sockets: \[socket],  uri: 'sip:alice@example.com',  password: 'superpassword'};// 创建UA实例const ua = new JsSIP.UA(configuration);
配置参数说明:
-
sockets:一个数组,包含用于连接 SIP 服务器的 Socket 实例。对于 WebSocket 传输,通常只需要一个 Socket。
-
uri:用户的 SIP URI,表示用户的身份,格式为
sip:username@domain
。 -
password:用户的认证密码。
-
register(可选):布尔值,指示 UA 是否在启动时自动注册。默认为
true
。 -
outbound_proxy_set(可选):出站代理地址,用于路由 SIP 消息。
-
session_timers(可选):布尔值,指示是否启用会话计时器。默认为
true
。 -
connection_recovery_max_interval(可选):连接恢复的最大时间间隔(毫秒),用于自动重连机制(10)。
完整的配置参数列表请参考 JSSIP 官方文档。
2.2.2 UA 的生命周期管理
JsSIP.UA
提供了以下方法管理其生命周期:
- start():连接到信令服务器,并恢复之前的状态(如果之前停止过)。如果配置中的
register
参数为true
,则会向 SIP 域注册。
ua.start();
- stop():保存当前注册状态,优雅地注销并终止活动会话(如果有的话),然后断开与信令服务器的连接。
ua.stop();
- register():手动触发注册过程。
ua.register();
- unregister(options = null):手动触发注销过程。参数
options
是一个对象,包含all
属性(布尔值),指示是否注销同一 SIP 用户的所有绑定(5)。
const options = { all: true };ua.unregister(options);
- isRegistered():检查 UA 是否已注册。
if (ua.isRegistered()) {  console.log('已注册');} else {  console.log('未注册');}
- isConnected():检查传输是否已连接。
if (ua.isConnected()) {  console.log('已连接');} else {  console.log('未连接');}
2.2.3 UA 事件监听
JsSIP.UA
定义了一系列事件,用于通知应用程序各种状态变化。可以通过on()
方法注册事件处理函数。
常用事件:
- connecting:在传输连接尝试时触发。
ua.on('connecting', function(data) {  console.log('正在连接到:', data.socket.url);});
- connected:传输连接建立后触发。
ua.on('connected', function(data) {  console.log('已连接到:', data.socket.url);});
- disconnected:传输连接断开时触发。
ua.on('disconnected', function(data) {  if (data.error) {  console.error('连接断开(错误):', data.reason);  } else {  console.log('连接断开:', data.reason);  }});
- registered:注册成功后触发。
ua.on('registered', function(data) {  console.log('注册成功:', data.response.status\_code);});
- unregistered:注销后触发。
ua.on('unregistered', function(data) {  console.log('注销成功');});
- registrationFailed:注册失败时触发。
ua.on('registrationFailed', function(data) {  console.error('注册失败:', data.cause);});
- registrationExpiring:注册即将过期时触发(在过期前几秒钟)。
ua.on('registrationExpiring', function() {  console.log('注册即将过期,正在自动重新注册...');  // 如果未处理此事件,JSSIP将自动重新注册});
- newRTCSession:收到新的 RTCSession(如来电)或发起新的 RTCSession 时触发。
ua.on('newRTCSession', function(data) {  if (data.originator === 'remote') {  console.log('收到来电');  // 处理来电  } else {  console.log('发起新会话');  }});
- newMessage:收到新的 MESSAGE 请求(即时消息)时触发。
ua.on('newMessage', function(data) {  console.log('收到新消息:', data.message.body);});
2.2.4 发送 SIP 请求
JsSIP.UA
提供了多种方法发送不同类型的 SIP 请求:
- call(target, options = null):发起多媒体呼叫。
const options = {  mediaConstraints: { audio: true, video: true },  eventHandlers: {  confirmed: function() {  console.log('呼叫已确认');  }  }};const session = ua.call('sip:bob@example.com', options);
- sendMessage(target, body, options = null):发送即时消息。
const options = {  contentType: 'text/plain',  eventHandlers: {  succeeded: function() {  console.log('消息发送成功');  }  }};ua.sendMessage('sip:bob@example.com', 'Hello, Bob!', options);
- sendInfo(contentType, body = null, options = null):发送 INFO 请求,用于传输带外信息。
ua.sendInfo('application/dtmf-relay', '1', {  extraHeaders: \['X-Custom-Header: value']});
- refer(target, options = null):发送 REFER 请求,用于呼叫转移。
const options = {  eventHandlers: {  referAccepted: function() {  console.log('呼叫转移已接受');  }  }};ua.refer('sip:carol@example.com', options);
- options():发送 OPTIONS 请求,查询服务器能力。
ua.options()  .then(function(response) {  console.log('服务器支持的方法:', response.headers\['allow']);  })  .catch(function(error) {  console.error('OPTIONS请求失败:', error);  });
2.3 传输层接口(Socket)
传输层接口(Socket)负责与 SIP 服务器建立连接,发送和接收 SIP 消息。JSSIP 提供了多种 Socket 实现,以适应不同的环境。
2.3.1 WebSocket 接口
在浏览器环境中,通常使用 WebSocket 传输 SIP 消息。JSSIP 提供了JsSIP.WebSocketInterface
类来实现这一功能。
创建 WebSocket 接口:
// 使用WSS(安全WebSocket)const secureSocket = new JsSIP.WebSocketInterface('wss://sip.example.com:7443');// 使用WS(不安全WebSocket)const insecureSocket = new JsSIP.WebSocketInterface('ws://sip.example.com:5066');
WebSocket 接口事件:
JsSIP.WebSocketInterface
继承自JsSIP.Socket
,提供了以下关键事件:
- onconnect:连接建立成功时触发。
secureSocket.onconnect = function() {  console.log('WebSocket连接成功');};
- ondisconnect:连接断开时触发。
secureSocket.ondisconnect = function(error) {  if (error) {  console.error('WebSocket连接断开(错误):', error);  } else {  console.log('WebSocket连接断开');  }};
- onmessage:收到消息时触发。
secureSocket.onmessage = function(message) {  console.log('收到SIP消息:', message);  // 处理接收到的SIP消息};
发送消息:
使用send()
方法发送 SIP 消息:
const message = new JsSIP.Message('OPTIONS sip:example.com SIP/2.0');secureSocket.send(message);
2.3.2 Node.js 专用接口
在 Node.js 环境中,JSSIP 提供了专门的 Socket 实现,使用websocket
模块代替浏览器内置的 WebSocket API。需要安装额外的包:
npm install jssip-node-websocket
使用 Node.js WebSocket 接口:
const NodeWebSocket = require('jssip-node-websocket');const socket = new NodeWebSocket('wss://sip.example.com');// 设置事件处理函数socket.onconnect = function() {  console.log('Node.js WebSocket连接成功');};socket.ondisconnect = function(error) {  console.log('Node.js WebSocket连接断开:', error);};socket.onmessage = function(message) {  console.log('收到消息:', message);};// 连接到服务器socket.connect();
Node.js 专用选项:
NodeWebSocket
构造函数接受一个可选的选项对象,用于配置底层的 WebSocket 连接:
const options = {  rejectUnauthorized: true, // 是否验证服务器证书(默认true)  ca: fs.readFileSync('ca.crt'), // 自定义CA证书  headers: {  'X-Custom-Header': 'value'  }};const socket = new NodeWebSocket('wss://sip.example.com', options);
2.3.3 自定义传输接口
JSSIP 允许开发者实现自定义的传输接口,以支持其他传输协议(如 HTTP 长轮询、UDP 等)。要创建自定义传输接口,需要实现JsSIP.Socket
接口的以下方法:
-
connect():建立连接。
-
disconnect():断开连接。
-
send(message):发送 SIP 消息。
-
close():关闭连接。
以下是一个简单的自定义 Socket 示例:
class CustomSocket extends JsSIP.Socket {  constructor(url) {  super();  this.url = url;  this.connected = false;  }     connect() {  // 实现连接逻辑  this.connected = true;  this.emit('connect');  }     disconnect() {  if (this.connected) {  this.connected = false;  this.emit('disconnect');  }  }     send(message) {  if (this.connected) {  console.log('发送消息:', message.toString());  this.emit('message', message);  } else {  throw new Error('Socket未连接');  }  }     close() {  this.disconnect();  }}
2.4 消息处理与解析
JSSIP 提供了JsSIP.Message
类来表示 SIP 消息,包括请求和响应。本节将介绍如何创建、解析和处理 SIP 消息。
2.4.1 创建 SIP 消息
创建 SIP 请求:
// 使用构造函数const request = new JsSIP.Message('INVITE sip:bob@example.com SIP/2.0');request.headers.set('From', 'sip:alice@example.com');request.headers.set('To', 'sip:bob@example.com');request.headers.set('Call-ID', '12345');request.headers.set('CSeq', '1 INVITE');request.headers.set('Contact', 'sip:alice@example.com');request.setBody('...SDP内容...');// 使用工厂方法const request = JsSIP.Message.createRequest('INVITE', 'sip:bob@example.com', {  from: 'sip:alice@example.com',  to: 'sip:bob@example.com',  callId: '12345',  cSeq: '1 INVITE',  contact: 'sip:alice@example.com'});
创建 SIP 响应:
// 使用构造函数const response = new JsSIP.Message('SIP/2.0 200 OK');response.headers.set('From', 'sip:alice@example.com');response.headers.set('To', 'sip:bob@example.com');response.headers.set('Call-ID', '12345');response.headers.set('CSeq', '1 INVITE');response.setBody('...SDP内容...');// 使用工厂方法const response = JsSIP.Message.createResponse(200, 'OK', request, {  to: 'sip:bob@example.com',  contact: 'sip:bob@example.com'});
2.4.2 解析 SIP 消息
JSSIP 可以自动解析接收到的 SIP 消息字符串:
const messageString = \`INVITE sip:bob@example.com SIP/2.0From: sip:alice@example.comTo: sip:bob@example.comCall-ID: 12345CSeq: 1 INVITEContent-Type: application/sdpContent-Length: 142v=0o=alice 2890844526 2890844526 IN IP4 192.168.1.100...\`;const message = JsSIP.Message.parse(messageString);console.log('方法:', message.method);console.log('URL:', message.url);console.log('头部:', message.headers);console.log('内容:', message.body);
2.4.3 消息头部操作
JSSIP 提供了方便的方法来操作 SIP 消息头部:
设置头部:
message.headers.set('From', 'sip:alice@example.com');message.headers.set('To', 'sip:bob@example.com;tag=123');
获取头部:
const fromHeader = message.headers.get('From');console.log('From头部:', fromHeader);const toHeader = message.headers.get('To');console.log('To头部:', toHeader);
删除头部:
message.headers.delete('User-Agent');
检查头部是否存在:
if (message.headers.has('Content-Length')) {  console.log('存在Content-Length头部');}
遍历所有头部:
message.headers.forEach(function(value, name) {  console.log(name + ': ' + value);});
2.4.4 消息事件处理
JsSIP.Message
类定义了一系列事件,用于通知应用程序消息处理的不同阶段:
发送消息事件:
const request = new JsSIP.Message('INVITE sip:bob@example.com SIP/2.0');request.on('sent', function() {  console.log('消息已发送');});request.on('failed', function(error) {  console.error('消息发送失败:', error);});request.on('success', function(response) {  console.log('收到成功响应:', response.status\_code);});request.send();
接收消息事件:
ua.on('newMessage', function(data) {  const message = data.message;     message.on('received', function() {  console.log('收到消息:', message.method);  });     message.on('processed', function() {  console.log('消息已处理');  });     // 发送响应  message.respond(200, 'OK');});
2.4.5 消息体处理
SIP 消息可以包含各种类型的内容,如 SDP、XML、JSON 等。JSSIP 提供了以下方法处理消息体:
设置消息体:
const sdp = \`v=0o=alice 2890844526 2890844526 IN IP4 192.168.1.100s=会话c=IN IP4 192.168.1.100t=0 0m=audio 49170 RTP/AVP 0a=rtpmap:0 PCMU/8000\`;message.setBody(sdp);message.headers.set('Content-Type', 'application/sdp');message.headers.set('Content-Length', Buffer.byteLength(sdp));
获取消息体:
const body = message.body;console.log('消息体:', body);
解析 SDP 内容:
const sdpParser = new JsSIP.SDP(sipMessage.body);const mediaSection = sdpParser.media\[0];console.log('媒体类型:', mediaSection.media);console.log('端口:', mediaSection.port);console.log('协议:', mediaSection.protocol);console.log('格式:', mediaSection.format);
2.5 多媒体会话管理
JSSIP 通过JsSIP.RTCSession
类管理基于 WebRTC 的多媒体会话,支持音视频通话和数据通道。本节将介绍如何创建和管理多媒体会话。
2.5.1 创建 RTCSession
RTCSession
可以通过JsSIP.UA
的call()
方法创建,用于发起出站会话;或者通过newRTCSession
事件接收,用于处理入站会话。
发起出站会话:
const options = {  mediaConstraints: { audio: true, video: true },  eventHandlers: {  confirmed: function() {  console.log('会话已确认');  }  }};const session = ua.call('sip:bob@example.com', options);
处理入站会话:
ua.on('newRTCSession', function(data) {  if (data.originator === 'remote') {  const incomingSession = data.session;     // 自动应答(示例)  incomingSession.answer({  mediaConstraints: { audio: true, video: true }  });  }});
2.5.2 RTCSession 事件
JsSIP.RTCSession
定义了丰富的事件,用于跟踪会话的各个阶段:
- connecting:在初始 INVITE 请求或 “200 OK” 响应传输前触发。
session.on('connecting', function(data) {  console.log('会话正在连接...');});
- sending:在发送初始 INVITE 之前触发(仅用于出站会话)。
session.on('sending', function(data) {  console.log('正在发送INVITE请求...');});
- progress:在接收或生成对 INVITE 请求的 1xx 响应时触发。
session.on('progress', function(data) {  console.log('收到临时响应:', data.response.status\_code);});
- accepted:当会话被接受(收到 2xx 响应)时触发。
session.on('accepted', function(data) {  console.log('会话已接受');});
- confirmed:当会话确认(ACK 收到 / 发送)时触发。
session.on('confirmed', function(data) {  console.log('会话已确认');});
- ended:当已建立的会话结束时触发。
session.on('ended', function(data) {  console.log('会话结束,原因:', data.cause);  // 清理资源});
- failed:当会话无法建立时触发。
session.on('failed', function(data) {  console.error('会话失败,原因:', data.cause);});
- peerconnection:当底层
RTCPeerConnection
创建后触发。
session.on('peerconnection', function(data) {  const peerConnection = data.peerconnection;     // 添加媒体流  peerConnection.addStream(localStream);     // 监听远程流事件  peerConnection.onaddstream = function(event) {  remoteVideo.srcObject = event.stream;  };});
- sdp:在处理 SDP 之前触发,允许修改 SDP 内容。
session.on('sdp', function(data) {  if (data.type === 'offer') {  // 修改SDP  data.sdp = data.sdp.replace('UDP/TLS/RTP/SAVPF', 'RTP/SAVPF');  }});
2.5.3 会话控制方法
JsSIP.RTCSession
提供了丰富的方法来控制会话行为:
- answer(options = null):应答入站会话。
session.answer({  mediaConstraints: { audio: true, video: false },  pcConfig: {  iceServers: \[{ urls: 'stun:stun.l.google.com:19302' }]  }});
- terminate(options = null):终止会话。
session.terminate();
- sendDTMF(tone, options = null):发送 DTMF 信号。
session.sendDTMF('1234#', {  duration: 160,  interToneGap: 1200});
- sendInfo(contentType, body = null, options = null):发送 INFO 请求。
session.sendInfo('application/dtmf-relay', '1');
- hold(options = null, done = null):保持会话。
session.hold();
- unhold(options = null, done = null):恢复被保持的会话。
session.unhold();
- renegotiate():重新协商媒体参数。
session.renegotiate();
- mute(options = null):静音本地音频或视频。
session.mute({ audio: true, video: false });
- unmute(options = null):取消静音。
session.unmute({ audio: true, video: false });
- isMuted():检查是否静音。
const muted = session.isMuted();console.log('音频是否静音:', muted.audio);console.log('视频是否静音:', muted.video);
2.5.4 媒体流管理
JsSIP.RTCSession
通过底层的RTCPeerConnection
管理媒体流。以下是媒体流管理的关键方法:
获取本地媒体流:
navigator.mediaDevices.getUserMedia({ audio: true, video: true })  .then(function(stream) {  localStream = stream;  localVideo.srcObject = stream;  })  .catch(function(error) {  console.error('获取媒体设备失败:', error);  });
添加本地媒体流到会话:
session.on('peerconnection', function(data) {  const peerConnection = data.peerconnection;  peerConnection.addStream(localStream);});
处理远程媒体流:
session.on('peerconnection', function(data) {  const peerConnection = data.peerconnection;     peerConnection.onaddstream = function(event) {  remoteVideo.srcObject = event.stream;  };     peerConnection.ontrack = function(event) {  if (event.track.kind === 'video') {  remoteVideo.srcObject = event.streams\[0];  } else if (event.track.kind === 'audio') {  remoteAudio.srcObject = event.streams\[0];  }  };});
替换媒体流:
function switchCamera() {  // 获取新的视频流  navigator.mediaDevices.getUserMedia({ audio: true, video: { facingMode: 'environment' } })  .then(function(newStream) {  // 替换旧流  const peerConnection = session.connection;  const tracks = localStream.getTracks();     tracks.forEach(function(track) {  if (track.kind === 'video') {  peerConnection.removeTrack(track);  track.stop();  }  });     localStream = newStream;  peerConnection.addStream(localStream);  localVideo.srcObject = localStream;  })  .catch(function(error) {  console.error('切换摄像头失败:', error);  });}
2.5.5 数据通道
除了音视频流,JsSIP.RTCSession
还支持通过RTCDataChannel
传输任意应用数据:
创建数据通道:
session.on('peerconnection', function(data) {  const peerConnection = data.peerconnection;     // 创建数据通道  const dataChannel = peerConnection.createDataChannel('chat');     // 监听消息事件  dataChannel.onmessage = function(event) {  console.log('收到数据:', event.data);  };     // 监听打开事件  dataChannel.onopen = function() {  dataChannel.send('Hello, peer!');  };});
接收数据通道:
session.on('peerconnection', function(data) {  const peerConnection = data.peerconnection;     peerConnection.ondatachannel = function(event) {  const dataChannel = event.channel;     dataChannel.onmessage = function(event) {  console.log('收到数据:', event.data);  };     dataChannel.onopen = function() {  dataChannel.send('Hello, sender!');  };  };});
三、SIP 注册与基本会话
3.1 SIP 注册流程
SIP 注册是用户代理(UA)向 SIP 服务器表明其可用性的过程,使服务器能够将传入的呼叫和消息路由到正确的终端。本节将详细介绍 SIP 注册的流程和在 JSSIP 中的实现。
3.1.1 注册过程概述
SIP 注册的基本流程如下:
-
用户代理发送 REGISTER 请求:UA 向 SIP 服务器发送 REGISTER 请求,包含用户身份(URI)、联系地址(Contact)和过期时间(Expires)。
-
服务器验证:SIP 服务器验证用户凭证(如用户名和密码)。
-
服务器响应:
-
如果验证成功,服务器返回 200 OK 响应,包含注册有效期。
-
如果验证失败,服务器返回 401 Unauthorized 或 403 Forbidden 响应。
-
刷新注册:注册具有有效期(通常为 3600 秒),UA 需要在过期前发送新的 REGISTER 请求以刷新注册状态。
-
注销:用户退出时,UA 发送 Expires 为 0 的 REGISTER 请求,通知服务器注销。
3.1.2 JSSIP 中的注册实现
在 JSSIP 中,注册过程由JsSIP.UA
类自动管理,但开发者可以通过配置和事件监听控制注册行为。
基本注册配置:
const socket = new JsSIP.WebSocketInterface('wss://sip.example.com');const configuration = {  sockets: \[socket],  uri: 'sip:alice@example.com',  password: 'superpassword',  register: true // 自动注册(默认值为true)};const ua = new JsSIP.UA(configuration);ua.start();
注册事件监听:
ua.on('registered', function(data) {  console.log('注册成功,有效期:', data.response.headers.get('Expires'));});ua.on('registrationFailed', function(data) {  console.error('注册失败,原因:', data.cause);     if (data.response && data.response.status\_code === 401) {  console.log('需要重新认证');  // 处理认证失败  }});ua.on('registrationExpiring', function() {  console.log('注册即将过期,正在自动刷新...');  // 如果未处理此事件,JSSIP将自动重新注册});
手动注册和注销:
// 手动注册ua.register();// 手动注销ua.unregister();// 强制注销所有绑定ua.unregister({ all: true });
3.1.3 注册参数配置
JSSIP 提供了丰富的配置参数,用于定制注册行为:
注册有效期:
const configuration = {  // ...其他配置...  expires: 3600 // 注册有效期(秒),默认3600};
自定义 Contact 头部:
const configuration = {  // ...其他配置...  contact\_uri: 'sip:alice@example.com;transport=wss', // 自定义Contact URI  contact\_params: { // Contact头部的额外参数  'x-extension': '1001',  'x-device': 'web'  }};
注册重试策略:
const configuration = {  // ...其他配置...  registration\_retries: 3, // 注册失败后的重试次数,默认3  registration\_retry\_delay: 5000 // 重试间隔(毫秒),默认5000};
代理注册:
如果需要通过代理服务器注册,可以配置outbound_proxy_set
参数:
const configuration = {  // ...其他配置...  outbound\_proxy\_set: 'sip:proxy.example.com'};
3.1.4 处理认证挑战
当 SIP 服务器返回 401 Unauthorized 响应时,表示需要认证。JSSIP 会自动处理基于 Digest 认证的挑战,但开发者可以通过事件监听自定义认证处理。
认证事件监听:
ua.on('registrationFailed', function(data) {  if (data.response && data.response.status\_code === 401) {  const challenge = data.response.headers.get('WWW-Authenticate');  console.log('收到认证挑战:', challenge);     // 可以在此处提供自定义认证处理  }});
自定义认证处理:
JSSIP 使用JsSIP.Auth
类处理认证。要自定义认证行为,可以创建自定义Auth
实例:
const auth = new JsSIP.Auth({  uri: 'sip:alice@example.com',  password: 'superpassword',  realm: 'example.com'});const configuration = {  // ...其他配置...  auth: auth};const ua = new JsSIP.UA(configuration);
摘要认证(Digest Authentication):
JSSIP 自动支持 RFC 2617 定义的摘要认证,无需额外配置。当收到 401 响应时,JSSIP 会根据服务器提供的 nonce 和 realm 计算摘要,并在后续请求中包含 Authorization 头部。
3.1.5 注册状态监控
JSSIP 提供了多种方法和事件,用于监控注册状态:
检查注册状态:
if (ua.isRegistered()) {  console.log('当前已注册');  console.log('注册到期时间:', ua.getExpires());} else {  console.log('当前未注册');}
注册状态变化事件:
ua.on('registered', function() {  console.log('注册成功');});ua.on('unregistered', function() {  console.log('已注销');});ua.on('registrationFailed', function() {  console.log('注册失败');});ua.on('registrationExpiring', function() {  console.log('注册即将过期');});
注册重试机制:
JSSIP 在注册失败后会自动重试,默认重试 3 次,每次间隔 5 秒。可以通过配置参数调整这些行为:
const configuration = {  // ...其他配置...  registration\_retries: 5, // 最大重试次数  registration\_retry\_delay: 3000 // 重试间隔(毫秒)};
3.2 基本呼叫流程
呼叫建立是 SIP 的核心功能。本节将介绍 SIP 呼叫的基本流程,以及如何使用 JSSIP 实现简单的呼叫功能。
3.2.1 呼叫流程概述
基本的 SIP 呼叫流程包括以下步骤:
-
INVITE 请求:主叫方发送 INVITE 请求,包含会话描述(SDP)。
-
100 Trying 响应:被叫方返回 100 Trying 临时响应,表示请求已收到,正在处理。
-
180 Ringing 响应:被叫方返回 180 Ringing 响应,表示正在振铃。
-
200 OK 响应:被叫方接受呼叫,返回 200 OK 响应,包含应答的 SDP。
-
ACK 请求:主叫方发送 ACK 请求,确认收到 200 OK 响应。
-
媒体流传输:双方根据协商的 SDP 参数建立媒体连接,开始传输音视频数据。
-
BYE 请求:任何一方发送 BYE 请求,终止会话。
-
200 OK 响应:对方返回 200 OK 响应,确认会话终止。
3.2.2 发起呼叫
在 JSSIP 中,使用JsSIP.UA
的call()
方法发起呼叫:
基本呼叫示例:
// 获取本地媒体流navigator.mediaDevices.getUserMedia({ audio: true, video: true })  .then(function(stream) {  localStream = stream;  localVideo.srcObject = stream;  })  .catch(function(error) {  console.error('获取媒体设备失败:', error);  });// 发起呼叫const options = {  mediaConstraints: { audio: true, video: true },  eventHandlers: {  confirmed: function() {  console.log('呼叫已确认');  },  ended: function() {  console.log('呼叫已结束');  }  }};const session = ua.call('sip:bob@example.com', options);
呼叫选项说明:
-
mediaConstraints:媒体约束,指定所需的媒体类型(音频和视频)。
-
eventHandlers:事件处理函数,用于监听呼叫的不同阶段。
-
pcConfig:RTCPeerConnection 配置,如 ICE 服务器地址。
-
extraHeaders:要添加到 INVITE 请求的额外头部。
-
sessionTimersExpires:会话计时器间隔(秒)。
3.2.3 接收呼叫
使用newRTCSession
事件监听入站呼叫:
接收呼叫示例:
ua.on('newRTCSession', function(data) {  if (data.originator === 'remote') {  const incomingSession = data.session;     // 自动应答(示例)  incomingSession.answer({  mediaConstraints: { audio: true, video: true }  });     // 监听会话结束事件  incomingSession.on('ended', function() {  console.log('来电已结束');  });  }});
处理不同类型的入站会话:
ua.on('newRTCSession', function(data) {  if (data.originator === 'remote') {  const session = data.session;     // 检查请求类型  if (session.direction === 'incoming') {  console.log('收到入站呼叫');     // 可以在此处实现振铃提示、用户确认等逻辑     // 自动应答  session.answer({  mediaConstraints: { audio: true, video: true }  });  }  }});
3.2.4 会话控制
JSSIP 提供了丰富的方法控制呼叫会话:
应答呼叫:
session.answer({  mediaConstraints: { audio: true, video: true },  pcConfig: {  iceServers: \[{ urls: 'stun:stun.l.google.com:19302' }]  }});
终止呼叫:
session.terminate();
保持呼叫:
session.hold();
恢复呼叫:
session.unhold();
静音 / 取消静音:
// 静音本地音频session.mute({ audio: true });// 取消静音session.unmute({ audio: true });
3.2.5 媒体协商与处理
JSSIP 自动处理 SDP 协商,但开发者可以通过事件监听和回调函数自定义媒体协商过程。
媒体约束配置:
const options = {  mediaConstraints: {  audio: true,  video: {  width: { min: 640, ideal: 1280 },  height: { min: 480, ideal: 720 }  }  }};const session = ua.call('sip:bob@example.com', options);
自定义 SDP 处理:
session.on('sdp', function(data) {  if (data.type === 'offer') {  // 修改SDP  data.sdp = data.sdp.replace('UDP/TLS/RTP/SAVPF', 'RTP/SAVPF');     // 添加自定义属性  data.sdp += '\na=my-custom-attribute: value';  }});
处理媒体流:
session.on('peerconnection', function(data) {  const peerConnection = data.peerconnection;     // 添加本地媒体流  peerConnection.addStream(localStream);     // 监听远程媒体流  peerConnection.onaddstream = function(event) {  remoteVideo.srcObject = event.stream;  };     // 监听媒体轨道变化  peerConnection.ontrack = function(event) {  if (event.track.kind === 'video') {  remoteVideo.srcObject = event.streams\[0];  } else if (event.track.kind === 'audio') {  remoteAudio.srcObject = event.streams\[0];  }  };});
3.2.6 呼叫事件监听
JSSIP 提供了丰富的事件,用于跟踪呼叫的各个阶段:
呼叫建立事件:
session.on('connecting', function() {  console.log('呼叫正在连接...');});session.on('sending', function() {  console.log('正在发送INVITE请求...');});session.on('progress', function(data) {  console.log('收到临时响应:', data.response.status\_code);});session.on('accepted', function() {  console.log('呼叫已接受');});session.on('confirmed', function() {  console.log('呼叫已确认');});
呼叫终止事件:
session.on('ended', function(data) {  console.log('呼叫结束,原因:', data.cause);  // 清理资源});session.on('failed', function(data) {  console.error('呼叫失败,原因:', data.cause);});
媒体事件:
session.on('peerconnection', function(data) {  const peerConnection = data.peerconnection;     peerConnection.oniceconnectionstatechange = function() {  console.log('ICE连接状态:', peerConnection.iceConnectionState);  };     peerConnection.onicegatheringstatechange = function() {  console.log('ICE收集状态:', peerConnection.iceGatheringState);  };});
3.3 即时消息与状态通知
除了音视频通话,SIP 还支持即时消息和状态通知功能。本节将介绍如何使用 JSSIP 实现这些功能。
3.3.1 发送即时消息
SIP 的 MESSAGE 方法用于发送即时消息,类似于电子邮件或短信。在 JSSIP 中,可以使用JsSIP.UA
的sendMessage()
方法发送即时消息。
基本消息发送示例:
const options = {  contentType: 'text/plain',  eventHandlers: {  succeeded: function() {  console.log('消息发送成功');  },  failed: function(error) {  console.error('消息发送失败:', error);  }  }};ua.sendMessage('sip:bob@example.com', 'Hello, Bob!', options);
发送不同类型的消息:
// 发送HTML格式的消息ua.sendMessage('sip:bob@example.com', '\<p>Hello, Bob!\</p>', {  contentType: 'text/html'});// 发送XML消息const xml = '\<presence xmlns="urn:ietf:params:xml:ns:pidf">\<tuple>\<status>\<basic>open\</basic>\</status>\</tuple>\</presence>';ua.sendMessage('sip:bob@example.com', xml, {  contentType: 'application/pidf+xml'});
发送包含自定义头部的消息:
ua.sendMessage('sip:bob@example.com', 'Hello', {  extraHeaders: \[  'X-Custom-Header: value',  'X-Another-Header: another value'  ]});
3.3.2 接收即时消息
使用newMessage
事件监听入站消息:
消息接收示例:
ua.on('newMessage', function(data) {  const message = data.message;     console.log('收到消息来自:', message.headers.get('From'));  console.log('消息内容:', message.body);  console.log('内容类型:', message.headers.get('Content-Type'));     // 发送响应  message.respond(200, 'OK');});
处理不同类型的消息:
ua.on('newMessage', function(data) {  const message = data.message;     switch (message.headers.get('Content-Type')) {  case 'text/plain':  console.log('纯文本消息:', message.body);  break;     case 'text/html':  console.log('HTML消息:', message.body);  break;     case 'application/pidf+xml':  console.log('状态信息:', message.body);  break;     default:  console.log('未知类型消息:', message.body);  }     // 发送响应  message.respond(200, 'OK');});
处理错误消息:
ua.on('newMessage', function(data) {  const message = data.message;     if (message.method === 'MESSAGE') {  // 处理正常消息  } else if (message.method === 'NOTIFY') {  // 处理状态通知  } else {  // 未知方法,发送错误响应  message.respond(405, 'Method Not Allowed');  }});
3.3.3 状态通知
SIP 的 SUBSCRIBE 和 NOTIFY 方法用于实现状态通知功能,允许用户订阅其他用户的状态(如在线、忙碌、离开等)。
订阅状态示例:
const options = {  event: 'presence',  expires: 3600,  eventHandlers: {  subscribed: function() {  console.log('订阅成功');  },  failed: function(error) {  console.error('订阅失败:', error);  }  }};ua.subscribe('sip:bob@example.com', options);
处理状态通知:
ua.on('newMessage', function(data) {  const message = data.message;     if (message.method === 'NOTIFY' && message.headers.get('Event') === 'presence') {  console.log('收到状态通知');  console.log('状态内容:', message.body);     // 发送响应  message.respond(200, 'OK');  }});
取消订阅:
ua.unsubscribe('sip:bob@example.com', {  event: 'presence',  expires: 0});
3.3.4 高级消息功能
JSSIP 还支持一些高级消息功能,如消息等待指示(MWI)和文件传输。
消息等待指示:
ua.on('newMessage', function(data) {  const message = data.message;     if (message.headers.get('Event') === 'message-summary') {  console.log('消息等待指示:', message.body);     // 解析消息摘要  const summary = message.body;  const parts = summary.split(';');  const voicemailCount = parts\[0].split(':')\[1];  const newVoicemailCount = parts\[1].split(':')\[1];     console.log('语音邮件总数:', voicemailCount);  console.log('新语音邮件数:', newVoicemailCount);     // 发送响应  message.respond(200, 'OK');  }});
文件传输:
通过发送包含文件内容的 MESSAGE 请求,可以实现简单的文件传输功能:
// 读取文件内容const fileReader = new FileReader();fileReader.onload = function(event) {  const fileContent = event.target.result;     // 发送文件  ua.sendMessage('sip:bob@example.com', fileContent, {  contentType: 'application/octet-stream',  extraHeaders: \[  'Content-Disposition: attachment; filename="document.pdf"'  ]  });};fileReader.readAsArrayBuffer(fileInput.files\[0]);
3.3.5 消息事件处理
JSSIP 为消息提供了丰富的事件,用于跟踪消息的发送和接收过程:
发送消息事件:
const message = ua.sendMessage('sip:bob@example.com', 'Hello');message.on('sent', function() {  console.log('消息已发送');});message.on('success', function(response) {  console.log('收到成功响应:', response.status\_code);});message.on('failure', function(response) {  console.error('收到失败响应:', response.status\_code);});message.on('error', function(error) {  console.error('消息处理错误:', error);});
接收消息事件:
ua.on('newMessage', function(data) {  const message = data.message;     message.on('received', function() {  console.log('消息已收到');  });     message.on('processed', function() {  console.log('消息已处理');  });     message.on('responded', function(response) {  console.log('已发送响应:', response.status\_code);  });});
四、高级 API 与应用场景
4.1 高级会话管理
在复杂的实时通信应用中,需要更精细地控制会话行为。本节将介绍 JSSIP 的高级会话管理功能。
4.1.1 会话转移与合并
SIP 支持会话转移(Call Transfer)和会话合并(Call Merging)功能,允许用户将正在进行的通话转移给第三方或合并多个通话。
盲转移(Blind Transfer):
盲转移是指在不与第三方协商的情况下直接将会话转移。在 JSSIP 中,可以使用refer()
方法实现盲转移:
// 转移当前会话到sip:carol@example.comsession.refer('sip:carol@example.com');
咨询转移(Consultative Transfer):
咨询转移允许用户先与第三方建立新会话,然后将当前会话转移给第三方。
// 发起咨询呼叫const consultSession = ua.call('sip:carol@example.com');consultSession.on('confirmed', function() {  // 建立咨询会话后,转移原会话  originalSession.refer('sip:carol@example.com', {  replaces: originalSession  });     // 结束咨询会话  consultSession.terminate();});
会话合并:
会话合并(也称为会议桥接)允许用户将多个会话合并为一个多方会议。实现会话合并需要服务器支持,JSSIP 本身不提供会议桥接功能,但可以通过与支持会议的 SIP 服务器配合实现。
// 将当前会话加入会议session.sendInfo('application/dtmf-relay', 'Conference');
4.1.2 多设备同步
在多设备环境中,用户可能在多个终端(如手机、电脑、平板)上同时注册。JSSIP 提供了支持多设备同步的功能。
多 UA 实例:
在同一应用中创建多个JsSIP.UA
实例,代表不同的 SIP 账户或同一账户的不同设备:
// 创建第一个UA实例const ua1 = new JsSIP.UA({  sockets: \[new JsSIP.WebSocketInterface('wss://sip.example.com')],  uri: 'sip:alice@example.com',  password: 'password1'});// 创建第二个UA实例const ua2 = new JsSIP.UA({  sockets: \[new JsSIP.WebSocketInterface('wss://sip.example.com')],  uri: 'sip:alice@example.com',  password: 'password2'});
设备状态同步:
通过订阅其他设备的状态,可以实现设备状态同步:
// 订阅其他设备的状态ua.subscribe('sip:alice@example.com;device=phone');// 处理状态通知ua.on('newMessage', function(data) {  if (data.message.method === 'NOTIFY' && data.message.headers.get('Event') === 'presence') {  console.log('收到设备状态通知:', data.message.body);  }});
呼叫前转:
通过修改注册 Contact 头部,可以实现呼叫前转:
const configuration = {  // ...其他配置...  contact\_uri: 'sip:alice@example.com;user=phone',  contact\_params: {  '+sip.instance': '\<urn:uuid:12345678-1234-5678-1234-567812345678>'  }};
4.1.3 呼叫保持与恢复
JSSIP 提供了hold()
和unhold()
方法来实现呼叫保持和恢复功能。
保持当前会话:
// 保持本地会话session.hold();// 保持远程会话(需要对方支持)session.hold({ remote: true });
检查保持状态:
const holdStatus = session.isOnHold();console.log('本地是否保持:', holdStatus.local);console.log('远程是否保持:', holdStatus.remote);
恢复被保持的会话:
// 恢复本地会话session.unhold();// 恢复远程会话(需要对方支持)session.unhold({ remote: true });
保持事件监听:
session.on('hold', function() {  console.log('会话已保持');});session.on('unhold', function() {  console.log('会话已恢复');});
4.1.4 媒体重协商
在会话过程中,可以动态修改媒体参数,如添加视频流、更换编解码器或调整带宽。这一过程称为媒体重协商。
重新协商媒体参数:
// 修改媒体约束session.mediaConstraints = { audio: true, video: true };// 触发重协商session.renegotiate();
添加视频流:
// 获取视频流navigator.mediaDevices.getUserMedia({ video: true })  .then(function(videoStream) {  // 将视频流添加到会话  session.connection.addStream(videoStream);     // 触发重协商  session.renegotiate();  });
更换编解码器:
session.on('sdp', function(data) {  if (data.type === 'offer') {  // 修改SDP以更换编解码器  data.sdp = data.sdp.replace('opus/48000/2', 'pcmu/8000');  }});session.renegotiate();
处理重协商事件:
session.on('renegotiating', function() {  console.log('正在重新协商媒体参数...');});session.on('renegotiated', function() {  console.log('媒体参数重新协商成功');});session.on('renegotiationFailed', function(error) {  console.error('媒体参数重新协商失败:', error);});
4.1.5 会话计时器管理
SIP 会话计时器(RFC 4028)用于管理会话的生命周期,确保会话在空闲一段时间后自动终止。JSSIP 支持会话计时器功能。
启用 / 禁用会话计时器:
const configuration = {  // ...其他配置...  session\_timers: true // 默认值为true};
设置会话计时器间隔:
// 发起呼叫时设置会话计时器const options = {  sessionTimersExpires: 1800 // 会话计时器间隔(秒)};ua.call('sip:bob@example.com', options);
会话计时器事件:
session.on('sessionTimerRefresh', function(expires) {  console.log('会话计时器刷新,新的有效期:', expires);});session.on('sessionTimerUpdate', function(expires) {  console.log('会话计时器更新,新的有效期:', expires);});session.on('sessionTimerTerminate', function() {  console.log('会话计时器终止会话');});
4.2 高级媒体处理
JSSIP 与 WebRTC 紧密结合,提供了丰富的媒体处理功能。本节将介绍如何利用这些功能实现高级媒体处理。
4.2.1 多流管理
现代 WebRTC 应用通常需要处理多个媒体流,如主视频流、屏幕共享流和数据通道。JSSIP 允许在单个会话中管理多个媒体流。
添加多个视频流:
// 获取主视频流navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' } })  .then(function(mainStream) {  // 获取屏幕共享流  return navigator.mediaDevices.getDisplayMedia({ video: true })  .then(function(screenStream) {  return { mainStream, screenStream };  });  })  .then(function(streams) {  session.on('peerconnection', function(data) {  const peerConnection = data.peerconnection;     // 添加主视频流  peerConnection.addStream(streams.mainStream);     // 添加屏幕共享流  peerConnection.addStream(streams.screenStream);  });  });
切换视频流:
function switchCamera() {  // 获取新的视频流  navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } })  .then(function(newStream) {  // 替换旧流  const peerConnection = session.connection;  const tracks = localStream.getTracks();     tracks.forEach(function(track) {  if (track.kind === 'video') {  peerConnection.removeTrack(track);  track.stop();  }  });     localStream = newStream;  peerConnection.addStream(localStream);  localVideo.srcObject = localStream;  });}
选择性转发:
选择性转发(Selective Forwarding Unit,SFU)是一种媒体处理技术,允许服务器选择性地将媒体流转发给不同的参与者。JSSIP 本身不提供 SFU 功能,但可以与支持 SFU 的服务器配合使用。
// 发送选择性转发指令session.sendInfo('application/sfu-control', 'subscribe:stream1');
4.2.2 音频处理
JSSIP 允许通过 Web Audio API 对音频流进行处理,如降噪、回声消除、音量调节等。
音量调节:
session.on('peerconnection', function(data) {  const peerConnection = data.peerconnection;     // 获取音频轨道  const audioTrack = localStream.getAudioTracks()\[0];     // 创建音频上下文  const audioContext = new (window.AudioContext || window.webkitAudioContext)();     // 创建音量节点  const gainNode = audioContext.createGain();  gainNode.gain.value = 0.5; // 设置音量为50%     // 连接音频节点  audioTrack.applyConstraints({ volume: 0.5 });});
音频效果处理:
session.on('peerconnection', function(data) {  const peerConnection = data.peerconnection;     // 获取音频轨道  const audioTrack = localStream.getAudioTracks()\[0];     // 创建音频上下文  const audioContext = new AudioContext();     // 创建效果节点(如混响)  const convolver = audioContext.createConvolver();     // 加载混响脉冲响应  fetch('reverb.wav')  .then(response => response.arrayBuffer())  .then(buffer => audioContext.decodeAudioData(buffer))  .then(audioBuffer => {  convolver.buffer = audioBuffer;  });     // 连接节点  audioTrack.applyConstraints({ echoCancellation: false });  audioTrack.applyConstraints({ noiseSuppression: false });     // 将效果应用于音频轨道  audioTrack.addSink(audioContext.createMediaStreamDestination());});
音频设备切换:
// 获取可用音频设备列表navigator.mediaDevices.enumerateDevices()  .then(function(devices) {  devices.forEach(function(device) {  if (device.kind === 'audiooutput') {  console.log('音频输出设备:', device.label);  }  });  });// 切换音频输出设备function switchAudioOutput(deviceId) {  const audioTrack = localStream.getAudioTracks()\[0];  audioTrack.applyConstraints({ deviceId: { exact: deviceId } });}
4.2.3 数据通道
除了音视频流,JSSIP 还支持通过数据通道传输任意应用数据,如聊天消息、文件传输和控制信号。
创建数据通道:
session.on('peerconnection', function(data) {  const peerConnection = data.peerconnection;     // 创建数据通道  const dataChannel = peerConnection.createDataChannel('chat');     // 监听消息事件  dataChannel.onmessage = function(event) {  console.log('收到数据:', event.data);  };     // 监听打开事件  dataChannel.onopen = function() {  dataChannel.send('Hello, peer!');  };     // 监听关闭事件  dataChannel.onclose = function() {  console.log('数据通道已关闭');  };});
接收数据通道:
session.on('peerconnection', function(data) {  const peerConnection = data.peerconnection;     peerConnection.ondatachannel = function(event) {  const dataChannel = event.channel;     dataChannel.onmessage = function(event) {  console.log('收到数据:', event.data);  };     dataChannel.onopen = function() {  dataChannel.send('Hello, sender!');  };  };});
可靠与不可靠传输:
数据通道支持可靠(类似 TCP)和不可靠(类似 UDP)传输模式:
// 创建可靠数据通道const reliableChannel = peerConnection.createDataChannel('reliable', {  ordered: true,  maxRetransmits: 3});// 创建不可靠数据通道const unreliableChannel = peerConnection.createDataChannel('unreliable', {  ordered: false,  maxRetransmits: 0});
4.2.4 屏幕共享
屏幕共享是现代视频会议应用的重要功能。JSSIP 支持通过 WebRTC 的getDisplayMedia()
API 实现屏幕共享。
实现屏幕共享:
// 请求屏幕共享navigator.mediaDevices.getDisplayMedia({ video: true })  .then(function(screenStream) {  // 将屏幕共享流添加到会话  session.on('peerconnection', function(data) {  const peerConnection = data.peerconnection;  peerConnection.addStream(screenStream);  });     // 显示本地屏幕共享预览  screenVideo.srcObject = screenStream;  })  .catch(function(error) {  console.error('屏幕共享失败:', error);  });
停止屏幕共享:
function stopScreenSharing() {  if (screenStream) {  // 从会话中移除屏幕共享流  session.connection.removeStream(screenStream);     // 停止媒体流  screenStream.getTracks().forEach(function(track) {  track.stop();  });     // 清除预览  screenVideo.srcObject = null;  }}
选择性屏幕共享:
// 选择特定窗口或显示器进行共享const options = {  video: {  cursor: 'always', // 显示鼠标光标  displaySurface: 'window' // 或'display'、'browser'  }};navigator.mediaDevices.getDisplayMedia(options)  .then(function(screenStream) {  // 处理屏幕共享流  });
4.2.5 媒体编解码器控制
JSSIP 允许开发者控制使用的媒体编解码器,以满足特定应用需求。
设置首选编解码器:
session.on('sdp', function(data) {  if (data.type === 'offer') {  // 重新排列编解码器顺序,将OPUS设为首选  data.sdp = data.sdp.replace('opus/48000/2', 'opus/48000/2')  .replace('pcmu/8000', 'pcmu/8000');  }});
禁用特定编解码器:
session.on('sdp', function(data) {  if (data.type === 'offer') {  // 移除VP8编解码器  data.sdp = data.sdp.replace(/a=rtpmap:(\d+) VP8\\/90000\[\s\S]\*?\n/g, '');  }});
自定义编解码器参数:
session.on('sdp', function(data) {  if (data.type === 'offer') {  // 设置OPUS编解码器的比特率  data.sdp = data.sdp.replace('opus/48000/2', 'opus/48000/2/128000');  }});
获取支持的编解码器:
// 获取本地支持的编解码器const codecs = JsSIP.RTCSession.getSupportedCodecs();console.log('支持的编解码器:', codecs);
4.3 与其他系统集成
JSSIP 可以与多种 SIP 服务器和通信系统集成,本节将介绍如何与常见系统集成。
4.3.1 与 FreeSWITCH 集成
FreeSWITCH 是一款开源的软交换平台,广泛用于构建 IP 电话系统。以下是 JSSIP 与 FreeSWITCH 集成的要点。
连接配置:
// FreeSWITCH的WebSocket地址const socket = new JsSIP.WebSocketInterface('ws://freeswitch.example.com:5066');// 用户代理配置const configuration = {  sockets: \[socket],  uri: 'sip:1000@freeswitch.example.com',  password: 'ClueCon', // FreeSWITCH默认密码  register: true};const ua = new JsSIP.UA(configuration);
处理 FreeSWITCH 的特殊要求:
FreeSWITCH 要求在 Contact 头部包含 transport 参数:
const configuration = {  // ...其他配置...  contact\_uri: 'sip:1000@freeswitch.example.com;transport=ws'};
配置媒体服务器:
在 FreeSWITCH 中,需要启用 WebSocket 监听:
\<!-- FreeSWITCH配置文件:/etc/freeswitch/sip\_profiles/external.xml -->\<param name="ws-binding" value="0.0.0.0:5066"/>\<param name="wss-binding" value="0.0.0.0:7443"/>
处理 DTMF 信号:
FreeSWITCH 支持多种 DTMF 传输方式,默认使用 INFO 方法:
session.sendDTMF('1234');
4.3.2 与 Asterisk 集成
Asterisk 是另一款流行的开源软交换平台。以下是 JSSIP 与 Asterisk 集成的要点。
连接配置:
const socket = new JsSIP.WebSocketInterface('wss://asterisk.example.com:7443');const configuration = {  sockets: \[socket],  uri: 'sip:1000@asterisk.example.com',  password: 'secret',  register: true};const ua = new JsSIP.UA(configuration);
Asterisk 配置:
在 Asterisk 中,需要启用 WebSocket 支持:
\# 在sip.conf中\[general]transport=ws,wss\# 在extensions.conf中\[default]exten => 1000,1,Answer()exten => 1000,2,Playback(hello-world)exten => 1000,3,Hangup()
处理认证:
Asterisk 默认使用 MD5 认证,JSSIP 会自动处理:
const configuration = {  // ...其他配置...  password: 'secret'};
处理呼叫转移:
Asterisk 支持多种呼叫转移方式,包括盲转移和咨询转移:
// 盲转移session.refer('sip:2000@asterisk.example.com');// 咨询转移const consultSession = ua.call('sip:2000@asterisk.example.com');consultSession.on('confirmed', function() {  originalSession.refer('sip:2000@asterisk.example.com');  consultSession.terminate();});
4.3.3 与 Kamailio 集成
Kamailio(前身为 OpenSER)是一款高性能的 SIP 服务器,适合大规模部署。以下是 JSSIP 与 Kamailio 集成的要点。
连接配置:
const socket = new JsSIP.WebSocketInterface('wss://kamailio.example.com:7443');const configuration = {  sockets: \[socket],  uri: 'sip:alice@kamailio.example.com',  password: 'password',  register: true};const ua = new JsSIP.UA(configuration);
Kamailio 配置:
在 Kamailio 中,需要启用 WebSocket 模块:
\# kamailio.cfgloadmodule "usrloc.so"loadmodule "registrar.so"loadmodule "auth.so"loadmodule "websocket.so"modparam("websocket", "ws\_bind\_addr", "0.0.0.0")modparam("websocket", "ws\_bind\_port", 5066)modparam("websocket", "wss\_bind\_port", 7443)modparam("websocket", "ws\_max\_size", 65535)
处理认证:
Kamailio 支持多种认证机制,包括摘要认证:
const configuration = {  // ...其他配置...  password: 'password'};
高级路由配置:
Kamailio 可以根据多种条件进行路由,如用户代理字符串:
// 设置自定义用户代理字符串const configuration = {  // ...其他配置...  user\_agent: 'MyCustomUA/1.0'};
4.3.4 与 WebRTC 网关集成
JSSIP 可以与 WebRTC 网关(如 Enterprise WebRTC Gateway)集成,实现与传统电话网络的互通。
连接配置:
const socket = new JsSIP.WebSocketInterface('wss://webrtc-gateway.example.com:7443');const configuration = {  sockets: \[socket],  uri: 'sip:1000@webrtc-gateway.example.com',  password: 'password',  register: true};const ua = new JsSIP.UA(configuration);
拨打 PSTN 电话:
// 拨打普通电话号码(如+1234567890)const options = {  mediaConstraints: { audio: true, video: false }};ua.call('sip:+1234567890@webrtc-gateway.example.com', options);
接收 PSTN 来电:
ua.on('newRTCSession', function(data) {  if (data.originator === 'remote') {  const incomingSession = data.session;     // 自动应答  incomingSession.answer({  mediaConstraints: { audio: true }  });  }});
处理 DTMF 信号:
在与 PSTN 通话时,DTMF 信号通常通过带外 INFO 方法传输:
session.sendDTMF('1234');
4.3.5 与 SIP-Trunk 提供商集成
JSSIP 可以与 SIP-Trunk 服务提供商(如 Twilio、Plivo、SignalWire)集成,实现与公共电话网络的连接。
SignalWire 集成示例:
const socket = new JsSIP.WebSocketInterface('wss://sip.signalwire.com:5061');const configuration = {  sockets: \[socket],  uri: 'sip:1234567890@example.signalwire.com',  password: 'your\_api\_token',  register: true};const ua = new JsSIP.UA(configuration);
拨打 PSTN 电话:
const options = {  mediaConstraints: { audio: true },  eventHandlers: {  confirmed: function() {  console.log('呼叫已接通');  }  }};ua.call('+1234567890', options);
接收 PSTN 来电:
ua.on('newRTCSession', function(data) {  if (data.originator === 'remote') {  const session = data.session;     // 自动应答  session.answer({  mediaConstraints: { audio: true }  });     // 播放提示音  session.on('peerconnection', function(data) {  const peerConnection = data.peerconnection;  peerConnection.addStream(beepStream);  });  }});
使用高级功能:
// 呼叫转移session.refer('+0987654321');// 会议桥接session.sendInfo('application/dtmf-relay', 'Conference');
4.4 移动应用开发
JSSIP 不仅可以用于 Web 应用,还可以用于移动应用开发。本节将介绍如何在移动环境中使用 JSSIP。
4.4.1 移动浏览器适配
移动浏览器与桌面浏览器在媒体设备访问和网络连接方面存在差异,需要特别适配。
移动设备媒体约束:
// 优化移动设备的视频约束const constraints = {  audio: true,  video: {  width: { ideal: 640 },  height: { ideal: 480 },  frameRate: { ideal: 30 }  }};navigator.mediaDevices.getUserMedia(constraints)  .then(function(stream) {  // 处理媒体流  });
网络连接优化:
移动设备通常使用无线网络或蜂窝数据,网络稳定性较差。JSSIP 提供了连接恢复机制:
const configuration = {  // ...其他配置...  connection\_recovery\_max\_interval: 60000, // 最大重连间隔(毫秒)  connection\_recovery\_min\_interval: 10000 // 最小重连间隔(毫秒)};
后台运行处理:
移动浏览器在应用进入后台时可能会限制资源使用。可以通过事件监听检测应用状态变化:
document.addEventListener('visibilitychange', function() {  if (document.hidden) {  // 应用进入后台,暂停媒体流  if (localStream) {  localStream.getTracks().forEach(function(track) {  track.enabled = false;  });  }  } else {  // 应用回到前台,恢复媒体流  if (localStream) {  localStream.getTracks().forEach(function(track) {  track.enabled = true;  });  }  }});
4.4.2 React Native 集成
JSSIP 可以与 React Native 框架集成,开发跨平台移动应用。需要使用react-native-webrtc
和jssip
库。
安装依赖:
npm install react-native-webrtc jssip
基本使用示例:
import { RTCPeerConnection, MediaStream, MediaStreamTrack, getUserMedia } from 'react-native-webrtc';import JsSIP from 'jssip';// 创建WebSocket接口const socket = new JsSIP.WebSocketInterface('wss://sip.example.com');// 配置对象const configuration = {  sockets: \[socket],  uri: 'sip:alice@example.com',  password: 'superpassword'};// 创建UA实例const ua = new JsSIP.UA(configuration);// 获取媒体设备getUserMedia({ audio: true, video: true })  .then(function(stream) {  // 处理媒体流  })  .catch(function(error) {  console.error('获取媒体设备失败:', error);  });// 发起呼叫const options = {  mediaConstraints: { audio: true, video: true },  eventHandlers: {  confirmed: function() {  console.log('呼叫已确认');  }  }};const session = ua.call('sip:bob@example.com', options);
处理移动设备特性:
// 处理设备旋转import { Dimensions } from 'react-native';const screenOrientation = Dimensions.get('window').width > Dimensions.get('window').height ? 'landscape' : 'portrait';// 切换摄像头function switchCamera() {  getUserMedia({ audio: true, video: { facingMode: 'environment' } })  .then(function(newStream) {  // 替换旧流  const tracks = localStream.getTracks();  tracks.forEach(function(track) {  if (track.kind === 'video') {  track.stop();  }  });     localStream = newStream;  session.connection.addStream(localStream);  });}
4.4.3 原生移动应用集成
JSSIP 也可以与原生移动应用(如 iOS 和 Android)集成,通过 WebView 组件运行 JavaScript 代码。
iOS 集成:
在 iOS 中,可以使用 WKWebView 加载包含 JSSIP 代码的 HTML 页面:
import WebKitclass ViewController: UIViewController, WKUIDelegate {  var webView: WKWebView!     override func viewDidLoad() {  super.viewDidLoad()     // 创建WKWebView  webView = WKWebView(frame: view.bounds)  webView.uiDelegate = self  view.addSubview(webView)     // 加载包含JSSIP代码的HTML页面  if let url = Bundle.main.url(forResource: "jssip", withExtension: "html") {  webView.loadFileURL(url, allowingReadAccessTo: url)  }  }}
Android 集成:
在 Android 中,可以使用 WebView 组件加载包含 JSSIP 代码的 HTML 页面:
import android.webkit.WebView;public class MainActivity extends AppCompatActivity {  private WebView webView;     @Override  protected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity\_main);     // 获取WebView实例  webView = findViewById(R.id.webview);     // 启用JavaScript  webView.getSettings().setJavaScriptEnabled(true);     // 加载包含JSSIP代码的HTML页面  webView.loadUrl("file:///android\_asset/jssip.html");  }}
与原生功能交互:
通过 JavaScript 与原生代码交互,可以实现设备功能调用和 UI 集成:
// JavaScript代码function callNativeFunction(functionName, params) {  // 在iOS中  if (window.webkit && window.webkit.messageHandlers) {  window.webkit.messageHandlers\[functionName].postMessage(params);  }  // 在Android中  else if (window.Android) {  window.Android\[functionName]\(params);  }}// 示例:调用原生摄像头callNativeFunction('openCamera', {});
4.4.4 移动设备优化
移动设备具有独特的特性和限制,需要进行专门的优化:
电池优化:
// 优化电池使用session.on('peerconnection', function(data) {  const peerConnection = data.peerconnection;     // 禁用不必要的ICE候选类型  peerConnection.iceTransportPolicy = 'relay';     // 降低视频分辨率  localStream.getVideoTracks()\[0].applyConstraints({  width: 320,  height: 240  });});
网络优化:
// 使用TURN服务器(如果NAT穿透失败)const configuration = {  // ...其他配置...  pcConfig: {  iceServers: \[  { urls: 'stun:stun.l.google.com:19302' },  { urls: 'turn:turn.example.com', username: 'user', credential: 'password' }  ]  }};// 启用带宽估计session.connection.bandwidthEstimation = true;
资源管理:
// 释放资源function releaseResources() {  if (localStream) {  localStream.getTracks().forEach(function(track) {  track.stop();  });  localStream = null;  }     if (session) {  session.terminate();  session = null;  }     if (ua) {  ua.stop();  ua = null;  }}// 监听应用终止事件window.addEventListener('beforeunload', releaseResources);
4.4.5 推送通知
在移动应用中,推送通知对于接收来电和消息至关重要。以下是实现推送通知的方法:
与 APNS/GCM 集成:
// 注册推送通知function registerPushNotification() {  // iOS使用APNs  if (window.webkit && window.webkit.messageHandlers) {  window.webkit.messageHandlers.registerForRemoteNotifications.postMessage({});  }  // Android使用FCM  else if (window.Android) {  window.Android.registerForPushNotifications();  }}// 处理推送通知function handlePushNotification(payload) {  if (payload.type === 'incomingCall') {  // 显示来电通知  showIncomingCallNotification(payload.number);  } else if (payload.type === 'newMessage') {  // 显示新消息通知  showNewMessageNotification(payload.sender, payload.message);  }}
后台唤醒:
当应用处于后台时,推送通知可以唤醒应用处理事件:
// 监听应用激活事件document.addEventListener('visibilitychange', function() {  if (document.hidden) {  // 应用进入后台,释放资源  releaseResources();  } else {  // 应用回到前台,重新连接  if (ua) {  ua.start();  }  }});
通知点击处理:
用户点击通知时,应用应处理相应的操作:
// 处理通知点击function handleNotificationClick(notificationId) {  switch (notificationId) {  case 'incomingCall':  // 处理来电  handleIncomingCall();  break;  case 'newMessage':  // 处理新消息  handleNewMessage();  break;  }}
五、安全机制与最佳实践
5.1 传输层安全
在实时通信中,传输安全至关重要。本节将介绍如何使用 TLS 和 WebSocket 安全机制保护 SIP 通信。
5.1.1 TLS 配置
TLS(Transport Layer Security)是一种加密协议,用于保护网络通信的机密性和完整性。JSSIP 支持通过 TLS 加密 SIP 消息。
使用 WSS 传输:
使用wss://
URL 创建 WebSocket 接口,启用 TLS 加密:
const secureSocket = new JsSIP.WebSocketInterface('wss://sip.example.com:7443');
配置 TLS 参数:
JSSIP 提供了多个配置参数,用于定制 TLS 行为:
const configuration = {  // ...其他配置...  ws\_secure: true, // 是否强制使用WSS(默认false)  ws\_verify: true, // 是否验证服务器证书(默认true)  ws\_ca\_certs: \['ca.crt'], // 自定义CA证书  ws\_reject\_unauthorized: true // 是否拒绝无效证书(默认true)};
自定义证书验证:
可以通过NodeWebSocket
的选项对象自定义证书验证逻辑:
const options = {  rejectUnauthorized: true,  ca: fs.readFileSync('ca.crt'),  requestCert: true,  agent: new https.Agent({  rejectUnauthorized: true,  ca: \[fs.readFileSync('ca.crt')]  })};const socket = new NodeWebSocket('wss://sip.example.com', options);
5.1.2 证书管理
证书是 TLS 的核心组件,正确管理证书对于安全通信至关重要。
获取服务器证书:
生产环境中应使用由可信 CA 颁发的证书。对于开发和测试环境,可以使用自签名证书:
\# 生成自签名证书openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365
配置服务器证书:
在 SIP 服务器(如 FreeSWITCH)中配置证书:
\<!-- FreeSWITCH配置 -->\<param name="certificate" value="/path/to/cert.pem"/>\<param name="private-key" value="/path/to/key.pem"/>\<param name="dh-param" value="/path/to/dh.pem"/>
信任自签名证书:
在客户端(浏览器或 Node.js)中信任自签名证书:
// Node.js环境const options = {  rejectUnauthorized: false, // 不验证证书(不推荐在生产环境中使用)  ca: fs.readFileSync('ca.crt') // 添加CA证书到信任列表};const socket = new NodeWebSocket('wss://sip.example.com', options);// 浏览器环境(需要用户手动信任证书)const socket = new JsSIP.WebSocketInterface('wss://sip.example.com');
5.1.3 WebSocket 安全配置
WebSocket 提供了多种安全机制,可以与 TLS 结合使用,提高通信安全性。
安全 WebSocket 连接:
始终使用wss://
而不是ws://
来建立 WebSocket 连接:
const secureSocket = new JsSIP.WebSocketInterface('wss://sip.example.com:7443');
自定义 WebSocket 头:
可以添加自定义头部,增强安全性:
const socket = new JsSIP.WebSocketInterface('wss://sip.example.com', {  headers: {  'X-Secure-Header': 'value',  'Strict-Transport-Security': 'max-age=31536000; includeSubDomains'  }});
防止 WebSocket 劫持:
使用Sec-WebSocket-Protocol
头指定 SIP 子协议:
const socket = new JsSIP.WebSocketInterface('wss://sip.example.com', {  protocols: \['sip']});
5.1.4 证书验证与错误处理
正确验证服务器证书是防范中间人攻击的关键。
证书验证错误处理:
// Node.js环境const options = {  rejectUnauthorized: true,  ca: fs.readFileSync('ca.crt')};const socket = new NodeWebSocket('wss://sip.example.com', options);socket.on('error', function(error) {  if (error.code === 'UNABLE\_TO\_VERIFY\_LEAF\_SIGNATURE') {  console.error('证书验证失败:', error);  // 处理证书错误  }});// 浏览器环境const socket = new JsSIP.WebSocketInterface('wss://sip.example.com');socket.on('error', function(error) {  console.error('WebSocket错误:', error);});
自定义证书验证逻辑:
在 Node.js 中,可以自定义证书验证逻辑:
const options = {  rejectUnauthorized: false,  ca: \[fs.readFileSync('ca.crt')],  checkServerIdentity: function(host, cert) {  // 自定义验证逻辑  if (cert.subject.CN !== host) {  throw new Error('证书CN不匹配');  }  }};const socket = new NodeWebSocket('wss://sip.example.com', options);
安全配置建议:
-
使用由可信 CA 颁发的证书
-
定期更新证书
-
启用证书吊销检查
-
使用强加密算法(如 TLS 1.3、ECDHE-ECDSA-AES256-GCM-SHA384)
-
禁用弱加密算法
-
实施证书钉扎(在关键应用中)
5.2 认证与授权
认证和授权是保护通信系统安全的基础机制。本节将介绍 SIP 中的认证机制及在 JSSIP 中的实现。
5.2.1 摘要认证
摘要认证(Digest Authentication)是 SIP 中最常用的认证机制,定义于 RFC 2617。JSSIP 自动支持摘要认证。
基本配置:
只需提供用户名和密码,JSSIP 会自动处理摘要认证:
const configuration = {  // ...其他配置...  uri: 'sip:alice@example.com',  password: 'superpassword'};
处理认证挑战:
当收到 401 Unauthorized 响应时,JSSIP 会自动根据服务器提供的 nonce 和 realm 计算摘要:
ua.on('registrationFailed', function(data) {  if (data.response.status\_code === 401) {  console.log('收到认证挑战');  // JSSIP会自动处理认证,无需额外代码  }});
摘要认证流程:
-
客户端发送 REGISTER 请求(无 Authorization 头部)
-
服务器返回 401 响应,包含 WWW-Authenticate 头部(包含 realm、nonce 等参数)
-
客户端计算摘要,在后续请求中包含 Authorization 头部
-
服务器验证摘要,返回 200 OK 或 403 Forbidden
5.2.2 证书认证
证书认证是一种更安全的认证方式,使用 X.509 证书验证客户端和服务器身份。
客户端证书配置:
在 Node.js 环境中,可以配置客户端证书:
const options = {  key: fs.readFileSync('client.key'),  cert: fs.readFileSync('client.crt'),  ca: fs.readFileSync('ca.crt')};const socket = new NodeWebSocket('wss://sip.example.com', options);
浏览器中的证书认证:
浏览器通常通过用户交互选择客户端证书:
const socket = new JsSIP.WebSocketInterface('wss://sip.example.com');socket.on('certificate', function(event) {  // 浏览器自动处理证书选择});
证书认证流程:
-
服务器在 TLS 握手过程中验证客户端证书
-
如果证书有效,服务器允许连接
-
客户端无需提供用户名和密码
5.2.3 访问控制
访问控制确保只有授权用户可以访问系统资源。JSSIP 可以与多种访问控制机制集成。
基于 IP 的访问控制:
在 SIP 服务器中配置 IP 白名单:
\# Asterisk配置示例deny=0.0.0.0/0.0.0.0permit=192.168.1.0/24
基于用户的访问控制:
在应用层实现授权逻辑:
ua.on('newRTCSession', function(data) {  if (data.originator === 'remote') {  // 检查用户权限  if (!isAuthorized(data.session.remote\_identity)) {  data.session.terminate();  return;  }     // 处理合法呼叫  data.session.answer();  }});
基于角色的访问控制:
根据用户角色限制功能访问:
function isUserAuthorized(role) {  return currentUser.roles.includes(role);}// 限制某些功能仅管理员可用if (isUserAuthorized('admin')) {  session.sendInfo('application/admin-control', 'command');}
5.2.4 安全令牌与 OAuth
OAuth 是一种流行的授权框架,可以与 SIP 结合使用,实现更灵活的认证和授权。
使用 OAuth 令牌:
// 获取OAuth令牌const accessToken = 'your\_access\_token';// 在SIP消息中包含Bearer令牌const configuration = {  // ...其他配置...  authorization: 'Bearer ' + accessToken};const ua = new JsSIP.UA(configuration);
OAuth 流程集成:
// 实现OAuth授权码流程function getAccessToken() {  return fetch('https://auth.example.com/oauth/token', {  method: 'POST',  headers: {  'Content-Type': 'application/x-www-form-urlencoded'  },  body: 'grant\_type=authorization\_code\&code=AUTH\_CODE\&client\_id=CLIENT\_ID\&client\_secret=CLIENT\_SECRET\&redirect\_uri=REDIRECT\_URI'  })  .then(response => response.json())  .then(data => data.access\_token);}// 使用获取的令牌创建UAgetAccessToken()  .then(accessToken => {  const configuration = {  // ...其他配置...  authorization: 'Bearer ' + accessToken  };     const ua = new JsSIP.UA(configuration);  });
令牌刷新:
function refreshAccessToken(refreshToken) {  return fetch('https://auth.example.com/oauth/token', {  method: 'POST',  headers: {  'Content-Type': 'application/x-www-form-urlencoded'  },  body: 'grant\_type=refresh\_token\&refresh\_token=' + refreshToken + '\&client\_id=CLIENT\_ID\&client\_secret=CLIENT\_SECRET'  })  .then(response => response.json())  .then(data => data.access\_token);}// 监听令牌过期事件setInterval(function() {  if (tokenExpired) {  refreshAccessToken(refreshToken)  .then(newAccessToken => {  // 更新UA配置  ua.configuration.authorization = 'Bearer ' + newAccessToken;  });  }}, 30 \* 60 \* 1000); // 每30分钟检查一次
5.3 数据保护与隐私
保护通信内容和用户隐私是实时通信应用的重要责任。本节将介绍如何保护数据隐私。
5.3.1 SRTP 加密
SRTP(Secure Real-Time Transport Protocol)是一种加密协议,用于保护媒体流的机密性、完整性和身份验证。
启用 SRTP:
JSSIP 默认启用 SRTP,无需额外配置:
const options = {  // ...其他配置...  mediaConstraints: { audio: true, video: true }};const session = ua.call('sip:bob@example.com', options);
SRTP 参数配置:
可以通过pcConfig
参数配置 SRTP:
const configuration = {  // ...其他配置...  pcConfig: {  srtpKeyAgreement: true,  srtpIceRestart: true  }};
SRTP 与 DTLS:
SRTP 通常与 DTLS(Datagram Transport Layer Security)结合使用,提供端到端加密:
session.on('peerconnection', function(data) {  const peerConnection = data.peerconnection;     // 配置DTLS  peerConnection.createDataChannel('dtls');  peerConnection.ondatachannel = function(event) {  const dataChannel = event.channel;  dataChannel.onmessage = function(event) {  // 处理DTLS消息  };  };});
5.3.2 数据匿名化
数据匿名化技术可以保护用户隐私,防止身份泄露。
匿名呼叫:
JSSIP 支持匿名呼叫,隐藏呼叫者身份:
const options = {  // ...其他配置...  anonymous: true};const session = ua.call('sip:bob@example.com', options);
匿名化流程:
-
客户端发送 INVITE 请求,包含 Privacy 头部(id=telephony)
-
服务器处理请求,隐藏呼叫者身份
-
服务器可能添加 P-Asserted-Identity 头部(如允许匿名)
伪身份:
使用临时身份代替真实身份进行通信:
const configuration = {  // ...其他配置...  contact\_uri: 'sip:temp-123@example.com',  contact\_params: {  privacy: 'id=telephony'  }};
5.3.3 敏感数据保护
保护敏感数据(如用户凭证、通话内容)是安全通信的基础。
敏感数据存储:
// 避免在代码中硬编码敏感信息const configuration = {  // ...其他配置...  password: getPasswordFromSecureStorage() // 从安全存储获取密码};// 安全存储密码的示例function getPasswordFromSecureStorage() {  // 使用安全的存储机制,如Web Crypto API  return window.crypto.subtle.importKey(  'raw',  new TextEncoder().encode('password'),  { name: 'AES-GCM' },  false,  \['encrypt', 'decrypt']  );}
防止敏感数据泄露:
// 避免在日志中记录敏感信息JsSIP.debug.enable('JsSIP:UA'); // 启用必要的日志JsSIP.debug.disable('JsSIP:MESSAGE'); // 禁用可能包含敏感信息的日志// 安全处理错误function handleError(error) {  console.error('发生错误:', error.message); // 避免记录完整的错误对象}
数据销毁:
// 销毁敏感数据function destroySensitiveData(data) {  // 使用安全的内存清理方法  if (typeof data === 'string') {  const array = new Uint8Array(data.length);  for (let i = 0; i < data.length; i++) {  array\[i] = data.charCodeAt(i);  }  window.crypto.subtle.importKey(  'raw',  array,  { name: 'AES-GCM' },  false,  \['encrypt']  );  }}
5.3.4 合规与隐私法规
实时通信应用需要遵守各种隐私法规,如 GDPR、CCPA 等。
数据主体权利:
// 实现数据访问请求function handleDataAccessRequest(userId) {  // 获取用户数据  const userData = getUserData(userId);     // 提供数据访问  return userData;}// 实现数据删除请求function handleDataDeletionRequest(userId) {  // 删除用户数据  deleteUserData(userId);     // 确认删除  return true;}
日志管理:
// 限制日志保留时间const LOG\_RETENTION\_PERIOD = 30 \* 24 \* 60 \* 60 \* 1000; // 30天function rotateLogs() {  // 删除超过保留期的日志  const now = Date.now();  logs.forEach(function(log, index) {  if (now - log.timestamp > LOG\_RETENTION\_PERIOD) {  logs.splice(index, 1);  }  });}// 定期轮转日志setInterval(rotateLogs, 24 \* 60 \* 60 \* 1000); // 每天一次
数据跨境传输:
// 实施数据本地化策略function storeDataLocally(data) {  // 确保数据存储在用户所在地区  if (currentRegion === 'EU') {  // 存储在欧盟数据中心  storeInEUDataCenter(data);  } else {  // 存储在本地数据中心  storeInLocalDataCenter(data);  }}// 限制数据跨境传输function transferDataOverseas(data, destination) {  // 实施适当的保障措施  if (isCrossBorderTransferRequired(data, destination)) {  // 应用标准合同条款  applyStandardContractualClauses(data);     // 传输数据  transferData(data, destination);  }}
5.4 安全最佳实践
本节将总结 JSSIP 应用开发中的安全最佳实践,帮助开发者构建安全可靠的实时通信系统。
5.4.1 通用安全建议
以下是适用于所有 JSSIP 应用的安全建议:
-
使用最新版本:定期更新 JSSIP 库,以获取最新的安全补丁和功能改进。
-
最小权限原则:为应用分配必要的最小权限,避免过度授权。
-
输入验证:验证所有输入数据,防止注入攻击。
-
输出编码:对所有输出数据进行适当编码,防止跨站脚本攻击。
-
错误处理:避免在错误消息中泄露敏感信息。
-
安全配置:使用安全的默认配置,如启用 TLS、禁用不必要的服务。
-
监控与日志:实施适当的监控和日志记录,及时发现安全事件。
-
安全测试:定期进行安全测试,包括渗透测试和漏洞扫描。
5.4.2 客户端安全措施
在客户端应用中,应采取以下安全措施:
-
安全存储:使用安全的方式存储敏感数据,如用户凭证。
-
权限管理:限制应用对敏感资源(如摄像头、麦克风)的访问。
-
防止代码注入:避免动态执行不可信代码。
-
内容安全策略:实施严格的 Content Security Policy(CSP),防止跨站脚本攻击。
-
证书验证:始终验证服务器证书,防止中间人攻击。
-
加密通信:使用 WSS 和 SRTP 保护所有通信。
-
输入过滤:过滤用户输入,防止恶意内容。
-
安全注销:在用户注销时清除所有敏感数据和会话信息。
5.4.3 服务器端安全措施
服务器端的安全同样重要,应采取以下措施:
-
认证与授权:实施强认证机制,如摘要认证或证书认证。
-
访问控制:限制对敏感资源的访问,基于用户角色和权限。
-
速率限制:防止暴力破解和拒绝服务攻击。
-
日志记录:记录所有安全相关事件,如登录尝试、异常活动。
-
漏洞管理:定期扫描和修复服务器漏洞。
-
补丁管理:及时应用安全补丁和更新。
-
监控与警报:实施实时监控,对异常活动发出警报。
-
安全配置:使用安全的服务器配置,如禁用弱加密算法。
5.4.4 安全开发流程
将安全融入开发流程的各个阶段:
-
安全需求分析:在项目开始阶段明确安全需求。
-
安全设计:在系统设计中融入安全机制。
-
安全编码:遵循安全编码规范,使用安全的开发工具。
-
安全测试:在开发过程中进行安全测试,如静态代码分析、动态测试。
-
安全部署:实施安全的部署流程,保护生产环境。
-
安全运维:建立安全的运维流程,监控和响应安全事件。
-
安全培训:对开发和运维团队进行安全培训。
-
安全审计:定期进行安全审计,评估系统安全性。
5.4.5 应急响应计划
即使采取了所有安全措施,安全事件仍可能发生。应制定应急响应计划:
-
事件分类:定义不同类型的安全事件及其严重程度。
-
报告机制:建立明确的事件报告流程。
-
响应团队:组建专门的安全响应团队。
-
响应流程:制定标准化的响应流程,包括遏制、调查、恢复和缓解措施。
-
沟通计划:确定如何与内部团队和外部相关方沟通安全事件。
-
恢复计划:制定系统恢复策略,确保业务连续性。
-
事后分析:对安全事件进行彻底分析,总结经验教训。
-
改进措施:根据事件分析结果,改进安全措施和流程。
六、性能优化与最佳实践
6.1 连接管理优化
连接管理是影响 JSSIP 应用性能的关键因素。本节将介绍如何优化 WebSocket 连接和 SIP 注册过程。
6.1.1 连接池技术
连接池技术可以复用已建立的 WebSocket 连接,减少连接建立和关闭的开销。
实现连接池:
class ConnectionPool {  constructor(url, maxConnections = 5) {  this.url = url;  this.maxConnections = maxConnections;  this.connections = \[];  this.availableConnections = \[];  this.createConnections();  }     createConnections() {  for (let i = 0; i < this.maxConnections; i++) {  const socket = new JsSIP.WebSocketInterface(this.url);  this.connections.push(socket);  this.availableConnections.push(socket);  }  }     getConnection() {  if (this.availableConnections.length > 0) {  return this.availableConnections.pop();  } else {  // 创建新连接(如果未达到最大连接数)  if (this.connections.length < this.maxConnections) {  const socket = new JsSIP.WebSocketInterface(this.url);  this.connections.push(socket);  return socket;  } else {  // 等待连接释放(示例实现,需更复杂的逻辑)  return new Promise(resolve => {  setInterval(() => {  if (this.availableConnections.length > 0) {  resolve(this.availableConnections.pop());  }  }, 100);  });  }  }  }     releaseConnection(socket) {  this.availableConnections.push(socket);  }     closeAllConnections() {  this.connections.forEach(socket => {  socket.close();  });  this.connections = \[];  this.availableConnections = \[];  }}// 使用连接池const connectionPool = new ConnectionPool('wss://sip.example.com');const socket = connectionPool.getConnection();
连接池配置参数:
const connectionPool = new ConnectionPool('wss://sip.example.com', {  maxConnections: 5, // 最大连接数  connectionTimeout: 30000, // 连接超时时间(毫秒)  idleTimeout: 60000 // 空闲连接超时时间(毫秒)});
连接健康检查:
setInterval(function() {  connectionPool.availableConnections.forEach(socket => {  if (socket.isClosed()) {  connectionPool.connections.splice(connectionPool.connections.indexOf(socket), 1);  connectionPool.availableConnections.splice(connectionPool.availableConnections.indexOf(socket), 1);  connectionPool.createConnections(1);  }  });}, 30000); // 每30秒检查一次
6.1.2 连接恢复策略
在网络不稳定的环境中,连接恢复策略对应用的可用性至关重要。
连接恢复配置:
JSSIP 提供了内置的连接恢复机制,可以通过配置参数调整:
const configuration = {  // ...其他配置...  connection\_recovery\_min\_interval: 5000, // 最小重连间隔(毫秒)  connection\_recovery\_max\_interval: 60000, // 最大重连间隔(毫秒)  connection\_recovery\_factor: 1.5 // 重连间隔增长因子};
指数退避算法:
JSSIP 默认使用指数退避算法实现连接恢复:
// 示例:手动实现指数退避let retryCount = 0;const maxRetries = 5;function connect() {  const socket = new JsSIP.WebSocketInterface('wss://sip.example.com');     socket.onconnect = function() {  console.log('连接成功');  retryCount = 0;  };     socket.ondisconnect = function(error) {  console.log('连接断开,将在', (2 \*\* retryCount) \* 1000, '毫秒后重试');  if (retryCount < maxRetries) {  retryCount++;  setTimeout(connect, (2 \*\* retryCount) \* 1000);  } else {  console.error('连接失败,超过最大重试次数');  }  };     socket.connect();}
连接优先级:
在多连接环境中,可以为不同类型的通信设置优先级:
class PriorityConnectionPool {  constructor(url, maxConnections) {  this.url = url;  this.maxConnections = maxConnections;  this.connections = \[];  this.availableConnections = \[];  this.createConnections();  }     createConnections() {  for (let i = 0; i < this.maxConnections; i++) {  const socket = new JsSIP.WebSocketInterface(this.url);  this.connections.push(socket);  this.availableConnections.push(socket);  }  }     getConnection(priority = 0) {  // 按优先级排序可用连接  this.availableConnections.sort((a, b) => b.priority - a.priority);     if (this.availableConnections.length > 0) {  const socket = this.availableConnections.pop();  socket.priority = priority;  return socket;  } else {  // 处理无可用连接的情况  return null;  }  }     releaseConnection(socket) {  this.availableConnections.push(socket);  }}
6.1.3 注册优化
SIP 注册是应用启动时的关键操作,优化注册过程可以提高应用性能。
批量注册:
在多账户环境中,可以批量处理注册请求:
function registerAccounts(accounts) {  return Promise.all(accounts.map(account => {  const configuration = {  sockets: \[new JsSIP.WebSocketInterface(account.url)],  uri: account.uri,  password: account.password  };     const ua = new JsSIP.UA(configuration);  return ua.start();  }));}
注册预取:
在应用启动时预注册常用账户:
const accounts = \[  { url: 'wss://sip.example.com', uri: 'sip:alice@example.com', password: 'password' },  { url: 'wss://sip.example.com', uri: 'sip:bob@example.com', password: 'password' }];registerAccounts(accounts);
智能注册:
根据用户行为预测需要注册的账户:
function predictAccountsToRegister() {  // 基于历史数据或用户行为预测需要注册的账户  return \['sip:alice@example.com'];}const predictedAccounts = predictAccountsToRegister();registerAccounts(predictedAccounts);
6.1.4 心跳机制
在长时间空闲的连接上,心跳机制可以保持连接活跃,防止被中间设备关闭。
实现心跳机制:
function keepAlive(socket) {  setInterval(function() {  if (socket.isConnected()) {  const message = new JsSIP.Message('OPTIONS sip:example.com SIP/2.0');  socket.send(message);  }  }, 30000); // 每30秒发送一次心跳}// 在连接建立后启动心跳socket.onconnect = function() {  keepAlive(socket);};
心跳消息优化:
// 使用轻量级的心跳消息const heartbeatMessage = new JsSIP.Message('OPTIONS \* SIP/2.0');heartbeatMessage.headers.set('Content-Length', 0);function sendHeartbeat(socket) {  socket.send(heartbeatMessage);}
心跳响应处理:
ua.on('newMessage', function(data) {  if (data.message.method === 'OPTIONS') {  // 发送心跳响应  data.message.respond(200, 'OK');  }});
6.2 媒体处理优化
媒体处理是实时通信应用的核心,优化媒体处理可以提高通话质量和用户体验。
6.2.1 编解码器优化
选择合适的编解码器并优化其参数,可以显著提高媒体传输效率。
选择高效编解码器:
// 优先使用高效编解码器(如OPUS)session.on('sdp', function(data) {  if (data.type === 'offer') {  // 重新排列编解码器顺序,将OPUS设为首选  data.sdp = data.sdp.replace('opus/48000/2', 'opus/48000/2')  .replace('pcmu/8000', 'pcmu/8000');  }});
编解码器参数优化:
// 设置OPUS编解码器的比特率session.on('sdp', function(data) {  if (data.type === 'offer') {  data.sdp = data.sdp.replace('opus/48000/2', 'opus/48000/2/128000'); // 设置比特率为128kbps  }});
动态调整编解码器:
// 根据网络状况动态调整编解码器function adjustCodecs(networkQuality) {  if (networkQuality === 'poor') {  // 降低视频分辨率和帧率,使用更高效的编解码器  session.mediaConstraints = {  audio: true,  video: {  width: 320,  height: 240,  frameRate: 15  }  };     session.renegotiate();  } else if (networkQuality === 'good') {  // 提高视频分辨率和帧率  session.mediaConstraints = {  audio: true,  video: {  width: 1280,  height: 720,  frameRate: 30  }  };     session.renegotiate();  }}
6.2.2 媒体流优化
媒体流优化可以减少带宽使用,提高播放性能。
分辨率适配:
// 根据显示区域自动调整视频分辨率function adaptResolution() {  const displayWidth = window.innerWidth;  const displayHeight = window.innerHeight;     session.mediaConstraints = {  audio: true,  video: {  width: { ideal: displayWidth },  height: { ideal: displayHeight },  frameRate: 30  }  };     session.renegotiate();}// 监听窗口大小变化window.addEventListener('resize', adaptResolution);
帧率控制:
// 根据网络状况调整帧率function adjustFrameRate(networkQuality) {  if (networkQuality === 'poor') {  session.mediaConstraints = {  audio: true,  video: {  frameRate: 15  }  };  } else {  session.mediaConstraints = {  audio: true,  video: {  frameRate: 30  }  };  }     session.renegotiate();}
码率控制:
// 限制视频码率session.on('peerconnection', function(data) {  const peerConnection = data.peerconnection;     peerConnection.getSenders().forEach(function(sender) {  if (sender.track.kind === 'video') {  sender.setParameters({  codecPreferences: \[{  mimeType: 'video/VP8',  parameters: {  'x-google-start-bitrate': 1000, // 初始码率(kbps)  'x-google-max-bitrate': 2000, // 最大码率(kbps)  'x-google-min-bitrate': 500 // 最小码率(kbps)  }  }]  });  }  });});
6.2.3 网络适配
在不同网络环境下,应用需要自适应调整行为,确保通信质量。
网络质量检测:
function monitorNetworkQuality() {  const networkInformation = navigator.connection || navigator.mozConnection || navigator.webkitConnection;     if (networkInformation) {  const type = networkInformation.effectiveType;  const downlink = networkInformation.downlink;  const rtt = networkInformation.rtt;     console.log('网络类型:', type);  console.log('下载速度:', downlink, 'Mbps');  console.log('往返时间:', rtt, 'ms');     // 根据网络状况调整媒体参数  if (type === '4g') {  adjustCodecs('good');  } else if (type === '3g') {  adjustCodecs('medium');  } else {  adjustCodecs('poor');  }  }}// 定期检测网络质量setInterval(monitorNetworkQuality, 5000);
自适应比特率:
// 实现自适应比特率控制session.on('peerconnection', function(data) {  const peerConnection = data.peerconnection;     peerConnection.addEventListener('outbound-rtp', function(event) {  const bitrate = event.bytesSent / (event.elapsedTime / 1000); // 计算当前码率  console.log('当前码率:', bitrate, 'bps');     if (bitrate > 2000000) {  // 码率过高,降低分辨率  adjustResolution('low');  } else if (bitrate < 1000000) {  // 码率过低,提高分辨率  adjustResolution('high');  }  });});
前向纠错:
// 启用前向纠错(FEC)session.on('peerconnection', function(data) {  const peerConnection = data.peerconnection;     peerConnection.getSenders().forEach(function(sender) {  if (sender.track.kind === 'video') {  sender.setParameters({  codecPreferences: \[{  mimeType: 'video/VP8',  parameters: {  'x-google-max-cpu-usage': 1.5,  'x-google-min-bitrate': 500,  'x-google-max-bitrate': 2000,  'x-google-start-bitrate': 1000,  'x-google-use-remote-bitrate-estimation': true,  'x-google-max-fec-packets': 2 // 启用FEC,最多2个冗余包  }  }]  });  }  });});
6.2.4 资源管理
合理管理媒体资源可以防止内存泄漏和性能下降。
媒体流清理:
// 停止并释放媒体流资源function stopMediaStream(stream) {  if (stream) {  stream.getTracks().forEach(function(track) {  track.stop();  });  }}// 在会话结束时清理媒体流session.on('ended', function() {  stopMediaStream(localStream);  stopMediaStream(remoteStream);});
视频预览优化:
// 优化视频预览,避免重复创建元素function showVideoPreview(stream, element) {  if (element.srcObject !== stream) {  element.srcObject = stream;  }}// 在不再需要时隐藏视频元素function hideVideoPreview(element) {  element.style.display = 'none';  element.srcObject = null;}
资源预加载:
// 预加载常用媒体资源function preloadResources() {  // 预加载音频提示  const audioContext = new (window.AudioContext || window.webkitAudioContext)();  const beepSound = new Audio('beep.wav');  beepSound.decodeAudioData(audioContext);     // 预创建媒体元素  const videoElement = document.createElement('video');  videoElement.style.display = 'none';  document.body.appendChild(videoElement);}
6.3 代码优化与最佳实践
代码优化是提高 JSSIP 应用性能的基础。本节将介绍 JavaScript 代码优化的最佳实践。
6.3.1 事件处理优化
高效的事件处理对于实时应用至关重要,可以减少延迟,提高响应速度。
事件委托:
使用事件委托减少事件监听器数量:
// 错误示例:为每个按钮添加单独的事件监听器document.querySelectorAll('.call-button').forEach(function(button) {  button.addEventListener('click', function() {  // 处理呼叫  });});// 正确示例:使用事件委托document.body.addEventListener('click', function(event) {  if (event.target.classList.contains('call-button')) {  // 处理呼叫  }});
防抖与节流:
防止频繁触发事件处理函数:
// 防抖函数function debounce(func, delay) {  let timeoutId;  return function() {  clearTimeout(timeoutId);  timeoutId = setTimeout(() => func.apply(this, arguments), delay);  };}// 节流函数function throttle(func, limit) {  let lastCall = 0;  return function() {  const now
**参考资料 **
[1] jssip - Libraries - cdnjs - The #1 free and open source CDN built to make life easier for developers https://cdnjs.com/libraries/jssip/3.0.25
[2] jssip - npm https://www.npmjs.com/package/jssip
[3] SIP.js 0.20.0版本简单Demo-CSDN博客 https://blog.csdn.net/Web_ChuXia/article/details/121694595
[4] JsSIP+FreeSwitch+Vue实现WebRtc音视频通话_vue jssip freeswitch-CSDN博客 https://blog.csdn.net/weixin_41978174/article/details/140974900
[5] jssip中文开发文档(完整版)-CSDN博客 https://blog.csdn.net/wh8_2011/article/details/80978220
[6] freeswitch + webRtc +jssip 实现web端语音通话_jssip+freeswitch+webrtc-CSDN博客 https://blog.csdn.net/leng778590995/article/details/107655781
[7] JS全栈开发路线 - 2025-抖音 https://www.iesdouyin.com/share/video/7529152637850258739/?did=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&from_aid=1128&from_ssr=1&iid=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&mid=7529153067754015540®ion=&scene_from=dy_open_search_video&share_sign=WhsznwJNPORQwYwz9wNZ1JimDYg4AqTozOZg4QMp80M-&share_version=280700&titleType=title&ts=1753487629&u_code=0&video_share_track_ver=&with_sec_did=1
[8] 2025年防控白皮书最新解读!-抖音 https://www.iesdouyin.com/share/video/7523131209551973673/?did=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&from_aid=1128&from_ssr=1&iid=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&mid=7523131152048196386®ion=&scene_from=dy_open_search_video&share_sign=KVkyA1uRLIEKTb7Rr5y.SJqRfs55cQzxypMEH_1ewJ4-&share_version=280700&titleType=title&ts=1753487629&u_code=0&video_share_track_ver=&with_sec_did=1
[9] 每天10分钟,听一听web高频考点,万一面试就问了这个呢 forwardRef 和useImperativeHandle是用来解决什么问题的(下)?-抖音 https://www.iesdouyin.com/share/video/7530917640827669812/?did=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&from_aid=1128&from_ssr=1&iid=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&mid=7530916334725647131®ion=&scene_from=dy_open_search_video&share_sign=7B2yvDcBL_6N0WgIK9XlxfX74itWWHO7eYLNpdzGLx0-&share_version=280700&titleType=title&ts=1753487629&u_code=0&video_share_track_ver=&with_sec_did=1
[10] JSSIP Fail over behaviour https://groups.google.com/g/jssip/c/UscFATnFD_o
[11] jssip - Libraries - cdnjs - The #1 free and open source CDN built to make life easier for developers https://cdnjs.com/libraries/jssip/3.9.0
[12] Application Security Best Practices in 2025 | Beetroot https://beetroot.co/cybersecurity/application-security-best-practices-for-modern-development-teams/
[13] JS Performance Tips: How to Speed up JavaScript Load Time [2025] https://brainhub.eu/library/js-performance-tips
[14] Job Scheduling Strategies for Parallel Processing https://www.jsspp.org/
[15] Software Performance Optimization Tips for 2025: The Ultimate Guide https://techlasi.com/savvy/software-performance-optimization-tips/
[16] Web Architecture & Performance - International JavaScript Conference https://javascript-conference.com/architecture-performance/
[17] UNPKG - jssip-sl-x https://unpkg.com/browse/jssip-sl-x@5.0.0/README.md
[18] GitHub - versatica/jssip-node-websocket: JsSIP.Socket interface for the Node.js based on the websocket module https://github.com/versatica/jssip-node-websocket
[19] JsSSIP and Asterisk SIP - Asterisk WebRTC - Asterisk Community https://community.asterisk.org/t/jsssip-and-asterisk-sip/88670
[20] jssip-node-websocket - npm https://www.npmjs.com/package/jssip-node-websocket
[21] How to setup JsSIP (WebRTC client) – Interoperability https://docs.brekeke.com/interop/how-to-setup-jssip-webrtc-client
[22] WebRTC Integrator’s Guide https://subscription.packtpub.com/book/business-and-other/9781783981267/1/ch01lvl1sec11/running-webrtc-with-sip
[23] WebRTC with SIP Over WebSockets | SignalWire Developer Portal https://developer.signalwire.com/platform/basics/guides/webrtc-with-sip-over-websockets/
[24] GitHub - versatica/JsSIP: JsSIP, the JavaScript SIP library https://github.com/versatica/JsSIP
[25] api documentation for jssip (v3.0.7) https://npmdoc.github.io/node-npmdoc-jssip/build/apidoc.html
[26] GitHub - jpotts18/react-native-jssip: JsSIP, the JavaScript SIP library https://github.com/jpotts18/react-native-jssip
[27] GitHub - versatica/tryit-jssip: New tryit-jssip application https://github.com/versatica/tryit-jssip
[28] GitHub - paneru-rajan/asterisk-jssip: This is the complete guide to install Sipml5 and Asterisk. I have used Vagrant, however, I will describe how to install on Ubuntu alone. https://github.com/paneru-rajan/asterisk-jssip
[29] SSL/TLS — PJSIP Project 2.15-dev documentation https://docs.pjsip.org/en/latest/specific-guides/security/ssl.html
[30] JsSip - Failed to parse SessionDescription. a=fingerprint:SHA-256 Failed to create fingerprint from the digest - Asterisk WebRTC - Asterisk Community https://community.asterisk.org/t/jssip-failed-to-parse-sessiondescription-a-fingerprint-sha-256-failed-to-create-fingerprint-from-the-digest/81863
[31] JSSE Utility :: Red Hat Integration https://access.redhat.com/webassets/avalon/d/red_hat_integration/2021.q3/apache-camel-3.10-doc/manual/latest/camel-configuration-utilities.html
[32] JavaMadeSoEasy.com (JMSE): Connection Pooling in java with example https://www.javamadesoeasy.com/2015/12/connection-pooling-in-java-with-example.html?m=1
[33] Configuring connection pooling for JMS connections https://www.ibm.com/docs/en/was-liberty/nd?topic=dmal-configuring-connection-pooling-jms-connections
[34] How to create a pool of connection in jsmpp? · Issue #147 · opentelecoms-org/jsmpp · GitHub https://github.com/opentelecoms-org/jsmpp/issues/147
[35] SignalWire RELAY | WebRTC with SIP over WebSockets | SignalWire https://signalwire.com/blogs/product/webrtc-using-sip-over-websockets
[36] Implement a connection pooling - Sun: Servlets and JavaServer Pages (JSP) | Tek-Tips https://www.tek-tips.com/threads/implement-a-connection-pooling.896673/
[37] Asynchronous Programming in JavaScript https://openreplay.hashnode.dev/best-practices-for-async-programming-in-javascript
(注:文档部分内容可能由 AI 生成)