文章目录
- 1. 简介
- 2. 组播地址和组
- 3. 客户端和服务器
- 4. 路由器和路由
- 5. 使用组播Socket
- 6. 构造函数
- 7. 与组播组通信
- 8. 案例实战
1. 简介
前面介绍的Socket都是单播Socket,它们提供点对点的通信。单播Socket在两个明确的端点之间创建一个连接,有一个发送方和一个接收方。尽管点对点的通信有很多用途,但不是非常重要,很多任务则需要另外的一种不同的通信模型——组播。组播是通过TCP或UDP的附加协议实现的。Internet组播建立在UDP端口之上,Java中的组播使用UDP中介绍的DatagramPacket,以及一个新的MulticastSocket类
以下是组播的基本流程:
- 创建组播组: 首先,需要创建一个组播组,它由一个组播地址表示。这个地址是在特定的IPv4或IPv6地址范围内选定的。
- 加入组播组: 接收者需要向他们的本地路由器表明他们想要加入一个特定的组播组。这个过程通常使用Internet Group Management Protocol (IGMP) 或者在IPv6环境中的Multicast Listener Discovery (MLD) 协议。
- 发送数据: 发送者向组播地址发送数据。这个过程并不需要知道谁是组的成员,只需要向组播地址发送数据即可。
- 路由器处理: 当路由器接收到组播数据时,它需要将数据复制并发送给所有请求接收这个组播组数据的网络接口。路由器使用特定的组播路由协议,如Protocol Independent Multicast (PIM) 或Distance Vector Multicast Routing Protocol (DVMRP) 等,来决定如何最有效地将数据路由到其他路由器。
- 接收数据: 最后,接收者从他们的本地路由器接收到组播数据。
组播比单播的点对点通信宽,但比广播通信窄而且目标更加明确。组播将数据从一个主机发送到多个不同的主机,但只发加入某个特定组播组的主机。(主机申请加入某个组播组,加入后也可以自愿离开,可订阅有点像),组播设计为尽可能无缝地用于Internet,大多数工作都由路由器完成,对于应用程序员是透明的。应用程序只是将数据报包发送给一个组播的地址,它在功能上与任何其他的IP地址没有什么区别,路由器将确保被分发到该组播组中的所有主机。需要注意数据报中称为TTL的首部字段。TTL是允许数据报经过的最大路由器跳数,当达到这个最大值时,即如果数据包已经经过这么多路由器,就会丢弃这个包。组播使用TTL作为一种专用的方法来限制包可以传输多远。
2. 组播地址和组
组播地址是称为组播组的一组主机共享地址。IPv4组播地址是CIDR组224.0.0.0/4中的IP地址。与所有的IP地址类似,组播地址可以有一个主机名。例如,组播地址224.0.1.1(网络时间协议分布式服务地址)就分配有主机名ntp.mcast.net。组播组是一组共享一个组播地址的Internet主机,任何发送给该组比地址的数据都会中继给组中的所有成员,主机可以在任何时候进入或离开组。组可以是永久的也可以是临时的。永久的组播组分配的地址不变,而不讨论组内的成员。大多数组播组都是临时的,只是在有成员时才存在。要创建一个新的组播组,所要做的就是在255.0.0.0到238.255.255.255间随机选择一个地址,为该地址构造一个InetAddress对象,开始向它发送数据。
3. 客户端和服务器
当一台主机希望向组播组发送数据时,它会将数据放在组播数据报中,组播数据报也就是发送到组播组的UDP数据报而已。组播数据通过UDP发送,虽然不可靠,但比通过面向连接到的TCP发送的数据要快上3倍。如果你要开发不能容忍数据丢失的组播应用程序,就要由你负责确定数据在传输中是否损害以及如何处理丢失的数据。一旦数据填充到一个或多个数据报,发送主机就把数据报发送到Internet,这就像发送正常(单播)UDP数据一样。发送主机首先向本地网络发送一个组播数据报,这个包立即到达相同子网中组播组的所有成员,如果这个包的TTL大于1,本地网络的组播路由器会把这个包转发到包含目标组成员的其他网络。当包到达一个最终目的地时,该外部网络的组播路由器会将这个包传输到作为组播成员的每个主机,如果必要,组播路由器还会将包重新传输到位于当前路由器和所有最终目的地之间路径上的下一个路由器。当数据到达组播组的一个主机时,该主机就像接收任何其他UDP数据报一样接收该数据,尽管包的目标地址和接收主机的地址不一致,主机之所有能识别数据包是发送给它的,这是因为它属于数据报所发往的组播组,接收主机必须监听正确的端口,准备在数据报到到达时进行处理。
4. 路由器和路由
下图展示了一个最简单的组播配置,一个服务器向四台连接同一个路由器的客户端发送相同的数据。组播Socket通过Internet向客户端的路由器发送一个数据流,这个路由器复制数据流,并发送到每个客户端。如果没有组播Socket,服务器就必须发出4个单独的数据流,并发送到每个客户端,大大降低了主干网的带宽。当然,实际的路由可能更加复杂,设计多层冗余路由器。不过,组播Socket的目标很简单:不管网络有多复杂,在任何指定的网段上,相同的数据绝不应发送多层。作为java,只需要创建一个MulfticastSocket,让这个Socket加入组播组,并在发送的DatagramPacket填充该组播组的地址即可,路由器和MulticastSocket类会处理其余的所有工作。
组播最大的限制在于是否有特殊的组播路由器(mrouter)。mrouter时重新配置的Internet路由器或工作站,支持IP组播扩展。为收发本地子网外的组播数据,你需要一个组播路由器。询问你的网络管理员,查看你的路由器是否支持组播。也可以尝试ping all-routers.mcast.net
,如果有路由器回应说明你的网络连接着一个组播路由器。
但要让你的包到达任何指定的主机,你的主机和远程主机必须有一个由组播路由器构造的路径。或者有些网站可能连接着特殊的组播隧道软件,可以通过所有的路由器都理解的单播UDP传输组播数据。
5. 使用组播Socket
下面使用java.net.MulticastSocket类来组播数据
public class MulticastSocket extends DatagramSocket implements Closeable ,AutoCloseable
该类继承了DatagramSocket类,所以两者十分类似:将数据放在DatagramPacket中,然后通过MulticastSocket收发DatagramPacket对象。要接受远程完整的组播的数据,使用MulticstSocket()构造函数创建一个MulticastSocket对象。
//监听端口2300
MulticastSocket ms=new MulticastSocekt(2300);
接下来,使用MulticastSocket的joinGroup方法加入到一个组播组
InetAddress group=InetAddress.getByname("224.2.2.2");
ms.joinGroup(group);
这会通知你与服务器路径上的路由器开始发送数据,并告诉本地主机要将你的IP包发往组播组。一旦加入到组播组,就可以接受UDP数据了
byte[] buffer=new byte[8192];
DatagramPacket dp= new DatagramPacket(buffer, buffer.length);
ms.receive()
不再希望接收数据时,可以通过leaveGroup()方法离开组播组。然后调用close方法关闭Socket。向组播地址发送数据与向单播地址发送UDP数据很类似,不需要加入组播组就可以向组播地址发送数据。可以创建一个新的DatagramPacket,在包中填充数据和组播组的地址,将这个包发入send()方法
InetAddress ia= InetAddress.getByname("experiment.mcast,net");
byte[] data= "iasdfjadsjf slakdf".getBytes("UTF-8");
DatagramPacket dp= new DatagramPacket(data,data.length,ia, port);
MyulticastSokcet ms= new MulticastSocket();
ms.send(dp);
6. 构造函数
构造函数很简单,选择监听的端口就可以了,或者让系统给你分配一个匿名端口
public MulticastSocket() throws SocketException
public MulticastSocket(int port) throws SocketException
public MulticastSocket(SocketAddress bindAddress) throws SocketException
可以向构造函数传入一个null来创建一个未绑定的Socket,之后再用bind()方法进行连接。有些Socket选项只能子啊绑定之前设置,所以这个构造函数在这种情况下很有用。
InetAddress和SocketAddress都是Java网络编程中用于处理网络地址的类,但它们在使用方式和功能上有些不同。
- InetAddress:这个类代表了互联网协议(IP)地址。一个InetAddress实例包含了一个IP地址的主机名和字面值。它不包含端口信息。所以,如果你要指定一个网络上的特定端点(即IP地址和端口号),你不能仅仅使用InetAddress。
- SocketAddress:这是一个抽象类,用于表示套接字地址(即组合了IP地址和端口号的网络端点)。在Java网络编程中,SocketAddress的一个常见子类是InetSocketAddress。一个InetSocketAddress实例包含了一个InetAddress和一个端口号。
总的来说,你可以认为InetAddress是一个IP地址,而SocketAddress(更确切地说,InetSocketAddress)是一个完整的网络端点地址(即包括IP地址和端口号)。如果你在编程时需要同时处理IP地址和端口号,那么你可能需要使用InetSocketAddress。如果你只需要处理IP地址,那么你可能只需要InetAddress。
7. 与组播组通信
一旦创建MulticastSocket,可以完成下面4种关键操作
- 加入组播组
- 向组中成员发送数据
- 接受组中的数据
- 离开组播组
MulticastSocket类为操作1和4提供了方法。发送和接受使用send和receive方法即可。可以以任何顺序完成这些操作,不过必须在加入组后才能从组中接受数据。向组中发送数据并不需要先加入组。
加入组
要加入一个组调用InetAddress或SocketAddress对象传递给joinGroup()方法:
public void joinGroup(InetAddress address) throws IOException
public void joinGroup(SocketAddress addrss, NetworkInterface interface)
接受数据就用UDP文章中介绍的方法即可。一个MulticastSocket对象可以加入多个组播组,组播组中的成员信息会被记录在组播路由器中,而不是对象中。在这里,你要使用存储在入站数据报中的地址来确定包要发往什么地址。NetworkInterface interface允许只加入指定本地网络上的组播组,例如下面代码尝试加入名为“eth0”的网络接口上IP地址为224.2.2.2的组。
MulticastSocket ms= new MulticastSocket();
SocketAddress group= new InetSocketAddress("224.2.2.2",40);
NetworkInterface ni= NetworkInterface.getByname("eth0");
if(ni!= null){
//来让多播套接字加入到指定的多播组,并指定使用特定的网络接口
ms.joinGroup(group,ni);
}else{
ms.joinGroup(group);
}
离开组并且关闭连接
public void leaveGroup(InetAddress address) throws IOException
public void leaveGroup(SocketAddress multicastAddress, NetworkInterface interface)throws IOException
这会通知本地组播路由器,告诉它停止向你发送数据。
发送组播数据
用MulticastSocket发送数据与用DatagramSocket发送数据很相似。
try{
InetAddress ia=InetAddress.getByname("experiment.mcast.net");
byte[] data="dasfdsafsdaf\r\n".getBytes();
DatagramPacket dp= new DatagramPacket(data,data.length,ia,port);
MulticastSocekt ms= new MulticastSocket();
ms.send(dp);
}catch(IOException ex){
System.err.println(ex);
}
默认情况下TTL是1,可以在发送数据前调用setTimeToLive(int a)方法设置TTL,其中a的值在0-255之间,使用getTimeToLive获取TTL值。
回送模式
一台主机能否接受自己发出去的组播包,也就是组播包是否能够回送,这取决于具体的平台。
//true表示不希望接受
public void setLoopbackMode(boolean disable) throws SocketException
public boolean getLoopbackMode()throws SocketException
不过这只是一个建议,并非所有的系统都支持回送模式
网络接口
在多宿主机器中,setInterface()方法和setNetworkInterface()方法可以选择用于组播收发的网络接口:
public void setInterface(InetAddress address) throws SocketException
public InetAddress getInterface() throws SocketException
public void setNetworkInterface(NetworkInterface interface)throws SocketException
public NetworkInterface getNetworkInterface()throws SocketException
多宿主机器(Multi-homed Machine)是指一台拥有两个或两个以上网络接口的计算机,例如,两个或两个以上的以太网卡或者无线网卡。这些网络接口可以连接到相同或不同的网络
8. 案例实战
下面的程序主要是为了验证确实能够接受一个特定主机的组播数据(组播窃听器)。
public class QuizCardBuilder {
public static void main(String[] args) {
InetAddress group;
int port;
try{
group=InetAddress.getByName("239.255.255.250");
port=1900;
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
MulticastSocket ms=null;
try{
ms=new MulticastSocket(port);
ms.joinGroup(group);
byte[] buffer=new byte[8192];
while(true){
DatagramPacket dp=new DatagramPacket(buffer, buffer.length);
ms.receive(dp);
String s=new String(dp.getData(),"8859_1");
System.out.println(s);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
现在考虑如何发送组播数据
public class MulticastSender{
public static void main(String[] args){
InetAddress is=null;
int port=0;
byte ttl=(byte)1;
try{
ia= InetAddress.getByname(args[0]);
port=InetAddress.parseInt(args[1]);
}catch(NumberFormatException| IndexOutofBoundException){
System.exit(0);
}
byte[] data="dasfsadfsdfsd\r\n".getBytes();
DatagramPacket dp=new DatagramPacket(data ,data.length, ia,port);
try(MulticastSocket ms= new MulticastSocket()){
ms.setTimeToLive(ttl);
ms.joinGroup(ia);
for(int i=1;i<10;i++)
{
ms.send(dp);
}
ms.leaveGroup(ia);
}catch(SocketException ex){
System.err.println(ex);
}catch(IOException ex){
System.err.println(ex);
}