Skip to content

Commit ffc7f27

Browse files
lherman-csSean-Der
authored andcommitted
Add c-data-channels example
Resolves pion#29
1 parent 55eb746 commit ffc7f27

File tree

6 files changed

+301
-0
lines changed

6 files changed

+301
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contribu
5858
* [CloudWebRTC|湖北捷智云技术有限公司](https://github.com/cloudwebrtc) - *Flutter example for SFU-WS*
5959
* [Atsushi Watanabe](https://github.com/at-wat) - *WebM muxer example*
6060
* [Jadon Bennett](https://github.com/jsjb)
61+
* [Lukas Herman](https://github.com/lherman-cs) - *C Data Channels example*
6162

6263
### License
6364
MIT License - see [LICENSE](LICENSE) for full text

c-data-channels/Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
c-data-channels: webrtc.so data-channels.c
2+
gcc -o $@ data-channels.c ./webrtc.so
3+
4+
webrtc.so: webrtc.go bridge.go
5+
go build -o $@ -buildmode=c-shared $^
6+
7+
clean:
8+
rm webrtc.so webrtc.h c-data-channels

c-data-channels/README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# c-data-channels
2+
c-data-channels is a Pion WebRTC application that shows how you can send/recv DataChannel messages from a web browser that's
3+
mostly identical to the pure Go implementation, https://github.com/pion/webrtc/tree/master/examples/data-channels.
4+
The main difference is that the OnDataChannel is fully implemented in C.
5+
6+
## Instructions
7+
### Clone c-data-channels
8+
```
9+
git clone https://github.com/pion/example-webrtc-applications.git
10+
```
11+
12+
### Go into the c-data-channels example directory
13+
```
14+
cd example-webrtc-applications/c-data-channels
15+
```
16+
17+
### Build it
18+
```
19+
make
20+
```
21+
22+
### Open data-channels example page
23+
[jsfiddle.net](https://jsfiddle.net/9tsx15mg/90/)
24+
25+
### Run data-channels, with your browsers SessionDescription as stdin
26+
In the jsfiddle the top textarea is your browser's session description, copy that and:
27+
#### Linux/macOS
28+
Run `echo $BROWSER_SDP | c-data-channels`
29+
#### Windows
30+
1. Paste the SessionDescription into a file.
31+
1. Run `c-data-channels < my_file`
32+
33+
### Input data-channels's SessionDescription into your browser
34+
Copy the text that `c-data-channels` just emitted and copy into second text area
35+
36+
### Hit 'Start Session' in jsfiddle
37+
Under Start Session you should see 'Checking' as it starts connecting. If everything worked you should see `New DataChannel foo 1`
38+
39+
Now you can put whatever you want in the `Message` textarea, and when you hit `Send Message` it should appear in your browser!
40+
41+
You can also type in your terminal, and when you hit enter it will appear in your web browser.
42+
43+
Congrats, you have used Pion WebRTC! Now start building something cool
44+
45+
## Organization
46+
47+
### bridge.go
48+
This file contains all of the bridging between Go and C. This is the only file that contains cgo stuff.
49+
50+
### webrtc.go
51+
This file is pure Go. It is mostly identical to the original data-channel example.
52+
53+
### Reference
54+
* https://github.com/golang/go/issues/20639
55+
* https://github.com/golang/go/issues/25832
56+
* https://github.com/pion/webrtc/tree/master/examples/data-channels/jsfiddle - jsfiddle source codes

c-data-channels/bridge.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package main
2+
3+
/*
4+
#include <stddef.h>
5+
#include <stdint.h>
6+
typedef struct GoDataChannelMessage
7+
{
8+
// bool in cgo is really weird, so it's simpler to just use classic int as bool
9+
int is_string;
10+
void *data;
11+
size_t data_len;
12+
} GoDataChannelMessage;
13+
14+
typedef uint16_t GoDataChannel;
15+
typedef void (*GoOnDataChannelFunc)(GoDataChannel);
16+
typedef void (*GoOnOpenFunc)(GoDataChannel);
17+
typedef void (*GoOnMessageFunc)(GoDataChannel, GoDataChannelMessage);
18+
19+
// Calling C function pointers is currently not supported, however you can
20+
// declare Go variables which hold C function pointers and pass them back
21+
// and forth between Go and C. C code may call function pointers received from Go.
22+
// Reference: https://golang.org/cmd/cgo/#hdr-Go_references_to_C
23+
inline void bridge_on_data_channel(GoOnDataChannelFunc cb, GoDataChannel d)
24+
{
25+
cb(d);
26+
}
27+
28+
inline void bridge_on_open(GoOnOpenFunc cb, GoDataChannel d)
29+
{
30+
cb(d);
31+
}
32+
33+
inline void bridge_on_message(GoOnMessageFunc cb, GoDataChannel d, GoDataChannelMessage msg)
34+
{
35+
cb(d, msg);
36+
}
37+
*/
38+
import "C"
39+
40+
import (
41+
"github.com/pion/webrtc/v2"
42+
)
43+
44+
var store = map[C.GoDataChannel]*webrtc.DataChannel{}
45+
46+
//export GoRun
47+
func GoRun(f C.GoOnDataChannelFunc) {
48+
Run(func(d *webrtc.DataChannel) {
49+
// Since cgo doesn't allow storing Go pointers in C, we need to store some data in C
50+
// that can tell Go how to get webrtc.DataChannel later. So, here we simply use data channel's
51+
// id, which is just a simple 16 unsigned int that we can pass easily from/to C.
52+
id := C.GoDataChannel(*d.ID())
53+
store[id] = d
54+
C.bridge_on_data_channel(f, id)
55+
})
56+
}
57+
58+
//export GoOnOpen
59+
func GoOnOpen(d C.GoDataChannel, f C.GoOnOpenFunc) {
60+
// get the actual DataChannel using a unique id
61+
dc := store[d]
62+
dc.OnOpen(func() {
63+
C.bridge_on_open(f, d)
64+
})
65+
}
66+
67+
//export GoOnMessage
68+
func GoOnMessage(d C.GoDataChannel, f C.GoOnMessageFunc) {
69+
dc := store[d]
70+
dc.OnMessage(func(msg webrtc.DataChannelMessage) {
71+
var isString int
72+
// Since C interprets non-zero to be true, we can simply set isString to be 1
73+
// or any non-zero value to make C to think that isString is true
74+
if msg.IsString {
75+
isString = 1
76+
}
77+
78+
cMsg := C.GoDataChannelMessage{
79+
is_string: C.int(isString),
80+
data: C.CBytes(msg.Data),
81+
data_len: C.ulong(len(msg.Data)),
82+
}
83+
C.bridge_on_message(f, d, cMsg)
84+
})
85+
}
86+
87+
//export GoSendText
88+
func GoSendText(d C.GoDataChannel, t *C.char) {
89+
dc := store[d]
90+
dc.SendText(C.GoString(t))
91+
}
92+
93+
//export GoLabel
94+
func GoLabel(d C.GoDataChannel) *C.char {
95+
dc := store[d]
96+
return C.CString(dc.Label())
97+
}

c-data-channels/data-channels.c

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#include "webrtc.h"
2+
#include <stdio.h>
3+
#include <stdlib.h>
4+
#include <unistd.h>
5+
#include <time.h>
6+
#include <string.h>
7+
8+
void on_data_channel(GoDataChannel d);
9+
void on_open(GoDataChannel d);
10+
void on_message(GoDataChannel d, struct GoDataChannelMessage msg);
11+
char *rand_seq(int n);
12+
13+
int main()
14+
{
15+
// Register data channel creation handling
16+
GoRun(on_data_channel);
17+
}
18+
19+
void on_data_channel(GoDataChannel d)
20+
{
21+
// Register channel opening handling
22+
GoOnOpen(d, on_open);
23+
// Register text message handling
24+
GoOnMessage(d, on_message);
25+
}
26+
27+
void on_open(GoDataChannel d)
28+
{
29+
char *label = GoLabel(d);
30+
// d = DataChannel.ID() since we pass this from Go
31+
printf("Data channel '%s'-'%d' open. Random messages will now be sent to any connected DataChannels every 5 seconds\n", label, d);
32+
free(label);
33+
34+
while (1)
35+
{
36+
sleep(5);
37+
char *message = rand_seq(15);
38+
printf("Sending '%s'\n", message);
39+
40+
// Send the message as text
41+
GoSendText(d, message);
42+
free(message);
43+
}
44+
}
45+
46+
void on_message(GoDataChannel d, struct GoDataChannelMessage msg)
47+
{
48+
char *label = GoLabel(d);
49+
printf("Message from DataChannel '%s': '%s'\n", label, (char *)(msg.data));
50+
// since we use C.CBytes and C.CString to convert label and msg.data,
51+
// the converted data are allocated using malloc. So, we need to free them.
52+
// Reference: https://golang.org/cmd/cgo/#hdr-Go_references_to_C
53+
free(label);
54+
free(msg.data);
55+
}
56+
57+
char *rand_seq(int n)
58+
{
59+
srand(time(0));
60+
char letters[52] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
61+
int n_letters = sizeof(letters) / sizeof(char);
62+
char *b = malloc(sizeof(char) * (n + 1));
63+
for (int i = 0; i < n; i++)
64+
{
65+
b[i] = letters[rand() % n_letters];
66+
}
67+
b[n] = '\0';
68+
return b;
69+
}

c-data-channels/webrtc.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/pion/example-webrtc-applications/internal/signal"
7+
"github.com/pion/webrtc/v2"
8+
)
9+
10+
// Run contains pure Go codes to do the heavy lifting. In fact, the codes
11+
// are almost identical to the data-channel example that is written in pure Go,
12+
// https://github.com/pion/webrtc/tree/master/examples/data-channels.
13+
// The only difference is that Run lets you to define the OnDataChannel callback in C.
14+
func Run(f func(*webrtc.DataChannel)) {
15+
// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
16+
17+
// Prepare the configuration
18+
config := webrtc.Configuration{
19+
ICEServers: []webrtc.ICEServer{
20+
{
21+
URLs: []string{"stun:stun.l.google.com:19302"},
22+
},
23+
},
24+
}
25+
26+
// Create a new RTCPeerConnection
27+
peerConnection, err := webrtc.NewPeerConnection(config)
28+
if err != nil {
29+
panic(err)
30+
}
31+
32+
// Set the handler for ICE connection state
33+
// This will notify you when the peer has connected/disconnected
34+
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
35+
fmt.Printf("ICE Connection State has changed: %s\n", connectionState.String())
36+
})
37+
38+
// Register data channel creation handling
39+
peerConnection.OnDataChannel(f)
40+
41+
// Wait for the offer to be pasted
42+
offer := webrtc.SessionDescription{}
43+
signal.Decode(signal.MustReadStdin(), &offer)
44+
45+
// Set the remote SessionDescription
46+
err = peerConnection.SetRemoteDescription(offer)
47+
if err != nil {
48+
panic(err)
49+
}
50+
51+
// Create an answer
52+
answer, err := peerConnection.CreateAnswer(nil)
53+
if err != nil {
54+
panic(err)
55+
}
56+
57+
// Sets the LocalDescription, and starts our UDP listeners
58+
err = peerConnection.SetLocalDescription(answer)
59+
if err != nil {
60+
panic(err)
61+
}
62+
63+
// Output the answer in base64 so we can paste it in browser
64+
fmt.Println(signal.Encode(answer))
65+
66+
// Block forever
67+
select {}
68+
}
69+
70+
func main() {}

0 commit comments

Comments
 (0)