Web Real-Time Communication API 是提供網頁進行視訊或語音串流通訊的技術,可以直接使用瀏覽器進行資料交換。WebRTC 可提供瀏覽器在不需要安裝外掛程式或第三方軟體下,分享應用程式的資料和進行電話會議。
因為 WebRTC 還在發展當中,各 browser 有不同程度的 codec 支援以及 WebRTC 功能。故建議使用 adapter ,這是 Google 提供,減少跨瀏覽器差異的 library。
browser compatability matrix 是不同瀏覽器的相容表。
WebRTC 有多種功能,包含 audio, video conferencing,檔案交換,screen sharing,identity management,支援傳統交換機的 DTMF signals,兩端點之間的連線不需要特殊的 drivers/plugins,也可不需要中間 server。
兩個 peers 之間是以 RTCPeerConnection 產生連線,當連線建立後,就能夠使用 MediaStream 或 RTCDataChannel。
MediaStream 包含了數個 media information tracks,是由 MediaStreamTrack 介面表示的物件,可包含多種 media data: audio, video, text,大部分的 streams 包含至少一條 audio track,一條 video track,可用來傳送 live media,或將 media 存檔。
也可以用 RTCDataChannel 介面在兩端之間,傳遞任意的 binray data,這邊可用來傳送 channel-information, 交換 metadata,game status, file transfer。
WebRTC References
Connection setup and management
Interfaces
Interfaces | Description |
---|---|
RTCPeerConnection | local 與 remote peer 之間的 WebRTC connection |
RTCDataChannel | connection 的兩個 peers 之間的 bi-directional data channel |
RTCDataChannelEvent | 將 RTCDataChannel 接到 RTCPerrConnection 的 event |
RTCSessionDescription | session 的參數,裡面包含了 description type,代表它所描述的部分 offer/answer negotiation process 以及該 seesion的 SDP descriptor |
RTCStatsReport | connection 或是 tack on connection 的統計資訊。以 RTCPeerConnection.getStats() 取得。使用 WebRTC 的統計資料有另外的 WebRTC Statistics API |
RTCIceCandidate | 代表一個可用來建立 RTCPeerConnection 的 candidate ICE server |
RTCIceTransport | ICE transport 的資訊 |
RTCPeerConnectionIceEvent | 在 RTCPeerConnection 中,有關 ICE candidates 的相關 events。event 是使用 type: icecandidate |
RTCRtpSender | 在 RTCPeerConnection 裡面的 MediaStreamTrack,用來encoding 傳輸資料 |
RTCRtpReceiver | 在 RTCPeerConnection 裡面的 MediaStreamTrack,用來decoding 傳輸資料 |
RTCTrackEvent | track event 介面。通常是 RTCRtpReceiver 接到 RTCPeerConnection 時發生,代表 RTCPeerConenction 產生了新的 MediaStreamTrack |
RTCSctpTransport | SCTP transport 的資訊,可在 RTCPeerConnection 的 data channel 收送資料時,透過 SCTP packets,存取Datagram Transport Layer Security(DTLS) |
Dictionaries
Dictionaries | Description |
---|---|
RTCConfiguration | RTCPeerConnection 的 conifguration options |
RTCIceServer | 定義如何連接 ICE Sever,例如 Stun or Turn server |
RTCRtpContributingSource | 有關 contributing source (SCRC) 的資訊,包含該 source 被播放了一個最新的 packet 的 most recent time |
ex:
var configuration = { iceServers: [{
urls: "stun:stun.services.mozilla.com",
username: "louis@mozilla.com",
credential: "webrtcdemo"
}, {
urls: ["stun:stun.example.com", "stun:stun-1.example.com"]
}]
};
var pc = new RTCPeerConnection(configuration);
Events
Events | Description |
---|---|
bufferedamountlow | data channel 目前 buffered 的資料量,也就是 bufferedAmount property 已經低於 bufferedAmountLowThreshold 這個 minimum buffered data size |
close | data channel 已結束 process,進入 closed state,data transport 已經完全 closed,在沒有完全 closed 的時候,會發送 closing event |
closing | RTCDataChannel 已轉換到 closing state,如果完全停止則是 close event |
connectionstatechange | 連線狀態(connectionState) 改變 |
datachannel | 有新的 RTCDataChannel 可以使用,事件類別為 RTCDataChannelEvent |
error | data channel 發生錯誤 RTCErrorEvent |
error | RTCDtlsTransport 發生錯誤 RTCErrorEvent,可能是 dtls-failure 或是 fingerprint-failure |
gatheringstatechange | RTCIceTransport 的 gathering state 已改變 |
icecandidate | RTCPeerConnectonIceEvent 當 local device 識別到一個新的 ICE candidate,並需要 local peer 去呼叫 setLocalDescription() 時,會產生此 event |
icecandidateerror | RTCPeerConnectionIceErrorEvent 在 gathering ICE candidates 發生錯誤時 |
iceconnectionstatechange | 當 ICE gathering state (屬性 icegatheringstate) 改變時,會發送給 RTCPeerConnection |
message | data channel 收到一個訊息,event type 為 MessageEvent |
negotiationneeded | 通知 RTCPeerconnection 需要處理 session negotiation,可在 setLocalDescription() 後,呼叫 createOffer() |
open | RTCDataChannel 的 data transport 已經 opened 或 re-opened |
selectedcandidatepairchane | RTCIceTransport 目前選用的 ICE candidates 改變 |
track | 當新的 track 在成功協調 media streaming 後,會產生 RTCTrackevent 發送給 RTCPeerConnection |
signalingstatechange | 當 signalingstate 改變時,送給 peer connection。這會發生在呼叫 setLocalDescription() 或 setRemoteDescription() 之後 |
statechange | RTCDtlsTransport 的狀態改變 |
statechange | RTCIceTransport 的狀態改變 |
statechange | RTCSctpTransport 的狀態改變 |
Types
Types | Description |
---|---|
RTCSctpTransport.state | 代表 RTCSctpTrnasport instance 的 state |
RTCSessionDescriptionCallback | 當要求 create offers 或 answers 時,RTCSessionDescriptionCallback 會傳給 RTCPeerConnection |
Identity and security
管理 identity and security,用在連線的認證
identity | Description |
---|---|
RTCIdentityProvider | user agent,可要求處理 identity assertion |
RTCIdentityAssertion | 代表 current connection 的 identity of the remote peer。如果目前沒有 peer,就會回傳 null,一但被設定,就不能被修改 |
RTCIdentityProviderRegistrar | Registers and identity provider (idP) |
RTCIdentityEvent | 代表 identity provider (idP) 產生了 identity assertion。event type 為 identityresult |
RTCIdentityErrorEvent | 代表 identity provider (idP) 產生了 error。event type 為 idpassertionerror 或 idpvalidationerror |
RTCertificate | RTCPeerConnection 用來認證的 certificate |
Telephony
PSTN 使用的 dialing tone
Interfaces
Interfaces | Description |
---|---|
RTCDTMFSender | 發送 DTMF signaling |
RTCDTMFToneChangeEvent | tonechange event 用來表示 DTMF 開始或結束 |
Events
Events | Description |
---|---|
tonechange | 當connection 開始播放 DTMF tone,或是 RTCDTMFSender 的 toneBuffer 已經送完時,會產生 event type: RTCDTMFToneChangeEvent |
WebRTC Connectivity
Signaling
WebRTC 必須搭配 signaling service 才能使用。用來交換 SDP 資訊,也就是 offer/answer 資料
- Peer A init connection 產生 Offer
- 透過 signal channel 發送 Offer 給 Peer B
- Peer B 接收 Offer,產生 Answer
- 透過 signal channel 回送 Answer
Session Descriptions
configuration of an endpoint on a WebRTC connection 就稱為 session description
以 SDP 描述,裡面包含 media type, format, transfer protocol, endpoint's IP/Port, 其他資訊
media data exchange 是以 Interactive Connectivity Establishment (ICE) 處理,該協定可讓兩個在 NAT 下的 devices 使用 intermediary 交換 offers, answers
每個 peer 都留存兩個 description:localdescription 及 remote description
當建立 call 或是 某一端要修改 configuration 時,就會透過 offer/answer process 處理,以下是基本的 steps
- caller 透過 MediaDevices.getUserMedia 取得 local Media
- caller 產生 RTCPeerConnection,呼叫 RTCPeerConnection.addTrack()
- caller 呼叫 RTCPeerConnection.createOffer() 產生 offer
- caller 呼叫 RTCPeerConnection.setLocalDescription() 發送 offer 到 local description
- caller 詢問 STUN servers 用以產生 ice candidates
- caller 使用 signaling server 傳送 offer 給 receiver
- receiver 接收 offer,呼叫 RTCPeerConnection.setRemoteDescription() 作為 remote description
- receiver 設定 call
- capture local media
- 呼叫 RTCPeerConnection.addTrack()
- receiver 透過 RTCPeerConnection.createAnswer() 產生 answer
- receiver 呼叫 RTCPeerConnection.setLocalDescription(),傳入 answer,也就是將 answer 設定為 local description
- receiver 使用 signaling server 發送 answer 給 caller
- caller 收到 answer
- caller 呼叫 RTCPeerConnection.setRemoteDescription() 將 answer 設定為 remote description
- 然後就能開始互傳 media
Pending and Current Descriptions
在處理 localDescription 與 remoteDescription 時,可能會因為格式不相容而被 reject,因此需要一個機制能夠 propose a new format,直到另一端接受
current description:由 RTCPeerConnection.currentLocalDescription 及 RTCPeerConenction.currentRemoteDescription 回傳,這也是目前雙方同意使用的 description。
pending description:由 RTCPeerConnection.pendingLocalDescription 及 RTCPeerConnection.pendingRemoteDescription 回傳,代表 setLocalDesciption() 或 setRemoteDescription() 目前考慮中的 description
RTCPeerConnection.localDescription 與 RTCPeerConnection.remoteDescription 回傳的 description,如果在 pending description 狀態,就是回傳 pending description,否則就回傳 current description
呼叫 setLocalDescription() 或 setRemoteDescription() 時,description 會先設定為 pending description,然後 WebRTC layer 開始評估要不要接受,一但 proposed description 被接受,current description 就會改成 pending description 的值,pending description 被改為 null
pendingLocalDescription 包含 offer or answer,還有 local ICE candidates
pendingRemoteDescription 包含 remote ICE candidates,可呼叫 RTCPeerconnection.addIceCandidate() 提供資訊
ICE Candidates
除了交換 media information,還需要交換 network connection 的 informations,也就是 ICE candidate 以及 peer 能夠通訊的方法 (directly or through a TURN server)
通常每個 peer 會先 propose 最佳的 candidates,理想狀態要使用 UDP (速度快,且容易 recover 中斷),但 ICE 標準也允許使用 TCP (當無法使用 UDP 時),另外不是所有瀏覽器都支援 ICE over TCP
UDP candidate types
host:實體 IP,可直接連線
srflx:Server Reflexive and Relayed Candidates
host 因為經過 NAT,透過 Stun/Turn server 產生的 candidate。connection 最初要求來自 STUN server 的 candidate,並將 request 傳給 remote peer
prflx: Peer Reflexive Candidates
來自 symmetric NAT,通常是 trickle ICE (也就是 additional candidate 交換,發生在 primary signaling 之後,在 verification 結束以前) 的 additional candidate
是直接發送給 peer host 產生的 candidate
relay
類似 srflx,但是由 Turn Server 產生的中繼 candidate IP:Port
tcp 有三種:active, passive, so
active
會打開 outbound connection,且不接受 incoming connection request。這是最常見的類型
passive
只會接受 incoming connection
so
會嘗試同時在兩個終端直接打開連線
Choosing a candidate pair
ICE layer 會選擇兩個 peers 中的某一個當作 controlling agent。ICE Agent 會決定 connection 最後使用的 candidate pair,另一個 peer 就稱為 controlled agent。可透過 RTCIceCandidate.transport.role 查詢
controlling agent 除了決定 candidate pair 以外,還會透過 STUN,signaling 選用的 controlled agent,更新 offer。controlled agent 只會等待通知使用哪一個 candidate pair
ICE session 可能會讓 controlling agent 選擇多個 candidate pair,每次分享資訊給 controlled agent 時,兩端會重新設定 connection 使用的 candidate pair
一但 ICE session 完成,configuration 就固定了,除非發生 ICE reset
在每個 candidate generation 結束時,會以 RTCIceCandidate 格式發送 end-of-candidates notification,也就是 candidate 屬性為空字串。這個 candidate 還是要呼叫 addIceCandidate() 加入 connection,以便通知 remote peer
如果在 negotiation 時,沒有其他的 candidates,就會發送 end-of-candidates notification,發送 candidate 屬性為 null 的 RTCIceCandidate。這個訊息不需要傳給 remote peer。這是 legacy notification of a state,可用 icegatheringstatechange event 偵測得知
ICE rollbacks
如果不像掛斷既有的 call,可作 renegotiation
ICE rollback 會 restore 上次連線中 signalingState 為 stable 的 SDP offer
發送 description 其 type 為 rollback,就可以 init rollback,該 description 的其他 properties 都會被忽略
當 peer 已經有先前產生的 offer 時,ICE agent 會自動產生 rollback。如果 local peer 的狀態為 have-local-offer,也就是 local pper 有發送過 offer
呼叫 setRemoteDescription() 可收到 received offer triggers rollback
完整的 exchange
Signaling and video calling
discovery 與 negotiation process 用以產生 conenction,稱為 signaling
WebRTC 需要兩個不同網路的端點,能夠互相找到對方,並協調 media format,需要透過第三方 server 處理 signaling
signaling server
兩個 devices 之間的 WebRTC 連線需要先透過 signaling server 協調兩端
WebRTC 不指定 transport mechanism,可使用 WebSocket 或是 XMLHttpRequest
server 不需要了解或是處理 signaling data content (SDP),只需要傳遞。ICE subsystem 要如何傳送 singaling data 給另一個 peer 是比較重要的問題。
準備給 signaling 使用的 chat server
chat server 透過 WebSocket 傳送 JSON string
可用類似方法,做 signaling 與 ICE negotiation
# 將訊息傳送給特定 target 的 function
function sendToOneUser(target, msgString) {
var isUnique = true;
var i;
for (i=0; i < connectionArray.length; i++) {
if (connectionArray[i].username === target) {
connectionArray[i].send(msgString);
break;
}
}
}
Designing the signaling protocol
需要一個 protocol 交換 message,以下利用 JSON object 作為範例
Exchange session descriptions
在開始 singaling process 時,user init call 並產生 offer,裡面包含 session description (SDP 格式),該資料要傳給 callee,callee 會以 answer 回應
設計用 video-offer
video-answer
分別代表 offer, answer
JSON 欄位 | 說明 |
---|---|
type | message type, video-offer or video-answer |
name | sender's name |
target | callee's name |
sdp | SDP string |
兩端可知道 codecs, codec parameters,但還不知道如何傳遞 media data。透過 ICE candidates 處理
Exchange ICE candidates
兩端需要交換 ICE candidates 協調真正的 connection,每個 ICE candidate 都會描述一個可使用的溝通管道。peer 會持續發送找到的 candidates,不管 streaming 是否已經開始了。
icecandidate
event 會送給 RTCPeerConenction,並以 pc.setLocalDescription(offer) 完成 process
如果有更好的 candidate,stream 可修改 formats
欄位 | 說明 |
---|---|
type | new-ice-candidate |
target | callee's name |
candidate | SDP candidate string 不需要處理訊息內容,只需要傳給 remote peer |
每個 ICE message 會提供 TCP/UDP, IP address, port, connection type (說明是 peer IP 或是 relay server),包含 NAT 資訊
note: 當收到 onicecandidate
時,要將 candidate 透過 signaling connection 傳給另一個 peer。收到 ICE candidate message 時,就呼叫 RTCPeerConnection.addIceCandidate()。建議不要嘗試修改 SDP
note: onicecandidate
event 與 createAnswer()
都是 async call,注意 signaling 不要修改處理順序。setRemoteDescription()
後,才能呼叫 addIceCandidate()
Signaling transaction flow
- Each user's client running within a web browser
- Each user's web browser
- The signaling server
- The web server hosting the chat service
ICE candidate exchange process
在 local ICE layer 收到的 candidate,各自發送給另一端,當兩端同意使用某個 candidate,就會開始傳遞 media
當網路狀況有異動,peer 可能會建議切換到不同 media resolution 或 不同 codec,就會啟動新的 exchange of candidates
client application
兩個 video elements,一個 hangup 按鈕
<div class="flexChild" id="camera-container">
<div class="camera-box">
<video id="received_video" autoplay></video>
<video id="local_video" autoplay muted></video>
<button id="hangup-button" onclick="hangUpCall();" disabled>
Hang Up
</button>
</div>
</div>
// 透過 WebSocket 發送訊息給 signaling server
function sendToServer(msg) {
var msgJSON = JSON.stringify(msg);
connection.send(msgJSON);
}
// users: 每個連線的 user 的 usernames
function handleUserlistMsg(msg) {
var i;
var listElem = document.querySelector(".userlistbox");
while (listElem.firstChild) {
listElem.removeChild(listElem.firstChild);
}
msg.users.forEach(function(username) {
var item = document.createElement("li");
item.appendChild(document.createTextNode(username));
item.addEventListener("click", invite, false);
listElem.appendChild(item);
});
}
// 當 user click 某個想要呼叫的 username,就呼叫 inivte
var mediaConstraints = {
audio: true, // We want an audio track
video: true // ...and we want a video track
};
function invite(evt) {
if (myPeerConnection) {
alert("You can't start a call because you already have one open!");
} else {
var clickedUsername = evt.target.textContent;
if (clickedUsername === myUsername) {
alert("I'm afraid I can't let you talk to yourself. That would be weird.");
return;
}
targetUsername = clickedUsername;
createPeerConnection();
navigator.mediaDevices.getUserMedia(mediaConstraints)
.then(function(localStream) {
document.getElementById("local_video").srcObject = localStream;
localStream.getTracks().forEach(track => myPeerConnection.addTrack(track, localStream));
})
.catch(handleGetUserMediaError);
}
}
// 如果 getUserMedia 發生 error 的錯誤處理
function handleGetUserMediaError(e) {
switch(e.name) {
case "NotFoundError":
alert("Unable to open your call because no camera and/or microphone" +
"were found.");
break;
case "SecurityError":
case "PermissionDeniedError":
// Do nothing; this is the same as the user canceling the call.
break;
default:
alert("Error opening your camera and/or microphone: " + e.message);
break;
}
closeVideoCall();
}
// 建立 PeerConnection
function createPeerConnection() {
myPeerConnection = new RTCPeerConnection({
iceServers: [ // Information about ICE servers - Use your own!
{
urls: "stun:stun.stunprotocol.org"
}
]
});
myPeerConnection.onicecandidate = handleICECandidateEvent;
myPeerConnection.ontrack = handleTrackEvent;
myPeerConnection.onnegotiationneeded = handleNegotiationNeededEvent;
myPeerConnection.onremovetrack = handleRemoveTrackEvent;
myPeerConnection.oniceconnectionstatechange = handleICEConnectionStateChangeEvent;
myPeerConnection.onicegatheringstatechange = handleICEGatheringStateChangeEvent;
myPeerConnection.onsignalingstatechange = handleSignalingStateChangeEvent;
}
- Starting negotiation
// 處理 negotiationneeded event
function handleNegotiationNeededEvent() {
myPeerConnection.createOffer().then(function(offer) {
return myPeerConnection.setLocalDescription(offer);
})
.then(function() {
sendToServer({
name: myUsername,
target: targetUsername,
type: "video-offer",
sdp: myPeerConnection.localDescription
});
})
.catch(reportError);
}
- session negotiation
- Handling the invitation
function handleVideoOfferMsg(msg) {
var localStream = null;
targetUsername = msg.name;
createPeerConnection();
var desc = new RTCSessionDescription(msg.sdp);
myPeerConnection.setRemoteDescription(desc).then(function () {
return navigator.mediaDevices.getUserMedia(mediaConstraints);
})
.then(function(stream) {
localStream = stream;
document.getElementById("local_video").srcObject = localStream;
localStream.getTracks().forEach(track => myPeerConnection.addTrack(track, localStream));
})
.then(function() {
return myPeerConnection.createAnswer();
})
.then(function(answer) {
return myPeerConnection.setLocalDescription(answer);
})
.then(function() {
var msg = {
name: myUsername,
target: targetUsername,
type: "video-answer",
sdp: myPeerConnection.localDescription
};
sendToServer(msg);
})
.catch(handleGetUserMediaError);
}
- sending ICE candidates
function handleICECandidateEvent(event) {
if (event.candidate) {
sendToServer({
type: "new-ice-candidate",
target: targetUsername,
candidate: event.candidate
});
}
}
- receiving ICE candidates
function handleNewICECandidateMsg(msg) {
var candidate = new RTCIceCandidate(msg.candidate);
myPeerConnection.addIceCandidate(candidate)
.catch(reportError);
}
- receiving new streams
function handleTrackEvent(event) {
document.getElementById("received_video").srcObject = event.streams[0];
document.getElementById("hangup-button").disabled = false;
}
- handling the removal of tracks
function handleRemoveTrackEvent(event) {
var stream = document.getElementById("received_video").srcObject;
var trackList = stream.getTracks();
if (trackList.length == 0) {
closeVideoCall();
}
}
- hangup
function hangUpCall() {
closeVideoCall();
sendToServer({
name: myUsername,
target: targetUsername,
type: "hang-up"
});
}
- end call
function closeVideoCall() {
var remoteVideo = document.getElementById("received_video");
var localVideo = document.getElementById("local_video");
if (myPeerConnection) {
myPeerConnection.ontrack = null;
myPeerConnection.onremovetrack = null;
myPeerConnection.onremovestream = null;
myPeerConnection.onicecandidate = null;
myPeerConnection.oniceconnectionstatechange = null;
myPeerConnection.onsignalingstatechange = null;
myPeerConnection.onicegatheringstatechange = null;
myPeerConnection.onnegotiationneeded = null;
if (remoteVideo.srcObject) {
remoteVideo.srcObject.getTracks().forEach(track => track.stop());
}
if (localVideo.srcObject) {
localVideo.srcObject.getTracks().forEach(track => track.stop());
}
myPeerConnection.close();
myPeerConnection = null;
}
remoteVideo.removeAttribute("src");
remoteVideo.removeAttribute("srcObject");
localVideo.removeAttribute("src");
remoteVideo.removeAttribute("srcObject");
document.getElementById("hangup-button").disabled = true;
targetUsername = null;
}
ICE connection state
function handleICEConnectionStateChangeEvent(event) {
switch(myPeerConnection.iceConnectionState) {
case "closed":
case "failed":
closeVideoCall();
break;
}
}
ICE signaling state
function handleSignalingStateChangeEvent(event) {
switch(myPeerConnection.signalingState) {
case "closed":
closeVideoCall();
break;
}
};
ICE gathering state
function handleICEGatheringStateChangeEvent(event) {
// Our sample just logs information to console here,
// but you can do whatever you need.
}
WebRTC Data Channel
建立 RTCPeerConnection 後,就能收送 media data,也可以使用 data channel,可安全地交換任何資料
因所有 WebRTC components 都要求加密,RTCDataChannel 會自動使用 Datagram Transport Layer Security (DTLS)
產生 data channel
RTCDataChannel 使用的 data transport 可用兩種方法產生
- 以 WebRTC 產生 transport,並宣告為 remote peer (會收到 datachannel event),這種方法比較簡單且常用,但比較沒有彈性
- 自己寫 code 協調 data transport,自己 signal 到另一端 peer,且要連接到 new channel
Automatic negotiation
通常可讓 peer connection 協調處理 RTCDataChannel connection
呼叫 createDataChannel() 且不設定 negotiated
屬性,或是設定為 false。就會讓 RTCPeerConnection 自動處理 RTCDataChannel
可透過傳給 RTCDataChannel 的 open
event 判斷是否有成功連線
let dataChannel = pc.createDataChannel("MyApp Channel");
dataChannel.addEventListener("open", (event) => {
beginTransmission(dataChannel);
});
Manual negotiation
先用 RTCPeerConnection.createDataChannel() 產生一個新的 RTCDataChannel,設定 negotiated
為 true
使用 webserver or others,要能 signal to the remote peer,並讓他產生自己的 RTCDataChannel,且 negotiated
為 true
let dataChannel = pc.createDataChannel("MyApp Channel", {
negotiated: true
});
dataChannel.addEventListener("open", (event) => {
beginTransmission(dataChannel);
});
requestRemoteChannel(dataChannel.id);
上面的 requestRemoteChannel 就是用來 trigger negotiation,產生 remote channel with the same ID as the local channel
Buffering
WebRTC data channel 支援 outbound data 的 buffering,會自動處理,且無法控制 buffer size。
使用 bufferedAmount, bufferedAmountLowThreshold, onbufferedamountlow, and bufferedamountlow
了解 message size limits
網路傳輸的封包有 size 限制
Firefox, Chrome 使用 usrsctp library 實作 SCTP,但兩個使用方法不同,firefox 會用多個 SCTP message 傳送 large message (deprecated),chrome 是用 series of messages
少於 16 kB 的訊息不需要考慮此問題
Concerns
目前不適合使用 RTCDataChannel 傳送超過 64kB 的 message,因為 SCTP 最初用在 signaling protocol,這邊假設 message 都很小,要支援超過網路 MTU 的 message 是後來增加的功能。這個技術需要訊息編上有順序的號碼,要依序傳送。
因為有越多越多 application 傳送大 message,造成重要的 signaling message 阻塞的問題。
目前 browser 利用 end-of-record (EOR) flag 支援 larger message,這表示 message 是 last one in a series。在支援 EOR 的狀況下,RTCDataChannel 可傳送大 message (可超過 256 kB,firefox 實作可傳送 1GB),在 256 kB 的狀況下,會發生 noticeable delays。
要解決此問題,新的 stream scheduler (SCTP ndata specification) 可 interleave 在不同 stream 傳送的 message,proposal 目前還在 IETF draft,如果實作,理論上可傳送任意大小的 message
firefox 在 bug 1381145 支援 ndata
chrome 在 bug 5696
Security
透過 WebRTC 傳送的資料都會被加密,RTCDataChannel 是用 Datagram Transport Layer Security (DTLS) 加密,based on TLS (https)
WebRTC 在兩個 agents 之間做 peer-to-peer conenction,資料不會傳給 web/application server
SCTP NAT
A----->B-----C
IP如下:
A:1.1.1.1
B:1.1.1.2; 2.2.2.1
C:2.2.2.2
需求為,A 需要使用sctp連通C
在B機器上添加iptables規則為:
iptables -t nat -I PREROUTING -d 1.1.1.2 -p sctp --dport 11111 -j DNAT --to-destination 2.2.2.2:11111
就是把A發出的目的地址:Port 由1.1.1.2:11111轉變為2.2.2.2:11111
同時在C的接口上使用tcpdump抓包,發現並沒有接收到sctp message,為檢驗網絡是否正常(包括路由等配置),僅將上述規則中的sctp改為tcp進行tcp的連通測試
iptables -t nat -I PREROUTING -d 1.1.1.1 -p tcp --dport 11111 -j DNAT --to-destination 2.2.2.2:11111
發現C機器上可以抓到tcp報文,說明網絡沒有問題,iptables的規則也沒有問題。
因為tcpdump抓包解包並不需要系統支持特定的協議,懷疑可能是iptables規則因為某種原因沒有生效,借助google發現瞭解決辦法:iptables-nat-not-work-for-sctp
載入nfconntrackproto_sctp即可,該模塊用來對sctp進行連接跟蹤
modprobe nf_conntrack_proto_sctp
連接跟蹤模塊可以參見:nf_conntrack連接跟蹤模塊
Why is SCTP not much used/known
- NAT: Doesn't cross NAT very well/at all (less than 1% internet home & enterprise routers do NAT on SCTP).
sctp over udp encapsulation does not work under NAT network
ORTC
Object Real-Time Communication 是新的規格,提供開發 WebRTC application 高階 API,ORTC 不使用 SDP,也不支援 Offer/Answer state machine。ORTC 使用 "sender", "receiver", "transport" 物件及設定參數,用以描述該物件的功能。"Tracks" 是由 sender 編碼,透過 transport 傳送,在 receiver 解碼。而 "data channels"是直接透過 transport 傳送。
ORTC 最初由 MS Edge 實作支援,但目前 ORTC 很多的物件定義,已經被吸收進入 WebRTC 1.0 核心標準中。
ref: ORTC draft spec
ref: WebRTC的现状和未来(上)