11-1. SIP 協定基礎
深入理解 SIP 如何建立語音與視訊通話連線
📞 SIP 協定基礎
🎯 什麼是 SIP?
💡 比喻:電話系統的「撥號」協定
打電話分兩階段:
1. 撥號階段:輸入號碼、等待接通(SIP 負責這部分)
2. 通話階段:實際對話(RTP 負責傳輸語音)
SIP 就像電話的「訊號系統」,負責:
- 找到對方在哪裡
- 詢問對方能否接聽
- 建立通話連線
- 掛斷電話SIP(Session Initiation Protocol) 是一種訊號協定,用於建立、修改和終止多媒體會話(語音、視訊通話)。
為什麼需要 SIP?
WebRTC vs SIP 的差異:
| 特性 | WebRTC | SIP |
|---|---|---|
| 定位 | Web 即時通訊 | 電信級通話系統 |
| 訊號協定 | 未定義(自訂) | 已定義(SIP) |
| 主要應用 | 瀏覽器通話 | VoIP、視訊會議 |
| 互通性 | 需要自訂 | 標準化(與電話系統互通) |
| 典型場景 | Google Meet、Zoom | Skype、電話系統、企業 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 訊息分為兩類:
- Request(請求):客戶端發送
- 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-Line:
INVITE 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 = 自己做的對講機(靈活、限於網頁)| 特性 | SIP | WebRTC |
|---|---|---|
| 訊號協定 | ✅ 已定義(SIP) | ❌ 未定義(需自訂) |
| 媒體協定 | RTP/RTCP | RTP/RTCP(相同) |
| NAT 穿透 | 需要額外配置 | ✅ 內建 ICE/STUN/TURN |
| 互通性 | ✅ 與電話系統互通 | ⚠️ 僅 Web 之間 |
| 適用場景 | VoIP、企業電話 | 瀏覽器通話 |
可以結合使用:
SIP 用於訊號(建立連線)
WebRTC 用於媒體(音視訊傳輸)
範例:
Alice (SIP Client) ←→ SIP Server ←→ Bob (WebRTC Browser)
這樣可以讓傳統 SIP 設備與 Web 瀏覽器互通!記憶技巧:
- SIP:Standard Interoperability Protocol(標準互通協定)
- WebRTC:Web 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 ──────────────────────────────────┤記憶口訣:「邀試響答認,媒掛確」
- 邀(INVITE)
- 試(100 Trying)
- 響(180 Ringing)
- 答(200 OK)
- 認(ACK)
- 媒(RTP 媒體流)
- 掛(BYE)
- 確(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 → TURNQ5: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 NoneTLS(加密):
// 使用 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)
🔗 延伸閱讀
- 上一篇:05-4. WebRTC 基礎
- 下一篇:06-2. SIP 呼叫流程
- RFC 3261:https://tools.ietf.org/html/rfc3261