多接收者
到目前为止,我们的套接字都处理的是两个实体之间的通信,通常是一个服务器和一个客户端。这种一对一的通信方法有时称为单播(unicast)。而对于某些信息,多个接收者都可能对其感兴趣。对于这种情况,我们可以向每个接收者单播一个数据副本,但是这样做效率可能非常低。由于将同样的数据发送了多次,在一个网络连接上单播同一数据的多个副本非常浪费带宽。实际上,如果我们想要以固定频率发送数据,网络连接带宽就已经限制了其所能支持的接收者数量。例如,如果我们的视频服务器以1Mbps的速率发送数据流,而其网络连接速率是3Mbps(良好的连接速率),那么该连接最多只能同时支持3个用户。
幸运的是网络提供了一个更有效地使用带宽的方法。我们可以将复制数据包的工作交给网络来做,而不是由发送者负责。在我们的视频服务器例子中,我们将数据流的单个副本通过服务器连接发送到网络,再由网络在适当的时候将数据复制成多份。使用这种复制模式,无论有多少客户端,服务器都只需要使用其网络连接的1Mbps带宽。
有两种类型的一对多(one-to-many)服务:广播(broadcast)和多播(multicast)。对于广播,(本地)网络中的所有主机都会接收到一份数据副本。对于多播,消息只是发送给一个多播地址(multicast address),网络只是将数据分发给那些表示想要接收发送到该多播地址的数据的主机。总的来说,只要UDP套接字允许广播或多播。
1广播
广播UDP数据报文与单播数据报文相似,唯一的区别是其使用的是一个广播地址而不是一个常规的(单播)IP地址。注意,IPv6并没有明确地提供广播地址;然而,有一个特殊的全节点(all - nodes)、本地连接范围(link-local-scope)的多播地址,FFO2::1,发送给该地址的消息将多播到一个连接上的所有节点。IPv4的本地广播地址(255.255.255.255)将消息发送到在同一广播网络上的每个主机。本地广播信息决不会被路由器转发。在以太网上的一个主机可以向在同一以太网内的其他主机发送消息,但是该消息不会被路由器转发。IPv4还指定了定向广播地址,允许向指定网络中的所有主机进行广播;然而,由于互联网上的大部分路由器都不转发定向广播消息,我们在此不对其进行介绍。
并不存在可以向网络范围内所有主机发送消息的广播地址。至于为什么没有,请考虑向互联网上每台主机发送广播消息可能产生的影响。在这种地址发送单个数据报文就可能会由路由器产生非常大量的数据包副本,并可能会耗尽所有网络的带宽。误用(恶意的或意外的)该地址的后果会非常严重,因此IP协议的设计者故意没有定义互联网范围的广播机制。
即使如此,本地广播功能还是非常有用的,它通常用于在网络游戏中处于同一本地(广播)网络的玩家之间交换状态信息。在Java中,单播和广播的代码是相同的。要实现具有广播功能的应用程序,我们可以简单地在VoteClientUDP.java中使用广播目的地址。不过这里有个问题,你能发现它吗?提示:广播不能使用连接。像以前那样运行VoteServerUDP.java程序(你还可以同时运行多个接收者)。注意:有些操作系统不允许普通用户进行广播操作,在这种系统上程序将无法正常运行。
2多播
与广播一样,多播与单播之间的一个主要区别是地址的形式。一个多播地址指示了一组接收者。IP协议的设计者为多播分配了一定范围的地址空间,IPv4中的多播地址范围是224.0.0.0到239.255.255.255,IPv6中的多播地址是任何由FF开头的地址。除了少数系统保留的多播地址外,发送者可以向以上范围内的任何地址发送数据。Java中多播应用程序主要通过MulticastSocke实例进行通信,它是DatagramSocket的一个子类。重点需要理解的是,一个MulticastSocket实例实际上就是一个UDP套接字(DatagramSocket),其包含了一些额外的可以控制的多播特定属性。我们的下一个例子实现了投票信息的多播发送者和接收者。
VoteMulticastSender.java
0 import java.io.IOException;
1 import java.net.DatagramPacket;
2 import java.net.InetAddress;
3 import java.net.MulticastSocket;
4
5 public class VoteMulticastSender {
6
7 public static final int CANDIDATEID = 475;
8
9 public static void main(String args[]) throws
IOException {
10
11 if ((args.length < 2) || (args.length > 3)) { // Test
# of args
12 throw new IllegalArgumentException("Parameter(s):
[]");
13 }
14
15 InetAddress destAddr = InetAddress.getByName(args[0]);
// Destination
16 if (!destAddr.isMulticastAddress()) { // Test if
multicast address
17 throw new IllegalArgumentException("Not a multicast
address");
18 }
19
20 int destPort = Integer.parseInt(args[1]); //
Destination port
21 int TTL = (args.length == 3) ?
Integer.parseInt(args[2]) : 1; // Set TTL
22
23 MulticastSocket sock = new MulticastSocket();
24 sock.setTimeToLive(TTL); // Set TTL for all datagrams
25
26 VoteMsgCoder coder = new VoteMsgTextCoder();
27
28 VoteMsg vote = new VoteMsg(true, true, CANDIDATEID,
1000001L);
29
30 // Create and send a datagram
31 byte[] msg = coder.toWire(vote);
32 DatagramPacket message = new DatagramPacket(msg,
msg.length, destAddr, destPort);
33 System.out.println("Sending Text-Encoded Request
(" + msg.length + " bytes): ");
34 System.out.println(vote);
35 sock.send(message);
36
37 sock.close();
38 }
39 }
VoteMulticastSender.java
我们的单播发送者和多播发送者仅有的重要区别是:1)对给定地址是否是多播地址进行了验证,2)为多播数据报文设置了初始的TTL值(生命周期, Time To Live)。每个IP数据报文中都包含了一个TTL,它被初始化为某个默认值,并在每个路由器转发该报文时递减(通常是减1)。当TTL值减为0时,就丢弃该数据报文。通过设置TTL的初始值,我们可以限制数据包从发送者开始所能传递到的最远距离。[ ]
与广播不同,网络多播只将消息副本发送给指定的一组接收者。这组接收者叫做多播组(multicast group),通过共享的多播(组)地址确定。接收者需要一种机制来通知网络它对发送到某一特定地址的消息感兴趣,以使网络将数据包转发给它。这种通知机制叫做加入一组(joining a group),可以由MulticastSocket类的joinGroup()方法实现。我们的多播接收者加入了一个特定的组,接收并打印该组的一条多播消息,然后退出。
VoteMulticastReceiver.java
0 import java.io.IOException;
1 import java.net.DatagramPacket;
2 import java.net.InetAddress;
3 import java.net.MulticastSocket;
4 import java.util.Arrays;
5
6 public class VoteMulticastReceiver {
7
8 public static void main(String[] args) throws
IOException {
9
10 if (args.length != 2) { // Test for correct # of args
11 throw new IllegalArgumentException("Parameter(s):
");
12 }
13
14 InetAddress address = InetAddress.getByName
(args[0]); // Multicast address
15 if (!address.isMulticastAddress()) { // Test if
multicast address
16 throw new IllegalArgumentException("Not a multicast
address");
17 }
18
19 int port = Integer.parseInt(args[1]); // Multicast port
20 MulticastSocket sock = new MulticastSocket(port); //
for receiving
21 sock.joinGroup(address); // Join the multicast group
22
23 VoteMsgTextCoder coder = new VoteMsgTextCoder();
24
25 // Receive a datagram
26 DatagramPacket packet = new DatagramPacket(new byte
[VoteMsgTextCoder.MAX_WIRE_LENGTH],
27 VoteMsgTextCoder.MAX_WIRE_LENGTH);
28 sock.receive(packet);
29
30 VoteMsg vote = coder.fromWire(Arrays.copyOfRange
(packet.getData(), 0, packet
31 .getLength()));
32
33 System.out.println("Received Text-Encoded Request
(" + packet.getLength()
34 + " bytes): ");
35 System.out.println(vote);
36
37 sock.close();
38 }
39 }
VoteMulticastReceiver.java
我们的多播和单播接收者唯一的重要区别是,多播接收者表明希望从哪个多播地址接收数据来加入多播组。本书的网站上还有另一个多播发送者和接收者的例子。MulticastImageSender.java将一组由命令行参数指定的图片(JPEG或GIF)以3秒的时间间隔传输。MulticastImageReceiver.java则接收每一个图片并在窗口中显示。
多播数据报文实际上可以通过DatagramSocket中发送,只需要简单地指定一个多播地址。不过MulticastSocket还有一些DatagramSocket没有的能力,包括1)允许指定数据报文的TTL,和2)允许指定和改变通过哪个接口将数据报文发送到组(接口由其互联网地址确定)。另一方面,一个多播消息接收者必须使用MulticastSocket来接收数据,因为它需要用到MulticastSocket加入组的功能。
MulticastSocket是DatagramSocket的一个子类,因此它提供了DatagramSocket的全部方法。在此,我们只对MulticastSocket中特有或修改过的方法进行介绍。
MulticastSocket: 创建
MulticastSocket()
MulticastSocket(int localPort)
MulticastSocket(SocketAddress bindaddr)
这些构造函数用来创建一个具有多播功能的UDP套接字。如果没有指定本地端口或指定为0,套接字将绑定到任意一个可以的本地端口。如果指定了地址,该套接字则只能从所指定的地址接收消息。
如果要接收数据报文,我们必须加入到一个多播组中。
MulticastSocket: 组管理
void joinGroup(InetAddress groupAddress)
void joinGroup(SocketAddress mcastaddr, NetworkInterfacenetIf)
void leaveGroup(InetAddress groupAddress)
void leaveGroup(SocketAddress mcastaddr,
NetworkInterface netIf)
joinGroup()和leaveGroup()方法用于管理该套接字的多播组成员资格信息。一个套接字可以同时为多个多播组成员。如果套接字加入一个已经加入了的组,或在没有加入任何组的情况下离开一个组,都可能抛出异常。还可以选择性地指定从哪个接口加入或离开多播组。
MulticastSocket: 设置/获取多播选项
int getTimeToLive()
void setTimeToLive(int ttl)
boolean getLoopbackMode()
void setLoopbackMode(boolean disable)
InetAddress getInterface()
NetworkInterface getNetworkInterface()
void setInterface(InetAddress inf)
void setNetworkInterface(NetworkInterface netIf)
getTimeToLive()和setTimeToLive()方法用于设置通过该套接字发送的所有数据报文的生存周期。如果套接字启用了回环模式,则会接收到自己发送的数据报文。getLoopbackMode()和setLoopbackMode()方法用于为多播套接字设置回环模式,将其设置为true时表示关闭回环模式。getInterface()方法,setInterface()方法,getNetworkInterface()以及setNetworkInterface()方法用于设置从哪个接口发送多播数据包。这主要用在有多个接口的主机上。默认的多播接口是与平台独立的。
决定使用广播还是使用多播需要考虑多方面的因素,包括接收者的网络地址和通信双方的知识。互联网广播的范围是限定在一个本地广播网络之内的,并对广播接收者的位置进行了严格的限制。多播通信可能包含网络中任何位置的接收者,[ ]因此多播有个好处就是它能够覆盖一组分布在各处的接收者。IP多播的不足在于接收者必须知道要加入的多播组的地址。而接收广播信息则不需要指定地址信息。在某些情况下,广播是一个比多播更好更易于发现的机制。所有主机在默认情况下都可以接收广播,因此,在一个网络中向所有主机询问"打印机在哪儿?"是件容易的事。
UDP单播、多播和广播都基于底层UDP套接字实现。这些实现的大部分语义都是,一个UDP数据报文将发送到所有与数据包的目标端口绑定的套接字上。也就是说,如果有一个DatagramSocket或MulticastSocket实例与本地端口X相绑定(没有指定本地地址,即野报文),地址为Y的主机可能会在以下几种情况下收到发向端口X的UDP数据报文:1)目的地址为Y的单播,2)主机Y上所有应用程序都加入了发送到的多播组,或3)向能够到达主机Y的网络进行广播。接收者可以使用connect()方法来限定数据报文的源地址和端口。同时,一个DatagramSocket实例也可以指定本地单播地址,这将阻止多播和广播数据包的转发。目的地址验证的例子见UDPEchoClientTimeout.java,数据报文分成多路处理的。
相关下载:
Java_TCPIP_Socket编程(doc)
http://download.csdn.net/detail/undoner/4940239
文献来源:
UNDONER(小杰博客) :http://blog.csdn.net/undoner
LSOFT.CN(琅软中国) :http://www.lsoft.cn