Sipdroid实现SIP(一): 注册
目录
- 注册: 预注册获取长号和用户注册
- 预注册返回长号
- 周期性用户注册
- Receiver类概述
- SipdroidEngine类概述
- Sipdroid类中的用户注册: 注册代理和注册事务
- 注册代理类RegisterAgent
- 注册事务类TransactionClient
- 参考资料
前言
Mark下学习过程中的问题, 然后一个一个解决!
- 为什么SIP协议还牵涉到RFC?
推测所有的实时传输协议都会同意划归到RFC, 就像所有的ZigBee, WSN等无线通信都划归到 IEEE 802.* 系列.
- 为什么Siddroid里总会有一个管注册的Register, 这个Register不管音视频流吗?
推测是管的, 是在接收到音视频流的信号, 逐层往上传递过程中的回调者之一: RegisterListener.
- UserAgentClient从Register中拿到UserAgentServer的地址, 不就可以绕过Register直接和UAS通话了吗?
是的, 所以在SIP协议中加入了Record-Route关键字, 用于控制是否开放端对端通信.
- 注册到服务器不是必须的
如果两个客户端互相知道对方IP和Port, 可以直接端对端通信. e.g. Yate客户端支持两种方式的SIP通话:
- 通过账号, 需要服务器
- 直接通话, 不需要服务器, 在两台电脑上装好Yate后启动客户端即可呼叫
- 服务器的功能
如果客户端A不知道B的IP和Port, 就会向服务器发送所呼叫的B号码, 由服务器查询该号码在注册表中对应的IP和Port, 查询后将呼叫信令转发过去. 同时基于安全等应用需求, SIP服务器起到了更多作用:
- 接入/注册认证
- 黑白名单
- 拥塞控制
- 路由接入, 操作维护, 网管
- 呼叫控制和处理
- 业务提供/支持
- ...
I. 注册: 预注册和用户注册
Sipdroid启动, 首先向预注册, 获取能向SIP服务器注册的长号码, 然后在子线程中进行用户周期注册, 两条主线:
preReg()------> WorkHandler().nativePreReg() JNI实现
registerThread.start()------>sendMsg()------>Receiver.engine().expire() SIP协议的正统注册流程
1.1 预注册
预注册前, 应用会先注册一些广播和监听器:
- regeisterReceiver() 注册全局广播监听器;
- registerOnSharedPreferenceChangeListener() 注册配置(主要是SIP服务器, 用户配置)变化监听器;
- proxy().registerListener() 预注册监听器. 还会启动一个后台服务: RtspServer, 这个和音视频流相关的Service在后面的取流时应该会再学习.
Sipdroid.on(this, true); //将注册标识位置true
registerReceiver();
3 settings.registerOnSharedPreferenceChangeListener(this);
4 VisProxy.proxyer().registerListener(this);//预注册监听器
startService(new Intent(this, CustomRtspServer.class)); //rtsp server初始化(暂略)
bindService(new Intent(this, CustomRtspServer.class), mRtspServerConnection, Context.BIND_AUTO_CREATE);
preRegister();//预注册
上面这段预注册代码中, 最关键的就是预注册监听器和注册. preRegister()的具体实现是VisProxy.proxyer().preReg(doorAddr, roomNum), 所以重点看下代理服务器VisProxy类:
public interface VisProxyListener
{
public void onCmdResult(int type, int result, Object arg);
} private CopyOnWriteArrayList<VisProxyListener> listeners = new CopyOnWriteArrayList<VisProxyListener>(); public void registerListener(VisProxyListener l)
{
listeners.add(l);
} public void unregisterListener(VisProxyListener l)
{
listeners.remove(l);
} private VisProxy()
{
//(synchronized)初始化注册线程WorkerHandler;
} private class WorkerHandler extends Handler
{
public void handleMessage(Message msg)
{
//nativePreReg(addr, roomno);
}
}
上面的代码是VisProxy.java的四个主要功能代码:
- 提供一个注册回调接口: onCmdResutl(int type, int resutl, Object arg)
- 提供两个注册回调接口调用方法: registerListener(VisProxyListener l)和unregisterListener(VisProxyListener l)
- 提供一个线程安全的VisProxy构造函数, 初始化预注册线程WorkerHandler和它的消息队列mLooper
- 预注册线程轮询: (1)执行预注册方法; (2)触发预注册回调方法
接口的所有调用者都在listeners列表里, 这是一个线程安全的随机访问的List. 其它类通过VisProxy().proxy().regeisterListener()和 VisProxy.proxy().unregisterListener()方法向listeners里添加/删除调用者, 线程通过轮询调用者队列, 触发回调方法. 回调的具体实现在继承了VisProxyListener的主Activity里:
public void onCmdResult(int type, int result, Object arg)
{
//预注册成功, 进行用户注册: register();
4 //预注册失败, 继续向代理服务器注册: VisProxy.proxyer().preRegDelay()
}
1.2 周期性用户注册
预注册完成, 返回能够向SIP服务器注册的长号码, 开始SIP协议中基本组件的初始化. 用户注册就是SIP基本组件之一. 在应用中, 共有两处用户注册的地方, 分别是:
- 配置发生变化时(见下文代码)
- 预注册成功的回调方法中(见上文代码)
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)
{
if(key.startsWith(Settings.PREF_ROOM_NUM) || key.startsWith(Settings.PREF_DOOR_IP))
{
preRegister();
}
else if(key.startsWith(Settings.PREF_SIP_IP))
{
register();
}
}
可以看到, 配置发生变化时, 如果是房间号(暨SIP长号)或门口机IP(暨长号码管理者)改变, 都需要重新预注册, 再用户注册, 因为Client和Proxy之间的通信链路发生了变化; 如果是SIP服务器IP改变, 则只需重新用户注册, 因为Client和Proxy的通信链路OK, 只是它们对SIP的注册失效了. 一个简单的三角关系.
对应用来说, SIP和其它系统功能都适合广播-接收模式, 所以统一交给Receiver类管理, 而Recevier类中所有和SIP有关的管理, 都交给SipdroidEngine类. 下面简单介绍下Receive类后, 继续回归用户注册--->SipdroidEngine类的详细介绍.
1.3 Receiver类概述
Receiver作为Sipdroid中的全局广播接收器, 接收各类广播, 然后向对应类派发任务. 从Receiver中枚举的ACTION可以看到Sipdroid源码响应了很多类型的广播: 接收网络状态变化, VPN状态变化, 配置改变, 亮度传感器处理, 有线耳机插入或拔出广播, 定位位置更新处理, 屏幕锁屏和解锁, 铃声/震动的开启/停止, 构造SipdroidEngine, 来电/去电/空闲/挂断的判断处理......
1 final static String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE";
final static String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR";
final static String ACTION_DATA_STATE_CHANGED = "android.intent.action.ANY_DATA_STATE";
final static String ACTION_DOCK_EVENT = "android.intent.action.DOCK_EVENT";
final static String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE";
final static String ACTION_SCO_AUDIO_STATE_CHANGED = "android.media.SCO_AUDIO_STATE_CHANGED";
final static String EXTRA_SCO_AUDIO_STATE = "android.media.extra.SCO_AUDIO_STATE";
final static String PAUSE_ACTION = "com.android.music.musicservicecommand.pause";
final static String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause";
final static String ACTION_DEVICE_IDLE = "com.android.server.WifiManager.action.DEVICE_IDLE";
final static String ACTION_VPN_CONNECTIVITY = "vpn.connectivity";
...
要回归用户注册, 就重点关注Receiver类中的SipdroidEngine构造方法:
public static synchronized SipdroidEngine engine(Context context) {
if (mSipdroidEngine == null) {
mSipdroidEngine = new SipdroidEngine();
mSipdroidEngine.StartEngine();
} else
mSipdroidEngine.CheckEngine();
return mSipdroidEngine;
}
II. SipdroidEngine类概述
Sipdroid源码对SIP协议的理解非常形象, 每一个SIP客户端都是一个UserAgentClient类对象, 每一次向服务器的注册都是由RegisterAgent类对象发起的RegisterTransaction, 每一个新的电话就是一个新的Session/Dialog类对象, 每个Session/Dialog中的行为如接听, 挂断, 拒接...也都是一个Transaction类对象, 对每一个Session的描述就是一个SipProvider类对象. 不知道为什么看到***Agent这里想到了"地狱使者"------神的代理人?(王之迷恋...). 总之用户注册是第一步, 所以先看RegisterTransaction和RegisterAgent吧, 首先从SipdroidEngine初始化和start中寻找用户注册相关:
public boolean StartEngine() {
//获取了wifi和电池权限, 是关于屏幕常亮和保持wifi在屏幕关闭后不会断线的设置
PowerManager pm = (PowerManager) getUIContext().getSystemService(Context.POWER_SERVICE);
WifiManager wm = (WifiManager) getUIContext().getSystemService(Context.WIFI_SERVICE);
...
//初始化各种代理对象: 使用者代理, 注册代理, SIP保活代理
uas = new UserAgent[LINES];
ras = new RegisterAgent[LINES];
kas = new KeepAliveSip[LINES];
... //初始化各种代理的"个人资料"
lastmsgs = new String[LINES];
sip_providers = new SipProvider[LINES];
user_profiles = new UserAgentProfile[LINES];
user_profiles[0] = getUserAgentProfile("");
for (int i = 1; i < LINES; i++)
user_profiles[i] = getUserAgentProfile(""+i);
//SipStack和UserAgent, RegisterAgent之间的关联?暂略
SipStack.init(null);
... register(); //注册代理进行注册
listen(); //注册完成SIP通路建立完成, 就可以监听来电了 return true;
}
在StartEngine()的最后, 找到了用户注册方法register()! 但是在研究注册流程前, 首先要了解下之前对各种Agent, AgentProfile和Provider初始化的意义, 所以简单介绍下我们的Transaction代理人们, 同时附上源码中的类注释:
- UserAgent
"简单的SIP使用者代理, 简称UA. 包括了音视频应用. 可以使用外部音视频接口作为媒体应用."
- RegisterAgent
"注册使用者代理. 向服务器中注册(一次或者周期性)联系人地址."
- SipProvider
"SipProvider类封装了SIP传输层协议, 传输层负责发送和接收SIP消息, 接收信息由SipProviderListener的回调接口实现."
酱就很好理解啦, 这个SipdroidEngine类是围绕Agent和Provider对象们展开的方法, 比如:
- halt(): 取消所有Agent注册, 释放wifi和点亮锁
- expire(): 置位所有Agent状态为UNREGISTERED, 重新register()
- StartEngine(): 启动SIP引擎
- CheckEngine(): 检查sip_providers(和状态代理服务器OutboundProxy有关, 暂略)
- Agent注册的回调方法: onUaRegistrationSuccess()/onUaRegistrationFailure()
- 网络电话接口: listen()/call()/hangup()/togglehold()/transfer()
III. SipdroidEngine类中的用户注册: 注册代理和注册事务
3.1 注册代理类RegisterAgent概述
用户注册的主体是注册代理类RegisterAgent, 一个普通功能类, 定义一些注册需要的变量, 比如username, expire_time, passwd...再定义一个注册方法和它的周边, duang~注册代理类封装完成.
public class RegisterAgent implements TransactionClientListener... {
public static final int ... //定义注册状态变量: 未注册/正在注册/已注册/正在重新注册...
RegisterAgentListener listener; //注册监听回调
SipProvider sip_provider; //SIP传输层
UserAgentProfile user_profile; //用户代理"个人简介" //注册相关
NameAddress target; //目标url
NameAddress contact; //自己的url
String username;
String realm; //Authorization中的realm参数, 表示范围
String passwd;
String next_nonce; //Authorization中的nonce参数, 相当于认证密码 // 构造含有认证信息的RegisterAgent
public RegisterAgent(SipProvider sip_provider, String target_url, String contact_url, String username, String realm, String passwd, RegisterAgentListener listener,UserAgentProfile user_profile...) {
...
init(sip_provider, target_url, contact_url, listener);
} //RegisterAgent普通构造函数, 不含认证信息
private void init(SipProvider sip_provider, String target_url, String contact_url, RegisterAgentListener listener) {
this.listener = listener;
this.sip_provider = sip_provider;
this.target = new NameAddress(target_url);
this.contact = new NameAddress(contact_url);
... // authentication
this.username = null;
this.realm = null;
...
}
}
3.2 注册代理类register()方法
注册代理类的核心在注册方法register():
public boolean register(int expire_time) {
//此处省略20行, 关于注册超时的时间计算
...
//创建一个注册请求msg: req
Message req = MessageFactory.createRegisterRequest(sip_provider,
target, target, new NameAddress(user_profile.contact_url), qvalue, icsi);
req.setExpiresHeader(new ExpiresHeader(String.valueOf(expire_time))); //添加了什么鬼认证给req
AuthorizationHeader ah = new AuthorizationHeader("Digest");
...
req.setAuthorizationHeader(ah); t = new TransactionClient(sip_provider, req, this, 30000);
t.request();
return true;
}
Sipdroid已经将SIP的各种功能分派到各个代理人(==代理类)去执行, 又将每个代理人的执行任务划归到Transaction, 真佩服作者的解耦思维. 现在对用户注册的理解又深了一层: 每一个用户将会有一个用户注册代理人, 管理向SIP服务器注册的一切Transactions, 包括代理自身的信息, 注册msg的创建, 注册transaction前的各种准备都完成后, 执行任务将调用TransactionClient().
3.2 注册事务类TransactionClient
从上面代码可以看到, 注册代理整理好注册所需的参数: sip_provider, req, timeout, 就向注册事务类提交本次Transaction, 具体的注册工作就要交给TransactionClient去执行啦. 源码里给出的类注释:
RFC 3261(注: 一种SIP会话建立的协议)中有对客户端事务的通用定义. TransactionClient用于响应一个新的SIP事务的创建, 每个SIP事务的创建都需要SipProvider组建的请求消息, 结束则需要一个最终回复.
网络状态的变化和信息接收都将通过事务监听器TransactionListener传递给客户端事务对象TransactionClient object.
因为TransactionClient类继承自Transaction类, 所以先学习下父类, 再学习子类. Trasaction类中用到了两种Java设计模式: 抽象类和继承接口类. 从下面的抽象类中可以归纳出Transaction的基本功能:
- 构造一个Transaction对象: 初始化SIP协议传输层管理对象sip_provider, 需要传输的SIP请求req, 初始化Transaction ID, 状态, 网络情况
- 监听SIP msg, 超时回调
public abstract class Transaction implements SipProviderListener, TimerListener {
//和Transaction相关的变量定义, 以及这些变量的调用方法
protected static int transaction_counter = 0;
static final int STATE_IDLE = 0;
static final int STATE_WAITING = 1;
... //SipProvider的onReceivedMessage()方法提供的SIP msg
SipProvider sip_provider;
//请求msg: req
Message request; //构造一个Transaction
protected Transaction(SipProvider sip_provider) {
this.sip_provider = sip_provider;
log = sip_provider.getLog();
this.transaction_id = null;
this.request = null;
this.connection_id = null;
this.transaction_sqn = transaction_counter++;
this.status = STATE_IDLE;
} //SipProvider接口实现: msg监听(SIP Provider暨SIP传输层监听到msg后, 回调给TransactinListener, 交给事务对象处理)
public void onReceivedMessage(SipProvider provider, Message msg) {} //TimerListener接口实现: 超时回调
public void onTimeout(Timer to) {} //终止transaction
public abstract void terminate();
}
作为TransactionClient的抽象类, Transaction类定义了事务类应当具备的基本方法, 具体实现由TransactionClient编写; 在继承抽象父类的同时, 也继承了父类的所有变量.
抽丝剥茧, 遍历了五个类: SipdroidEngine-->RegisterAgent-->TransactionClient-->Transaction-->SipProvider, 终于理清了SIP用户注册的路线, 就这, 还没有深入了解SIP底层通信协议的报文组装. SIP本身的逻辑如果用C源码比如linphone看应该会更加清晰, 但是放在面向对象编程里, 各种设计模式和封装, 玩的就是套路...
用户代理类UserAgent概述
在SipdroidEngine.startEngine()时初始化, 等待注册代理注册成功, 这个类对象就会派上用场, 管理SIP账户的呼入呼出. UserAgent类里有一些重要的变量和方法:
public class UserAgent extends CallListenerAdapter { public UserAgentProfile user_profile; //用户代理"任务简介"
protected SipProvider sip_provider; //SIP传输层相关类
public static final int ... //UA的状态值: 正在呼叫/有来电/空闲... public void setAudio(boolean enable) {
user_profile.audio = enable; //音频使能
} public String getSessionDescriptor() {
return local_session; //返回Session描述
} //用户代理构造函数, 初始化需要参数:传输层+用户代理"个人简介"
public UserAgent(SipProvider sip_provider, UserAgentProfile user_profile) {
this.sip_provider = sip_provider;
log = sip_provider.getLog();
this.user_profile = user_profile;
realm = user_profile.realm;
ser_profile.initContactAddress(sip_provider); //如果没有设置contact_ual或from_url, 就创建
} //呼叫功能具体实现, 需要参数: 被叫url
public boolean call(String target_url, boolean send_anonymous) {
if (Receiver.call_state != UA_STATE_IDLE){...} //判断当前主叫状态, 是否IDLE
hangup(); // modified
changeStatus(UA_STATE_OUTGOING_CALL,target_url);
//构造主叫url: from_url; 构造被叫url: 获取target_url
call.call(target_url, local_session, icsi);
return true;
}
......
}
参考资料
[1]. SIP协议中的认证方式 http://www.cnblogs.com/fengyv/archive/2013/02/05/2892729.html
配置信息: 存储和共享
Sipdroid对所有SIP协议相关的配置文件通过SharedPreference保存, 方便数据在进程内的保存和分享. 调用也很简单, 源码作者直接给出了说明, 如何在当前类/其它类中调用/修改Settings的key/value/variables:
/*-
* ****************************************
* **** HOW TO USE SHARED PREFERENCES *****
* ****************************************
*
* If you need to check the existence of the preference key
* in this class: contains(PREF_USERNAME)
* in other classes: PreferenceManager.getDefaultSharedPreferences(Receiver.mContext).contains(Settings.PREF_USERNAME)
* If you need to check the existence of the key or check the value of the preference
* in this class: getString(PREF_USERNAME, "").equals("")
* in other classes: PreferenceManager.getDefaultSharedPreferences(Receiver.mContext).getString(Settings.PREF_USERNAME, "").equals("")
* If you need to get the value of the preference
* in this class: getString(PREF_USERNAME, DEFAULT_USERNAME)
* in other classes: PreferenceManager.getDefaultSharedPreferences(Receiver.mContext).getString(Settings.PREF_USERNAME, Settings.DEFAULT_USERNAME)
*/
Sipdroid实现SIP(一): 注册的更多相关文章
- Sipdroid实现SIP(六): SIP中的请求超时和重传
目录 一. Sipdroid的请求超时和重传 二. SIP中超时和重传的定义 三. RFC中超时和重传的定义 一. Sipdroid的请求超时和重传 Sipdroid实现SIP协议栈系列, 之前的文章 ...
- Sipdroid实现SIP(四): 传输层和应用层之间的枢纽SipProvider
目录 一. 概述 二. 主要变量 三. 主要方法 四. 在Sipdroid中的应用 一. 概述 在整套Sipdroid源码中, 类SipProvider是最靠近TCP/UDP的一层, 在Sipdroi ...
- Sipdroid实现SIP(三): 消息接收处理
I. 注册回调 RegisterAgent类 在TransactionClient Fail/Success的回调中, 调用RegisterAgentListener的Register Fail/Su ...
- sip协议注册时response值的计算方法
sip注册时有四个步骤, 1.客户端向服务端发送不带Authorization字段的注册请求 2.服务端回401,在回复消息头中带WWW_Authorization 3.客户端向服务端发送带Autho ...
- Sipdroid实现SIP(五): 用Java实现的UserAgent
I. 概述 UserAgent是SIP协议中的一个概念, 将"打电话"功能中的主叫和被叫逻辑上封装成UserAgent, 就像将"注册"功能的发起方和接收方封装 ...
- Sipdroid实现SIP(二): 呼叫请求
INVITE 许多介绍sip的文章没有介绍以下几点细节: 重传, Timer A, B Transaction的有限状态机, 记录当前Transactin的进展情况 与INVITE消息相关的行为(Cl ...
- 使用SIP Servlet为Java EE添加语音功能
会话发起协议(Session Initiation Protocol,SIP)是一种信号传输协议,用于建立.修改和终止两个端点之间的会话.SIP 可用于建立 两方呼叫.多方呼叫,或者甚至 Intern ...
- VoLTE的前世今生...说清楚VoIP、VoLTE、CSFB、VoWiFi、SIP、IMS那些事...
转:https://mp.weixin.qq.com/s?__biz=MzA3MTA3OTIwMw==&mid=401344844&idx=1&sn=497b351f524af ...
- SIP MGCP和H323的区别
在Windows中内置的NetMeeting就是典型的H.323协议客户端,而比较常见的SIP系统是微软开发的MSN Messenger系统.首先,由用户A向SIP服务器发出呼叫请求,请求的信息包含自 ...
随机推荐
- mac 下nginx加入开机启动
通过brew install nginx后设置开机启动项 sudo cp /usr/local/opt/nginx/*.plist /Library/LaunchDaemonssudo launchc ...
- ASP.NET MVC实现剪切板功能
前言 关于复制粘贴的功能,好像不用劳师动众的写后端代码,JS就可以,但正如大家所知道的,兼容性问题,当然这么通用的功能怎么可能没有一个通用的方案呢,于是便找到了一款jquery插件 jquery.cl ...
- nginx 重定向到index.php
location /keywords { index index.php; try_files $uri $uri/ /keywords/i ...
- IOS学习之路二十三(EGOImageLoading异步加载图片开源框架使用)
EGOImageLoading 是一个用的比较多的异步加载图片的第三方类库,简化开发过程,我们直接传入图片的url,这个类库就会自动帮我们异步加载和缓存工作:当从网上获取图片时,如果网速慢图片短时间内 ...
- NodeJS的url信息截取模块url-extract
NodeJS的url信息截取模块url-extract2013-09-12 22:49 by Justany_WhiteSnow, 212 阅读, 0 评论, 收藏, 编辑 上一篇文章,介绍了怎么利用 ...
- jQuery Easing 动画效果扩展
jQuery API提供了简单的动画效果如淡入淡出以及自定义动画效果,而今天我给大家分享的是一款jQuery动画效果扩展增强插件jquery.easing.js,使用该插件可以实现直线匀速运功.变加速 ...
- 深入浅出 ThreadLocal(一)
本文参考http://lavasoft.blog.51cto.com/62575/51926/,对其中的程序进行了改写 一.概述 ThreadLocal是什么呢?其实ThreadLocal并非是一个线 ...
- 黑马程序员:Java基础总结----枚举
黑马程序员:Java基础总结 枚举 ASP.Net+Android+IO开发 . .Net培训 .期待与您交流! 枚举 为什么要有枚举 问题:要定义星期几或性别的变量,该怎么定义?假设用1-7分别 ...
- android调用js
1.android中利用webview调用网页上的js代码. Android 中可以通过webview来实现和js的交互,在程序中调用js代码,只需要将webview控件的支持js的属性设置为true ...
- 结构-行为-样式-css&html横纵居中最佳实践
最近在做手机端的H5项目,有个标题是在一根横线中的,就是水平居中加垂直居中(如图一).这应该是前端开发中经常遇到的一个场景了,做的次数多了就有一些体会,我今天就总结了下这种结构的实现思路:首先,用元素 ...