-
Notifications
You must be signed in to change notification settings - Fork 13
Transport Protocol
The Transport Protocol is divided into the following layers:
- The Connection Layer: The layer that establishes a two-way, kept-alive, stateless connection between the Client and the Server, and allows FRAMES to be transported.
- The Section Layer: The layer that allows the performant, ordered, safe transportation of SECTIONS.
- The Data Layer: The layer that allows the transportation of JSONs. Heartbeat happens here.
The Transport Protocol demands that applications provide an Handshake JSON to send directly after the connection is opened. This JSON must be provided when opening the connection.
The Interface of the Client-side Connection
.
Note that this is not the documentation of the actual implementation in bilibili-danmaku-client
. This section only describes the exposed interface of the Transport Protocol to Clients and Servers in pseudocode. Also, the implementation might not 100% conform to this interface. For the implementation documentation, see the code.
function open(options: ServerConnectionOptions | ClientConnectionOptions): Connection
Open a new Connection
.
- param
options
: The options used to open the connection. Required. - return: The
Connection
construted. Non-null.
class ConnectionOptions {
url: String,
handshakeJson: Object,
}
- property
url
: The URL representing the Server to connect to. Required. - property
handShakeJson
: The Handshake JSON to send to the Server. Required.
Methods are listed seperately.
class Connection {
url: String,
state: String,
}
- property
url
: The URL representing the Server that thisConnection
connects to. Read-only. - property
state
: The string representing the state of thisConnection
. Read-only. See lifecycle.
function Connection.close(): void
If Connection
is in Opening State or Opened State, switch state to closing
and request Connection
to close. The Connection
will only be closed after the close
event. See lifecycle.
function Connection.send(data: Object): void
If Connection
is in Opened State, send given data
to the other side of this Connection
.
- param
data
: The data to send. Required.
function Connection.on(event: String, callback: Function): void
Add callback
as listener to event
. Recommended to call this right after open()
. Listeners will be called on order of on()
, if the former listener throws an error, behavior is undefined. For event definitions see events.
The Transport Protocol defines the following states:
- Opening State: When the connection is opening, when any of the 3 layers haven't finished setting up.
- Opened State: When the connection is established and the Data Layer is ready to send data.
-
Closing State: When the connection is closing, after
Connection.close()
is called and closing is reqested. - Closed State: When the connection is closed.
The states transfers as follows:
- Start from Opening State: Right after
open()
. - From Opening State
- to Closing State: When
Connection.close()
is called before the connection is opened. - to Closed State: When an error occurs while opening. Emit event
close
. Emit eventerror
with the error thrown. - to Opened State: When the connection is successfully established. Emit event
open
.
- to Closing State: When
- From Opened State
- to Closing State: When
Connection.close()
is called. - to Closed State: When any of the layers closed or thrown an error automatically. Emit event
close
. If an error was thrown, emit eventerror
with the error thrown.
- to Closing State: When
- From Closing State:
- to Closed State: When connection is successfully closed. Emit event
close
.
- to Closed State: When connection is successfully closed. Emit event
- End in Closed State.
The Connection Layer is rather simple. Currently, the Transport Procotol simply uses a WebSocket under scema wss:
and transports FRAMES by transporting their binary data with binary data frames.
On receiving a data frame, if it is a binary data frame, the frame is passed on to the Section Layer. Otherwise, discard it.
In Opening State, the Connection Layer initializes first and tries to establish a WebSocket connection using Connection.url
. If an error occurs, the Connection Layer fails to initialize.
In Opened State, the Connection Layer accepts errors and close requests from the Server, according to the WebSocket Protocol. On errors the Connection Layer throws the error. On Server-requested close, the Connection Layer closes.
In Closing State, the Connection Layer closes the underlying WebSocket using the 1006 Going Away code and reason. The Connection Layer closes after the WebSocket is closed.
The Section Layer introduces FRAME and SECTION.
A FRAME is an array of SECTIONs. The binary data of a FRAME is the concatenation of the binary data of its SECTIONs. Here concatenation means appending the bytes of the operands in encounter order.
A SECTION has 2 parts: a HEADER part and a DATA part. The binary data of a SECTION is the concatenation of the binary data of the HEADER part and the DATA part.
The HEADER is has 4 properties:
-
length: int
: The length of the whole SECTION, the HEADER included. -
protoVer: int
: The version of the current Transport Protocol. Currently 2. -
controlFlag: boolean
: Whether this SECTION is used for controlling and not data transporting. -
opCode: int
: The operation code of this SECTION. -
binaryFlag: boolean
: Whether this SECTION contain non-JSON data.
Its binary data, with 16 bytes is represented as:
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
LL LL LL LL VV VV 00 0C PP PP PP PP 00 00 00 0B
Where:
-
L
meanslength
, encoded as 32-bit Big-Endian unsigned integer. -
V
meansprotoVer
, encoded as 16-bit Big-Endian signed integer. -
C
meanscontrolFlag
, encoded1
fortrue
and0
forfalse
. -
P
meansopCode
, encoded as 32-bit Big-Endian signed integer. -
B
meansbinaryFlag
, encoded1
fortrue
and0
forfalse
.
The DATA part is a arbitrary array of bytes, named data
. Its binary data is exactly its byte array.
In Opening State and Closing State, the Section Layer does nothing special.
If the Data Layer want's to transfer several SECTIONs in a short period of time, the Section Layer can choose to bundle these SECTIONs together into one single FRAME to take advantage of system resources. When and how do bundling take place is for the implementation to decide.
On receiving a FRAME from the Connection Layer, it should be debundled into one or more SECTIONs, which MUST be implemented. The debundling process happens as follows:
- Let
offset
be 0. - Try to read 16 bytes from
offset
and form a HEADER part, call itheader
. On error, throw it. - Try to read
header.length - 16
bytes fromoffset + 16
and form a DATA part, call itdata
. On error, throw it. - Form a SECTION with
header
anddata
, pass the SECTION to the Data Layer. - Set
offset
tooffset + header.length
. Jump to step 1.
If the debundling process throws an error, the Section Layer throws the error.
This section only describes the Data Layer on the client side.
The Data Layer introduces 5 types of SECTIONs.
controlFlag | opCode | binaryFlag | data | |
---|---|---|---|---|
Handshake Section | true |
7 |
false |
custom |
Handshake ACK Section | true |
8 |
true |
[] |
Data Section | false |
5 |
false |
custom |
Heartbeat Section | true |
2 |
true |
'[object Object]' |
Heartbeat ACK Section | true |
3 |
true |
[0x00 * 4] |
And defines 3 processes: the Handshake Process, the Convertion Process, and the Heartbeat Process. All processes run on the Data Layer.
In the Handshake Process, the Client makes a handshake with the Server and establish the connection.
In Opening State, a Handshake Section is sent with the handshakeJson
param passed to open()
. If no Handshake ACK Section is received after a period of time, the Data Layer fails with an error. Otherwise, it finished opening.
On receiving a SECTION from the Section Layer, the Data Layer compares its HEADER with these 5 SECTIONs. On match, it tries to convert the SECTION to the matching one as follows:
- If the target SECTION is binary, return
data
of the DATA part directly. - Try to convert
data
of the DATA part of the SECTION to stringtext
using UTF-8 encoding. On error, throw it. - Try to convert
text
to an JSON Object,obj
. On error, throw it. - Return
obj
.
After that, the converted SECTION is routed according to its type:
- Handshake Section and Handshake ACK Section: offered to the Handshake Process.
-
Data Section: offered to the application by emitting event
data
withdata
of the DATA part. - Heartbeat Section and Heartbeat ACK Section: offered to the Heartbeat Process.
If the convertion throws an error, the Data Layer throws the error in whatever state. If the convertion finishes, use obj
to construct the matching SECTION.
On sending a SECTION, send a SECTION with the same HEADER part and a DATA part with data
being the result of running the convertion as follows:
- If the SECTION is binary, return
data
of its DATA part. - Let
text
be running JSON stringify ondata
of the DATA part. - Let
bytes
be the result of encodingtext
with UTF-9 encoding. - Return
bytes
.
In the Heartbeat Process, the Client sends heartbeats regularly to the Server to remain the connection open.
In Opened State, a Heartbeat Section is sent every 30 seconds. If no Heartbeat ACK Section is received after a period of time, the Data Layer throws an error. In other states, it stops sending heartbeats.
Emitted when the Connection
has successfully opened.
Emitted when the Connection
is closed.
Emitted when a error was thrown in Opening State or Opened State.
- param
err: Error
: The error thrown.
Emitted when a Data Section is received with valid JSON data.
- param
data: Object
: The data received.