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通话:

  1. 通过账号, 需要服务器
  2. 直接通话, 不需要服务器, 在两台电脑上装好Yate后启动客户端即可呼叫
  • 服务器的功能

如果客户端A不知道B的IP和Port, 就会向服务器发送所呼叫的B号码, 由服务器查询该号码在注册表中对应的IP和Port, 查询后将呼叫信令转发过去. 同时基于安全等应用需求, SIP服务器起到了更多作用:

  1. 接入/注册认证
  2. 黑白名单
  3. 拥塞控制
  4. 路由接入, 操作维护, 网管
  5. 呼叫控制和处理
  6. 业务提供/支持
  7. ...

 

I. 注册: 预注册和用户注册

Sipdroid启动, 首先向预注册, 获取能向SIP服务器注册的长号码, 然后在子线程中进行用户周期注册, 两条主线:

preReg()------> WorkHandler().nativePreReg() JNI实现

registerThread.start()------>sendMsg()------>Receiver.engine().expire() SIP协议的正统注册流程

1.1 预注册

预注册前, 应用会先注册一些广播和监听器: 

  1. regeisterReceiver() 注册全局广播监听器;
  2. registerOnSharedPreferenceChangeListener() 注册配置(主要是SIP服务器, 用户配置)变化监听器;
  3. 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的四个主要功能代码:

  1. 提供一个注册回调接口: onCmdResutl(int type, int resutl, Object arg)
  2. 提供两个注册回调接口调用方法: registerListener(VisProxyListener l)和unregisterListener(VisProxyListener l)
  3. 提供一个线程安全的VisProxy构造函数, 初始化预注册线程WorkerHandler和它的消息队列mLooper
  4. 预注册线程轮询: (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. 配置发生变化时(见下文代码)
  2. 预注册成功的回调方法中(见上文代码)
 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代理人们, 同时附上源码中的类注释:

  1. UserAgent

    "简单的SIP使用者代理, 简称UA. 包括了音视频应用. 可以使用外部音视频接口作为媒体应用."

  2. RegisterAgent

    "注册使用者代理. 向服务器中注册(一次或者周期性)联系人地址."

  3. 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的基本功能:

  1. 构造一个Transaction对象: 初始化SIP协议传输层管理对象sip_provider, 需要传输的SIP请求req, 初始化Transaction ID, 状态, 网络情况
  2. 监听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)
*/

 

posted @ 2016-12-08 11:32  Elsa_Rong  阅读(2380)  评论(0编辑  收藏  举报