兩年前寫的筆記,可能有些鏈接和方式已經不對了,自己評估!
名詞解釋
- realm: 用於描述服務器或服務器中的上下文的字符串。realm告訴客戶端使用哪個用戶名和密碼組合來認證請求用於描述服務器或服務器內的上下文的字符串。
- allocation: 客戶端向服務端申請分配授予中繼傳輸地址,以及相關狀態,例如權限和到期定時器。它要求不能重複allocate申請,沒有釋放請求協議,而在在一段時間內自動失效(在turnserver中配置)。而如果希望一直使用,則需要定時使用refresh請求保持allocate的資源。在成功的allocate之後,發送數據之前,需要有一個createPermission的請求,這個請求目的是對通信的兩端進行授權,否則中繼不會轉發收到的包到對端,而是丟棄。這個授權與allocate類似,也沒有釋放請求協議,並且會在一段時間內自動失效。如果希望一直使用,則需要定時再次使用相同的請求參數發送createPermission請求。
- peer: 被轉發的對象,turn客戶端發送數據給turn服務,服務再把數據轉發到peers,peers回覆服務,服務把轉發給turn客戶端。peer就是被轉發的對象。
- Transport Address: 一個ip地址和一個port的組合
- Server-Reflexive Transport Address: NAT的“公共端”上的傳輸地址。該地址由NAT分配以對應於特定主機傳輸地址。
- Long-Term Credential: 簡單來說就是,使用hmac對realm、key、username的組合進行加密,並取base64的值;詳情請看rfc5389#section-15.4
- candidate:candidate是一個ip端口對(ip地址和端口號),在ICE中它有3類組合{Local Address:子網傳輸地址,Server Reflexive Address:NAT上傳輸地址,Relayed Address:TURN上轉發地址(存在TURN的情況纔有)}
概要
用一張圖片來說明客戶端A向客戶端B發起通話這個過程中,信令服務和STUN服務承擔怎樣的角色
-
房間服務
房間服務用於創建和管理通話會話的狀態。Google搭建了一個可用的房間服務appr.tc,而且Google也給出這個房間服務的源碼,我們可以依據它的代碼去搭建我們自己的房間服務用於本地測試以及項目使用。 -
信令服務
信令服務主要工作是會話控制,網絡和媒體信息交換。具體閱讀WebRTC的一篇官方文章Getting Started with WebRTC,找到Signaling: session control, network and media information
。 -
會話控制消息:初始化或者關閉通信並報告錯誤
-
網絡配置:獲取NAT外的ip和port
-
媒體能力:獲取當前終端和其他終端{瀏覽器、Android、IOS}支持的編碼解碼器{格式、能力}、採集分辨率、採樣率、位寬等信息。
-
ICE服務
ICE服務是對STUN和TURN服務的整合,用於處理peers間的打洞和打洞不通的情況下的轉發。
房間服務、信令服務、ICE(STUN、TURN)服務、Web服務的安裝與配置
Google推薦的服務安裝平臺是ubuntu,所以我們需要準備一個ubuntu版本爲14.04及以上的系統。
房間服務 安裝與配置
房間服務是負責房間的創建於管理,也負責整合其他的服務。
這裏採用webrtc提供的apprtc項目去架設我們自己的房間服務,此項目依賴Google_App_Engine_SDK_for_Python和Grunt
安裝
依據官方的說明能很快就能裝好房間服務
- 安裝依賴
依賴nodejs、npm、pip這三個執行程序
~/webrtc_server$ sudo apt install nodejs nodejs-legacy npm pip
- 下載代碼
~/webrtc_server$ git clone https://github.com/webrtc/apprtc.git
- 編譯
此服務是基於js編寫的,所以需要配置一下js環境;更新js包管理工具npm
,安裝js構建工具grunt
;-g
安裝到全局目錄下,默認全局目錄爲/usr/local/lib/node_modules
安裝依賴工具
~/webrtc_server/apprtc$ sudo npm install -g npm grunt-cli
安裝依賴包;安裝在當前目錄的node_modules
目錄
~/webrtc_server/apprtc$ npm install
編譯apprtc
;生成的文件在再當前目錄下的out
目錄
~/webrtc_server/apprtc$ grunt build
下載運行apprtc工具-GAE
此房間服務需要依賴Google的應用引擎(Google APP Engine)來執行;用瀏覽器打開GAE倉庫,把網頁拉到最後,找到最新的版本號,然後把剛剛的參考地址+版本號去下載;例如google_appengine_1.9.49.zip
~/webrtc_server/apprtc$ wget https://storage.googleapis.com/appengine-sdks/featured/google_appengine_1.9.49.zip
~/webrtc_server/apprtc$ unzip google_appengine_1.9.49.zip
- 配置
每一個peer都會連接房間服務,房間服務需要提供信令服務、打洞/轉發服務的訪問地址,所以此配置需要依據其他兩個服務的配置而定。
constants.py
修改如下:
-TURN_BASE_URL = 'https://computeengineondemand.appspot.com'
-TURN_URL_TEMPLATE = '%s/turn?username=%s&key=%s'
-CEOD_KEY = '4080218913'
+TURN_BASE_URL = 'http://192.168.201.64:2016' #Web服務地址
+TURN_URL_TEMPLATE = '%s/turn/%s/%s' # web服務的地址,rest樣式
+CEOD_KEY = 'boyaa_media' # key,這個需要與coturn的配置相同
WSS_INSTANCES = [{
- WSS_INSTANCE_HOST_KEY: 'apprtc-ws.webrtc.org:443',
+ WSS_INSTANCE_HOST_KEY: '192.168.201.64:8089', #信令服務器
WSS_INSTANCE_NAME_KEY: 'wsserver-std',
WSS_INSTANCE_ZONE_KEY: 'us-central1-a'
}, {
- WSS_INSTANCE_HOST_KEY: 'apprtc-ws-2.webrtc.org:443',
+ WSS_INSTANCE_HOST_KEY: '192.168.201.64:8089', #信令服務器
WSS_INSTANCE_NAME_KEY: 'wsserver-std-2',
WSS_INSTANCE_ZONE_KEY: 'us-central1-f'
}]
apprtc.py
修改如下:
- wss_url = 'wss://' + wss_host_port_pair + '/ws'
- wss_post_url = 'https://' + wss_host_port_pair
+ wss_url = 'ws://' + wss_host_port_pair + '/ws'
+ wss_post_url = 'http://' + wss_host_port_pair
- 啓動房間服務
配置GAE環境
~/webrtc_server/apprtc$ export PAHT=$PATH:${PWD}/google_appengine
啓動服務
選項--host
房間服務監聽地址,選項--port
房間服務監聽端口;選項--admin_host
後臺管理監聽地址,選項--admin_port
後臺管理監聽端口;選項--log_level
打印級別{debug,info,warning,critical,error};選項--skip_sdk_update_check
運行dev_appserver.py命令時不檢查更新;
~/webrtc_server/apprtc$ dev_appserver.py --host=0.0.0.0 --admin_host=0.0.0.0 --storage_path=/home/stone/mywork/webrtc_server/log --logs_path=apprtc.log out/app_engine/ --log_level debug
信令服務
房間服務apprtc
裏自帶了一個信令服務[Collider](https://github.com/webrtc/apprtc/blob/master/src/collider)
,是一個Go語言編寫的基於WebSocket實現的一個信令服務器。
- 安裝依賴
~/webrtc_server$ sudo apt install go
- 創建collider安裝目錄
我會把信令服務當作一個單獨的服務處理,所以會爲它創建一個單獨的目,並命名爲collider
;由於go的語言的安裝會從環境變量GOPATH
尋找可安裝的目錄,所以需要把創建的目錄collider
加入到環境變量GOPATH
中。
~/webrtc_server$ mkdir -p collider/src && export GOPATH=${PWD}/collider
- “下載”代碼
代碼已經存放在房間服務裏面,只需要建立軟連接或者進行一次拷貝,建議是通過軟連接的方式(源目錄需要絕對路徑),這樣下次升級房間服務的時候可以直接升級信令服務,只需要重新編譯就可以了。
~/webrtc_server/apprtc$ ln -s ${PWD}/src/collider/collider $GOPATH/src
~/webrtc_server/apprtc$ ln -s ${PWD}/src/collider/collidermain $GOPATH/src
~/webrtc_server/apprtc$ ln -s ${PWD}/src/collider/collidertest $GOPATH/src
- 下載相關依賴與編譯
選項get
下載指定包以及相關依賴資源;選項install
編譯指定包,編譯後的文件存放在$GOPATH/bin
目錄下;如果get失敗,請檢查下環境變量GOPATH
。
~/webrtc_server/apprtc$ go get collidermain
~/webrtc_server/apprtc$ go install collidermain
- 修改配置
需要與房間服務關聯起來,這樣信令服務才知道要服務那個房間服務;需要修改的地方是:監聽端口、房間服務地址、tls模式;從安全角度考慮,後期我們上線以後應該還是需要支持tls模式的,目前測試階段暫時不做支持,因爲需要一個認證的證書。每次修改配置文件都需要重新執行一次安裝的動作go install collidermain
修改如下:
-var tls = flag.Bool("tls", true, "whether TLS is used")
-var port = flag.Int("port", 443, "The TCP port that the server listens on")
-var roomSrv = flag.String("room-server", "https://appr.tc", "The origin of the room server")
+var tls = flag.Bool("tls", false, "whether TLS is used")
+var port = flag.Int("port", 8089, "The TCP port that the server listens on")
+var roomSrv = flag.String("room-server", "http://192.168.201.64:8080", "The origin of the room server")
- 啓動信令服務
collider信令服務支持命令行配置和文件形式配置,命令行配置會覆蓋文件配置;配置文件是GOPATH/src/collidermain/main.go
;命令行參數:-port
指定監聽端口,-tls
設置是否採用tls模式,-room-server
設定房間服務地址。
$ $GOPATH/bin/collidermain
turn服務
Google實現的
- 安裝依賴
coturn的“瞬時認證”需要依賴數據庫,目前我採用最簡單的sqlite3,如果我們的用戶量較大可以考慮採用其他數據庫;它還依賴openssl做認證、加解密,依賴libevent2做監聽;
$ sudo apt install openssl libevent-core-2.0-5 libevent-dev sqlite sqlite3
- 下載
它提供了兩種安裝方式,源碼安裝和deb包安裝,由於我需要簡單閱讀源碼,所以選擇源碼安裝方式。
~/webrtc_server$ git clone https://github.com/coturn/coturn.git
- 編譯
~/webrtc_server/coturn$ ./configure && make -j && sudo make install
-
默認配置
配置文件:/usr/local/etc/turnserver.conf
認證證書: 私鑰/usr/local/etc/turn_server_pkey.pem
,公鑰/usr/local/etc/turn_server_cert.pem
Log文件:/var/tmp/turn.log
數據庫文件:/usr/local/var/db/turndb
-
配置
初始化數據庫,我們採用sqlite3,利用coturn的配置創建數據庫文件並創建相應的表
~/webrtc_server/coturn$ sudo rm -f /usr/local/var/db/turndb
~/webrtc_server/coturn$ cat turndb/schema.sql | sudo sqlite3 /usr/local/var/db/turndb
創建證書,並拷貝到默認目錄,此證書用到的密碼應該與配置文件turnserver.conf
中static-auth-secret
的一樣
~/webrtc_server/coturn$ openssl req -new -x509 -newkey rsa:4096 -days 3650 -keyout privkey.pem -out server.pem
~/webrtc_server/coturn$ openssl rsa -in privkey.pem -out privkey.pem
~/webrtc_server/coturn$ sudo mv privkey.pem /usr/local/etc/turn_server_pkey.pem
~/webrtc_server/coturn$ sudo mv server.pem /usr/local/etc/turn_server_cert.pem
配置文件,由於項目需求,我們暫時不考慮stun,所以會去掉stun的監聽以減輕服務器監聽負擔(佔用的資源感覺可以忽略不計),採用REST API方式認證;創建一個空配置文件\/usr\/local\/etc\/turnserver.conf
,配置文件內容如下:
listening-device=ens32 #監聽設備名字,通過ifconfig查看
listening-port=3478 #stun/turn共用的監聽端口,根據看到的文檔沒有發現分離方式
listening-ip=192.168.201.64 #監聽的ip,如果不設置會默認監聽ipv4/v6的本地地址以及host地址,我只需要監聽ipv4的host地址,所以需要指定它;可以存在多個監聽地址,寫多個listening-ip即可
relay-device=ens32 #轉發設備名字
relay-ip=192.168.201.64 #轉發地址,我也僅僅需要ipv4的host地址,所以手動指定,也可以多個
verbose #開啓普通打印
fingerprint #取設備server的fingerprint給到client
use-auth-secret #使用瞬時認證
static-auth-secret=boyaa_media #瞬時認證的key
realm=boyaa.com #域名,一定需要設置的
no-dtls #不支持dtls模式
no-stdout-log #不在終端輸出日誌
log-file=/var/log/turnserver.log #指定log存放位置及名字類型,最後的文件名字會是turnserver_日期.log
no-stun #不支持stun,我們只需要轉發
no-loopback-peers #不支持perr使用loopback地址(127.0.0.1 和 ::1)
no-multicast-peers #不支持使用廣播地址(224.0.0.0及以上地址)
mobility #移動ICE規範支持,我也不懂啥意思
cli-ip=192.168.201.64 #command-line-interface地址,也就是telnet登陸地址,一般調試用,我需要用它看turnserver的狀態
cli-password=coturn #telnet登陸密碼
- 啓動coturn服務
服務的配置有兩種形式{配置文件、命令行配置},可以同時使用兩種形式的配置,命令行配置會覆蓋文件配置;-o
選項以守護進程方式啓動;-c
指定配置文件;-n
僅僅採用命令行方式配置;-v
普通模式的Log輸出;-V
很囉嗦的打印,官方都不建議打開;-S
僅僅使用stun服務。
$ sudo turnserver
- 查看監聽端口
netstat選項說明:-l
僅顯示監聽的socket;-n
顯示數字地址和端口(192.168.1.102:3487這類地址);-p
顯示socket所屬的pid和進程名;-t
顯示tcp協議的socket;-u
顯示udp協議的socket。
這些ip和端口REST-API使用的。
$ sudo netstat -lnptu | grep turnserver | uniq | grep tcp
- 配置REST API
我們需要一個Web服務用於給webrtc提供“瞬時密碼”,依據coturn REST API規範去編寫這個Web服務,提供一個GET
方法;這個Web服務還需要向turn服務寫入這個“瞬時密碼”。
客戶端和服務端的驗證過程如下圖:
python實現如下:
import web
import json
import hmac
import sqlite3
from hashlib import sha1
import time, datetime
urls = ('/turn/([^/]+)/[^/]+','turn')
class turn:
def GET(self,username):
ttl = 86400
end_time = int(time.time()) + ttl
turn_username = str(end_time)+":"+username
key = 'boyaa_media'
hashed = hmac.new(key, turn_username, sha1)
credential = hashed.digest().encode("base64").rstrip('\n')
data = {
'ttl': ttl,
'iceServers': [
{
'urls': [
"turn:192.168.201.64:3478?transport=udp",
"turn:192.168.201.64:3478?transport=tcp",
"turn:192.168.201.64:5349?transport=tcp",
"turn:192.168.201.64:5766?transport=tcp",
],
'username': turn_username,
'credential': credential
},
],
}
conn = sqlite3.connect('/usr/local/var/db/turndb')
conn.execute("insert into turn_secret (realm,value) values (?,?)",(realm,credential));
conn.commit()
conn.close()
return json.dumps(data)
if __name__=="__main__":
app = web.application(urls, globals())
app.run()
- 啓動Web服務
在後面指定監聽端口號,我使用2016,由於需要訪問到需要root權限的數據庫,所以使用sudo執行
$ sudo python webrtc_restapi_server.py 2016
- 配置文件其他選項說明
選項 | 說明 |
---|---|
listening-device | 監聽的網絡設備名字,通過命令ifconfig 查看 |
listening-port | TURN的監聽端口 |
tls-listening-port | TURN TLS監聽端口 |
listening-ip | 轉發服務器監聽ip,可以有多個(設置多個listening-ip即可),同時支持IPv4 IPv6,默認使用本ip |
aux-server | 輔助服務,用於stun/turn |
relay-device | 轉發服務器的設備名字 |
relay-ip | 轉發ip,默認採用本地ip地址作爲轉發地址,端口號爲服務器自動分配 |
external-ip | turn server共有地址和私有地址的隱射,書寫形式: external-ip=60.70.80.91 或者 external-ip=60.70.80.91/172.17.19.101 |
relay-threads | 單個連接的最大併發數,如果不設置這個字段默認爲當前設備的CPU個數 |
min-port | 最小分配端口號,轉發服務器申請的最小端口號 |
max-port | 最大分配端口號,轉發服務器申請的最大端口號 |
verbose | 詳細日誌,默認關閉,調試的時候或許可以用到 |
Verbose | 這個是verbose的擴展,會輸出更加詳細的日誌,官方都推薦打開 |
fingerprint | 指紋,默認關閉,TODO |
lt-cred-mech | long-term認證,可以通過turnadmin -k -u user -p pass -r realm 獲取key |
no-auth | 和lt-cred-mech的作用是相反的,允許匿名訪問turn server,如果沒有定義任何用戶並且沒有定義定義此選擇,那麼默認是開啓的,如果存在一個用戶或者開啓lt-cred-mech,那麼默認是關閉的 |
use-auth-secret | 靜態認證密碼,這是一種較安全的做法,客戶端通過http向服務器請求一組加密後的密碼,客戶端返回一個lt認證過的密碼,用戶可以使用這組密碼與turn server通信。具體查看TURN REST API |
static-auth-secret | 給TURN REST API 使用的靜態密碼,否則動態生成一個祕密 |
server-name | 認證服務器 |
oauth | 開啓認證 |
user | 設置用戶以及密碼,這個和use-auth-secret是對立存在的。客戶端可以直接通過這個用戶名和密碼來訪問turn server,使用形式:user=username1:key1 user=username1:password1 ,存在多個用戶就使用使用多次;key通過turnadmin生成 |
userdb | SQLite數據庫文件的位置,使用形式:userdb=/var/db/turndb |
psql-userdb | PostgreSQL數據庫的連接配置,使用形式:psql-userdb="host=<host> dbname=<database-name> user=<database-user> password=<database-user-password> connect_timeout=30" |
mysql-userdb | MySQL數據庫的連接配置,使用形式:mysql-userdb="host=<host> dbname=<database-name> user=<database-user> password=<database-user-password> port=<port> connect_timeout=<seconds> read_timeout=<seconds>" |
mongo-userdb | MongoDB數據庫的連接配置,使用形式:mongo-userdb="mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]" |
redis-statsdb | Redis數據庫的連接配置,使用形式:redis-statsdb="ip=<ip-address> dbname=<database-number> password=<database-user-password> port=<port> connect_timeout=<seconds>" |
realm | 域名,任意字符串,例如boyaa.com |
user-quota | 每一個用戶可以申請的名額個數 |
total-quota | 總共可以申請的名額個數 |
max-bps | 輸入和輸出的最大碼率 |
bps-capacity | 總吞吐量 |
no-udp | 不監聽upd客戶端,默認監聽 |
no-tcp | 不監聽tcp客戶端,默認監聽 |
no-tls | 不監聽tls客戶端,默認監聽 |
no-dtls | 不監聽dtls客戶端,默認監聽 |
no-udp-relay | 不轉發到upd peer,默認轉發 |
no-tcp-relay | 不轉發到tcp peer,默認轉發 |
max-allocate-lifetime | allocate失效時間,默認是3600秒 |
channel-lifetime | channel通道失效時間,默認是600秒 |
permission-lifetime | 權限失效時間,默認死300秒 |
cert | 證書位置 |
pkey | 私鑰位置 |
no-stdout-log | 日誌不輸出到終端,默認輸出 |
log-file | 日誌輸出位置,如果設定,默認情況會同時輸出到終端和log文件內 |
simple-log | 日誌的輸出格式(without PID and date appendage) |
alternate-server | 交替server,用戶輪流接收客戶端的ALLOCATE requests 請求,存在多個server就定義多次 |
stun-only | 只提供stun功能 |
no-stun | 不提供stun功能 |
rest-api-separator | 設置TURN REST API 的分隔符號 |
no-loopback-peers | 不允許peer使用loopback地址(127.x.x.x and ::1) |
no-multicast-peers | 不允許peer使用廣播地址(224.0.0.0 and above, and FFXX:*) |
max-allocate-timeout | 設置allocate超時時間,單位是秒,使用形式:max-allocate-timeout=60 , |
denied-peer-ip | 不允許訪問的ip範圍 |
allowed-peer-ip | 允許訪問的ip範圍 |
no-cli | 關閉telnet支持 |
cli-ip | 設置telnet登陸ip |
cli-port | 設置telnet端口號 |
參考
- apprtc,房間服務的安裝和配置(依據其他服務來配置房間服務)
- collider,信令服務的安裝和配置
- coturn,coturn的安裝和配置
- coturn wiki,coturn的詳細介紹
- Setup ephemeral password for TURN, Learn RTC in less than 200 Lines of code,瞭解client與幾個server之間是如何交互的
- REST,瞭解什麼是REST
- A REST API For Access To TURN Services,瞭解WebRTC需要的REST API {Request、Response}樣式
- 思科介紹介紹WebRTC的文章,就是從這裏得到如何搭建coturn的提示的
擴展閱讀
- NAT,瞭解NAT是什麼,以及NAT類型{完全圓錐型NAT、受限圓錐型NAT、端口受限圓錐型NAT、對稱NAT}
- ICE,瞭解ICE是啥
- STUN,知道啥是STUN(是一種網絡協議,允許客戶端找出自己的公網地址,查出自己位於哪種類型的NAT之後以及NAT爲某一個本地端口所綁定的Internet端端口);瞭解STUN 流程是如何通過stun協議判斷client的NAT類型的
- TURN,瞭解TURN的是幹啥的
- SDP for the WebRTC,在創建本地媒體之後需要把這些信息按照SDP規範封裝起來(音視頻編碼格式、採樣率、位寬、分辨率等),peer端接收到這些信息以後創建指定類型的解碼器,與代碼結合起來看規範會容易理解一些;
sessiondescription.h
包含了對SDP的拆分的封裝;此文檔主要以例子爲主 - SDP-RFC4145,如果不想閱讀RFC文檔,想快速瞭解SDP可以看看wiki-SDP對SDP字段的解釋
- 路由形式,瞭解路由形式,特別是單播和多播{任播,廣播,多播,單播,geocase}
- webrtcstats,瞭解webrtc的一些統計
other
- 信令服務器的選擇
在這篇# How to Select a Signaling Protocol for Your Next WebRTC Project?文章中,WebRTC給出了一些技術方案SIP over WebSockets、XMPP/Jingle、WebSockets、XHR/Comet和Data Channel。並對這些方案進行了分析。