上次说到屏幕广播可以通过屏幕差别对比来减小数据传输量。这样虽然有效果,但是如果PC端一多的话,作用还是显得乏力。所以,这只能运用于PC少的情况的下。
在我们组大神昊哥的提醒下,我决定用winsock2写一个IP组播。
IP组播是对硬件组播的抽象,是对标准IP网络层协议的扩展。它通过使用特定的IP组播地址,按照最大投递的原则,将IP数据包传输到一个组播群组的主机集合。它的基本方法是:当某一个人向一组人发送数据时,它不必将数据向每一个人都发送数据,只需将数据发送到一个特定的预约的组地址,所有加入该组的人均可以收到这份数据。这样对发送者而言,数据只需发送一次就可以发送到所有接收者,大大减轻了网络的负载和发送者的负担。
关于组播的地址:
224.0.0.0--239.255.255.255,没有像单播ip段那样有广播地址和网络地址之分了。
具体:224.0.0.0--224.0.0.255 本地保留,给知名协议使用,ttl=1。其中224.0.0.1是本网所有主机接收,224.0.0.2是本网所有路由器接收。
224.0.1.0~238.255.255.255 预留组播地址,[1]多播地址应从此范围内选择。
239.0.0.0--239.255.255.255 私有组播地址。
232.0.0.0--232.255.255.255 特定源多播。
具体:224.0.0.0--224.0.0.255 本地保留,给知名协议使用,ttl=1。其中224.0.0.1是本网所有主机接收,224.0.0.2是本网所有路由器接收。
224.0.1.0~238.255.255.255 预留组播地址,[1]多播地址应从此范围内选择。
239.0.0.0--239.255.255.255 私有组播地址。
232.0.0.0--232.255.255.255 特定源多播。
在组播里面是没有明确的服务器和客户机的概念。就相当于大家加进了一个小组。只要一个组员发布消息,其他的都能知道。
不过在屏幕广播里面只有一台PC需要发送数据。所以我还是分成两部分来写,服务端发,客户端收。
// 组播(服务器).cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <sys/types.h> #include <WinSock2.h> #include <windows.h> #include <iostream> #include <ws2tcpip.h> using namespace std; #pragma comment(lib,"ws2_32.lib") #define BUFFSIZE 2048 #define MCASTADDR "224.8.8.8" #define MCASTPORT 8888 int _tmain(int argc, _TCHAR* argv[]) { //定义变量 int sockfd,sockr; struct sockaddr_in addr,local; char szError[100]; char buf[]="Hellp,World!"; int ttl = 255;//随便改 //初始化 WSADATA WSAData; WORD wVersionRequested; wVersionRequested = MAKEWORD(2,2); if (WSAStartup(wVersionRequested,&WSAData)!=0) { printf("WinSock启动失败 "); exit(1); } if ((sockfd = WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,WSA_FLAG_MULTIPOINT_C_LEAF|WSA_FLAG_MULTIPOINT_D_LEAF|WSA_FLAG_OVERLAPPED))==INVALID_SOCKET) { printf("socket failed with:%d ",WSAGetLastError()); WSACleanup(); return -1; } addr.sin_family = AF_INET; addr.sin_port = htons(8888); addr.sin_addr.s_addr = inet_addr("224.8.8.1"); if ((sockr = WSAJoinLeaf(sockfd,(SOCKADDR*)&addr,sizeof(addr),NULL,NULL,NULL,NULL,JL_SENDER_ONLY)==INVALID_SOCKET)) { printf("WSAJoinLeaf() failed:%d ",WSAGetLastError()); closesocket(sockfd); WSACleanup(); return -1; } while (TRUE) { memset(buf,0,sizeof(buf)); cin>>buf; if (sendto (sockfd, buf, sizeof(buf) , 0, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR) { wsprintf ((LPWSTR)szError, TEXT("sendto failed! Error: %d"), WSAGetLastError ()); MessageBox (NULL, (LPCWSTR)szError, TEXT("Error"), MB_OK); closesocket (sockfd); return FALSE; } else { printf("send ok "); } } closesocket (sockfd); closesocket (sockr); WSACleanup (); return 0; }
接下来是客户端
// 组播(客户端).cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <sys/types.h> #include <ws2tcpip.h> #include <WinSock2.h> #include <windows.h> #include <iostream> using namespace std; #pragma comment(lib,"ws2_32.lib") #define BUFFSIZE 2048 #define MCASTADDR "224.8.8.8" #define MCASTPORT 8888 int _tmain(int argc, _TCHAR* argv[]) { int sockfd,sockr; int sock_reuse = 1; struct sockaddr_in recver_addr,local; struct ip_mreq multicast; char szError[100]; struct ip_mreq mcast; int index = 0,iRecvLen; char szMessageA[1024*320]; WSADATA WSAData; WORD wVersionRequested; wVersionRequested = MAKEWORD(2,2); if (WSAStartup (wVersionRequested , &WSAData) != 0) { printf ("recver:Initialize Winsock error!"); exit(1); } if ((sockfd=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0, WSA_FLAG_MULTIPOINT_C_LEAF|WSA_FLAG_MULTIPOINT_D_LEAF|WSA_FLAG_OVERLAPPED))==INVALID_SOCKET) { printf("socket failed with:%d ",WSAGetLastError()); WSACleanup(); return -1; } // local.sin_family = AF_INET; // local.sin_port = htons(MCASTPORT); // local.sin_addr.s_addr = INADDR_ANY; // recver_addr.sin_family = AF_INET; recver_addr.sin_port = htons(MCASTPORT); recver_addr.sin_addr.s_addr = inet_addr(MCASTADDR); if (sockr = WSAJoinLeaf(sockfd,(SOCKADDR*)&recver_addr,sizeof(recver_addr),NULL,NULL,NULL,NULL,JL_RECEIVER_ONLY)==INVALID_SOCKET) { printf("WSAJoinLeaf() failed:%d ",WSAGetLastError()); closesocket(sockfd); WSACleanup(); return -1; } printf("Receive on %s:%d ", MCASTADDR,MCASTPORT); iRecvLen = sizeof (recver_addr); memset(szMessageA, 0, sizeof(szMessageA)); while (true) { if (recvfrom (sockfd, szMessageA, sizeof(szMessageA), 0, (struct sockaddr FAR *) &recver_addr, &iRecvLen) == SOCKET_ERROR) { wsprintf ((LPWSTR)szError, TEXT("recvfrom failed! Error: %d"), WSAGetLastError ()); MessageBox (NULL, (LPCWSTR)szError, TEXT("Error"), MB_OK); closesocket (sockfd); return FALSE; } else { cout<<szMessageA<<endl; } } shutdown (sockfd, 0x00); closesocket (sockfd); closesocket(sockr); WSACleanup (); return 0; }
这两个代码就是服务器发送数据到224.8.8.8:8888,客户端再从224.8.8.8:8888接收。
这样一来,大大的减少了数据传输量。 不过IP组播用的是UDP协议,这是不可靠的协议,可能会丢包。至于解决方法,明天再写吧。~~
PS:两个代码不能在同一台电脑运行,会出现套接字冲突。
参考文章:http://xue23.blog.163.com/blog/static/9793442005614355170/