目錄
一. Sipdroid的請求超時和重傳
二. SIP中超時和重傳的定義
三. RFC中超時和重傳的定義
一. Sipdroid的請求超時和重傳
Sipdroid實現SIP協議棧系列, 之前的文章僅涉及了SIP消息的基本概念, 比如:
- 請求型消息: INVITE, REGISTER...
- 應答型消息: 100 Trying, 180 Ringing, 200 OK, BYE, ACK...
- 攜帶SDP信息
- 攜帶認證信息
這篇文章更深入一些, 介紹了SIP作為一種可靠傳輸, 涉及到的超時和重傳機制. 也是在調試bug時發現的新大陸.
1.1 出現的問題
注冊在同一個SIP Server的兩個SIP Client互相呼叫時, 會話建立前的SIP信令一切正常, 雙發也開始響鈴, 但是被叫5s內不接聽或拒接, 主叫就會發送CANCEL, 被叫就會回復100 Trying + 487 + 200 OK, 然后會話中斷. 也就是響鈴時間只有5s, 主叫就取消了呼叫.
1.2 初步推測
- 被叫回復的100 Trying和180 Ringing都沒有收到?
- 服務器轉發的被叫回復的100 Trying和180 Ringing都沒有收到?
- 主叫的代碼里是否有修改響鈴時間的參數?
- 主叫本地有什么超時CANCEL的機制被魔法般地啟動了?
1.3 解決方法
從主叫對象ua開始追蹤基類: UA->InviteDialog->InviteTransactionClient, 發現在發送各類請求msg前有一個線程類InnerTimer對象的啟動, 這個線程的休眠時間, 就是響鈴時間.
1.4 問題原因
Sipdroid的請求超時參數設置錯誤. RFC推薦的32000被設置成5000, 導致INVITE請求在5s內沒有收到200 OK回執, 就被視為超時, 主叫主動CANCEL了呼叫請求. 要修改的代碼在SipStack類里, 該類描述了SIP協議棧的一些基本屬性, 包括SIP默認使用端口, 默認傳輸協議, 默認超時時間等. 修改參數如下:
/** * starting retransmission timeout (milliseconds); called T1 in RFC2361; * they suggest T1=500ms */ public static long retransmission_timeout = 2000; /** * maximum retransmission timeout (milliseconds); called T2 in RFC2361; they * suggest T2=4sec */ public static long max_retransmission_timeout = 16000; /** transaction timeout (milliseconds); RFC2361 suggests 64*T1=32000ms */ //public static long transaction_timeout = 5000;[CHG]這里應該修改為RFC推薦值 public static long transaction_timeout = 32000; /** clearing timeout (milliseconds); T4 in RFC2361; they suggest T4=5sec */ public static long clearing_timeout = 5000;
一直知道TCP協議里有重傳算法, 確保了傳輸可靠性, 而且是應用層基本無法質疑的可靠.
在有超時機制的msg發送前, 初始化類對象, 它們一般會被命名為transaction
class InnerTimer extends Thread { long timeout; InnerTimerListener listener; public InnerTimer(long timeout, InnerTimerListener listener) { this.timeout = timeout; this.listener = listener; start(); } public void run() { if (listener != null) { try { Thread.sleep(timeout); listener.onInnerTimeout(); } catch (Exception e) { e.printStackTrace(); } listener = null; } } }
class InnerTimerST extends java.util.TimerTask { static java.util.Timer single_timer = new java.util.Timer(true); // long timeout; InnerTimerListener listener; public InnerTimerST(long timeout, InnerTimerListener listener) { // this.timeout=timeout; this.listener = listener; single_timer.schedule(this, timeout); } public void run() { if (listener != null) { listener.onInnerTimeout(); listener = null; } } }
參考
[1] [SIP協議]學習初學筆記