目录
引言:
sip协议是什么?
sip的网络结构(重点)
sip的特点
sip使用的url
sip协议的应用领域
sip协议基本的消息类型
请求消息
响应消息
sip协议的消息结构(这个是重点)
sip的常见会话流程(重点)
注册
注销
基于sip协议实现简单的网络视频通话
步骤:
效果:
代码:
-
引言:
最近,公司让我去了解sip协议,自己花了些的时间去了解了相关的知识,但我发现网上的资料比较乱,没有的对sip协议做一个很好的总结,而且实践很少,对于新手学习sip而言,需要花费大量的时间去找相关的资料,所以我在这里总结一下我自己的学习心得,让大家能花较短的时间也可以对sip协议快速入门。
-
sip协议是什么?
SIP(Session Initiation Protocol,即初始会话协议)是 IETF 提出的基于文本编码的 IP 电话/多媒体会议协议。用于建立、修改并终止多媒体会话。
SIP 协 议可用于发起会话,也可以用于邀请成员加入已经用其它方式建立的会话。 多媒体会话可以是点到点的话音通信或视频通信,也可以是多点参与的话音 或视频会议等。SIP 协议透明地支持名字映射和重定向服务,便于实现 ISDN, 智能网以及个人移动业务。
SIP 协议可以用多点控制单元(MCU)或全互连的方式代替组播发起多方呼 叫。与 PSTN 相连的 IP 电话网关也可以用 SIP 协议来建立普通电话用户之间 的呼叫。
总结:sip协议是一种基于文本编码的协议,用于建立、修改并终止多媒体会话,多使用在IP通话(基于网络来实现通话,类似与微信通话)
扩展知识:ip电话和传统的电话有什么区别呢?如何进行区分呢,分别都是使用了那些协议和技术呢?
区别:ip电话是一个统称,只要是基于互联网来实现实时通话,都可以成为ip电话,想我们生活中的微信通话,qq通话等都是属于ip电话的一种,而基于sip协议来实现的sip话机通话也是IP电话的一种。
区分:在生活中,向我们日常手机那种需要手机卡才能拨打电话的称为传统通话,而只需要有网络就可以拨打电话的就是ip电话了
协议和技术:传统的话机主要是基于PSTN来实现的,使用电路交换技术来传输数据;而qq,微信等是有自己定义的协议来实现,sip话机是基于sip协议来实现,使用分组交换技术传输数据。
-
sip的网络结构(重点)
sip的网络结构主要有5部分组成
用户代理:SIP 用户代理(UA) 是终端用户设备,如用于创建和管理 SIP 会话的移动电话、多媒体手持设备、PC、PDA 等。用户代理客户机发出消息。用户代理服务器对消息进行响应(常见的用户代理就有sip话机,实现了sip协议的软件换设备(app,网页,其他软件));
代理服务器:用于转发sip请求,作为 User Agent Client 和 User Agent Server 间的中间媒体, 它转发 User Agent Client 来的的邀请,在转发之前,根据被叫标识请求位置 服务器获得被叫的可能位置,然后分别向它们发出邀请;
重定向服务器:接受 User Agent Client 来的邀请,根据被叫标识请求位置服 务器获得被叫的可能位置,把这些信息返回给邀请的发起者(User Agent Client),和 Proxy Server 的不同之处就在于它不转发邀请,邀请由主叫终端 自己完成;
本地服务器和注册服务器:主要用于登记分组终端的当前位置和位置服务的原始数据
如果还需要连接传统的PSTN,则需要添加一个网关,将SIp转化成PSTN信号
sip的网络结构基本组成
在同一个局域网的
在不同局域网的
-
sip的特点
协议简单:
SIP协议被设计为非常简单,具有有限的命令集。它也是基于文本的,因此任何人都可以读取在SIP会话中的端点之间传递的SIP消息,SIP协议是一个Client/Sever协议,因此SIP消息分两种:请求消息和响应消息。请求消息是SIP客户端为了激活特定操作而发给服务器端的消息。
可读性强:sip协议是基于文本编码的,在网络中获取后可以直接阅读。所以协议传输的数据可读性强。
扩展性强:sip协议的网络组成是基于分布式部署的,用户可以根据自己的需要部署一台或多台网络组件,如果想连接PSTN网,则可以通过使用id网关来实现异构网的搭建,所以扩展性强。
-
sip使用的url
结构:name@domian
案例:
alice@atlanta.com
bob@biloxi.com
1001@192.168.8.248
1002@192.168.8.248
-
sip协议的应用领域
SIP(Session Initiation Protocol)是一种应用层协议,用于建立、修改和终止多媒体会话,如电话呼叫、视频会议等。SIP协议的应用领域包括以下几个方面:
互联网电话(VoIP):SIP是VoIP中最常用的协议之一,它可以用于建立和管理Internet上的语音通信。通过SIP,用户可以通过互联网进行电话呼叫,无需传统电话线路。
多媒体会议:SIP可以用于建立和管理多媒体会议,包括音频、视频和文本等形式的通信。通过SIP,多个参与者可以在不同的地理位置之间进行实时的多媒体会话。
即时消息和在线聊天:SIP可以用于发送即时消息和进行在线聊天。它支持文本消息的传输和处理,并可以实现用户之间的实时通信。
网络电话交换(Softswitch):SIP可以作为网络电话交换的控制协议,用于连接不同的电话网络和设备。通过SIP,电话呼叫可以在不同的网络之间进行转接和路由。
增强业务通信:SIP还可以用于增强业务通信,如语音信箱、呼叫转移、呼叫等待等功能。它提供了一种灵活的方式来定制和管理通信服务。
总的来说,SIP协议在VoIP、多媒体会议、即时消息和业务通信等领域都有广泛的应用。它为实现实时通信和多媒体交互提供了一种标准化和可扩展的解决方案。
-
sip协议基本的消息类型
SIP 协议是以层协议的形式组成的,就是说它的行为是以一套相对独立的处理阶段来描述的,每个阶段之间的关系不是很密切。
SIP 协议将 Server 和 User Agent 之间的通讯的消息分为两类:请求消息和响应消息。
请求消息
INVITE:表示主叫用户发起会话请求,邀请其他用户加入一个会话。也可以用在呼叫建立后用于更新会话(此时该INVITE又称为Re-invite)。
ACK:客户端向服务器端证实它已经收到了对INVITE请求的最终响应。
PRACK:表示对1xx响应消息的确认请求消息。
BYE:表示终止一个已经建立的呼叫。
CANCEL:表示在收到对请求的最终响应之前取消该请求,对于已完成的请求则无影响。
REGISTER:表示客户端向SIP服务器端注册列在To字段中的地址信息。
OPTIONS:表示查询被叫的相关信息和功能。
以上方法以外,还有其他扩展的方法,如INFO、NOTIFY等等.
响应消息
服务器向客户反馈对应请求的处理结果的 SIP 消息,包括 1xx、2xx、 3xx、4xx、5xx、6xx 响应。
常用的一些响应消息:
100试呼叫(Trying)
180振铃(Ringing)
181呼叫正在前转(Call is Being Forwarded)
200成功响应(OK)
302临时迁移(Moved Temporarily)
400错误请求(Bad Request)
401未授权(Unauthorized)
403禁止(Forbidden)
404用户不存在(Not Found)
408请求超时(Request Timeout)
480暂时无人接听(Temporarily Unavailable)
486线路忙(Busy Here)
504服务器超时(Server Time-out)
600全忙(Busy Everywhere)
-
sip协议的消息结构(这个是重点)
注意:这个看起来很繁琐,感觉不需要记,但实际上在日常开发中,如果需要要抓包去查看信息的话,这个是必须要懂得,每个字段代表什么意思要记下来
请求行:
请求行分为请求和响应二种类型:
请求:请求方法 请求路径 协议版本号
INVITE sip:bob@192.0.2.4 SIP/2.0
INVITE sip:bob@biloxi.com SIP/2.0
响应:协议版本号 响应码 原因短语
SIP/2.0 100 Trying
SIP/2.0 180 Ringing
请求头:
Via: SIP/2.0/UDP pc33.atlanta.com
;branch=z9hG4bKnashds8
Max-Forwards: 70
To: Bob <sip:bob@biloxi.com>
From: Alice <sip:alice@atlanta.com>
;tag= 1928301774
Call-ID: a84b4c76e66710
CSeq: 314159 INVITE
Contact: <sip:alice@pc33.atlanta.com>
Content-Type: application/sdp
Content-Length: 142
请求体:
主要由SDP协议组成
-
sip的常见会话流程(重点)
注册
注销
基本得呼叫流程
主动呼叫
-
基于sip协议实现简单的网络视频通话
这里是基于sip.js来实现得一个简单得web在线呼叫
步骤:
- 从github上下载一个打包号得sip.js文件(这里需要注意,我使用得是15.10版本,然后现在更新到了2.1版了,从1.6版本之后,变化比较大,所以如果要使用我的代码的话,版本不能超过1.6)
- 安装一个sip服务器,现在sip.js支持三种类型的服务器,分别是freeswich,onsip,asterisk,他们之间的差别我就不介绍了,现在国内主流的是使用freeswich这个作为sip服务器,本案例就是选用freeswich作为sip服务器
- 在运行freeswich服务器后,完成注册就可以实现视频通话了(这里需要注意现在必须要在同一个类型的浏览器才能实现正常的通话)
freeswich的安装教程:Windows下FreeSWITCH的安装及使用_freeswitch管理工具_rivercoder的博客-CSDN博客
效果:
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>sip练习</title>
</head>
<!-- 引入sipjs -->
<script src="./js/sip-0.15.10.js"></script>
<body>
<div style="
width: 300px;
height: 250px;
margin: 0 auto;
background-color: antiquewhite;
" id="registerDiv">
</form>
<p align="center">
<tr>
<td>用户名:<input type="text" name="username" id="username" value="" placeholder="请输入sip账号"></td>
</tr><br />
<tr>
<td>密 码:<input type="password" id="password" name="password" value="" placeholder="请输入密码">
</td>
</tr><br />
</p>
<p align="center">
<td><input type="submit" name="register" value="register" id="register" "></td>
</p>
<form>
</div>
<div style="
width: 500px;
height: 450px;
margin: 0 auto;
background-color: antiquewhite;
" id="mainDiv">
<audio id="ringtone" loop src="./sounds/ringtone.wav"></audio> <!--//来电提示音-->
<audio id="ringbacktone" loop src="./sounds/ringbacktone.wav"></audio> <!--//电话呼叫后声音-->
<audio id="dtmfTone" src="./sounds/dtmf.wav"></audio>
</form>
<p align="center">
<tr>
<td>账号:<input type="text" name="username" id="number" value="" placeholder="请输入sip账号"></td>
</tr><br />
<tr>
<td>消息:<input type="text" id="msg" name="msg" value="hello" placeholder="要发送的消息">
</td>
</tr><br />
</p>
<button id="startCall">Start Call</button>
<button id="endCall">End Call</button>
<button id="transfer">transfer</button>
<button id="hold">hold</button>
<button id="unhold">unhold</button>
<button id="mute">mute</button>
<button id="unmute">unmute</button>
<button id="sendMessage">Send Message</button>
<form>
<div style="width: 90%; height: 100%;background-color: #f6f2f2; border: 2px solid blue; padding:0px; margin-top: 4px;">
<video id="remoteView" style="width: 200px; height: 200px; background-color: #c5e5cd; margin-left: 20px;" autoplay></video>
<video id="localVideo" autoplay="autoplay" muted="muted"
style="width: 200px; height: 200px; background-color: #d8d0d0; margin-left: 20px;">
</video>
</div>
</div>
<script>
var ua;
//初始化按钮变量
var sessionall;
var startCall = document.getElementById('startCall');
var endCall = document.getElementById('endCall');
var transfer = document.getElementById('transfer');
var hold = document.getElementById('hold');
var unhold = document.getElementById('unhold');
var sendMessage = document.getElementById('sendMessage');
var mute = document.getElementById('mute');
var unmute = document.getElementById('unmute');
var remoteView = document.getElementById('remoteView');
var localVideo = document.getElementById('localVideo');
var currentSession=null; //当前会话
<!-- 实现注册功能 -->
var test =document.getElementById("register").addEventListener('click', function (e) {
var username = document.getElementById("username").value;
var password = document.getElementById("password").value;
//定义一个config
var configuration={
uri: 'sip:' +username + '@192.168.8.248:5060',
displayName: username,
password: password,
transportOptions: {
//freeswich会运行websocket服务器,一个是ws类型,端口对应5066,用于本地测试,一个是wss类型,端口对应7443,用于实际开发,相当于加入了ssl的ws
wsServers: ['ws://192.168.8.248:5066']
},
register: true // 启用注册功能
};
//开始注册sip
ua=new SIP.UA(configuration);
//登录成功后进行回调
ua.on('registered', function () {
alert('登录成功');
document.getElementById("registerDiv").style.display = "none";
document.getElementById("mainDiv").style.display="block";
});
// 接听来电
ua.on('invite', function (session) {
// 开始振铃
Ring.startRingTone();
var url = session.remoteIdentity.uri.toString() + "来电了,是否接听";
//弹出是否接听对话框
var isaccept = confirm(url);
if (isaccept) {
//接受来电
session.accept({
sessionDescriptionHandlerOptions: {
constraints: {
audio: true,
video: true
}
}
});
session.on("terminated", function (message, cause) {
Ring.stopRingTone();
})
/**
*
*/
session.on('accepted', function (response, cause) {
console.error(response);
console.error(session);
Ring.stopRingTone();
// If there is a video track, it will attach the video and audio to the same element
//
// Gets remote tracks
var pc = session.sessionDescriptionHandler.peerConnection;
var x=pc.getReceivers();
if(pc.getReceivers()){
var remoteStream = new MediaStream();
//接听会话
pc.getReceivers().forEach(function (receiver) {
remoteStream.addTrack(receiver.track);
});
remoteView.srcObject = remoteStream;
remoteView.play();
}
var y=pc.getSenders();
//发起会话
if (pc.getSenders()) {
var localStream = new MediaStream();
pc.getSenders().forEach(function (sender) {
localStream.addTrack(sender.track);
});
localVideo.srcObject = localStream;
localVideo.play();
}
})
session.on('bye', function (resp, cause) {
Ring.stopRingTone();
});
}
else {
//拒绝来电
session.reject();
}
}); //接听会话结束
},
false);
// 页面加载时调用
window.onload = function () {
document.getElementById("mainDiv").style.display = "none";
};
/**
* 绑定方法
* @param obj 要绑定事件的元素
* @param ev 绑定的事件
* @param fn 绑定事件的函数
*/
function bindEvent(obj, ev, fn) {
if (obj.attachEvent) {
obj.attachEvent("on" + ev, fn);
}
else {
obj.addEventListener(ev, fn, false);
}
}
//发送消息
bindEvent(sendMessage, 'click', function () {
// Sends a new message
var number = document.getElementById("number").value;
var src=number+"@192.168.8.248"
var message = document.getElementById("msg").value;
ua.message(src, message);
// When receiving a message, prints it out
ua.on('message', function (message) {
console.log(message.body);
});
})
/**
* 拨打电话
*/
bindEvent(startCall, 'click', function () {
var number = document.getElementById("number").value;
var sippath=number+ "@192.168.8.248";
//创建一个新的出站(用户代理客户端)会话
currentSession = ua.invite(number, {
sessionDescriptionHandlerOptions: {
constraints: {
audio: true,
video: true
},
alwaysAcquireMediaFirst: true // 此参数是sip.js官方修复在firefox遇到的bug所设置
}
});
//开始嘟嘟嘟
Ring.startRingbackTone();
//每次收到成功的最终(200-299)响应时都会触发。
currentSession.on("accepted", function (response, cause) {
console.log(response);
Ring.stopRingbackTone();
//获取seesion
// Gets remote tracks
var pc = currentSession.sessionDescriptionHandler.peerConnection;
//接听会话
if(pc.getReceivers()){
var remoteStream = new MediaStream();
pc.getReceivers().forEach(function (receiver) {
remoteStream.addTrack(receiver.track);
});
remoteView.srcObject = remoteStream;
remoteView.play();
}
//发起会话
if (pc.getSenders()) {
var localStream = new MediaStream();
pc.getSenders().forEach(function (sender) {
localStream.addTrack(sender.track);
});
localVideo.srcObject = localStream;
localVideo.play();
}
})
//挂机时会触发
currentSession.on("bye", function (response, cause) {
Ring.stopRingbackTone();
console.log(response);
})
//请求失败时触发,无论是由于最终响应失败,还是由于超时,传输或其他错误。
currentSession.on("failed", function (response, cause) {
Ring.stopRingbackTone();
console.log(response);
})
/**
*
*/
currentSession.on("terminated", function (message, cause) {
Ring.stopRingbackTone();
})
/**
* 对方拒绝
*/
currentSession.on('rejected', function (response, cause) {
Ring.stopRingbackTone();
})
})
//振铃
var Ring = {
/**
* 振铃
*/
startRingTone: function () {
let play = document.getElementById("ringtone").play();
play.then(() => {
}).catch((err) => {
});
},
/**
* 停止振铃
*/
stopRingTone: function () {
document.getElementById("ringtone").pause();
},
startRingbackTone: function () {
let play = document.getElementById("ringbacktone").play();
play.then(() => {
}).catch((err) => {
});
},
stopRingbackTone: function () {
document.getElementById("ringbacktone").pause();
}
};
</script>
</body>
</html>