11-1. SIP 協定基礎

深入理解 SIP 如何建立語音與視訊通話連線

📞 SIP 協定基礎

🎯 什麼是 SIP?

💡 比喻:電話系統的「撥號」協定

打電話分兩階段:
1. 撥號階段:輸入號碼、等待接通(SIP 負責這部分)
2. 通話階段:實際對話(RTP 負責傳輸語音)

SIP 就像電話的「訊號系統」,負責:
- 找到對方在哪裡
- 詢問對方能否接聽
- 建立通話連線
- 掛斷電話

SIP(Session Initiation Protocol) 是一種訊號協定,用於建立、修改和終止多媒體會話(語音、視訊通話)。

為什麼需要 SIP?

WebRTC vs SIP 的差異:

特性WebRTCSIP
定位Web 即時通訊電信級通話系統
訊號協定未定義(自訂)已定義(SIP)
主要應用瀏覽器通話VoIP、視訊會議
互通性需要自訂標準化(與電話系統互通)
典型場景Google Meet、ZoomSkype、電話系統、企業 PBX
WebRTC:瀏覽器內的通話
SIP:可以與傳統電話系統互通的 VoIP

範例:
- 用 Skype 打給手機號碼 → SIP
- 企業內部分機系統 → SIP
- Google Meet 網頁通話 → WebRTC

🏗️ SIP 架構

核心組件

┌─────────────────────────────────────────┐
│         SIP 系統架構                     │
└─────────────────────────────────────────┘

User Agent ←→ SIP Proxy ←→ Registrar
(客戶端)      (代理伺服器)   (註冊伺服器)

1️⃣ User Agent(使用者代理)

💡 比喻:你的電話機
你用它來打電話和接電話

分為兩部分:

  • UAC(User Agent Client):發起呼叫
  • UAS(User Agent Server):接收呼叫
Alice 打給 Bob:
- Alice 的 UAC 發送 INVITE
- Bob 的 UAS 接收 INVITE

範例(使用 JsSIP):

// 建立 SIP User Agent
const socket = new JsSIP.WebSocketInterface('wss://sip.example.com');
const configuration = {
    sockets: [socket],
    uri: 'sip:alice@example.com',
    password: 'password123'
};

const ua = new JsSIP.UA(configuration);
ua.start();

// 發起呼叫(UAC)
const session = ua.call('sip:bob@example.com', {
    mediaConstraints: {
        audio: true,
        video: true
    }
});

// 接收呼叫(UAS)
ua.on('newRTCSession', (event) => {
    const session = event.session;

    if (session.direction === 'incoming') {
        console.log('來電:', session.remote_identity.uri);

        // 接聽
        session.answer({
            mediaConstraints: {
                audio: true,
                video: true
            }
        });
    }
});

2️⃣ SIP Proxy(代理伺服器)

💡 比喻:電話交換機
幫你轉接電話,但不參與通話內容

功能:

  • 路由 SIP 訊息
  • 身份驗證
  • 負載平衡
  • 防火牆穿透
Alice → Proxy → Bob

Alice 不需要知道 Bob 的真實 IP
Proxy 負責找到 Bob 並轉發請求

訊息流:

Alice                 Proxy                 Bob
  │                     │                     │
  ├─ INVITE ───────────>│                     │
  │                     ├─ INVITE ───────────>│
  │                     │<─ 180 Ringing ──────┤
  │<─ 180 Ringing ──────┤                     │
  │                     │<─ 200 OK ───────────┤
  │<─ 200 OK ───────────┤                     │
  ├─ ACK ──────────────────────────────────────>│
  │                     │                     │
  │<══════════ RTP 媒體流(直連)═══════════>│

Python SIP Proxy(使用 SIP Simple):

from sipsimple.application import SIPApplication
from sipsimple.account import AccountManager
from sipsimple.configuration.settings import SIPSimpleSettings

class SimpleSIPProxy:
    def __init__(self):
        self.app = SIPApplication()

    def start(self):
        self.app.start(self)

        # 註冊路由規則
        settings = SIPSimpleSettings()
        settings.sip.transport_list = ['udp', 'tcp', 'tls']

        print("SIP Proxy 啟動於 5060 port")

    def route_request(self, request):
        """路由 SIP 請求"""
        destination = request.RURI.user  # 目標使用者
        # 查詢註冊表,找到目標位置
        target_uri = self.lookup_user(destination)

        if target_uri:
            # 轉發請求
            self.forward_request(request, target_uri)
        else:
            # 使用者不存在
            self.send_response(request, 404, "Not Found")

3️⃣ Registrar(註冊伺服器)

💡 比喻:電話簿
記錄「誰」的電話號碼是「哪個 IP」

功能: 儲存使用者的聯絡資訊(IP 位址、Port)

Alice 開機:
REGISTER sip:example.com
Contact: <sip:alice@192.168.1.100:5060>

Registrar 記錄:
alice@example.com → 192.168.1.100:5060

註冊流程:

Alice                           Registrar
  │                                 │
  ├─ REGISTER ─────────────────────>│
  │  To: alice@example.com          │
  │  Contact: 192.168.1.100:5060    │
  │                                 │
  │<─ 401 Unauthorized ──────────────┤
  │  (請先認證)                      │
  │                                 │
  ├─ REGISTER (含認證) ─────────────>│
  │  Authorization: Digest ...      │
  │                                 │
  │<─ 200 OK ────────────────────────┤
  │  (註冊成功,有效期 3600 秒)      │

JavaScript 註冊:

// 使用 JsSIP 註冊
const ua = new JsSIP.UA({
    uri: 'sip:alice@example.com',
    password: 'password123',
    ws_servers: 'wss://sip.example.com:8089/ws',
    register: true,  // 自動註冊
    register_expires: 3600  // 註冊有效期(秒)
});

ua.on('registered', () => {
    console.log('註冊成功');
});

ua.on('unregistered', () => {
    console.log('取消註冊');
});

ua.on('registrationFailed', (event) => {
    console.error('註冊失敗:', event.cause);
});

ua.start();

4️⃣ Redirect Server(重定向伺服器)

💡 比喻:查號台
告訴你正確的號碼,但不幫你轉接
Alice → Redirect Server

Alice: 我要找 Bob
Redirect: Bob 的新號碼是 sip:bob@newdomain.com(302 Moved)
Alice: 謝謝(重新撥打新號碼)

📨 SIP 訊息格式

SIP 訊息類型

SIP 訊息分為兩類:

  1. Request(請求):客戶端發送
  2. Response(回應):伺服器回應

Request(請求)格式

INVITE sip:bob@example.com SIP/2.0
Via: SIP/2.0/UDP 192.168.1.100:5060;branch=z9hG4bK776asdhds
Max-Forwards: 70
To: Bob <sip:bob@example.com>
From: Alice <sip:alice@example.com>;tag=1928301774
Call-ID: a84b4c76e66710@pc33.example.com
CSeq: 314159 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

欄位說明:

  • Request-LineINVITE sip:bob@example.com SIP/2.0

    • Method:INVITE
    • Request-URI:sip:bob@example.com
    • SIP Version:SIP/2.0
  • Via:訊息經過的路徑

  • To:接收者

  • From:發送者

  • Call-ID:通話唯一識別碼

  • CSeq:命令序號

  • Contact:實際聯絡位址

  • Content-Type:訊息內容格式(通常是 SDP)


Response(回應)格式

SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.1.100:5060;branch=z9hG4bK776asdhds
To: Bob <sip:bob@example.com>;tag=a6c85cf
From: Alice <sip:alice@example.com>;tag=1928301774
Call-ID: a84b4c76e66710@pc33.example.com
CSeq: 314159 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

狀態碼類別:

  • 1xx:臨時回應(100 Trying, 180 Ringing)
  • 2xx:成功(200 OK)
  • 3xx:重定向(302 Moved Temporarily)
  • 4xx:客戶端錯誤(404 Not Found, 486 Busy Here)
  • 5xx:伺服器錯誤(500 Server Internal Error)
  • 6xx:全域失敗(603 Decline)

🔧 SIP 方法(Methods)

核心方法

💡 比喻:電話的操作按鍵
每個按鍵代表不同的功能
方法功能比喻
INVITE建立會話撥號 📞
ACK確認確認接通 ✅
BYE結束會話掛斷 📵
CANCEL取消請求取消撥號 ❌
REGISTER註冊登記號碼 📝
OPTIONS查詢能力詢問功能 ❓
INFO傳送訊息發送 DTMF 訊號
UPDATE更新會話切換喇叭/靜音 🔇

1️⃣ INVITE(建立會話)

// 發起語音通話
const session = ua.call('sip:bob@example.com', {
    mediaConstraints: { audio: true, video: false }
});

session.on('accepted', () => {
    console.log('通話已接通');
});

session.on('ended', () => {
    console.log('通話已結束');
});

session.on('failed', (event) => {
    console.error('通話失敗:', event.cause);
});

2️⃣ ACK(確認)

三次握手建立通話:

Alice                               Bob
  │                                  │
  ├─ INVITE ────────────────────────>│
  │                                  │
  │<─ 200 OK ─────────────────────────┤
  │                                  │
  ├─ ACK ───────────────────────────>│
  │                                  │
  │<═══════ RTP 媒體流 ══════════════>│

ACK 是自動發送的,不需要手動呼叫。


3️⃣ BYE(結束會話)

// 掛斷電話
session.terminate();

// 監聽對方掛斷
session.on('ended', (event) => {
    if (event.originator === 'remote') {
        console.log('對方已掛斷');
    } else {
        console.log('我方已掛斷');
    }
});

4️⃣ CANCEL(取消請求)

撥號中途取消:

Alice                               Bob
  │                                  │
  ├─ INVITE ────────────────────────>│
  │                                  │
  │<─ 180 Ringing ────────────────────┤
  │  (響鈴中)                        │
  ├─ CANCEL ────────────────────────>│
  │  (取消撥號)                      │
  │<─ 200 OK (for CANCEL) ────────────┤
  │                                  │
  │<─ 487 Request Terminated ─────────┤
  │  (INVITE 已終止)                 │
  ├─ ACK ───────────────────────────>│
// 取消撥號(響鈴時)
const session = ua.call('sip:bob@example.com', options);

// 在接通前取消
session.on('progress', () => {
    console.log('響鈴中...');

    // 5 秒後無人接聽,取消
    setTimeout(() => {
        session.terminate();  // 發送 CANCEL
    }, 5000);
});

5️⃣ REGISTER(註冊)

前面已介紹,用於註冊使用者位置。


6️⃣ OPTIONS(查詢能力)

查詢對方支援什麼功能:
- 支援視訊嗎?
- 支援即時訊息嗎?
- 支援哪些編碼器?
// 查詢伺服器能力
ua.request('OPTIONS', 'sip:example.com', {
    eventHandlers: {
        onSuccessResponse: (response) => {
            console.log('支援的方法:', response.getHeader('Allow'));
            console.log('支援的編碼:', response.getHeader('Accept'));
        }
    }
});

📡 SIP 與 SDP

💡 比喻:
SIP = 電話撥號(建立連線)
SDP = 通話參數(用什麼音質、解析度)

SIP 負責訊號,SDP 描述媒體參數。

SDP 範例

v=0
o=alice 2890844526 2890844526 IN IP4 192.168.1.100
s=Video Call
c=IN IP4 192.168.1.100
t=0 0
m=audio 49170 RTP/AVP 0 8
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
m=video 51372 RTP/AVP 99
a=rtpmap:99 H264/90000
a=fmtp:99 profile-level-id=42e01f

解析:

m=audio 49170 RTP/AVP 0 8
- 媒體類型:audio(音訊)
- Port:49170
- 協定:RTP/AVP
- Payload:0 (PCMU), 8 (PCMA)

m=video 51372 RTP/AVP 99
- 媒體類型:video(視訊)
- Port:51372
- Payload:99 (H.264)

在 SIP 中傳遞 SDP:

INVITE sip:bob@example.com SIP/2.0
...
Content-Type: application/sdp
Content-Length: 200

v=0
o=alice ...
m=audio 49170 RTP/AVP 0
m=video 51372 RTP/AVP 99

🎓 常見面試題

Q1:SIP 和 WebRTC 有什麼不同?

答案:

比喻:
SIP = 電信公司的電話系統(標準化、可互通)
WebRTC = 自己做的對講機(靈活、限於網頁)
特性SIPWebRTC
訊號協定✅ 已定義(SIP)❌ 未定義(需自訂)
媒體協定RTP/RTCPRTP/RTCP(相同)
NAT 穿透需要額外配置✅ 內建 ICE/STUN/TURN
互通性✅ 與電話系統互通⚠️ 僅 Web 之間
適用場景VoIP、企業電話瀏覽器通話

可以結合使用:

SIP 用於訊號(建立連線)
WebRTC 用於媒體(音視訊傳輸)

範例:
Alice (SIP Client) ←→ SIP Server ←→ Bob (WebRTC Browser)

這樣可以讓傳統 SIP 設備與 Web 瀏覽器互通!

記憶技巧:

  • SIPStandard Interoperability Protocol(標準互通協定)
  • WebRTCWeb Real-Time Communication(網頁即時通訊)

Q2:SIP 的完整呼叫流程是什麼?

答案:

基本流程(7 步驟):

Alice                   Proxy                   Bob
  │                       │                       │
  ├─ 1. INVITE ─────────>│                       │
  │                       ├─ 2. INVITE ─────────>│
  │                       │                       │
  │                       │<─ 3. 100 Trying ──────┤
  │<─ 100 Trying ─────────┤                       │
  │                       │                       │
  │                       │<─ 4. 180 Ringing ─────┤
  │<─ 180 Ringing ────────┤                       │
  │                       │                       │
  │                       │<─ 5. 200 OK ──────────┤
  │<─ 200 OK ────────────┤                       │
  │                       │                       │
  ├─ 6. ACK ─────────────────────────────────────>│
  │                       │                       │
  │<══════════ 7. RTP 媒體流(直連)═══════════>│
  │                       │                       │
  ├─ 8. BYE ─────────────────────────────────────>│
  │<─ 9. 200 OK ──────────────────────────────────┤

記憶口訣:「邀試響答認,媒掛確」

  1. 邀(INVITE)
  2. 試(100 Trying)
  3. 響(180 Ringing)
  4. 答(200 OK)
  5. 認(ACK)
  6. 媒(RTP 媒體流)
  7. 掛(BYE)
  8. 確(200 OK)

Q3:SIP 如何處理視訊通話?

答案:

SIP 本身只負責訊號,視訊由 RTP 傳輸。

完整堆疊:

┌────────────────────────────────┐
│  應用層:通話控制              │
└────────────────────────────────┘
              ↓
┌────────────────────────────────┐
│  SIP:訊號協定                 │
│  - INVITE, ACK, BYE           │
│  - SDP 交換(媒體參數)        │
└────────────────────────────────┘
              ↓
┌────────────────────────────────┐
│  RTP:媒體傳輸                 │
│  - 音訊:PCMU, Opus            │
│  - 視訊:H.264, VP8            │
└────────────────────────────────┘
              ↓
┌────────────────────────────────┐
│  UDP:傳輸層                   │
└────────────────────────────────┘

視訊通話流程:

// 1. 發起視訊通話
const session = ua.call('sip:bob@example.com', {
    mediaConstraints: {
        audio: true,
        video: true  // 啟用視訊
    },
    rtcOfferConstraints: {
        offerToReceiveAudio: true,
        offerToReceiveVideo: true
    }
});

// 2. SIP 交換 SDP(包含視訊參數)
// INVITE 訊息會包含:
// m=audio 49170 RTP/AVP 0
// m=video 51372 RTP/AVP 99
// a=rtpmap:99 H264/90000

// 3. 建立 RTP 連線傳輸視訊
session.connection.addEventListener('addstream', (event) => {
    const remoteVideo = document.querySelector('#remoteVideo');
    remoteVideo.srcObject = event.stream;
});

// 4. 切換攝影機/螢幕分享
const screenStream = await navigator.mediaDevices.getDisplayMedia({
    video: true
});

const videoTrack = screenStream.getVideoTracks()[0];
const sender = session.connection.getSenders().find(s => s.track.kind === 'video');

sender.replaceTrack(videoTrack);  // 切換為螢幕分享

面試重點:

  • ✅ SIP 負責「建立」通話(訊號)
  • ✅ RTP 負責「傳輸」音視訊(媒體)
  • ✅ SDP 描述媒體參數(編碼、解析度)

Q4:SIP 如何處理 NAT 穿透?

答案:

SIP 的 NAT 穿透比 WebRTC 複雜。

問題:

Alice (內網)            NAT            Internet
192.168.1.100    →   203.0.113.5   →  SIP Server

INVITE 訊息中的 Contact:
  Contact: <sip:alice@192.168.1.100:5060>  ❌ 錯誤!

Server 會回應到 192.168.1.100(內網 IP),無法送達

解決方案:

1. 使用 STUN 查詢公網 IP:

Alice → STUN Server:我的公網 IP 是?
STUN → Alice:203.0.113.5:54321

INVITE 訊息更新:
  Contact: <sip:alice@203.0.113.5:54321>  ✅ 正確

2. 使用 SIP ALG(Application Layer Gateway):

NAT 路由器會自動修改 SIP 訊息中的 IP

原始訊息:
  Contact: <sip:alice@192.168.1.100:5060>

NAT 修改後:
  Contact: <sip:alice@203.0.113.5:54321>

3. 使用 TURN 中繼(媒體層):

SIP 訊號:Alice ←→ SIP Server ←→ Bob
RTP 媒體:Alice ←→ TURN Server ←→ Bob

即使 NAT 穿透失敗,仍可透過 TURN 傳輸

4. 使用 ICE(與 WebRTC 相同):

Modern SIP Clients 支援 ICE
自動嘗試:Host → STUN → TURN

Q5:SIP 如何保證訊息可靠性?

答案:

SIP 支援 UDP 和 TCP:

UDP(預設):

  • ✅ 低延遲
  • ❌ 不可靠(可能遺失)
  • 💡 使用「重傳機制」補救

TCP:

  • ✅ 可靠傳輸
  • ❌ 延遲較高
  • 💡 適合訊息較大時

UDP 重傳機制:

Alice 發送 INVITE
│
├─ 0 秒:發送 INVITE
├─ 0.5 秒:未收到回應,重傳
├─ 1.5 秒:未收到回應,重傳
├─ 3.5 秒:未收到回應,重傳
├─ ...(指數退避)
└─ 32 秒:超時,放棄

收到回應後立即停止重傳

Python 實作:

import socket
import time

class SIPClient:
    def __init__(self):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.sock.settimeout(0.5)  # 500ms 超時

    def send_invite(self, destination, message):
        retries = 0
        max_retries = 7
        timeout = 0.5  # 初始超時

        while retries < max_retries:
            try:
                # 發送訊息
                self.sock.sendto(message.encode(), destination)
                print(f"[重傳 {retries}] 發送 INVITE")

                # 等待回應
                response, addr = self.sock.recvfrom(4096)
                print(f"收到回應:{response.decode()}")
                return response

            except socket.timeout:
                retries += 1
                timeout *= 2  # 指數退避
                self.sock.settimeout(timeout)

        print("訊息發送失敗")
        return None

TLS(加密):

// 使用 TLS 加密 SIP 訊號
const ua = new JsSIP.UA({
    uri: 'sip:alice@example.com',
    password: 'password',
    ws_servers: 'wss://sip.example.com:8089/ws',  // WSS = WebSocket Secure
    use_preloaded_route: false
});

📝 總結

SIP 是電信級的通話訊號協定:

  • 標準化:與傳統電話系統互通 ☎️
  • 靈活:支援語音、視訊、訊息 📞📹💬
  • 分離:訊號(SIP)與媒體(RTP)分離 🔀
  • 成熟:20+ 年歷史,廣泛應用 🏢

記憶口訣:「標(標準)、靈(靈活)、分(分離)、熟(成熟)」

SIP vs WebRTC:

SIP:企業電話系統、VoIP 運營商
WebRTC:網頁即時通訊、線上會議
兩者可結合:SIP 訊號 + WebRTC 媒體

典型應用:

  • 📞 企業 PBX(分機系統)
  • 🌐 VoIP 服務(Skype、Discord)
  • 📱 手機 VoIP App
  • 🏢 視訊會議系統(Cisco、Polycom)

🔗 延伸閱讀

0%