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,是一个上限

  1. RM_SEND_WINDOW send_window;
  2. send_window.WindowSizeInBytes = 8000 * 1000;
  3. send_window.WindowSizeInMSecs = 1;
  4. send_window.RateKbitsPerSec = (send_window.WindowSizeInBytes/send_window.WindowSizeInMSecs)*8;
  5. int rc = setsockopt(s, IPPROTO_RM, RM_RATE_WINDOW_SIZE, (char *)&send_window, sizeof(send_window));
  6. if (rc == SOCKET_ERROR)
  7. {
  8. cout << "setsockopt(): RM_RATE_WINDOW_SIZE failed with error code " << WSAGetLastError() << endl;
  9. }

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

RateKbitsPerSec/8 = WindowSizeInBytes * WindowSizeInMSecs

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

真的可靠么?

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

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

源码

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

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

下面是server端代码

  1. #define _WINSOCK_DEPRECATED_NO_WARNINGS
  2. #define WIN32_LEAN_AND_MEAN
  3. #include <iostream>
  4. #include<winsock2.h>
  5. #include<WS2tcpip.h> //ip_mreqͷ
  6. #include <wsrm.h>
  7. #include <stdio.h>
  8. using namespace std;
  9. #pragma comment(lib,"ws2_32.lib")
  10. int main() {
  11. WSADATA WSAData;
  12. WORD sockVersion = MAKEWORD(2, 2);
  13. if (WSAStartup(sockVersion, &WSAData) != 0)
  14. return 0;
  15. FILE *fp;
  16. fopen_s(&fp, "test.webm", "rb+");
  17. SOCKET s;
  18. SOCKADDR_IN salocal, sasession;
  19. int dwSessionPort;
  20. s = socket(AF_INET, SOCK_RDM, IPPROTO_RM);
  21. salocal.sin_family = AF_INET;
  22. salocal.sin_port = htons(0); // Port is ignored here
  23. salocal.sin_addr.s_addr = htonl(INADDR_ANY);
  24. bind(s, (SOCKADDR *)&salocal, sizeof(salocal));
  25. //
  26. // Set all relevant sender socket options here
  27. //
  28. //
  29. // Now, connect <entity type="hellip"/>
  30. // Setting the connection port (dwSessionPort) has relevance, and
  31. // can be used to multiplex multiple sessions to the same
  32. // multicast group address over different ports
  33. //
  34. dwSessionPort = 1234;
  35. sasession.sin_family = AF_INET;
  36. sasession.sin_port = htons(dwSessionPort);
  37. sasession.sin_addr.s_addr = inet_addr("224.4.5.6");
  38. RM_SEND_WINDOW send_window;
  39. send_window.WindowSizeInBytes = 8000;
  40. send_window.WindowSizeInMSecs = 1;
  41. send_window.RateKbitsPerSec = (send_window.WindowSizeInBytes/send_window.WindowSizeInMSecs)*8;
  42. int rc = setsockopt(s, IPPROTO_RM, RM_RATE_WINDOW_SIZE, (char *)&send_window, sizeof(send_window));
  43. if (rc == SOCKET_ERROR)
  44. {
  45. cout << "setsockopt(): RM_RATE_WINDOW_SIZE failed with error code " << WSAGetLastError() << endl;
  46. }
  47. connect(s, (SOCKADDR *)&sasession, sizeof(sasession));
  48. //
  49. // We're now ready to send data!
  50. //
  51. char pSendBuffer[1400];
  52. sockaddr_in serverAddr;
  53. int iAddrlen = sizeof(serverAddr);
  54. while (1) {
  55. if (feof(fp))
  56. break;
  57. memset(pSendBuffer, 0, 1400);
  58. int data_size = fread(pSendBuffer, 1, 1400, fp);
  59. LONG error;
  60. error = sendto(s, pSendBuffer, data_size, 0, (sockaddr*)&serverAddr,iAddrlen);
  61. if (error == SOCKET_ERROR)
  62. {
  63. fprintf(stderr, "send() failed: Error = %d\n",
  64. WSAGetLastError());
  65. }
  66. }
  67. WSACleanup();
  68. return 0;
  69. }

下面是client端代码

  1. #include <iostream>
  2. #include<winsock2.h>
  3. #include<WS2tcpip.h> //ip_mreqͷ
  4. #include <wsrm.h>
  5. #include <stdio.h>
  6. using namespace std;
  7. #pragma comment(lib,"ws2_32.lib")
  8. int main() {
  9. WSADATA WSAData;
  10. WORD sockVersion = MAKEWORD(2, 2);
  11. if (WSAStartup(sockVersion, &WSAData) != 0)
  12. return 0;
  13. SOCKET s,
  14. sclient;
  15. SOCKADDR_IN salocal,
  16. sasession;
  17. int sasessionsz, dwSessionPort;
  18. FILE * fp;
  19. fopen_s(&fp, "aaatest.webm", "wb+");
  20. s = socket(AF_INET, SOCK_RDM, IPPROTO_RM);
  21. //
  22. // The bind port (dwSessionPort) specified should match that
  23. // which the sender specified in the connect call
  24. //
  25. dwSessionPort = 1234;
  26. salocal.sin_family = AF_INET;
  27. salocal.sin_port = htons(dwSessionPort);
  28. salocal.sin_addr.s_addr = inet_addr("224.4.5.6");
  29. int receive_buf_size = 65536 * 10;
  30. if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char*)&receive_buf_size, sizeof(receive_buf_size)) < 0)
  31. {
  32. std::cout << "setsockopt():SO_RCVBUF failed with error code" << WSAGetLastError() << std::endl;
  33. }
  34. bind(s, (SOCKADDR *)&salocal, sizeof(salocal));
  35. //
  36. // Set all relevant receiver socket options here
  37. //
  38. listen(s, 10);
  39. sasessionsz = sizeof(sasession);
  40. sclient = accept(s, (SOCKADDR *)&sasession, &sasessionsz);
  41. if (setsockopt(sclient, SOL_SOCKET, SO_RCVBUF, (char*)&receive_buf_size, sizeof(receive_buf_size)) < 0)
  42. {
  43. std::cout << "setsockopt():SO_RCVBUF failed with error code" << WSAGetLastError() << std::endl;
  44. }
  45. //
  46. // accept will return the client socket and we are now ready
  47. // to receive data on the new socket!
  48. //
  49. LONG BytesRead;
  50. char pTestBuffer[1400];
  51. sockaddr_in clientAddr;
  52. int iAddrlen = sizeof(clientAddr);
  53. while (1)
  54. {
  55. memset(pTestBuffer, 0, 1400);
  56. cout << "start" << endl;
  57. BytesRead = recvfrom(sclient, pTestBuffer, 1400, 0, (sockaddr*)&clientAddr, &iAddrlen);
  58. cout << "end" << endl;
  59. if (BytesRead == 0)
  60. {
  61. fprintf(stdout, "Session was terminated\n");
  62. }
  63. else if (BytesRead == -1)
  64. {
  65. std::cout << "no data?!" << std::endl;
  66. }
  67. if (BytesRead > 0)
  68. {
  69. fwrite(pTestBuffer, 1, BytesRead, fp);
  70. std::cout << BytesRead << std::endl;
  71. }
  72. }
  73. fclose(fp);
  74. WSACleanup();
  75. return 0;
  76. }

Reliable Multicast Programming(PGM)协议的更多相关文章

  1. RSVP协议的基本概念介绍

    2010-06-12 14:12 佚名 互联网 字号:T | T 对于RSVP协议的简单介绍和图解.通过文章,我们将对这个含义的基本概念和结构,以及工作方式等方面的知识有所了解.希望对大家有所帮助. ...

  2. 云计算(9)--Gossip:multicast problem

    Gossip/Epidemic ptotocol 解决的问题是multicast problem Gossip 协议是电脑之间的通信协议,受启发与现实社会的流言蜚语.现代分布式系统通常用gossip协 ...

  3. JMS学习十(ActiveMQ支持的传输协议)

    ActiveMQ提供了一种连接机制,这种连接机制使用传输连接器(TransportConnector)实现客户端与代理(client - to - broker)之间的通信. 网络连接器(networ ...

  4. 111个知名Java项目集锦,包括url和描述

    转:http://www.cnblogs.com/wangs/p/3282183.html 项目名称   项目描述 ASM Java bytecode manipulation framework A ...

  5. 2015-07学习总结——网络编程(TCP/IP)

    之前学习的主要内容是单机上的处理,比如编程语言.游戏编程.数据库.多媒体编解码.其实对网络也有些接触,比如WWW.HTTP.UDP/TCP.RTP.RTMP.SNMP.FTP.单播组播.Telnet. ...

  6. 分布式系统一致性问题和Raft一致性算法

    一致性问题 一致性算法是用来解决一致性问题的,那么什么是一致性问题呢? 在分布式系统中,一致性问题(consensus problem)是指对于一组服务器,给定一组操作,我们需要一个协议使得最后它们的 ...

  7. [转载] 一致性问题和Raft一致性算法

    原文: http://daizuozhuo.github.io/consensus-algorithm/ raft 协议确实比 paxos 协议好懂太多了. 一致性问题 一致性算法是用来解决一致性问题 ...

  8. IP组播技术介绍及实现例子

    引 言 近年来,随着Internet的迅速普及和爆炸性发展,在Internet上产生了许多新的应用,其中不少是高带宽的多媒体应用,譬如网 络视频会议.网络音频/视频广播.AOD/VOD.股市行情发布. ...

  9. ZeroMQ 教程 002 : 高级技巧

    本文主要译自 zguide - chapter two. 但并不是照本翻译. 上一章我们简单的介绍了一个ZMQ, 并给出了三个套路的例子: 请求-回应, 订阅-发布, 流水线(分治). 这一章, 我们 ...

随机推荐

  1. ES6兼容ie9, flex兼容ie9

    vue兼容ES6 在 ie9 的环境上,es6 的部分新对象.表达式,并不支持,解决方案是使用 babel-polyfill 组件,它可以将 es6 的代码翻译成低版本浏览器可以识别的 es5 代码 ...

  2. Bugku 多次

    网址:http://123.206.87.240:9004/1ndex.php?id=1 前言:bugku中一涉及多次注入的题 1.异或注入(判断字符是否被过滤) 0X00   很明显 注入点在id上 ...

  3. 2 Linux磁盘管理

    Linux磁盘管理:磁盘管理好坏直接关系到整个系统的性能问题常用三个命令:df.du.fdiskdf:列出文件系统的整体磁盘使用量 df 参数 目录或文件名 -a:理出所有文件系统,包括系统特有的 / ...

  4. 使用vnc远程操控Centos7.6

    安装vncserver [root@elegant-snap-3 ~]# yum install tigervnc-server -y Loaded plugins: fastestmirror De ...

  5. MMORPG服务器场景中的aoi算法思考

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/lwtbn1/article/details/37961695 最近在做一个移动平台上的MMORPG项 ...

  6. python参数传递

    1.形式参数:在定义函数时,函数名后面括号中的参数为“形式参数”,也称形参 2.实际参数:在调用一个函数时,函数名后面括号种的参数为“实际参数”,也就是将函数的调用者提供给函数的参数称为实际参数,也称 ...

  7. ABP 报错 System.InvalidOperationException: 实例失败。

    错误:System.InvalidOperationException: 实例失败. 解决:数据库链接写错了,斜杠的问题 core:两根斜杠 .net framework:一根斜杠 core的写法: ...

  8. Spring---SSH整合(二)

    基于Spring---SSH整合,使用SSH编写后台: User模块层 TreeNode.hbm.xml <?xml version="1.0" encoding=" ...

  9. BFS实现8数码问题,思考与总结

    BFS实现8数码问题,思考与总结 今天中午学习了二叉树的线索化与线索化遍历,突然有一种想实现八数码问题的冲动,因为它的初级解决方式是BFS(广度优先搜索算法).于是我开始编程. 没想到一编就是一个下午 ...

  10. shell 命令行

    转:Davygeek 1. -eq           //等于 -ne           //不等于 -gt            //大于 (greater ) -lt            / ...