IP多播(也称多址广播或组播)技术,是一种允许一台或多台主机(多播源)发送单一数据包到多台主机(一次的,同时的)的TCP/IP网络技术。多播作为一点对多点的通信,是节省网络带宽的有效方法之一。在网络音频/视频广播的应用中,当需要将一个节点的信号传送到多个节点时,无论是采用重复点对点通信方式,还是采用广播方式,都会严重浪费网络带宽,只有多播才是最好的选择。多播能使一个或多个多播源只把数据包发送给特定的多播组,而只有加入该多播组的主机才能接收到数据包。目前,IP多播技术被广泛应用在网络音频/视频广播、AOD/VOD、网络视频会议、多媒体远程教育、“push”技术(如股票行情等)和虚拟现实游戏等方面。   一、IP多播技术简介  1.IP多播地址和多播组
  IP多播通信必须依赖于IP多播地址,在IPv4中它是一个D类IP地址,范围从224.0.0.0到239.255.255.255,并被划分为局部链接多播地址、预留多播地址和管理权限多播地址三类。其中,局部链接多播地址范围在224.0.0.0~224.0.0.255,这是为路由协议和其它用途保留的地址,路由器并不转发属于此范围的IP包;预留多播地址为224.0.1.0~238.255.255.255,可用于全球范围(如Internet)或网络协议;管理权限多播地址为239.0.0.0~239.255.255.255,可供组织内部使用,类似于私有IP地址,不能用于Internet,可限制多播范围。  使用同一个IP多播地址接收多播数据包的所有主机构成了一个主机组,也称为多播组。一个多播组的成员是随时变动的,一台主机可以随时加入或离开多播组,多播组成员的数目和所在的地理位置也不受限制,一台主机也可以属于几个多播组。此外,不属于某一个多播组的主机也可以向该多播组发送数据包。   2.IP多播技术的硬件支持 
  要实现IP多播通信,要求介于多播源和接收者之间的路由器、集线器、交换机以及主机均需支持IP多播。目前,IP多播技术已得到硬件、软件厂商的广泛支持。   (1)主机 
  支持IP多播通信的平台包括Windows CE 2.1、Windows 95、Windows 98、Windows NT 4和Windows 2000等,运行这些操作系统的主机都可以进行IP多播通信。此外,新生产的网卡也几乎都提供了对IP多播的支持。 
  (2)集线器和交换机 
  目前大多数集线器、交换机只是简单地把多播数据当成广播来发送接收,但一些中、高档交换机提供了对IP多播的支持。例如,在3COM SuperStack 3 Swith 3300交换机上可启用802.1p或IGMP多播过滤功能,只为已侦测到IGMP数据包的端口转发多播数据包。 
  (3)路由器 
  多播通信要求多播源节点和目的节点之间的所有路由器必须提供对Internet组管理协议(IGMP)、多播路由协议(如PIM、DVMRP等)的支持。   当一台主机欲加入某个多播组时,会发出“主机成员报告”的IGMP消息通知多播路由器。当多播路由器接收到发给那个多播组的数据时,便会将其转发给所有的多播主机。多播路由器还会周期性地发出“主机成员查询”的IGMP消息,向子网查询多播主机,若发现某个多播组已没有任何成员,则停止转发该多播组的数据。此外,当支持IGMP v2的主机(如Windows 98/2000计算机)退出某个多播组时,还会向路由器发送一条“离开组”的IGMP消息,以通知路由器停止转发该多播组的数据。但只有当子网上所有主机都退出某个多播组时,路由器才会停止向该子网转发该多播组的数据。  使用多播路由协议,路由器可建立起从多播源节点到所有目的节点的多播路由表,从而实现在子网间转发多播数据包。例如,PIM(协议独立多播)就是一种多播路由协议,它有两种类型:稀疏模式(sparse-mode)和密集模式(dense-mode)。以Cisco 2621路由器为例,启用IP多播转发功能的基本设置如下:   c2621(config)# ip multicast-routing 启动IP多播,使路由器成为一个多播路由器 
  c2621(config)# int f0/0 配置快速以太网端口0 
  c2621(config-if)# ip pim dense-mode(或sparse-mode)启动PIM,同时激活IGMP协议 
  c2621(config-if)# int f0/1 配置快速以太网端口1
  c2621(config-if)# ip pim dense-mode(或sparse-mode)   二、IP多播应用的编程方法  在实际应用中,编程人员通常需要自己编制底层网络应用程序来实现网上的底层通信,如具体实现IP多播通信的功能。编制底层网络应用程序通常要借助于网络数据通信编程接口,而在不同的操作系统中所提供的网络编程接口是有所不同的,如在Microsoft Windows环境下的网络编程接口就是Windows套接字(Windows Socket,简称Winsock)。 
Winsock提供了包括TCP/IP、IPX等多种通信协议下的编程接口。不同的Windows版本支持不同的Winsock版本,其中Windows 95等早期版本本身只支持Winsock1.1(16位)下的编程(可以通过安装相关的软件包使其支持Winsock2.0),而Windows98、Windows NT4.0、Windows 2000则直接支持Winsock2.0(32位)。Winsock2.0是Winsock1.1的扩展,除兼容Winsock1.1 API外,还定义了一套可支持IP多播的与协议无关的API。   使用Winsock 2.0实现IP多播的一般步骤如下:   1.初始化Winsock资源 
  在使用Winsock之前,必须调用WSAStartup()函数初始化Windows Sockets DLL。它允许应用程序或DLL指定Windows Sockets API要求的版本。   2.创建套接字 
  调用WSASocket()函数可以创建一个使用UDP协议的套接字,它是加入多播组的初始化套接字,并且以后数据的发送和接收都在该套接字上进行。针对IP多播通信,可将参数dwFlags设置为WSA_FLAG_MULTIPOINT_C_LEAF、WSA_FLAG_MULTIPOINT_D_LEAF和WSA_FLAG_OVERLAPPED的位和,指明IP多播通信在控制层面和数据层面都是“无根的”,只存在叶节点,它们可以任意加入一个多播组,而且从一个叶节点发送的数据会传送到每一个叶节点(包括它自己);创建的套接字具有重叠属性。   3.设置套接字的选项 
  调用setsockopt()函数为套接字设置SO_REUSEADDR选项,以允许套接字绑扎到一个已在使用的地址上。   4.绑定套接字 
  调用bind()函数绑定套接字,从而将创建好的套接字与本地地址和本地端口联系起来。对于多播通信来说,发送和接收数据通常采用同一个端口。   5.设置多播套接字的模式 
  WSAIoctl()函数的命令码SIO_MULTICAST_LOOP用来允许或禁止多播通信时发送出去的通信流量是否也能够在同一个套接字上被接收(即多播返回)。值得注意的是,在Windows 95/98/NT 4中,默认是允许多播返回,但不能设置禁止,否则会出错;只有在Windows 2000以上版本中,才能设置允许/禁止多播返回。 
  WSAIoctl()函数的命令码SIO_MULTICAST_SCOPE用来设置多播传播的范围,即生存时间TTL。每当多播路由器转发多播数据包时,数据包中的TTL值都会被减1,若数据包的TTL减少到0,则路由器将抛弃该数据包。TTL的值是多少,多播数据便最多能经过多少个多播路由器。例如,TTL值为0,则多播只能在本地主机的多个套接字间传播,而不能传播到“网线”上;TTL值为1(默认值),则多播数据遇到第一个路由器,便会被它“无情”地丢弃,不允许传出本地网络之外,即只有同一个网络内的多播组成员才能收到多播数据。   6.加入一个多播组 
  调用WSAJoinLeaf()函数可加入一个多播组并指定角色(发送者/接收者)。调用时,参数dwFlags可指定套接字作为发送者(JL_SENDER_ONLY)、接收者(JL_RECEIVER_ONLY)或身兼两者(JL_BOTH)。调用成功后会返回一个多播套接字,调用closesocket()函数关闭该套接字就离开了多播组,此时可以调用WSAJoinLeaf()函数再次加入多播组。注意,对多播组数据的接收和发送不能在该套接字上完成。   7.向多播组发送数据 
  调用sendto()函数,可在指定的UDP套接字上向指定的多播组发送多播数据。调用时,参数to应指向多播组的IP地址。值得注意的是,若一个应用程序只是打算给多播组发送数据,便不必加入一个多播组。   8.等待事件 
  调用WSAAsyncSelect()函数,使套接字置于非阻塞模式,这时应用程序就可在该套接字上接收以Windows消息为基础的网络事件通知。例如,若参数lEvent值为FD_READ,则应用程序可在套接字上接收到“数据正等待被读入”的通知。 
  9.从多播组接收数据 
  调用recvfrom函数,可在指定的UDP套接字上读取输入数据。多播通信中数据的发送与接收一般采用同一个端口,因此其发送套接字和接收套接字是一样的。  10.关闭套接字,释放Winsock资源。 
  在多播通信结束后,先调用closesocket()函数关闭多播套接字和UDP套接字,然后调用WSACleanup()函数结束对Windows Sockets DLL的使用。   三、应用实例 
  为了说明IP多播技术的应用方法,本人在Visual C++.NET环境下设计了一个简单的基于Windows Socket 2的IP多播应用程序,通过该例子读者可以掌握IP多播应用程序设计的一般方法。该程序的具体设计方法如下:   1.在Visual Studio.NET中建立一个基于对话框的MFC项目CMulticastSocket。注意在“高级功能”设置中不要选择“Windows套接字”,这是因为MFC只支持Windows Socket 1而不支持Windows Socket 2。为了能使用Winsock 2 API编程,在程序中应包含“winsock2.h”头文件,并在项目中加入ws2_32.lib的静态库,该静态库应设置在项目属性的“链接器”\“输入”\“附加依赖项”中。   2.在对话框(类名CCMulticastSocketDlg)资源中,设置它的Caption为“WinSock 2多播应用程序”,并添加以下控件:   静态文本:Caption为“接收到的信息:”; 
  编辑框:ID为IDC_RECEIVE_EDIT,Read Only、Auto Vscroll、Vertical Scroll和Multiline属性值都为True 
  静态文本:Caption为“发送的信息:” 
  编辑框:ID为IDC_SEND_EDIT 
  第一个按钮:Caption为“加入多播组(&J)”,ID为IDC_JOIN_BUTTON 
  第二个按钮:Caption为“多播发送(&S)”,ID为IDC_SEND_BUTTON 
  第三个按

解决方案 »

  1.   


      该程序的主要代码可参见程序清单,相关函数的详细说明可参看Microsoft MSDN帮助系统。为了节省篇幅,程序中省略了部分自动生成的和用于错误处理的代码。 程序清单:// CMulticastSocketDlg.cpp : 实现文件 
    #include "stdafx.h" 
    #include "winsock2.h" 
    #include "CMulticastSocket.h" 
    #include "CMulticastSocketDlg.h" 
    …… 
    DWORD cbRet; 
    SOCKET Sock,SockM; //UDP套接字,多播套接字 
    BOOL bFlag,bJoin; 
    SOCKADDR_IN local,Remote,From; //分别指向本地、多播组和数据来源的IP地址与端口 
    int Fromlen; 
    char ReceiveBuf[32000]; //接收缓冲区 
    BOOL bDataReceived; 
    …… 
    BEGIN_MESSAGE_MAP(CCMulticastSocketDlg, CDialog) 
    …… 
    ON_BN_CLICKED(IDC_JOIN_BUTTON, OnBnClickedJoinButton) 
    ON_BN_CLICKED(IDC_LEAVE_BUTTON, OnBnClickedLeaveButton) 
    ON_BN_CLICKED(IDC_QUIT_BUTTON, OnBnClickedQuitButton) 
    ON_BN_CLICKED(IDC_SEND_BUTTON, OnBnClickedSendButton) 
    ON_WM_TIMER() 
    ON_MESSAGE(WM_SOCK_MSG,OnSocketMsg) 
    END_MESSAGE_MAP() BOOL CCMulticastSocketDlg::OnInitDialog() 

    CDialog::OnInitDialog(); 
    …… 
    SetTimer(1,100,NULL); //设置定时器 
    Fromlen=sizeof(From); 
    bDataReceived=TRUE; 
    bJoin=FALSE; 
    return TRUE; // 除非设置了控件的焦点,否则返回 TRUE 

    …… 
    void CCMulticastSocketDlg::OnBnClickedJoinButton() //加入多播组 

    if(m_JoinDlg.DoModal()==IDOK) 

    WORD wVersionRequested; 
    WSADATA wsaData; 
    int 北京中庆; 
    wVersionRequested = MAKEWORD(2,2); 
    北京中庆 = WSAStartup(wVersionRequested, &wsaData); //初始化WinSock2资源 
    if(北京中庆!= 0){ 
    AfxMessageBox("不能加载Windows套接字动态链接库,MB_OK"); 
    return; 

    if (LOBYTE(wsaData.wVersion) !=2 || HIBYTE(wsaData.wVersion) !=2){ 
    AfxMessageBox("WinSock DLL不支持2.0版本,MB_OK"); 
    WSACleanup( ); 
    return; 

    //创建一个套接字 
    Sock=WSASocket(AF_INET,SOCK_DGRAM,IPPROTO_UDP, 
    (LPWSAPROTOCOL_INFO)NULL,0,WSA_FLAG_OVERLAPPED 
    | WSA_FLAG_MULTIPOINT_C_LEAF|WSA_FLAG_MULTIPOINT_D_LEAF); bFlag=TRUE; //设置套接字选项,使套接字为可重用端口地址 
    setsockopt(Sock,SOL_SOCKET,SO_REUSEADDR,(char*)&bFlag,sizeof(bFlag)); //将套接字绑定到用户指定端口及默认的接口 
    memset(&local,0,sizeof(local)); 
    local.sin_family=AF_INET; 
    local.sin_port=htons((USHORT)m_JoinDlg.m_nPort); 
    local.sin_addr.s_addr=htonl(INADDR_ANY); 
    bind(Sock,(struct sockaddr FAR *)&local,sizeof(local)); //设置多播数据报传播范围(生存时间TTL) 
    WSAIoctl(Sock,SIO_MULTICAST_SCOPE,&m_JoinDlg.m_nTTL,sizeof(int), 
    NULL,0,&cbRet,NULL,NULL); 
    //设置多播返回(LOOKBACK) 
    BOOL nLoopBack=m_JoinDlg.m_Loopback; 
    WSAIoctl(Sock,SIO_MULTIPOINT_LOOPBACK,&nLoopBack,sizeof(nLoopBack), 
    NULL,0,&cbRet,NULL,NULL); memset(&Remote,0,sizeof(Remote)); 
    Remote.sin_family=AF_INET; 
    Remote.sin_addr.s_addr=inet_addr(m_JoinDlg.m_IPAddress); 
    Remote.sin_port=htons(m_JoinDlg.m_nPort); 
    //加入到指定的多播组,并指定为既作为发送者又作为接收者(JL_BOTH) 
    SockM=WSAJoinLeaf(Sock,(sockaddr*)&Remote,sizeof(Remote), 
    NULL,NULL,NULL,NULL,JL_BOTH); 
    WSAAsyncSelect(Sock,m_hWnd,WM_SOCK_MSG,FD_READ); //注册网络消息及其网络事件 
    bJoin=TRUE; 

    } void CCMulticastSocketDlg::OnBnClickedSendButton() //多播发送 

    if(bJoin){ 
    UpdateData(TRUE); 
    const char* strMessage=LPCTSTR(m_SendMessage); 
    int nSize=m_SendMessage.GetLength()+1; 
    sendto(Sock,strMessage,nSize,0,(sockaddr*)&Remote,sizeof(Remote)); 

    else 
    AfxMessageBox("请先加入多播组!"); 
    m_SendMessage=""; 
    UpdateData(FALSE); 
    } void CCMulticastSocketDlg::OnBnClickedLeaveButton() 
    { //离开多播组 
    closesocket(SockM); 
    closesocket(Sock); 
    WSACleanup(); 
    m_SendMessage=""; 
    m_ReceiveMessage=""; 
    bDataReceived=TRUE; 
    bJoin=FALSE; 
    UpdateData(FALSE); 
    } void CCMulticastSocketDlg::OnBnClickedQuitButton() 
    { //退出 
    DestroyWindow(); 
    } void CCMulticastSocketDlg::OnTimer(UINT nIDEvent) //定时器处理函数,实现接收信息的定时更新 

    if(bDataReceived) 

    m_ReceiveMessage+=ReceiveBuf; 
    m_ReceiveMessage+="\r\n"; 
    bDataReceived=FALSE; 
    UpdateData(FALSE); 

    CDialog::OnTimer(nIDEvent); 
    } LRESULT CCMulticastSocketDlg::OnSocketMsg(WPARAM wParam,LPARAM lParam) 

    //检索网络事件 
    switch(WSAGETSELECTEVENT(lParam)){ 
    case FD_READ: 
    recvfrom(Sock,ReceiveBuf,32000,0,(sockaddr *)&From,&Fromlen); 
    bDataReceived=TRUE; //设置已接收到一条信息标志 
    break; 

    return TRUE; 
    }  
     --------------------------------------------------------------------------------
     
      

  2.   

    搂主的文章我收藏了!!!
    我也有一个组播通讯的例子, 希望对大家有帮助!!
    ///////////////////////////////////////////////////
    send.c在win2000下运行:#include <winsock2.h>
    #include <ws2tcpip.h>
    #include <stdio.h>#define BUFFSIZE 2048
    #define IP_ADDR      "224.8.8.1"     
    #define DEST_PORT         8888int main(int argc,char *argv[])
    {
        int sockfd;
        struct sockaddr_in addr;
        char szError[100];
    char buf[] = "Hello, World!";
    int ttl = 255 ; // Arbitrary TTL value.    WSADATA WSAData;
        WORD  wVersionRequested;
        wVersionRequested = MAKEWORD(2, 2);
       if (WSAStartup (wVersionRequested , &WSAData) != 0) 
        {
            printf ("recver:Initialize Winsock error!");
            exit(1);
        }
        if (LOBYTE(WSAData.wVersion) != 2 || HIBYTE(WSAData.wVersion) != 2 ) {
            WSACleanup( );
            printf ("setsockopt failed! Error: %d", WSAGetLastError ());
            exit(1);
        }     addr.sin_family = AF_INET;
        addr.sin_port = htons(DEST_PORT);
        addr.sin_addr.s_addr = inet_addr(IP_ADDR);
        
        if ((sockfd=socket(AF_INET,SOCK_DGRAM,0)) < 0) {
            printf("sender:new a socket error!\n");
            exit(1);    
        } if(setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&ttl, sizeof(ttl))!=0) 
    {
    printf("error setsockopt IP_MULTICAST_TTL\n");
    exit(1);
    }
    /*
        if (bind(sockfd,(struct sockaddr *)&recver_addr,sizeof(recver_addr)) < 0) {
            closesocket(sockfd);
            printf("recver:bind socket error!\n");
            exit(1);    
        }
    */
        
      if (sendto (sockfd, buf, sizeof(buf) , 0, (struct sockaddr *)&addr, sizeof(addr))
    == SOCKET_ERROR)
      {
        wsprintf (szError, TEXT("sendto failed! Error: %d"), 
                  WSAGetLastError ());
        MessageBox (NULL, szError, TEXT("Error"), MB_OK);
        closesocket (sockfd);
        return FALSE;
      }
      else
      {
        printf("send ok\n");
      } // Close Sock.
    closesocket (sockfd);
    WSACleanup ();
    return 0;
    }
    ///////////////////////////////////////////////////////
    receive.c可以在win98下运行#include <winsock2.h>
    #include <ws2tcpip.h>
    #include <stdio.h>#define BUFFSIZE 2048
    #define RECV_IP_ADDR      "224.8.8.1"     
    #define DEST_PORT         8888int main(int argc,char *argv[])
    {
        int sockfd;
        int sock_reuse = 1;
        struct ip_mreq multicast;
        struct sockaddr_in recver_addr;
        char szError[100];  int index = 0,                      // Integer index
          iRecvLen;                       // Length of recv_sin
      char szMessageA[1024*320];               // ASCII string 
      TCHAR szMessageW[1024*320];              // Unicode string
        
        WSADATA WSAData;
        WORD  wVersionRequested;
        wVersionRequested = MAKEWORD(2, 2);
        if (WSAStartup (wVersionRequested , &WSAData) != 0) 
        {
            printf ("recver:Initialize Winsock error!");
            exit(1);
        }
        if (LOBYTE(WSAData.wVersion) != 2 || HIBYTE(WSAData.wVersion) != 2 ) {
            WSACleanup( );
            printf ("setsockopt failed! Error: %d", WSAGetLastError ());
            exit(1);
        }     multicast.imr_multiaddr.s_addr = inet_addr(RECV_IP_ADDR);
        multicast.imr_interface.s_addr = htonl(INADDR_ANY);
        recver_addr.sin_family = AF_INET;
        recver_addr.sin_port = htons(DEST_PORT);
        recver_addr.sin_addr.s_addr = INADDR_ANY;
        
        if ((sockfd=socket(AF_INET,SOCK_DGRAM,0)) < 0) {
            printf("recver:new a socket error!\n");
            exit(1);    
        }
        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&sock_reuse, sizeof(sock_reuse)) < 0) {
              printf("recver : socket options set error");
              exit(1);
        }    if (bind(sockfd,(struct sockaddr *)&recver_addr,sizeof(recver_addr)) < 0) {
            closesocket(sockfd);
            printf("recver:bind socket error!\n");
            exit(1);    
        }
        
        if (setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,(char *)&multicast,sizeof(multicast)) < 0 ) {
            sprintf (szError, TEXT("setsockopt failed! Error: %d"), WSAGetLastError ());
            printf("%s\n", szError);
            closesocket(sockfd);
            exit(1);
        } printf("Receive on %s:%d\n", RECV_IP_ADDR, DEST_PORT);
      iRecvLen = sizeof (recver_addr);
      memset(szMessageA, 0, sizeof(szMessageA));
      // Receive data from the multicasting group server.
      if (recvfrom (sockfd, 
                    szMessageA,
                    sizeof(szMessageA),         
                    0,
                    (struct sockaddr FAR *) &recver_addr,
                    &iRecvLen) == SOCKET_ERROR)
      {
        wsprintf (szError, TEXT("recvfrom failed! Error: %d"), 
                  WSAGetLastError ());
        MessageBox (NULL, szError, TEXT("Error"), MB_OK);
        closesocket (sockfd);
        return FALSE;
      }
      else
      {
        // Convert the ASCII string to a Unicode string.
        for (index = 0; index <= sizeof (szMessageA); index++)
          szMessageW[index] = szMessageA[index];    MessageBox (NULL, szMessageW, TEXT("Info"), MB_OK);
      }  // Disable receiving on Sock before closing it.
      shutdown (sockfd, 0x00);  // Close Sock.
      closesocket (sockfd);  WSACleanup ();
        return 0;
    }
      

  3.   

    这里怎么有这么无聊的人把广告做到这里来!
    我想知道的一个问题就是:多播能不能穿越NAT传播到无公用IP的子网内,或从子网内能不能传到公网和其他子网内
      

  4.   

    强烈建议斑竹把他封杀:  winhonges()
      

  5.   

    严重同意楼上,强烈要求封杀:winhonges
    做广告做到这个地方来了,没有细看,不知道是不是搞传销的。