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在后面的取流时应该会再学习.
1 Sipdroid.on(this, true); //将注册标识位置true 2 registerReceiver();
3 settings.registerOnSharedPreferenceChangeListener(this);
4 VisProxy.proxyer().registerListener(this);//预注册监听器 11 startService(new Intent(this, CustomRtspServer.class)); //rtsp server初始化(暂略) 12 bindService(new Intent(this, CustomRtspServer.class), mRtspServerConnection, Context.BIND_AUTO_CREATE); 14 preRegister();//预注册
上面这段预注册代码中, 最关键的就是预注册监听器和注册. preRegister()的具体实现是VisProxy.proxyer().preReg(doorAddr, roomNum), 所以重点看下代理服务器VisProxy类:
1 public interface VisProxyListener 2 { 3 public void onCmdResult(int type, int result, Object arg); 4 } 5 6 private CopyOnWriteArrayList<VisProxyListener> listeners = new CopyOnWriteArrayList<VisProxyListener>(); 7 8 public void registerListener(VisProxyListener l) 9 { 10 listeners.add(l); 11 } 12 13 public void unregisterListener(VisProxyListener l) 14 { 15 listeners.remove(l); 16 } 17 18 private VisProxy() 19 { 20 //(synchronized)初始化注册线程WorkerHandler; 21 } 22 23 private class WorkerHandler extends Handler 24 { 25 public void handleMessage(Message msg) 26 { 27 //nativePreReg(addr, roomno); 28 } 29 }
上面的代码是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里:
1 public void onCmdResult(int type, int result, Object arg) 2 { 3 //预注册成功, 进行用户注册: register();
4 //预注册失败, 继续向代理服务器注册: VisProxy.proxyer().preRegDelay() 5 }
1.2 周期性用户注册
预注册完成, 返回能够向SIP服务器注册的长号码, 开始SIP协议中基本组件的初始化. 用户注册就是SIP基本组件之一. 在应用中, 共有两处用户注册的地方, 分别是:
- 配置发生变化时(见下文代码)
- 预注册成功的回调方法中(见上文代码)
1 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) 2 { 3 if(key.startsWith(Settings.PREF_ROOM_NUM) || key.startsWith(Settings.PREF_DOOR_IP)) 4 { 5 preRegister(); 6 } 7 else if(key.startsWith(Settings.PREF_SIP_IP)) 8 { 9 register(); 10 } 11 }
可以看到, 配置发生变化时, 如果是房间号(暨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"; 2 final static String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR"; 3 final static String ACTION_DATA_STATE_CHANGED = "android.intent.action.ANY_DATA_STATE"; 4 final static String ACTION_DOCK_EVENT = "android.intent.action.DOCK_EVENT"; 5 final static String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE"; 6 final static String ACTION_SCO_AUDIO_STATE_CHANGED = "android.media.SCO_AUDIO_STATE_CHANGED"; 7 final static String EXTRA_SCO_AUDIO_STATE = "android.media.extra.SCO_AUDIO_STATE"; 8 final static String PAUSE_ACTION = "com.android.music.musicservicecommand.pause"; 9 final static String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause"; 10 final static String ACTION_DEVICE_IDLE = "com.android.server.WifiManager.action.DEVICE_IDLE"; 11 final static String ACTION_VPN_CONNECTIVITY = "vpn.connectivity";
...
要回归用户注册, 就重点关注Receiver类中的SipdroidEngine构造方法:
1 public static synchronized SipdroidEngine engine(Context context) { 2 if (mSipdroidEngine == null) { 3 mSipdroidEngine = new SipdroidEngine(); 4 mSipdroidEngine.StartEngine(); 5 } else 6 mSipdroidEngine.CheckEngine(); 7 return mSipdroidEngine; 8 }
II. SipdroidEngine类概述
Sipdroid源码对SIP协议的理解非常形象, 每一个SIP客户端都是一个UserAgentClient类对象, 每一次向服务器的注册都是由RegisterAgent类对象发起的RegisterTransaction, 每一个新的电话就是一个新的Session/Dialog类对象, 每个Session/Dialog中的行为如接听, 挂断, 拒接...也都是一个Transaction类对象, 对每一个Session的描述就是一个SipProvider类对象. 不知道为什么看到***Agent这里想到了"地狱使者"------神的代理人?(王之迷恋...). 总之用户注册是第一步, 所以先看RegisterTransaction和RegisterAgent吧, 首先从SipdroidEngine初始化和start中寻找用户注册相关:
1 public boolean StartEngine() { 2 //获取了wifi和电池权限, 是关于屏幕常亮和保持wifi在屏幕关闭后不会断线的设置 3 PowerManager pm = (PowerManager) getUIContext().getSystemService(Context.POWER_SERVICE); 4 WifiManager wm = (WifiManager) getUIContext().getSystemService(Context.WIFI_SERVICE);
...
5 //初始化各种代理对象: 使用者代理, 注册代理, SIP保活代理 6 uas = new UserAgent[LINES]; 7 ras = new RegisterAgent[LINES]; 8 kas = new KeepAliveSip[LINES];
...
9 //初始化各种代理的"个人资料" 10 lastmsgs = new String[LINES]; 11 sip_providers = new SipProvider[LINES]; 12 user_profiles = new UserAgentProfile[LINES]; 13 user_profiles[0] = getUserAgentProfile(""); 14 for (int i = 1; i < LINES; i++) 15 user_profiles[i] = getUserAgentProfile(""+i);
16 //SipStack和UserAgent, RegisterAgent之间的关联?暂略 17 SipStack.init(null); 18 ... 19 20 register(); //注册代理进行注册 21 listen(); //注册完成SIP通路建立完成, 就可以监听来电了 22 23 return true; 24 }
在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():
1 public boolean register(int expire_time) { 2 //此处省略20行, 关于注册超时的时间计算 3 ... 4 //创建一个注册请求msg: req 5 Message req = MessageFactory.createRegisterRequest(sip_provider, 6 target, target, new NameAddress(user_profile.contact_url), qvalue, icsi); 8 req.setExpiresHeader(new ExpiresHeader(String.valueOf(expire_time))); 9 10 //添加了什么鬼认证给req 11 AuthorizationHeader ah = new AuthorizationHeader("Digest"); 12 ... 13 req.setAuthorizationHeader(ah); 14 15 t = new TransactionClient(sip_provider, req, this, 30000); 16 t.request(); 17 return true; 18 }
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, 超时回调
1 public abstract class Transaction implements SipProviderListener, TimerListener { 2 //和Transaction相关的变量定义, 以及这些变量的调用方法 3 protected static int transaction_counter = 0; 4 static final int STATE_IDLE = 0; 5 static final int STATE_WAITING = 1; 6 ... 7 8 //SipProvider的onReceivedMessage()方法提供的SIP msg 9 SipProvider sip_provider; 10 //请求msg: req 11 Message request; 12 13 //构造一个Transaction 14 protected Transaction(SipProvider sip_provider) { 15 this.sip_provider = sip_provider; 16 log = sip_provider.getLog(); 17 this.transaction_id = null; 18 this.request = null; 19 this.connection_id = null; 20 this.transaction_sqn = transaction_counter++; 21 this.status = STATE_IDLE; 22 } 23 24 //SipProvider接口实现: msg监听(SIP Provider暨SIP传输层监听到msg后, 回调给TransactinListener, 交给事务对象处理) 25 public void onReceivedMessage(SipProvider provider, Message msg) {} 26 27 //TimerListener接口实现: 超时回调 28 public void onTimeout(Timer to) {} 29 30 //终止transaction 31 public abstract void terminate(); 32 }
作为TransactionClient的抽象类, Transaction类定义了事务类应当具备的基本方法, 具体实现由TransactionClient编写; 在继承抽象父类的同时, 也继承了父类的所有变量.
抽丝剥茧, 遍历了五个类: SipdroidEngine-->RegisterAgent-->TransactionClient-->Transaction-->SipProvider, 终于理清了SIP用户注册的路线, 就这, 还没有深入了解SIP底层通信协议的报文组装. SIP本身的逻辑如果用C源码比如linphone看应该会更加清晰, 但是放在面向对象编程里, 各种设计模式和封装, 玩的就是套路...
用户代理类UserAgent概述
在SipdroidEngine.startEngine()时初始化, 等待注册代理注册成功, 这个类对象就会派上用场, 管理SIP账户的呼入呼出. UserAgent类里有一些重要的变量和方法:
1 public class UserAgent extends CallListenerAdapter { 2 3 public UserAgentProfile user_profile; //用户代理"任务简介" 4 protected SipProvider sip_provider; //SIP传输层相关类 5 public static final int ... //UA的状态值: 正在呼叫/有来电/空闲... 6 7 public void setAudio(boolean enable) { 8 user_profile.audio = enable; //音频使能 9 } 10 11 public String getSessionDescriptor() { 12 return local_session; //返回Session描述 13 } 14 15 //用户代理构造函数, 初始化需要参数:传输层+用户代理"个人简介" 16 public UserAgent(SipProvider sip_provider, UserAgentProfile user_profile) { 17 this.sip_provider = sip_provider; 18 log = sip_provider.getLog(); 19 this.user_profile = user_profile; 20 realm = user_profile.realm; 21 ser_profile.initContactAddress(sip_provider); //如果没有设置contact_ual或from_url, 就创建 22 } 23 24 //呼叫功能具体实现, 需要参数: 被叫url 25 public boolean call(String target_url, boolean send_anonymous) { 26 if (Receiver.call_state != UA_STATE_IDLE){...} //判断当前主叫状态, 是否IDLE 27 hangup(); // modified 28 changeStatus(UA_STATE_OUTGOING_CALL,target_url); 29 //构造主叫url: from_url; 构造被叫url: 获取target_url 30 call.call(target_url, local_session, icsi); 31 return true; 32 }
...... 33 }
参考资料
[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)
*/