# SIP 是什麼?
SIP(Session Initiation Protocol)是一種基於 TCP/IP 的應用層網路協議,專門用於建立、修改與終止 IP 網路上的多媒體通訊會話。這些通訊會話可以涵蓋文字、語音、影像和視訊等多種內容。
由於 SIP 是架構在應用層之上,開發 SIP 應用程式時,可以不受底層傳輸協定或網路架構的限制。這帶來以下幾個優點:
- 容易開發:SIP 訊息格式簡潔,基於類似 HTTP 的文字格式。
- 高度擴充性:能支援多種傳輸協定,例如 TCP、UDP 或 TLS 等。
- 靈活整合:SIP 不僅可在傳統網路(如 TCP/UDP)運作,還能架構在各種不同類型的傳輸網路上,方便系統整合與跨平台部署。
# PJSUA2 介紹 & 使用說明
PJSUA2 是一個基於 PJSIP 的高階 C++ API,提供更簡單、易用的介面來實現 SIP 功能,並支援多種平台(包括 Python 綁定)。
透過 PJSUA2,開發者可以在 Python 中快速實現 SIP 功能,例如:
- 發起與接收電話
- 管理多媒體會話
- 處理事件回調
PJSUA2 封裝了底層的實作細節,使開發過程更直觀,降低學習門檻,同時保留了 PJSIP 強大的功能與靈活性。
## 使用方法
### 0. 匯入pjsua2 package
```
import pjsua2 as pj
```
### 1. 建立Endpoint
Endpoint是SIP當中的核心,它負責管理整個 SIP 會話和傳輸層的資源,並提供 API 讓你可以進行操作,例如建立傳輸、註冊帳號、發起通話等。
備註:需要做一個迴圈讓Endpoint保持開啟,否則初始化&啟動後就會直接結束。
```
# 創建 Endpoint 物件
lib = pj.Endpoint()
lib.libCreate()
# 初始化 Endpoint
ep_cfg = pj.EpConfig()
ep_cfg.logConfig.level = 4 # 4: 顯示所有日誌 (0 ~ 5)
lib.libInit(ep_cfg) # 初始化 Endpoint
# 創建傳輸層
transport_cfg = pj.TransportConfig()
transport_cfg.port = 5060 # SIP 端口
lib.transportCreate(pj.PJSIP_TRANSPORT_UDP, transport_cfg)
# 啟動
lib.libStart()
# 進入事件循環,保持程序運行
try:
print("SIP transport created, waiting for events...")
while True:
# 不斷運行,保持程序活躍
pass
except KeyboardInterrupt:
# 用戶按下 Ctrl+C 時結束循環
print("Exiting...")
lib.libDestroy()
```
- Endpoint 就像一個 SIP 應用的總管理者,所有與 SIP 協議相關的動作(如建立通訊通道、註冊帳號、通話管理)都需要通過 Endpoint 進行初始化和管理。
- 作為 SIP 通信的核心元件,管理網絡層的連接、傳輸協議以及會話狀態,確保客戶端之間的訊息和呼叫能夠正確地發送和接收。
- 同時處理不同網域和使用者之間的註冊、身份驗證和消息傳遞等過程。
**Endpoint 是用來處理 SIP 通信的橋樑,負責與SIP伺服器和其他 SIP 客戶端進行通信。**
### 2. 註冊帳號
為不同的使用者,在不同的網域下建立帳號。這些帳號會被註冊到 SIP 伺服器,讓使用者能夠利用事先建立好的 Endpoint 來進行訊息交換和通信。
通過這些帳號,使用者可以在同一個系統中發送和接收呼叫、訊息以及其他 SIP 操作。
```
# account 設定
user_name = "1001"
domain_name = "10.0.0.100"
password = "1234"
acc_config = pj.AccountConfig() # 設置帳號配置
acc_config.idUri = f"sip:{user_name}@{domain_name}" # 使用者帳號
acc_config.regConfig.registrarUri = f"sip:{domain_name}" # 註冊服務器 URI
acc_config.sipConfig.authCreds.append(pj.AuthCredInfo("digest", "*", user_name, 0, password))
account = pj.Account()
account.create(acc_config)
print("帳號設定完成!")
```
domain 的主要作用是用來指定你的 SIP 伺服器的位置,並讓你的帳號可以註冊到該伺服器。
換句話說,你的 SIP Endpoint(即程式)會使用一個端口來進行通訊,而帳號則是在特定的 domain 上註冊。Domain 只是用來幫助區分不同的帳號和指向註冊伺服器,它並不直接影響 Endpoint 的端口設置。
### 3. 建立SIP通話
```
# 通話類別
class MyCall(pj.Call):
def onCallState(self, prm):
ci = self.getInfo()
print(f"呼叫狀態: {ci.stateText}")
# 發起 SIP 呼叫
call = MyCall(account)
call.makeCall("sip:destination@sipserver.com")
print("正在呼叫對方...")
```
### 4. 接收來電
```
class MyAccount(pj.Account):
def onIncomingCall(self, call_prm):
print("接收到來電!")
call = MyCall(self, call_prm.callId)
call.answer(200) # 回應 200 OK,接聽來電
print("來電已接聽")
```
### 5. 完整範例
直接跑server.py & client.py,即可傳送訊息透過client -> server
<details><summary>server.py</summary>
import pjsua2 as pj
# 1. 創建 Endpoint 並初始化
lib = pj.Endpoint()
lib.libCreate()
# 設置日誌等級
ep_cfg = pj.EpConfig()
ep_cfg.logConfig.level = 4
lib.libInit(ep_cfg)
# 2. 創建 UDP Transport 監聽端口 5060
transport_cfg = pj.TransportConfig()
transport_cfg.port = 5060
lib.transportCreate(pj.PJSIP_TRANSPORT_UDP, transport_cfg)
# 3. 啟動 Endpoint
lib.libStart()
print("SIP 伺服器已啟動,正在監聽 5060 端口...")
# 4. 自定義帳號以處理來電和訊息
class MyAccount(pj.Account):
def onIncomingCall(self, call):
print("收到來電!")
call_prm = pj.CallOpParam(True) # 自動接聽
call.answer(call_prm)
def onIncomingSubscribe(self, prm):
print("收到訂閱訊息!")
def onInstantMessage(self, prm):
print(f"收到訊息: {prm.msgBody}")
# 5. 設定帳號
acc_cfg = pj.AccountConfig()
acc_cfg.idUri = "sip:server@10.0.0.122" # server SIP 帳號
account_1 = MyAccount()
account_1.create(acc_cfg)
print("SIP 伺服器已準備好,等待訊息或呼叫...")
# 6. 保持程式運行
try:
while True:
pass
except KeyboardInterrupt:
print("伺服器關閉...")
finally:
lib.libDestroy()
</details>
<details><summary> client.py </summary>
import pjsua2 as pj
import time
# 創建 Endpoint 並初始化
lib = pj.Endpoint()
lib.libCreate()
# 設置日誌等級
ep_cfg = pj.EpConfig()
ep_cfg.logConfig.level = 4
lib.libInit(ep_cfg)
# 創建 UDP Transport 監聽端口 5061
transport_cfg = pj.TransportConfig()
transport_cfg.port = 5061
lib.transportCreate(pj.PJSIP_TRANSPORT_UDP, transport_cfg)
# 啟動 Endpoint
lib.libStart()
print("SIP 客戶端已啟動,正在註冊...")
# 定義帳號並註冊到 Server端 (這邊為客戶端)
class MyAccount(pj.Account):
def onIncomingCall(self, call):
print("收到來電!")
call_prm = pj.CallOpParam(True) # 自動接聽
call.answer(call_prm)
def onInstantMessage(self, prm):
print(f"收到訊息: {prm.msgBody}")
# 設定帳號,這裡的idUri和registrarUri要和server的domain匹配
# 也就是說我們透過domain建立一個連結帳戶,當作之後溝通使用
acc_cfg = pj.AccountConfig()
acc_cfg.idUri = "sip:cilent_2@10.0.0.122" # Server 端 SIP 帳號
acc_cfg.regConfig.registrarUri = "sip:10.0.0.122" # 伺服器地址
account = MyAccount()
account.create(acc_cfg)
# 註冊後等待幾秒鐘,讓註冊完成
time.sleep(2)
# 傳送訊息到 server1
buddy_config = pj.BuddyConfig()
buddy_config.uri = "sip:cilent_1@10.0.0.122" # 對方 SIP 帳號
buddy = pj.Buddy()
buddy.create(account, buddy_config)
# 發送訊息
im_param = pj.SendInstantMessageParam()
im_param.content = "你好,這是來自 client2 的訊息!"
if buddy.isValid():
buddy.sendInstantMessage(im_param)
print("SIP 訊息已發送!")
else:
print("Buddy 創建失敗,無法發送訊息。")
# 保持程序運行
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("客戶端關閉...")
finally:
lib.libDestroy()
</details>
# 總結
SIP(Session Initiation Protocol)是一個網路通訊協議,用於建立、管理和終止網路設備之間的對話。我們可以把 SIP 想像成一套 通訊架構,而 Endpoint 就是這個架構中的「溝通橋樑」,負責處理 SIP 訊息的傳遞與管理。
- Endpoint 的角色
- 每個設備(或程式)都必須先建立一個 Endpoint,它是 SIP 通訊的核心起點,負責和其他設備或伺服器進行通訊。
Endpoint 就像是一個中轉站,通過這個中轉站,你可以註冊帳號、接收來電、發送請求等。
- 註冊帳號
- 當建立了 Endpoint 後,Client 需要透過 向伺服器(SIP 伺服器)註冊(建立一個帳號),讓伺服器知道 Client 的存在與位置。
- 帳號註冊後,Client 就能夠透過伺服器和其他已經向伺服器註冊的 Client 進行通訊。
- 通訊方式
- SIP 支援多種傳輸協定,如 UDP、TCP 和 TLS 等,這些方式決定了訊息的傳輸路徑。
- 每個設備或進程都需要獨立的 Endpoint,確保各自能正確建立橋樑,進而完成通訊任務。
## 白話文懶人包
- **SIP 協議** 就像是一套郵政系統的標準。
- **Endpoint** 就像是一個郵局,負責接收和發送信件(SIP 訊息)。
- **帳號註冊** 則是將自己的地址(Client 的位置資訊)通知給伺服器,這樣其他人才能找到你並和你通訊。
- 當所有郵局(Endpoints)和地址(帳號)都設置完成後,設備之間就能透過網路傳送訊息或建立語音、視訊等會話。