zoukankan      html  css  js  c++  java
  • Reliable Multicast Programming(PGM)协议

    Reliable Multicast Programming (PGM)实际通用可靠多播协议,在某种程度上保证多播的可靠性。是IP上层协议,和TCP还有UDP同级,工作在传输层。

    在组播传输视频项目中,发现在网络较差的时候,组播传输视频性能下降迅猛,组播的视频几乎到了无法直视的地步,已经不是马赛克什么的问题了,简直就是一张臭抹布。

    但是上面的要求是让接收端达到1080p 16fps的播放效果,此时组播接收端的实时网络速率只有50KB/s左右,这种情况下要从软件上处理的话(因为路由器不好换),需要让组播的丢包率降低才行,但是使用iperf测了下当时网络的丢包率,能丢到80%,丢到他姥姥家?

    但是此时带宽利用率却很低,赶紧换成udp单播试了一下,速度能上去,也不怎么花屏了,不清楚是不是确认机制的问题。
    但是总不能说把组播换成单播,当接入的接收端变多的时候,不清楚单播效果会不会也变差。

    这个时候发现了PGM,“可靠”多播协议,有不少基于PGM实现的库,打算先用windows上的写个demo出来。

    想要使用PGM需要先在网络适配器上安装协议,安装完成后会在属性中出现可靠多播协议

    然后就是开发了,官网文档提供的demo很棒,copy下来几乎就能跑起来。但是除了官网文档,相关资料就比较少了,头文件我还找了半天,环境上坑不少,记录一下

    wsrm.h头文件

    首先是windows sdk,我试了一下如果是8.1的sdk的话,是找不到wsrm.h头文件的,我有装10.0.17134.0,8.1还有10.0.15063.0三个版本的windows sdk,用everything找了一下这个头文件,得到了下面图示结果

    8.1应该是没有,剩下两个版本均可以使用,更新的版本应该也行。

    vs2017以上的话在visual studio installer里面修改多装个sdk就行了

    传输速度

    PGM本身也有发送窗口的概念,如果使用默认设置,窗口小,发送速度非常慢,每秒最多只有70KB左右,这时候需要设置socket选项
    RateKbitsPerSec 的单位是kilobits/s,是一个上限

        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;
    
        }
    

    RM_SEND_WINDOW结构体就这么三个成员,第一个是每秒速度了,第二个是发送窗口的大小,第三个是窗口大小毫秒,其中windows会强制让
    RateKbitsPerSec/8 = WindowSizeInBytes * WindowSizeInMSecs

    PS:WindowSzieInMSecs的值需要调整,当WindowSizeInBytes=8000并且WindowSzieInMSecs=1时,发送端较大概率会阻塞,原因未知,可能是发包速度过快导致

    真的可靠么?

    在文章一开始的时候可靠被加上了双引号,为的是表明这个协议并不是想象中的那么可靠。

    发送窗口大小有限,如果需要恢复重传的数据在发送窗口之外了,那数据就是不可恢复的,一般当发送端速率过快接收端接收速度明显跟不上时,就会出现不可恢复现象。一旦出现不可恢复数据时,windows就会让接收端的连接重置,此时就不能继续接收。

    源码

    因为找不到对应的头文件让我着实头疼了很久,相关文档少,还不告诉我头文件是什么,这太不爽了,就好比让你看着门后面有啥,就是不给你钥匙。

    pgm分为server端和client端,功能是发送文件,根据msdn的文档编写的

    下面是server端代码

    #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
    ",
                    WSAGetLastError());
            }
        }
    
        WSACleanup();
        return 0;
    }
    

    下面是client端代码

    #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
    ");
            }
            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;
    }
    
  • 相关阅读:
    Oracle 定时查询数据插入新表中(job+存储过程)
    serialVersionUID的作用
    如何解决"The superclass "javax.servlet.http.HttpServlet" was not found on the Java Build Path"
    如何解决maven archetype加载太慢的方法
    Java中NIO、BIO、AIO相关概念及应用场景
    Sql多条件排序
    Oracle学习笔记—Oracle左连接、右连接、全外连接以及(+)号用法(转载)
    Oracle学习笔记—oracle体系架构及状态(nomount、mount和open)简介
    Oracle学习笔记—归档模式
    Oracle学习笔记—Db_name、Db_domain、Global_name、Service_name、Instance_name和Oracle_SID(转载)
  • 原文地址:https://www.cnblogs.com/lenomirei/p/11324394.html
Copyright © 2011-2022 走看看