11-2. SIP 呼叫流程詳解

深入解析 SIP 各種呼叫場景、轉接、保留與會議功能

📱 SIP 呼叫流程詳解

🎯 基本呼叫流程

💡 比喻:打電話的完整過程

1. 撥號(INVITE)
2. 電話響鈴(180 Ringing)
3. 對方接聽(200 OK)
4. 確認接通(ACK)
5. 開始通話(RTP)
6. 掛斷電話(BYE)

最簡單的場景(無 Proxy)

Alice                               Bob
(192.168.1.100)               (192.168.1.200)
  │                                  │
  ├─ INVITE ───────────────────────>│  撥號
  │                                  │
  │<─ 100 Trying ─────────────────────┤  處理中
  │                                  │
  │<─ 180 Ringing ────────────────────┤  響鈴
  │                                  │
  │<─ 200 OK ─────────────────────────┤  接聽
  │                                  │
  ├─ ACK ───────────────────────────>│  確認
  │                                  │
  │<══════ RTP 媒體流 ═══════════════>│  通話中
  │                                  │
  ├─ BYE ───────────────────────────>│  掛斷
  │                                  │
  │<─ 200 OK ─────────────────────────┤  確認掛斷

詳細訊息內容:

1. INVITE(Alice → Bob):

INVITE sip:bob@192.168.1.200:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.100:5060;branch=z9hG4bK776
Max-Forwards: 70
To: Bob <sip:bob@192.168.1.200>
From: Alice <sip:alice@192.168.1.100>;tag=9fxced76sl
Call-ID: 3848276298220188511@192.168.1.100
CSeq: 1 INVITE
Contact: <sip:alice@192.168.1.100:5060>
Content-Type: application/sdp
Content-Length: 142

v=0
o=alice 2890844526 2890844526 IN IP4 192.168.1.100
s=-
c=IN IP4 192.168.1.100
t=0 0
m=audio 49170 RTP/AVP 0
a=rtpmap:0 PCMU/8000

2. 180 Ringing(Bob → Alice):

SIP/2.0 180 Ringing
Via: SIP/2.0/UDP 192.168.1.100:5060;branch=z9hG4bK776
To: Bob <sip:bob@192.168.1.200>;tag=314159
From: Alice <sip:alice@192.168.1.100>;tag=9fxced76sl
Call-ID: 3848276298220188511@192.168.1.100
CSeq: 1 INVITE
Contact: <sip:bob@192.168.1.200:5060>
Content-Length: 0

3. 200 OK(Bob → Alice):

SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.1.100:5060;branch=z9hG4bK776
To: Bob <sip:bob@192.168.1.200>;tag=314159
From: Alice <sip:alice@192.168.1.100>;tag=9fxced76sl
Call-ID: 3848276298220188511@192.168.1.100
CSeq: 1 INVITE
Contact: <sip:bob@192.168.1.200:5060>
Content-Type: application/sdp
Content-Length: 131

v=0
o=bob 2890844730 2890844730 IN IP4 192.168.1.200
s=-
c=IN IP4 192.168.1.200
t=0 0
m=audio 38060 RTP/AVP 0
a=rtpmap:0 PCMU/8000

4. ACK(Alice → Bob):

ACK sip:bob@192.168.1.200:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.100:5060;branch=z9hG4bK777
Max-Forwards: 70
To: Bob <sip:bob@192.168.1.200>;tag=314159
From: Alice <sip:alice@192.168.1.100>;tag=9fxced76sl
Call-ID: 3848276298220188511@192.168.1.100
CSeq: 1 ACK
Content-Length: 0

5. BYE(Alice → Bob):

BYE sip:bob@192.168.1.200:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.100:5060;branch=z9hG4bK778
Max-Forwards: 70
To: Bob <sip:bob@192.168.1.200>;tag=314159
From: Alice <sip:alice@192.168.1.100>;tag=9fxced76sl
Call-ID: 3848276298220188511@192.168.1.100
CSeq: 2 BYE
Contact: <sip:alice@192.168.1.100:5060>
Content-Length: 0

🔀 透過 Proxy 的呼叫流程

💡 比喻:透過總機轉接
Alice 打給總機,總機找到 Bob 並轉接
Alice              Proxy Server              Bob
  │                      │                     │
  ├─ 1. INVITE ────────>│                     │
  │                      ├─ 2. INVITE ───────>│
  │                      │                     │
  │<─ 3. 100 Trying ─────┤                     │
  │                      │                     │
  │                      │<─ 4. 180 Ringing ───┤
  │<─ 5. 180 Ringing ────┤                     │
  │                      │                     │
  │                      │<─ 6. 200 OK ────────┤
  │<─ 7. 200 OK ─────────┤                     │
  │                      │                     │
  ├─ 8. ACK ─────────────────────────────────>│
  │                      │                     │
  │<══════════ 9. RTP 媒體流(直連)═════════>│
  │                      │                     │
  ├─ 10. BYE ────────────────────────────────>│
  │<─ 11. 200 OK ──────────────────────────────┤

重點:

  • ✅ Proxy 只轉發「訊號」(SIP 訊息)
  • ✅ RTP 媒體流「直連」(不經過 Proxy)
  • ✅ ACK 和 BYE 直接發送給對方(使用 Contact header)

Via Header 追蹤路徑:

Alice 發送 INVITE:
Via: SIP/2.0/UDP alice.com:5060;branch=z9hG4bK776

Proxy 轉發時加上自己的 Via:
Via: SIP/2.0/UDP proxy.com:5060;branch=z9hG4bK123
Via: SIP/2.0/UDP alice.com:5060;branch=z9hG4bK776

Bob 回應時,按照 Via 順序回傳(先回給 Proxy,再回給 Alice)

📍 Redirect Server 流程

💡 比喻:查號台
總機告訴你正確的號碼,但不幫你轉接
Alice              Redirect Server           Bob
  │                      │                     │
  ├─ INVITE ───────────>│                     │
  │  To: bob@old.com     │                     │
  │                      │                     │
  │<─ 302 Moved ─────────┤                     │
  │  Contact: bob@new.com│                     │
  │                      │                     │
  ├─ ACK ───────────────>│                     │
  │                      │                     │
  ├─ INVITE ─────────────────────────────────>│
  │  To: bob@new.com     │                     │
  │                      │                     │
  │<─ 200 OK ───────────────────────────────────┤
  ├─ ACK ─────────────────────────────────────>│
  │<══════════ RTP 媒體流 ═══════════════════>│

302 Moved Temporarily:

SIP/2.0 302 Moved Temporarily
Via: SIP/2.0/UDP alice.com:5060;branch=z9hG4bK776
To: Bob <sip:bob@old.com>;tag=314159
From: Alice <sip:alice@example.com>;tag=9fxced76sl
Call-ID: 123@alice.com
CSeq: 1 INVITE
Contact: <sip:bob@new.com>  ← 新的聯絡地址
Content-Length: 0

⏸️ Call Hold(通話保留)

💡 比喻:讓對方等一下
就像打電話時說「請稍等」,然後放下聽筒去做事

保留通話流程

Alice              Bob
  │                 │
  │<═══ RTP ═══════>│  通話中
  │                 │
  ├─ re-INVITE ────>│  (保留通話)
  │  a=sendonly     │  只發送,不接收(靜音)
  │                 │
  │<─ 200 OK ───────┤
  │  a=recvonly     │  只接收,不發送
  │                 │
  ├─ ACK ──────────>│
  │                 │
  │──> RTP ─────────>│  Alice 單向發送保留音樂
  │                 │
  ...(Alice 去處理其他事情)
  │                 │
  ├─ re-INVITE ────>│  (恢復通話)
  │  a=sendrecv     │  雙向通話
  │                 │
  │<─ 200 OK ───────┤
  │  a=sendrecv     │
  │                 │
  ├─ ACK ──────────>│
  │<═══ RTP ═══════>│  恢復正常通話

SDP 範例:

保留通話(a=sendonly):

v=0
o=alice 2890844526 2890844527 IN IP4 192.168.1.100
s=-
c=IN IP4 192.168.1.100
t=0 0
m=audio 49170 RTP/AVP 0
a=rtpmap:0 PCMU/8000
a=sendonly  ← 只發送(播放保留音樂),不接收

恢復通話(a=sendrecv):

m=audio 49170 RTP/AVP 0
a=rtpmap:0 PCMU/8000
a=sendrecv  ← 雙向通話

JavaScript 實作:

// 保留通話
async function holdCall(session) {
    const pc = session.connection;

    // 設定只發送
    const senders = pc.getSenders();
    senders.forEach(sender => {
        if (sender.track) {
            sender.track.enabled = false;  // 停止發送媒體
        }
    });

    // 發送 re-INVITE
    await session.renegotiate({
        rtcOfferConstraints: {
            offerToReceiveAudio: false,  // 不接收音訊
            offerToReceiveVideo: false
        }
    });

    console.log('通話已保留');
}

// 恢復通話
async function resumeCall(session) {
    const pc = session.connection;

    // 恢復發送
    const senders = pc.getSenders();
    senders.forEach(sender => {
        if (sender.track) {
            sender.track.enabled = true;
        }
    });

    // 發送 re-INVITE
    await session.renegotiate({
        rtcOfferConstraints: {
            offerToReceiveAudio: true,
            offerToReceiveVideo: true
        }
    });

    console.log('通話已恢復');
}

🔀 Call Transfer(轉接)

💡 比喻:把電話轉給別人
Alice 和 Bob 通話中,Alice 想把 Bob 轉給 Charlie

1️⃣ Blind Transfer(盲目轉接)

不確認 Charlie 是否接聽,直接轉過去
Alice        Bob        Charlie
  │           │            │
  │<══RTP════>│            │  Alice ↔ Bob 通話中
  │           │            │
  ├─ REFER ─>│            │  (轉給 Charlie)
  │  Refer-To: Charlie     │
  │           │            │
  │<─ 202 Accepted ────────┤
  │           │            │
  │           ├─ INVITE ──>│  Bob 打給 Charlie
  │           │            │
  │           │<─ 200 OK ──┤
  │           ├─ ACK ─────>│
  │           │<══RTP═════>│  Bob ↔ Charlie 通話中
  │           │            │
  ├─ BYE ───>│            │  Alice 掛斷
  │<─ 200 OK ─┤            │

REFER 訊息:

REFER sip:bob@example.com SIP/2.0
Via: SIP/2.0/UDP alice.com:5060;branch=z9hG4bK776
To: Bob <sip:bob@example.com>;tag=314159
From: Alice <sip:alice@example.com>;tag=9fxced76sl
Call-ID: 123@alice.com
CSeq: 101 REFER
Refer-To: <sip:charlie@example.com>  ← 轉給 Charlie
Referred-By: <sip:alice@example.com>
Content-Length: 0

JavaScript 實作:

// 盲目轉接
function blindTransfer(session, targetURI) {
    session.refer(targetURI);

    session.on('refer', (event) => {
        console.log('轉接請求已發送');
        session.terminate();  // 掛斷與 Alice 的通話
    });
}

// 使用
blindTransfer(currentSession, 'sip:charlie@example.com');

2️⃣ Attended Transfer(咨詢轉接)

先打給 Charlie 確認,再轉接
Alice     Bob     Charlie
  │        │         │
  │<═RTP═>│         │  Alice ↔ Bob 通話中
  │        │         │
  ├─ INVITE ────────>│  Alice 打給 Charlie(保留 Bob)
  │        │         │
  │<─ 200 OK ────────┤
  ├─ ACK ──────────>│
  │<═RTP═══════════>│  Alice ↔ Charlie 通話中
  │        │         │
  │        │         │  Alice 咨詢 Charlie 是否接受轉接
  │        │         │
  ├─ REFER ────────>│  (告訴 Charlie 接受 Bob 的來電)
  │  Refer-To: Bob   │
  │        │         │
  │<─ 202 Accepted ──┤
  │        │         │
  │        ├─ INVITE >│  Charlie 打給 Bob
  │        │         │
  │        │<─ 200 OK┤
  │        ├─ ACK ──>│
  │        │<══RTP══>│  Bob ↔ Charlie 通話中
  │        │         │
  ├─ BYE ──────────>│  Alice 掛斷與 Charlie 的通話
  ├─ BYE ──>│         │  Alice 掛斷與 Bob 的通話

JavaScript 實作:

// 咨詢轉接
async function attendedTransfer(sessionWithBob, targetURI) {
    // 1. 保留與 Bob 的通話
    await holdCall(sessionWithBob);

    // 2. 打給 Charlie
    const sessionWithCharlie = ua.call(targetURI, options);

    sessionWithCharlie.on('accepted', async () => {
        console.log('Charlie 接聽了');

        // 3. 詢問 Charlie 是否接受轉接
        const accept = confirm('將 Bob 轉給 Charlie?');

        if (accept) {
            // 4. 執行轉接
            sessionWithBob.refer(targetURI, {
                replaces: sessionWithCharlie
            });

            // 5. 掛斷兩個通話
            sessionWithBob.terminate();
            sessionWithCharlie.terminate();
        } else {
            // 恢復與 Bob 的通話
            await resumeCall(sessionWithBob);
            sessionWithCharlie.terminate();
        }
    });
}

// 使用
attendedTransfer(currentSession, 'sip:charlie@example.com');

🎪 Conference Call(三方通話)

💡 比喻:開會
多人同時在線上討論

架構選擇

1. Mixing(混音):

Alice ─┐
Bob ───┼──> Conference Server(MCU)
Charlie┘     ↓
         混合音訊
             ↓
    廣播給所有人(一個流)

2. Full Mesh(完全網狀):

Alice ←→ Bob
  ↓  ╲  ↗
  ↓   Charlie
  ↓  ↗
  ↓↗

每個人都連線到其他所有人
3 人 = 3 條連線
10 人 = 45 條連線(n*(n-1)/2)

Conference 建立流程(使用 MCU)

Alice              Conference Server         Bob
  │                        │                   │
  ├─ INVITE ──────────────>│                   │
  │  Conference-ID: 123    │                   │
  │                        │                   │
  │<─ 200 OK ───────────────┤                   │
  ├─ ACK ─────────────────>│                   │
  │<══════ RTP ═══════════>│                   │
  │                        │                   │
  │                        │<─ INVITE ─────────┤
  │                        │  Conference-ID: 123│
  │                        │                   │
  │                        ├─ 200 OK ─────────>│
  │                        │<─ ACK ─────────────┤
  │                        │<══════ RTP ═══════>│
  │                        │                   │
  │       Conference Server 混合音訊          │
  │<══════════ 混合音訊 ═══════════════════════>│

JavaScript 實作(使用 SFU):

// 建立會議室
class ConferenceCall {
    constructor(conferenceURI) {
        this.conferenceURI = conferenceURI;
        this.sessions = [];
    }

    // 加入會議
    async join() {
        const session = ua.call(this.conferenceURI, {
            mediaConstraints: {
                audio: true,
                video: true
            }
        });

        session.on('accepted', () => {
            console.log('已加入會議');
            this.sessions.push(session);
        });

        session.on('ended', () => {
            console.log('已離開會議');
            const index = this.sessions.indexOf(session);
            this.sessions.splice(index, 1);
        });

        return session;
    }

    // 邀請其他人
    async invite(targetURI) {
        // 發送 REFER 給會議伺服器
        const referSession = this.sessions[0];
        referSession.refer(targetURI);
    }

    // 離開會議
    leave() {
        this.sessions.forEach(session => {
            session.terminate();
        });
        this.sessions = [];
    }
}

// 使用
const conference = new ConferenceCall('sip:conf123@conference.example.com');
await conference.join();

// 邀請 Bob
conference.invite('sip:bob@example.com');

// 邀請 Charlie
conference.invite('sip:charlie@example.com');

🎵 Early Media(早期媒體)

💡 比喻:響鈴音、語音提示
打電話時聽到的「嘟嘟嘟」或「您撥打的電話正在通話中」

問題:

一般流程:
INVITE → 180 Ringing → 200 OK → ACK → RTP

問題:在 200 OK 之前無法播放媒體(響鈴音、提示音)

解決:使用 183 Session Progress

Alice                               Bob
  │                                  │
  ├─ INVITE ───────────────────────>│
  │                                  │
  │<─ 183 Session Progress ───────────┤
  │  (包含 SDP)                      │
  │                                  │
  │<══════ Early RTP ═══════════════>│  播放響鈴音
  │                                  │
  │<─ 180 Ringing ────────────────────┤
  │                                  │
  │<─ 200 OK ─────────────────────────┤
  │                                  │
  ├─ ACK ───────────────────────────>│
  │<══════ RTP ═══════════════════════>│  正常通話

183 Session Progress:

SIP/2.0 183 Session Progress
Via: SIP/2.0/UDP alice.com:5060;branch=z9hG4bK776
To: Bob <sip:bob@example.com>;tag=314159
From: Alice <sip:alice@example.com>;tag=9fxced76sl
Call-ID: 123@alice.com
CSeq: 1 INVITE
Content-Type: application/sdp
Content-Length: 147

v=0
o=bob 2890844730 2890844730 IN IP4 192.168.1.200
s=-
c=IN IP4 192.168.1.200
t=0 0
m=audio 38060 RTP/AVP 0
a=rtpmap:0 PCMU/8000
a=sendonly  ← 只發送(播放響鈴音)

應用場景:

  • 📞 自訂響鈴音
  • 🎙️ 語音提示(「請撥分機號碼」)
  • 📢 廣告(「本通話由 XX 贊助」)

🎓 常見面試題

Q1:SIP 的三次握手是哪三次?

答案:

INVITE → 200 OK → ACK

就像 TCP 三次握手:
SYN → SYN-ACK → ACK

為什麼需要 ACK?

問題場景:
Alice ─ INVITE ──> Bob
Alice <─ 200 OK ─── Bob

如果 Alice 沒收到 200 OK,會重傳 INVITE
Bob 會重傳 200 OK

但如果 Alice 收到了 200 OK,如何讓 Bob 知道?
→ 發送 ACK 確認

Alice ─ ACK ──> Bob
(Bob 收到 ACK,停止重傳 200 OK)

ACK 的特殊性:

  • ✅ ACK 不會被回應(沒有 200 OK for ACK)
  • ✅ ACK 直接發送給對方(不經過 Proxy)
  • ✅ 使用 Contact header 中的 URI

記憶技巧:「邀答認」(INVITE、200 OK、ACK)


Q2:re-INVITE 用在哪些場景?

答案:

re-INVITE 用於修改現有會話的參數(不是建立新通話)。

常見場景:

1. Call Hold(保留通話):

// 修改 SDP:a=sendonly
session.renegotiate();

2. 新增/移除媒體:

// 原本只有音訊,新增視訊
session.renegotiate({
    mediaConstraints: {
        audio: true,
        video: true  // 新增視訊
    }
});

3. 更換編碼器:

m=audio 49170 RTP/AVP 0 8
原本:PCMU (0), PCMA (8)

re-INVITE:
m=audio 49170 RTP/AVP 99
更換為:Opus (99)

4. 切換網路(IP 變更):

WiFi 切換到 4G,IP 改變
發送 re-INVITE 更新 SDP 中的 IP

5. NAT Keep-Alive:

定期發送 re-INVITE 保持 NAT 通道開啟

re-INVITE 流程:

Alice                Bob
  │<══ RTP ═════════>│  通話中
  │                  │
  ├─ re-INVITE ─────>│  修改參數
  │  (新的 SDP)      │
  │                  │
  │<─ 200 OK ────────┤
  │  (新的 SDP)      │
  │                  │
  ├─ ACK ──────────>│
  │<══ 新的 RTP ════>│  使用新參數

Q3:Forking 是什麼?

答案:

Forking(分叉) 是指一個 INVITE 請求被轉發到多個目標。

💡 比喻:同時撥打多個號碼
打給 Bob,但 Bob 有手機、辦公室電話、家裡電話
全部同時響鈴,誰先接就是誰

Parallel Forking(並行分叉)

Alice          Proxy          Bob's Mobile    Bob's Office
  │               │                 │               │
  ├─ INVITE ─────>│                 │               │
  │               ├─ INVITE ───────>│               │
  │               ├─ INVITE ─────────────────────────>│
  │               │                 │               │
  │               │<─ 180 Ringing ───┤               │
  │<─ 180 Ringing ┤                 │               │
  │               │<─ 180 Ringing ─────────────────────┤
  │               │                 │               │
  │               │<─ 200 OK ────────┤  (手機先接聽)
  │<─ 200 OK ─────┤                 │               │
  │               │                 │               │
  │               ├─ CANCEL ─────────────────────────>│  取消其他
  ├─ ACK ────────────────────────────>│               │
  │<═══════════ RTP ════════════════>│               │

Proxy 配置(Kamailio):

# Parallel Forking
if (is_method("INVITE")) {
    lookup("location");  # 查詢所有註冊位置

    # Bob 註冊了 3 個裝置
    # sip:bob@mobile.com
    # sip:bob@office.com
    # sip:bob@home.com

    t_relay();  # 同時發送 INVITE 到所有裝置
}

Sequential Forking(循序分叉)

先試手機,沒接再試辦公室,最後試家裡
Alice          Proxy          Mobile    Office    Home
  │               │              │         │         │
  ├─ INVITE ─────>│              │         │         │
  │               ├─ INVITE ────>│         │         │
  │               │              │         │         │
  │               │<─ 180 ───────┤         │         │
  │               │              │         │         │
  │               │<─ 486 Busy ──┤  (忙線)  │         │
  │               │              │         │         │
  │               ├─ INVITE ─────────────>│         │
  │               │              │         │         │
  │               │<─ 180 ─────────────────┤         │
  │               │              │         │         │
  │               │<─ 408 Timeout ─────────┤ (無人接聽)
  │               │              │         │         │
  │               ├─ INVITE ─────────────────────────>│
  │               │              │         │         │
  │               │<─ 200 OK ───────────────────────────┤
  │<─ 200 OK ─────┤              │         │         │
  ├─ ACK ─────────────────────────────────────────────>│
  │<═══════════ RTP ═════════════════════════════════>│

Q4:如何處理無人接聽(No Answer)?

答案:

使用計時器 + 480 Temporarily Unavailable

Alice              Proxy              Bob
  │                   │                 │
  ├─ INVITE ─────────>│                 │
  │                   ├─ INVITE ───────>│
  │                   │                 │
  │                   │<─ 180 Ringing ──┤
  │<─ 180 Ringing ────┤                 │
  │                   │                 │
  │                   │  (等待 30 秒)   │
  │                   │                 │
  │                   ├─ CANCEL ───────>│  超時取消
  │                   │<─ 200 OK ───────┤
  │                   │<─ 487 Request Terminated ─┤
  │                   │                 │
  │<─ 480 Temporarily Unavailable ──────┤
  ├─ ACK ────────────>│                 │

Proxy 配置:

# Kamailio
if (is_method("INVITE")) {
    t_set_fr(30000);  # 設定超時為 30 秒

    t_on_failure("NO_ANSWER");

    t_relay();
}

failure_route[NO_ANSWER] {
    if (t_check_status("408")) {  # Request Timeout
        # 轉接到語音信箱
        t_relay("sip:voicemail@example.com");
    }
}

JavaScript 實作:

const session = ua.call('sip:bob@example.com', options);

// 30 秒無人接聽,自動取消
const timer = setTimeout(() => {
    if (session.isInProgress()) {
        session.terminate();  // 發送 CANCEL
        console.log('無人接聽,已取消');
    }
}, 30000);

session.on('accepted', () => {
    clearTimeout(timer);  // 接聽了,取消計時器
});

Q5:SIP 如何防止循環路由?

答案:

使用 Max-Forwards header

💡 比喻:TTL (Time To Live)
每經過一個 Proxy,Max-Forwards 減 1
減到 0 時,丟棄訊息並回應 483 Too Many Hops

循環路由場景:

Proxy A ──> Proxy B ──> Proxy C ──> Proxy A ──> ...
(無限循環)

Max-Forwards 機制:

Alice 發送 INVITE:
Max-Forwards: 70

經過 Proxy A:
Max-Forwards: 69

經過 Proxy B:
Max-Forwards: 68

...

經過 Proxy 70:
Max-Forwards: 0  → 回應 483 Too Many Hops

483 Too Many Hops:

SIP/2.0 483 Too Many Hops
Via: SIP/2.0/UDP proxy70.com:5060;branch=z9hG4bK999
To: Bob <sip:bob@example.com>;tag=314159
From: Alice <sip:alice@example.com>;tag=9fxced76sl
Call-ID: 123@alice.com
CSeq: 1 INVITE
Content-Length: 0

Proxy 實作(檢查 Max-Forwards):

def handle_invite(request):
    max_forwards = int(request.headers.get('Max-Forwards', 70))

    if max_forwards == 0:
        # 已達上限,回應錯誤
        send_response(request, 483, "Too Many Hops")
        return

    # 減 1 並轉發
    request.headers['Max-Forwards'] = str(max_forwards - 1)
    forward_request(request)

📝 總結

SIP 呼叫流程重點:

  • 基本流程:INVITE → 180 Ringing → 200 OK → ACK → RTP → BYE
  • 保留:re-INVITE with a=sendonly
  • 轉接:REFER(Blind/Attended)
  • 會議:MCU/SFU 混音或 Full Mesh
  • Early Media:183 Session Progress

記憶口訣:「基保轉會早」

  • 基(基本流程)
  • 保(保留)
  • 轉(轉接)
  • 會(會議)
  • 早(Early Media)

關鍵差異:

INVITE vs re-INVITE:
- INVITE:建立新會話
- re-INVITE:修改現有會話

Blind vs Attended Transfer:
- Blind:直接轉,不確認
- Attended:先確認,再轉

🔗 延伸閱讀

0%