Straight from one program to another
WebRTC lets two programs talk directly, peer to peer, even when both sit behind home routers, with no server in the middle carrying their messages. Browsers use it for video calls. The xTalk Suite's dataChannelXT uses its data channels, through libdatachannel, to send text and bytes; its handlers begin with dc, and its helpers, such as dcStateName, come with it.
The examples run both peers in one script on one computer, so each waits a moment for the other with wait until, never more than five seconds. In an app each peer is a different program, and events tell it when something happens.
A peer
A peer is your end of one connection. dcCreatePeer makes one; its text names ICE servers, helpers that find a way through routers (empty is enough on one computer, and a line such as stun:stun.l.google.com:19302 helps across the internet). Its state and how far it has got finding its addresses are numbers, which dcStateName and dcGatheringName say in words. dcFreePeer frees it, and its handle stops working:
put dcInit() && "(0 is good)"
put dcLibraryVersion() begins with "libdatachannel"
put dcCreatePeer(empty) into tPeer
put tPeer > 0
put dcStateName(dcPeerState(tPeer))
put dcGatheringName(dcGatheringState(tPeer))
get dcFreePeer(tPeer)
put dcStateName(dcPeerState(tPeer))
0 (0 is good) true true new new unknown
Offer and answer
Before two peers can talk they swap two short texts: an offer and an answer. Each lists the addresses its peer can be reached at, its candidates. Making a channel makes a peer write its offer, which is complete when dcGatheringState is 2; dcLocalDescription gives the text. How the text travels is up to you, a web server, a chat, an email or a QR code, and is called signalling:
put dcCreatePeer(empty) into tA
put dcCreateChannel(tA, "chat") into tChannel
put the milliseconds + 5000 into tUntil
wait until dcGatheringState(tA) = 2 or the milliseconds > tUntil with messages
put dcLocalDescriptionType(tA)
put dcLocalDescription(tA) into tOffer
put line 1 of tOffer
put tOffer contains "a=candidate"
get dcFreePeer(tA)
offer v=0 true
Connecting the two
With both peers in one script, the texts go straight across: A writes its offer, B takes it with dcSetRemoteDescription and writes its answer, and A takes B's answer the same way. The peers then try each other's addresses, and the channel opens:
put dcCreatePeer(empty) into tA
put dcCreatePeer(empty) into tB
put dcCreateChannel(tA, "chat") into tChannel
put the milliseconds + 5000 into tUntil
wait until dcGatheringState(tA) = 2 or the milliseconds > tUntil with messages
get dcSetRemoteDescription(tB, dcLocalDescription(tA), "offer")
get dcSetRemoteDescription(tA, dcLocalDescription(tB), "answer")
wait until dcChannelIsOpen(tChannel) or the milliseconds > tUntil with messages
put "A is" && dcStateName(dcPeerState(tA))
put dcChannelIsOpen(tChannel)
put dcChannelLabel(tChannel)
get dcFreePeer(tA)
get dcFreePeer(tB)
get dcPoll()
A is connected true chat
The last line reads and drops the events the run left over, so that they do not reach the next example.
Messages are events
dcSendText sends text on an open channel, and dcSendData sends bytes, up to 60000 a message (-4 means too big). On the other side each message is an event: dcPoll gives every event since the last poll, oldest first, as an array, each with a "name", the "peer" and "channel" it is about, and more. A message is a "dcMessage" event, with its text under "text" and bytes under "payload". In an app, poll on a timer, or let dcStartPolling do it:
put dcCreatePeer(empty) into tA
put dcCreatePeer(empty) into tB
put dcCreateChannel(tA, "chat") into tChannel
put the milliseconds + 5000 into tUntil
wait until dcGatheringState(tA) = 2 or the milliseconds > tUntil with messages
get dcSetRemoteDescription(tB, dcLocalDescription(tA), "offer")
get dcSetRemoteDescription(tA, dcLocalDescription(tB), "answer")
wait until dcChannelIsOpen(tChannel) or the milliseconds > tUntil with messages
put dcSendText(tChannel, "Hello, B!") && "(0 is good)"
put dcSendData(tChannel, numToByte(0) & numToByte(255)) && "(0 is good)"
put dcSendData(tChannel, binaryEncode("x60001", "")) && "for 60001 bytes"
repeat until the number of lines of tGot is 2 or the milliseconds > tUntil
wait 20 milliseconds with messages
repeat for each element tEvent in dcPoll()
if tEvent["name"] is "dcMessage" and tEvent["text"] is not empty then
put "B got the text" && tEvent["text"] & return after tGot
else if tEvent["name"] is "dcMessage" then
put "B got" && the number of bytes in tEvent["payload"] && "bytes" & return after tGot
end if
end repeat
end repeat
put line 1 to -1 of tGot
get dcFreePeer(tA)
get dcFreePeer(tB)
get dcPoll()
0 (0 is good) 0 (0 is good) -4 for 60001 bytes B got the text Hello, B! B got 2 bytes
Channels your way
dcCreateChannelEx makes a channel with its options: a protocol name; unordered, so a message need not wait for the one before it; retries and a time limit in milliseconds (0 retries means a lost message stays lost, fine for game moves where only the newest counts, and -1 means no limit); and negotiated with a stream id, so both sides make the channel themselves and nothing is announced:
put dcCreatePeer(empty) into tA
-- label, protocol, unordered, retries, time limit, negotiated, stream id
put dcCreateChannelEx(tA, "moves", "game", true, 0, -1, false, -1) into tChannel
put dcChannelLabel(tChannel) && dcChannelProtocol(tChannel)
put dcChannelPeer(tChannel) = tA
put dcCreateChannelEx(tA, "control", empty, false, -1, -1, true, 5) into tChannel
put dcChannelStreamId(tChannel)
get dcFreePeer(tA)
moves game true 5
Closing down
dcCloseChannel closes a channel, and both ends get a "dcChannelClosed" event. dcClosePeer hangs up the whole connection, and the other side sees its state go to closed. dcFreePeer then frees the peer and its channels:
put dcCreatePeer(empty) into tA
put dcCreatePeer(empty) into tB
put dcCreateChannel(tA, "chat") into tChannel
put the milliseconds + 5000 into tUntil
wait until dcGatheringState(tA) = 2 or the milliseconds > tUntil with messages
get dcSetRemoteDescription(tB, dcLocalDescription(tA), "offer")
get dcSetRemoteDescription(tA, dcLocalDescription(tB), "answer")
wait until dcChannelIsOpen(tChannel) or the milliseconds > tUntil with messages
get dcCloseChannel(tChannel)
get dcClosePeer(tA)
wait until dcPeerState(tB) = 5 or the milliseconds > tUntil with messages
put dcChannelIsOpen(tChannel)
put "B is" && dcStateName(dcPeerState(tB))
get dcFreePeer(tA)
get dcFreePeer(tB)
get dcPoll()
false B is closed
Across the internet, two peers also need a way to swap their offer and answer, and sometimes a TURN server to relay for them when no direct route exists. Learn dataChannelXT (Help > Examples) and the Dictionary say more.
Try it yourself
Write a command connectPair that makes two peers and a channel, connects them and returns the three handles as items (A, B, channel), or empty after five seconds without an open channel. Use it to send three numbered lines from A to B, and put what B gets.
Show a solution
function connectPair
local tA, tB, tChannel, tUntil
put dcCreatePeer(empty) into tA
put dcCreatePeer(empty) into tB
put dcCreateChannel(tA, "chat") into tChannel
put the milliseconds + 5000 into tUntil
wait until dcGatheringState(tA) = 2 or the milliseconds > tUntil with messages
get dcSetRemoteDescription(tB, dcLocalDescription(tA), "offer")
get dcSetRemoteDescription(tA, dcLocalDescription(tB), "answer")
wait until dcChannelIsOpen(tChannel) or the milliseconds > tUntil with messages
if not dcChannelIsOpen(tChannel) then
get dcFreePeer(tA)
get dcFreePeer(tB)
return empty
end if
return tA, tB, tChannel
end connectPair
put connectPair() into tPair
repeat with tIndex = 1 to 3
get dcSendText(item 3 of tPair, "line" && tIndex)
end repeat
put the milliseconds + 5000 into tUntil
repeat until the number of lines of tGot is 3 or the milliseconds > tUntil
wait 20 milliseconds with messages
repeat for each element tEvent in dcPoll()
if tEvent["name"] is "dcMessage" then put tEvent["text"] & return after tGot
end repeat
end repeat
put line 1 to -1 of tGot
get dcFreePeer(item 1 of tPair)
get dcFreePeer(item 2 of tPair)
get dcPoll()
line 1 line 2 line 3
Words in this lesson
dcInitdcCreatePeerdcCreateChanneldcCreateChannelExdcGatheringStatedcLocalDescriptiondcLocalDescriptionTypedcSetRemoteDescriptiondcChannelIsOpendcSendTextdcSendDatadcPolldcPeerStatedcStateNamedcBufferedAmountdcCloseChanneldcClosePeerdcFreePeerEach one finds every lesson that uses it. The Dictionary has the whole story of each, with its syntax and more examples.