Reliable Multicast Programming (PGM) Protocol

Reliable Multicast Programming (PGM) is a general reliable multicast protocol that guarantees the reliability of multicast to some extent.It is an IP upper-level protocol, and has UDP peers with TCP, working in the transport layer.

In the multicast transmission video project, it is found that when the network is poor, the performance of multicast transmission video degrades rapidly, and the multicast video can hardly be seen directly. It is no longer a mosaic problem, it is simply a smelly cloth.

However, the above requirements are for the receiver to reach 1080p 16fps playback effect, when the real-time network rate of the multicast receiver is only about 50KB/s. In this case, if you want to handle it from the software (because the router is not replaced well), you need to reduce the multicast packet loss rate, but the iperf is used to measure the network packet loss at that time.Rate, 80% to his grandmother's house?

However, the bandwidth utilization is very low at this time. Try to change to udp unicast immediately. The speed can go up and the screen is not too fancy. It is not clear whether this is a problem of the confirmation mechanism.
However, it is not always possible to change multicast to unicast. When more receivers are accessed, it is not clear if the unicast effect will also deteriorate.

At this time, PGM was discovered, the "reliable" multicast protocol, there are many libraries based on PGM implementation, plan to write demo on windows first.

To use PGM, you need to first install the protocol on the network adapter, which will result in a reliable multicast protocol in the properties.

Then there's the development. The demo provided by the official website documentation is so great that you can copy it and almost run.But apart from the official website documents, there is less relevant information. I have been searching for the header files for half a day, and there are many pits in the environment. Take a note of them

wsrm.h header file

First of all, Windows sdk. I tried to find the wsrm.h header file if it is 8.1 sdk. I have three versions of windows sdk, 10.0.17134.0, 8.1 and 10.0.15063.0. I looked for this header file with everythin and got the result of the following illustration

8.1 should not, the remaining two versions are available, and the newer version should be available as well.

If vs2017 is above, modify more than one sdk in the visual studio installer

transmission speed

PGM itself also has the concept of a send window. If you use the default settings, the window is small and the sending speed is very slow, with a maximum of around 70KB per second, you need to set the socket option at this time
The unit of WindowSizeInBytes is kilobits/s, which is an upper limit

    RM_SEND_WINDOW send_window;
    send_window.WindowSizeInBytes = 8000 * 1000;
    send_window.WindowSizeInMSecs = 1;
    send_window.RateKbitsPerSec = (send_window.WindowSizeInBytes/send_window.WindowSizeInMSecs)*8;


    int rc = setsockopt(s, IPPROTO_RM, RM_RATE_WINDOW_SIZE, (char *)&send_window, sizeof(send_window));
    if (rc == SOCKET_ERROR)
    {

        cout << "setsockopt(): RM_RATE_WINDOW_SIZE failed with error code " << WSAGetLastError() << endl;

    }

The RM_SEND_WINDOW structure has three members: the first is the speed per second, the second is the size of the sending window, and the third is the size of the window in milliseconds, where windows forces the
RateKbitsPerSec/8 = WindowSizeInBytes * WindowSizeInMSecs

PS: The value of WindowSzieInMSecs needs to be adjusted. When WindowSizeInBytes=8000 and WindowSzieInMSecs=1, the sender probability will be blocked for an unknown reason, possibly because the outgoing speed is too fast

Really reliable?

The article was initially reliably quoted to indicate that the protocol was not as reliable as it might have been.

The sending window is limited in size. If the data that needs to be retransmitted is outside the sending window, the data will not be recoverable. Generally, when the sender's rate is too fast and the receiver's reception speed is obviously not up to speed, the unrecoverable phenomenon will occur.Once unrecoverable data occurs, windows resets the connection at the receiver and cannot continue receiving.

Source code

Because it has been a long time since I couldn't find the corresponding header file, there are few related documents, and I still can't tell you what the header file is. It's so uncomfortable, it's like letting you see what's behind the door, or just not giving you the key.

pgm is divided into server side and client side, the function is to send files, according to Documentation for msdn Written

Here is the server-side code

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define WIN32_LEAN_AND_MEAN

#include <iostream>
#include<winsock2.h>
#include<WS2tcpip.h>    //ip_mreqͷ
#include <wsrm.h>
#include <stdio.h>

using namespace std;
#pragma comment(lib,"ws2_32.lib")

int main() {
    WSADATA WSAData;
    WORD sockVersion = MAKEWORD(2, 2);
    if (WSAStartup(sockVersion, &WSAData) != 0)
        return 0;
    
    FILE *fp;
    fopen_s(&fp, "test.webm", "rb+");

    SOCKET        s;
    SOCKADDR_IN   salocal, sasession;
    int           dwSessionPort;

    s = socket(AF_INET, SOCK_RDM, IPPROTO_RM);

    salocal.sin_family = AF_INET;
    salocal.sin_port = htons(0);    // Port is ignored here
    salocal.sin_addr.s_addr = htonl(INADDR_ANY);

    bind(s, (SOCKADDR *)&salocal, sizeof(salocal));

    //
    // Set all relevant sender socket options here
    //

    //
    // Now, connect <entity type="hellip"/>
    // Setting the connection port (dwSessionPort) has relevance, and
    // can be used to multiplex multiple sessions to the same
    // multicast group address over different ports
    //
    dwSessionPort = 1234;
    sasession.sin_family = AF_INET;
    sasession.sin_port = htons(dwSessionPort);
    sasession.sin_addr.s_addr = inet_addr("224.4.5.6");

    RM_SEND_WINDOW send_window;
    send_window.WindowSizeInBytes = 8000;
    send_window.WindowSizeInMSecs = 1;
    send_window.RateKbitsPerSec = (send_window.WindowSizeInBytes/send_window.WindowSizeInMSecs)*8;

    int rc = setsockopt(s, IPPROTO_RM, RM_RATE_WINDOW_SIZE, (char *)&send_window, sizeof(send_window));
    if (rc == SOCKET_ERROR)
    {

        cout << "setsockopt(): RM_RATE_WINDOW_SIZE failed with error code " << WSAGetLastError() << endl;

    }
    connect(s, (SOCKADDR *)&sasession, sizeof(sasession));

    //
    // We're now ready to send data!
    //
    char pSendBuffer[1400];

    sockaddr_in serverAddr;
    int iAddrlen = sizeof(serverAddr);




    while (1) {
        if (feof(fp))
            break;
        memset(pSendBuffer, 0, 1400);

        int data_size = fread(pSendBuffer, 1, 1400, fp);
        
        LONG        error;

        error = sendto(s, pSendBuffer, data_size, 0, (sockaddr*)&serverAddr,iAddrlen);

        if (error == SOCKET_ERROR)
        {
            fprintf(stderr, "send() failed: Error = %d\n",
                WSAGetLastError());
        }
    }

    WSACleanup();
    return 0;
}

Here is the client-side code

#include <iostream>
#include<winsock2.h>
#include<WS2tcpip.h>    //ip_mreqͷ
#include <wsrm.h>
#include <stdio.h>

using namespace std;
#pragma comment(lib,"ws2_32.lib")

int main() {
    WSADATA WSAData;
    WORD sockVersion = MAKEWORD(2, 2);
    if (WSAStartup(sockVersion, &WSAData) != 0)
        return 0;


    SOCKET        s,
        sclient;
    SOCKADDR_IN   salocal,
        sasession;
    int           sasessionsz, dwSessionPort;

    FILE * fp;
    fopen_s(&fp, "aaatest.webm", "wb+");

    s = socket(AF_INET, SOCK_RDM, IPPROTO_RM);

    //
    // The bind port (dwSessionPort) specified should match that
    // which the sender specified in the connect call
    //
    dwSessionPort = 1234;
    salocal.sin_family = AF_INET;
    salocal.sin_port = htons(dwSessionPort);
    salocal.sin_addr.s_addr = inet_addr("224.4.5.6");
    int receive_buf_size = 65536 * 10;
    if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char*)&receive_buf_size, sizeof(receive_buf_size)) < 0)
    {
        std::cout << "setsockopt():SO_RCVBUF failed with error code" << WSAGetLastError() << std::endl;
    }


    bind(s, (SOCKADDR *)&salocal, sizeof(salocal));

    //
    // Set all relevant receiver socket options here
    //

    listen(s, 10);

    sasessionsz = sizeof(sasession);
    sclient = accept(s, (SOCKADDR *)&sasession, &sasessionsz);

    if (setsockopt(sclient, SOL_SOCKET, SO_RCVBUF, (char*)&receive_buf_size, sizeof(receive_buf_size)) < 0)
    {
        std::cout << "setsockopt():SO_RCVBUF failed with error code" << WSAGetLastError() << std::endl;
    }
    //
    // accept will return the client socket and we are now ready
    // to receive data on the new socket!
    //
    LONG BytesRead;
    char pTestBuffer[1400];

    sockaddr_in clientAddr;
    int iAddrlen = sizeof(clientAddr);

    while (1)
    {
        memset(pTestBuffer, 0, 1400);
        cout << "start" << endl;
        BytesRead = recvfrom(sclient, pTestBuffer, 1400, 0, (sockaddr*)&clientAddr, &iAddrlen);
        cout << "end" << endl;
        if (BytesRead == 0)
        {
            fprintf(stdout, "Session was terminated\n");
        }
        else if (BytesRead == -1)
        {
            std::cout << "no data?!" << std::endl;
        }
        if (BytesRead > 0)
        {
            fwrite(pTestBuffer, 1, BytesRead, fp);
            std::cout << BytesRead << std::endl;
        }
    }
    fclose(fp);
    WSACleanup();
    return 0;
}

Keywords: C++ socket Windows network SDK

Added by FrostedFlakes on Sat, 10 Aug 2019 04:39:39 +0300