# 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)和地址(帳號)都設置完成後,設備之間就能透過網路傳送訊息或建立語音、視訊等會話。