概述 例子来自一个golang的webrtc项目https://github.com/pion/webrtc ,这个项目提供了webrtc的go语言实现。
DataChannel Data Communication
DataChannel使用SCTP 来传输数据,提供可靠传输,确保数据有序发送。
DataChannel可以发送文本或者二进制数据,意思就是发送什么数据都可以。
DataChannel的连接建立过程 下面这张图来自博客WebRTC源码分析——呼叫建立过程之三(创建PeerConnection) :
这张图可以清晰的看出整个连接建立的过程,其中除了两个客户端之外,还有两个重要的角色Signal-Server 和Stun-Server :
Signal-Server ,信令服务器。两个客户端要首先连上信令服务器,这样才能互相知道对方的存在,而且在P2P的连接真正建立成功之前,两端的数据交互全部依靠信令服务器来传输。
Stun-Server ,stun服务器。客户端需要去询问它来得到自己的NAT地址,这样最后两端才能进行直接连接。
这里主要关注这个地方:
caller调用SetLocalDescription
,然后发送offer 到callee(经过信令服务器)。
callee收到offer ,先调用SetRemoteDescription
,然后生成answer ,同样的SetLocalDescription
,最后发送answer 到caller(经过信令服务器)。
手动建立DataChannel代码 代码就是webrtc项目下自带的例子,简单改了几行,让两边都可以互相发送消息。
caller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 package mainimport ( "fmt" "github.com/pion/webrtc/v3" "myWebrtc1/signal" ) func main () { config := webrtc.Configuration{ ICEServers: []webrtc.ICEServer{ { URLs: []string {"stun:stun.l.google.com:19302" }, }, }, } pc, err := webrtc.NewPeerConnection(config) sendChannel, err := pc.CreateDataChannel("foo" , nil ) if err != nil { panic (err) } sendChannel.OnClose(func () { fmt.Println("sendChannel has closed" ) }) sendChannel.OnOpen(func () { fmt.Println("sendChannel has opened" ) }) sendChannel.OnMessage(func (msg webrtc.DataChannelMessage) { fmt.Printf("Message from DataChannel %s payload %s\n" , sendChannel.Label(), string (msg.Data)) }) pc.OnICEConnectionStateChange(func (state webrtc.ICEConnectionState) { fmt.Println(state) }) pc.OnICECandidate(func (candidate *webrtc.ICECandidate) { if candidate != nil { fmt.Println(signal.Encode(*pc.LocalDescription())) } }) offer, err := pc.CreateOffer(nil ) if err != nil { panic (err) } if err := pc.SetLocalDescription(offer); err != nil { panic (err) } answer := webrtc.SessionDescription{} signal.Decode(signal.MustReadStdin(), &answer) err = pc.SetRemoteDescription(answer) if err != nil { panic (err) } go func () { for { message := signal.MustReadStdin() if err := sendChannel.SendText(message); err != nil { panic (err) } } }() select {} }
callee:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 package mainimport ( "fmt" "github.com/pion/webrtc/v3" "myWebrtc1/signal" ) func main () { config := webrtc.Configuration{ ICEServers: []webrtc.ICEServer{ { URLs: []string {"stun:stun.l.google.com:19302" }, }, }, } peerConnection, err := webrtc.NewPeerConnection(config) if err != nil { panic (err) } peerConnection.OnICEConnectionStateChange(func (connectionState webrtc.ICEConnectionState) { fmt.Printf("ICE Connection State has changed: %s\n" , connectionState.String()) }) peerConnection.OnDataChannel(func (d *webrtc.DataChannel) { fmt.Printf("New DataChannel %s %d\n" , d.Label(), d.ID()) d.OnOpen(func () { fmt.Printf("Data channel '%s'-'%d' open.\n" , d.Label(), d.ID()) }) d.OnMessage(func (msg webrtc.DataChannelMessage) { fmt.Printf("Message from DataChannel '%s': '%s'\n" , d.Label(), string (msg.Data)) }) go func () { for { message := signal.MustReadStdin() if err := d.SendText(message); err != nil { panic (err) } } }() }) offer := webrtc.SessionDescription{} signal.Decode(signal.MustReadStdin(), &offer) err = peerConnection.SetRemoteDescription(offer) if err != nil { panic (err) } answer, err := peerConnection.CreateAnswer(nil ) if err != nil { panic (err) } gatherComplete := webrtc.GatheringCompletePromise(peerConnection) err = peerConnection.SetLocalDescription(answer) if err != nil { panic (err) } <-gatherComplete fmt.Println(signal.Encode(*peerConnection.LocalDescription())) select {} }
其中的辅助函数也是例子里面的:
MustReadStdin()
从输入读入一行数据。
Encode()
和Decode()
,将一个对象先进行marshall或者unmarshall,然后编解码成base64格式。
注意到上面对连接过程的描述中,除了caller和callee,还有两个重要角色stun 和信令服务器 。
这里stun 可以直接使用公开的服务器stun:stun.l.google.com:19302
,也可以自己搭建一个。
信令服务器则就是人,也就是人工手动操作:
启动caller,将输出的Description
复制下来。
启动callee,将caller的Description
输入进去。
callee输出Description
,同样的复制下来。
将callee的Description
输入到caller。
连接建立成功,两边可以开始互相发送消息。
存在的问题 有时候在完成两边的Description
交换之后,会卡住,然后连接建立超时,目前未研究出原因。