IP多播技术[为软件高校杯做准备]
转眼10月份将至,今年的高校杯应该轮到我参加了吧..得准备一下先.开发课题已经选好
<< 多媒体远程教育>>,开发语言:C# 模式:C/S 基与WinForm. 其中涉及到一个技术就是多播.
IP多播(也称多址广播或组播)技术,是一种允许一台或多台主机(多播源)发送单一数据包到多台主机(一次的,同时的)的TCP/IP网络技术。多播作为一点对多点的通信,是节省网络带宽的有效方法之一。在网络音频/视频广播的应用中,当需要将一个节点的信号传送到多个节点时,无论是采用重复点对点通信方式,还是采用广播方式,都会严重浪费网络带宽,只有多播才是最好的选择。多播能使一个或多个多播源只把数据包发送给特定的多播组,而只有加入该多播组的主机才能接收到数据包。目前,IP多播技术被广泛应用在网络音频/视频广播、AOD/VOD、网络视频会议、多媒体远程教育(我要做的就是这个软件 : D.....)、“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的使用。
三、应用实例
下面得代码是多播代理类:)
2using System.Net;
3using System.Net.Sockets;
4using System.Threading;
5
6namespace UDPReceiver
7{
8public delegate void OnReceiveEventHandler(string xmlPack);
9
10/// <summary>
11/// MultiCast 的摘要说明。
12/// </summary>
13public class MultiCast
14{
15private string addr;
16private int port;
17private UdpClient subscriber;
18private bool bShutDown = true;
19private Thread thReceive;
20
21/// <summary>
22/// 数据到达事件
23/// </summary>
24public event OnReceiveEventHandler OnReceive;
25
26/// <summary>
27/// 构造
28/// </summary>
29/// <param name="multiCastIP">组播IP</param>
30/// <param name="udpPort">端口</param>
31public MultiCast(string multiCastIP,int udpPort)
32{
33addr = multiCastIP;
34port = udpPort;
35OnReceive += new OnReceiveEventHandler(MultiCast_OnReceive);
36}
37
38/// <summary>
39/// 启动监听
40/// </summary>
41public void Startup()
42{
43if (!bShutDown)
44return;
45
46subscriber = new UdpClient(port);
47IPAddress ipMultiCast = IPAddress.Parse(addr);
48
49try
50{
51subscriber.JoinMulticastGroup(ipMultiCast);
52}
53catch(Exception ex)
54{
55System.Windows.Forms.MessageBox.Show("无法加入到多播组[" + addr + "]\n\n" + ex.ToString());
56return;
57}
58
59//抛出监听线程
60bShutDown = false;
61thReceive = new Thread(new ThreadStart(Receive));
62 thReceive.Start();
63}
64
65/// <summary>
66/// 关闭监听
67/// </summary>
68public void ShutDown()
69{
70bShutDown = true;
71thReceive.Join(500);
72
73try
74{
75thReceive.Abort();
76}
77catch
78{
79
80}
81
82subscriber.Close();
83subscriber = null;
84}
85
86private void MultiCast_OnReceive(string xmlPack)
87{
88//do nothing
89}
90
91
92private void Receive()
93{
94byte[] bytes = null;
95IPEndPoint ipFrom = null;
96string xml = "";
97
98while(!bShutDown)
99{
100bytes = subscriber.Receive(ref ipFrom);
101
102if (bytes != null)
103{
104if (bytes.Length > 0)
105{
106xml = System.Text.ASCIIEncoding.ASCII.GetString(bytes);
107//激发事件
108OnReceive(xml);
109}
110 }
111}
112}
113}
114}
服务器段:
2{
3 byte[] message = new byte[2048];
4 Socket s = new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);
5
6 public RecvBC()
7 {
8 IPEndPoint ipep=new IPEndPoint(IPAddress.Any, 7758);
9 s.Bind(ipep);
10 IPAddress ip=IPAddress.Parse("224.0.0.1");
11 s.SetSocketOption(SocketOptionLevel.IP,SocketOptionName.AddMembership,
12 new MulticastOption(ip,IPAddress.Any));
13 }
14
15 public byte[] Recv()
16 {
17 s.Receive(message );
18 return message;
19 }
20
21 ~RecvBC()
22 {
23 s.Close();
24 }
25}
26
27
28public class SendBC
29{
30 public SendBC()
31 {}
32
33 public void Send(byte[] message)
34 {
35 Socket s = new Socket(AddressFamily.InterNetwork,SocketType.Dgram, ProtocolType.Udp);
36 IPAddress ip=IPAddress.Parse("224.0.0.1");
37 s.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership,
38 new MulticastOption(ip));
39 s.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 5);
40 IPEndPoint ipep=new IPEndPoint(ip, 7758);
41 s.Connect(ipep);
42 s.Send(message,message.Length,SocketFlags.None);
43 s.Close();
44 }
45}
46
47
再给个实例
string k="来自【"+home_ip+"】&";
data=this.richTextBox1.Text.Trim();
string a=k+data+"<the end>";
byte []bytedata=System.Text.Encoding.Default.GetBytes(a);
//server的ip和port
ipadd=IPAddress.Parse("225.1.1.10");//多播IP地址
endpoint=new IPEndPoint(ipadd,9999);
////////////
try
{
sclient=new Socket(AddressFamily.InterNetwork,SocketType.Dgram,System.Net.Sockets.ProtocolType.Udp);
socketdone.Reset();
sclient.BeginSendTo(bytedata,0,bytedata.Length,0,endpoint,new AsyncCallback(this.sendcallback),sclient);
socketdone.WaitOne();//阻塞线程
sclient.Shutdown(SocketShutdown.Both);
sclient.Close();
Application.DoEvents();
sclient=null;
this.button1.Enabled=true;
}
catch(Exception my)
{
MessageBox.Show(my.Message.ToString(),"try(client)");
socketdone.Reset();
return;
}
}
public void sendcallback(IAsyncResult ar)
{
try
{
Socket sclient=(Socket)ar.AsyncState;
int bytesend=sclient.EndSend(ar);
socketdone.Set();
}
catch(Exception my)
{
MessageBox.Show(my.Message.ToString(),"sendcallback(client)");
sclient.Shutdown(SocketShutdown.Both);
sclient.Close();
sclient=null;
}
}
IPEndPoint localend1=new IPEndPoint(IPAddress.Any,9999);//本地终端
//EndPoint my_endpoint=(EndPoint)localend1;
//create socket
slistener=new Socket(System.Net.Sockets.AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);
slistener.Bind(localend1);//UDP协议不支持端口尝试侦听操作
IPAddress ipadd=IPAddress.Parse("225.1.1.10");//多播IP地址
IPEndPoint endpoint=new IPEndPoint(IPAddress.Any,0);//定义远程终端
rem_endpoint=(EndPoint)endpoint;
//多播设置
MulticastOption option1=new MulticastOption(ipadd,IPAddress.Any);
//套接字设置
slistener.SetSocketOption(System.Net.Sockets.SocketOptionLevel.IP,SocketOptionName.AddMembership,option1);//IGMP协议是在IP模块中实现的
slistener.SetSocketOption(System.Net.Sockets.SocketOptionLevel.IP,SocketOptionName.MulticastTimeToLive,5);//IGMP协议是在IP模块中实现的,IP多路广播的生存时间
try
{
while(true)
{
socketevent.Reset();
buffer=new byte[1024];
slistener.BeginReceiveFrom(buffer,0,buffer.Length,0, ref rem_endpoint,new AsyncCallback(ReceiveCallback),slistener);
socketevent.WaitOne();
Application.DoEvents();
slistener.Close();
slistener=null;
}
}
catch(SocketException my)
{
MessageBox.Show(my.Message.ToString(),"error1(server)",System.Windows.Forms.MessageBoxButtons.OK,System.Windows.Forms.MessageBoxIcon.Hand);
}
public static ManualResetEvent socketevent=new ManualResetEvent(false);
public void ReceiveCallback(IAsyncResult ar)
{
try
{
Socket handler=(Socket)ar.AsyncState;
int bytesread=handler.EndReceiveFrom(ar,ref rem_endpoint);
//if there is some data
if(bytesread>0)
{
//append it to the main string
box=System.Text.Encoding.Default.GetString(buffer,0,buffer.Length);
box=box.Trim();
//see the file is over
if(box.IndexOf("<the end>")>0)
{
string []a=box.Split('&');
string b=a[1].Replace("<the end>","");
//委托更新UI线程
this.richTextBox1.Invoke(new UPDATE_richtext(this.update_richtext),new object[]{a[0],b});
this.richTextBox1.Invoke(new CHANG_color(this.chang_color),new object[]{this.richTextBox1.Text});
socketevent.Set();
buffer=null;
handler.Shutdown(SocketShutdown.Both);
handler.Close();
}
else
{
handler.BeginReceiveFrom(buffer,0,buffer.Length,0, ref rem_endpoint,new AsyncCallback(ReceiveCallback),handler);
}
}
}
catch(Exception my)
{
MessageBox.Show(my.Message.ToString());
}
在ReceiveCallback函数
if(bytesread>0)
这里设置断点
把Receive中
slistener.Close();
slistener=null;
去掉
专题完(有个问题就是音频传输)