A Group is an entity that binds multiple sockets, and is required to establish a "bonded connection". Groups can be used in the same way as sockets for performing a transmission. A group is connected as long as at least one member-socket connection is alive. As long as a group is in the connected state, some member connections may get broken and new member connections can be established.
Groups are fully flexible. There's no limitation how many single connections they can use as well as when you want to establish a new connection. On the other hand, broken connections are not automatically reestablished. The application should track the existing connections and reestablish broken ones if needed. But then, the application is also free to keep as many links as it wants, including adding new links to the group while it is being used for transmission, or removing links from the list if they are not to be further used.
How the links are utilized within a group depends on the group type. The simplest type, broadcast, utilizes all links at once to send the same data.
To learn more about socket groups and their abilities, please read the SRT Socket Groups document.
Before we begin, let's review first how to establish a connection for a single socket.
Keep in mind these important changes to SRT:
-
Specifying family (
AF_INET/AF_INET6
) when creating a socket is no longer required. The existingsrt_socket
function redirects to a newsrt_create_socket
function that takes no arguments. The family is decided at the first call tosrt_bind
orsrt_connect
, and is extracted from the value of thesa_family
field of thesockaddr
structure passed to this call. -
There's no distinction between transmission functions bound to message or file mode. E.g. all 3 functions:
srt_send
,srt_sendmsg
andsrt_sendmsg2
can be used for sending data in any mode - all depends on what your application needs.
Let's review quickly how to establish a socket connection in the caller-listener arrangement.
On the listener side, you create a listening endpoint. Starting with creating a socket:
SRTSOCKET sock = srt_create_socket();
The listener needs to bind it first (note: simplified code):
sockaddr_any sa = CreateAddr("0.0.0.0", 5000);
srt_bind(sock, sa.get(), sa.len);
srt_listen(sock, 5);
sockaddr_in target;
SRTSOCKET connsock = srt_accept(sock, &target, sizeof target);
The caller side can use default system selected address and simply connect to the target:
SRTSOCKET connsock = srt_create_socket();
sockaddr_any sa = CreateAddr("target.address", 5000);
srt_connect(connsock, sa.get(), sa.len);
After the connection is established, you use the send/recv functions to
transmit the data. In this case we'll utilize the most advanced versions,
srt_sendmsg2
and srt_recvmsg2
.
Sender side does:
SRT_MSGCTRL mc = srt_msgctrl_default;
packetdata = GetPacketData();
srt_sendmsg2(connsock, packetdata.data(), packetdata.size(), &mc);
Receiver side does:
SRT_MSGCTRL mc = srt_msgctrl_default;
vector<char> packetdata(SRT_LIVE_DEF_PLSIZE);
int size = srt_recvmsg2(connsock, packetdata.data(), packetdata.size(), &mc);
packetdata.resize(size);
Except for several details, most of the API used for sockets can be used for
groups. Groups also have numeric identifiers, just like sockets, which
are in the same domain as sockets, except that there is one bit reserved to
indicate that the identifier is for a group, bound to a SRTGROUP_MASK
symbol.
IMPORTANT: Socket groups are designed to utilize specific features. The broadcast or backup group are designed to provide link redundancy (to keep transmission running in case when one of the links gets broken). The balancing groups allow to share the bandwidth load between links. In order to be able to utilize any of these features, every member link in the group must be routed through a different network path. Some terminal parts of these links can be common for them all - but if so, for these parts these features will not be used: a broken network path in this part would break all links at once, and the "balanced" traffic will go through one route path as a whole anyway. SRT has no possibility to check if you configured your links right. This means that on the caller side you need to use different target address for every link, while on the listener side you should use a different network device for every link.
For the listener side, note that groups only replace the communication socket. Listener sockets still have to be used:
SRTSOCKET sock = srt_create_socket();
To handle group connections, you need to set SRTO_GROUPCONNECT
option:
int gcon = 1;
srt_setsockflag(sock, SRTO_GROUPCONNECT, &gcon, sizeof gcon);
sockaddr_any sa = CreateAddr("0.0.0.0", 5000);
srt_bind(sock, sa.get(), sa.len);
srt_listen(sock, 5);
sockaddr_in target;
SRTSOCKET conngrp = srt_accept(sock, &target, sizeof target);
Here the (mirror) group will be created automatically upon the first connection
and srt_accept
will return its ID (not Socket ID). Further connections in the
same group will be then handled in the background. This conngrp
returned
here is however the exact ID you will use for transmission.
On the caller side, you start from creating a group first. We'll use the broadcast group type here:
SRTSOCKET conngrp = srt_create_group(SRT_GTYPE_BROADCAST);
This will need to make the first connection this way:
sockaddr_any sa = CreateAddr("target.address.link1", 5000);
srt_connect(conngrp, sa.get(), sizeof sa);
Then further connections can be done by calling srt_connect
again:
sockaddr_any sa2 = CreateAddr("target.address.link2", 5000);
srt_connect(conngrp, sa.get(), sa2.len);
IMPORTANT: This method can be easily used in non-blocking mode, as
you don't have to wait for the connection to be established. If you do
this in the blocking mode, the first srt_connect
call will block
until the connection is established. While it can be done this way,
it's usually unwanted.
So for blocking mode we use a different solution. Let's say, you have 3 addresses:
sockaddr_any sa1 = CreateAddr("target.address.link1", 5000);
sockaddr_any sa2 = CreateAddr("target.address.link2", 5000);
sockaddr_any sa3 = CreateAddr("target.address.link3", 5000);
You have to prepare the array for them and then use one group-connect function:
SRT_SOCKGROUPDATA gdata [3] = {
srt_prepare_endpoint(NULL, &sa1, sizeof sa1),
srt_prepare_endpoint(NULL, &sa2, sizeof sa2),
srt_prepare_endpoint(NULL, &sa3, sizeof sa3)
};
srt_connect_group(conngrp, gdata, 3);
This does the same as srt_connect
, but blocking rules are different:
it blocks until at least one connection from the given list is established.
Then it returns and allows the group to be used for transmission, while
continuing with the other connections in the background. Note that some group
types may require certain conditions to be satisfied, like a minimum number of
connections.
If you use non-blocking mode, then srt_connect_group
behaves the same as
running srt_connect
in a loop for all required endpoints.
Once the connection is ready, you use the conngrp
id for transmission, exactly
the same way as above for the sockets.
There's one additional thing to be covered here: just how much should the application be involved with socket groups?
The object of type SRT_MSGCTRL
is used to exchange some extra information with
srt_sendmsg2
and srt_recvmsg2
. Of particular interest in this case are two fields:
grpdata
grpdata_size
These fields have to be set to the pointer and size of an existing SRT_SOCKGROUPDATA
type array, which will be filled by this call (you can also obtain it separately
by the srt_group_data
function). The array must have a maximum possible size
to get information about every single member link. Otherwise it will not fill and
return the proper size in grpdata_size
.
The application should be interested here in two types of information:
- the size of the filled array
- the
sockstate
field in every element
From the sockstate
field you can track every member connection as to whether its
state is still SRTS_CONNECTED
. If a connection is detected as broken after
the call to a transmission function (srt_sendmsg2/srt_recvmsg2
) then the
connection will appear in these data only once, and with sockstate
equal to SRTS_BROKEN
. It will not appear anymore in later calls, and it won't
appear at all if you check the data through srt_group_data
.
Example:
SRT_SOCKGROUPDATA gdata[3];
SRT_MSGCTRL mc = srt_msgctrl_default;
mc.grpdata = gdata;
mc.grpdata_size = 3;
//...
srt_sendmsg2(conngrp, packetdata.data(), packetdata.size(), &mc);
for (int i = 0; i < 3; ++i)
if (mc.grpdata[i].sockstate == SRTS_BROKEN)
ReestablishConnection(mc.grpdata[i].id);
In the above example the socket ID is used to identify the
item in the application's link table, at which point a decision is made. If
the connection is to be revived, this function should call srt_connect
on it.
There might be only an attempt to establish the link, in which case
you'll get first the SRTS_CONNECTING
state here, and then a failed socket
will simply disappear. Therefore the function should also check how many
items were returned in this array, match them with existing connections,
and filter out connections that are unexpectedly not established.