Github示例:https://github.com/Nuclear-Core-Learning/TCPIP-Socket/tree/master/src/Chapter4
目录
单播、组播、广播、任播的定义
单播(unicast)
是指封包在计算机网络的传输中,目的地址为单一目标的一种传输方式。它是现今网络应用最为广泛,通常所使用的网络协议或服务大多采用单播传输,例如一切基于TCP的协议。
组播(multicast)
也叫多播, 多点广播或群播。 指把信息同时传递给一组目的地址。它使用策略是最高效的,因为消息在每条网络链路上只需传递一次,而且只有在链路分叉的时候,消息才会被复制。
多播组通过 D 类 IP 地址和标准 UDP 端口号指定。D 类 IP 地址在 224.0.0.0 和 239.255.255.255 的范围内(包括两者)。地址 224.0.0.0 被保留,不应使用。
广播(broadcast)
是指封包在计算机网络中传输时,目的地址为网络中所有设备的一种传输方式。实际上,这里所说的“所有设备”也是限定在一个范围之中,称为“广播域”。
任播(anycast)
是一种网络寻址和路由的策略,使得资料可以根据路由拓朴来决定送到“最近”或“最好”的目的地。
详细介绍(来自维基百科)
单播
每次只有两个实体相互通信,发送端和接收端都是唯一确定的。
在IPv4网络中,0.0.0.0到223.255.255.255属于单播地址。
你对小月月喊“小月月”,那么只有小月月回过头来答应你。
组播
“组播”这个词通常用来指代IP组播。IP组播是一种通过使用一个组播地址将数据在同一时间以高效的方式发往处于TCP/IP网络上的多个接收者的协议。此外,它还常用来与RTP等音视频协议相结合。
互联网架构师戴夫·克拉克是这样描述IP组播的:“你把数据包从一头放进去,网络就会试图将它们传递到想要得到它们的人那里。”
组播报文的目的地址使用D类IP地址, D类地址不能出现在IP报文的源IP地址字段。
你在大街上大喊一声“美女”, 会有一群女性回头看你。
组播地址(参考 iana)
组播组可以是永久的也可以是临时的。组播组地址中,有一部分由官方分配的,称为永久组播组。永久组播组保持不变的是它的ip地址,组中的成员构成可以发生变化。永久组播组中成员的数量都可以是任意的,甚至可以为零。那些没有保留下来供永久组播组使用的ip组播地址,可以被临时组播组利用。
224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用; 224.0.1.0~224.0.1.255是公用组播地址,Internetwork Control Block; 224.0.2.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效; 239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。永久的组播地址:
224.0.0.0 基准地址(保留) 224.0.0.1 所有主机的地址 (包括所有 路由器地址) 224.0.0.2 所有组播路由器的地址 224.0.0.3 不分配 224.0.0.4 dvmrp路由器 224.0.0.5 所有ospf路由器 224.0.0.6 ospf DR/BDR 224.0.0.7 st路由器 224.0.0.8 st主机 224.0.0.9 rip-2路由器 224.0.0.10 Eigrp路由器 224.0.0.11 活动代理 224.0.0.12 dhcp 服务器/中继代理 224.0.0.13 所有pim路由器 224.0.0.14 rsvp封装 224.0.0.15 所有cbt路由器 224.0.0.16 指定sbm 224.0.0.17 所有sbms 224.0.0.18 vrrp以太网传输单播ip报文的时候,目的mac地址使用的是接收者的mac地址。但是在传输组播报文时,传输目的不再是一个具体的接收者,而是一个成员不确定的组,所以使用的是组播mac地址。组播mac地址是和组播ip地址对应的。iana(internet assigned number authority)规定,组播mac地址的高24bit为0x01005e,mac 地址的低23bit为组播ip地址的低23bit。
由于ip组播地址的后28位中只有23位被映射到mac地址,这样就会有32个ip组播地址映射到同一mac地址上。
广播
并非所有的计算机网络都支持广播,例如X.25网络和帧中继都不支持广播,而且也没有在“整个互联网范围中”的广播。IPv6亦不支持广播,广播相应的功能由组播代替。
通常,广播都是限制在局域网中的,比如以太网或令牌环网络。因为广播在局域网中造成的影响远比在广域网中小得多。
以太网和IPv4网都用全1的地址表示广播,分别是ff:ff:ff:ff:ff:ff和255.255.255.255。
令牌环网络使用IEEE 802.2控制域中的一个特殊值来表示广播。你在公司大喊一声“放假了”, 全部同事都会响应,大叫爽死了。
任播
任播是与单播、广播和组播不同的方式。
在单播中,在网络位址和网络节点之间存在一一对应的关系。
在广播和组播中,在网络位址和网络节点之间存在一对多的关系:每一个目的位址对应一群接收可以复制资讯的节点。
在任播中,在网络位址和网络节点之间存在一对多的关系:每一个位址对应一群接收节点,但在任何给定时间,只有其中之一可以接收到传送端来的资讯。
在互联网中,通常使用边界网关协议来实现任播。
作为老板,你在公司大喊一声“开发组的过来一个人”, 总会有一个人灰溜溜去响应, 挨批还是发钱啊?
单播、多播、广播的封装
TCP与UDP 区别
TCP(传输控制协议) | UDP(用户数据报协议) | |
---|---|---|
是否连接 | 面向连接 | 面向非连接 |
传输可靠性 | 可靠的 | 不可靠的 |
应用场合 | 传输大量的数据 | 少量数据 |
速度 | 慢 | 快 |
多播基础示例
服务端:VoteMulticastReceiver.java
package Chapter4;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.util.Arrays;
import Chapter3.VoteMsg;
import Chapter3.VoteMsgTextCoder;
/**
*
* @TODO (功能说明:组播多播https://baike.baidu.com/item/%E7%BB%84%E6%92%AD%E5%9C%B0%E5%9D%80/6095039?fr=aladdin)
* @author PJL
* @package Chapter4
* @motto 学习需要毅力,那就秀毅力
* @file VoteMulticastReceiver.java
* @time 2019年10月28日 下午10:02:47
* <li>
* 224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用;
* 224.0.1.0~224.0.1.255是公用组播地址,可以用于Internet;
* 224.0.2.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效;
* 239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。
* </li>
*/
public class VoteMulticastReceiver {
public static void main(String[] args) throws IOException {
if(args.length==0) {
args=new String[2];
args[0]="224.0.0.221";
args[1]="8380";
}
if (args.length != 2) { // Test for correct # of args
throw new IllegalArgumentException("Parameter(s): <Multicast Addr> <Port>");
}
InetAddress address = InetAddress.getByName(args[0]); // Multicast address
if (!address.isMulticastAddress()) { // Test if multicast address
throw new IllegalArgumentException("Not a multicast address");
}
int port = Integer.parseInt(args[1]); // Multicast port
MulticastSocket sock = new MulticastSocket(port); // for receiving
sock.joinGroup(address); // Join the multicast group
VoteMsgTextCoder coder = new VoteMsgTextCoder();
// Receive a datagram
DatagramPacket packet = new DatagramPacket(new byte[VoteMsgTextCoder.MAX_WIRE_LENGTH],
VoteMsgTextCoder.MAX_WIRE_LENGTH);
sock.receive(packet);
VoteMsg vote = coder.fromWire(Arrays.copyOfRange(packet.getData(), 0, packet.getLength()));
System.out.println("Received Text-Encoded Request (" + packet.getLength() + " bytes): ");
System.out.println(vote);
sock.close();
}
}
客户端:VoteMulticastSender.java
package Chapter4;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import Chapter3.VoteMsg;
import Chapter3.VoteMsgCoder;
import Chapter3.VoteMsgTextCoder;
/**
*
* @TODO (功能说明:组播多播https://baike.baidu.com/item/%E7%BB%84%E6%92%AD%E5%9C%B0%E5%9D%80/6095039?fr=aladdin)
* @author PJL
* @package Chapter4
* @motto 学习需要毅力,那就秀毅力
* @file VoteMulticastSender.java
* @time 2019年10月28日 下午10:03:11
* <li>
* 224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用;
* 224.0.1.0~224.0.1.255是公用组播地址,可以用于Internet;
* 224.0.2.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效;
* 239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。
* </li>
*/
public class VoteMulticastSender {
public static final int CANDIDATEID = 475;
public static void main(String args[]) throws IOException {
if(args.length==0) {
args=new String[3];
args[0]="224.0.0.221";
args[1]="8380";
args[2]="255";
}
if ((args.length < 2) || (args.length > 3)) { // Test # of args
throw new IllegalArgumentException("Parameter(s): <Multicast Addr> <Port> [<TTL>]");
}
InetAddress destAddr = InetAddress.getByName(args[0]); // Destination
if (!destAddr.isMulticastAddress()) { // Test if multicast address
throw new IllegalArgumentException("Not a multicast address");
}
int destPort = Integer.parseInt(args[1]); // Destination port
int TTL = (args.length == 3) ? Integer.parseInt(args[2]) : 1; // Set TTL
// MulticastSocket 实例实际上就是一个 UDP 套接字(DatagramSocket)
MulticastSocket sock = new MulticastSocket();
sock.setTimeToLive(TTL); // Set TTL for all datagrams
VoteMsgCoder coder = new VoteMsgTextCoder();
VoteMsg vote = new VoteMsg(true, true, CANDIDATEID, 1000001L);
// Create and send a datagram
byte[] msg = coder.toWire(vote);
DatagramPacket message = new DatagramPacket(msg, msg.length, destAddr, destPort);
System.out.println("Sending Text-Encoded Request (" + msg.length + " bytes): ");
System.out.println(vote);
sock.send(message);
sock.close();
}
}
简单封装示例
服务端:MulticastListener.java
package Chapter4;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
/**
*
* @TODO (功能说明:服务端)
* @author PJL
* @package Chapter4
* @motto 学习需要毅力,那就秀毅力
* @file MulticastListener.java
* @time 2019年10月28日 下午10:52:20
*/
public class MulticastListener {
private int port;
private String host;
public MulticastListener(String host, int port) {
this.host = host;
this.port = port;
}
public void listen() {
byte[] data = new byte[256];
try {
InetAddress ip = InetAddress.getByName(this.host);
MulticastSocket ms = new MulticastSocket(this.port);
//ms.setBroadcast(true);
ms.joinGroup(ip);
DatagramPacket packet = new DatagramPacket(data, data.length);
ms.receive(packet);
String message = new String(packet.getData(), 0, packet.getLength());
System.out.println(message);
ms.send(packet);
ms.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
int port = 1234;
String host = "224.0.0.1";
MulticastListener ml = new MulticastListener(host, port);
while (true) {
ml.listen();
}
}
}
客户端:MulticastSender.java
package Chapter4;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
/**
*
* @TODO (功能说明:客户端)
* @author PJL
* @package Chapter4
* @motto 学习需要毅力,那就秀毅力
* @file MulticastSender.java
* @time 2019年10月28日 下午10:52:01
*/
public class MulticastSender {
private int port;
private String host;
private String data;
public MulticastSender(String data, String host, int port) {
this.data = data;
this.host = host;
this.port = port;
}
public void send() {
try {
InetAddress ip = InetAddress.getByName(this.host);
DatagramPacket packet = new DatagramPacket(this.data.getBytes(), this.data.length(), ip, this.port);
MulticastSocket ms = new MulticastSocket();
ms.send(packet);
ms.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
int port = 1234;
String host = "224.0.0.1";
String data = "hello world.";
MulticastSender ms = new MulticastSender(data, host, port);
ms.send();
}
}
单播、多播、广播完整封装示例
UDPMulticastModel.java 任意播未实现。
package Chapter4;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Scanner;
import java.util.Timer;
import java.util.TimerTask;
/**
*
* @TODO (功能说明:单播、多播、广播https://www.2cto.com/kf/201610/555014.html)
* @author PJL
* @package Chapter4
* @motto 学习需要毅力,那就秀毅力
* @file UDPMulticast.java
* @time 2019年10月28日 下午10:23:00
*/
public class UDPMulticastModel {
private static int PORT_A = 7777;
private static int PORT_B = 9999;
private static String MULTICAST_ADDRESS = "225.0.0.1";
private static String EXIT = "exit";
private enum SocketType {
UNICAST, // 单播
MULTICAST, // 多播(组播)
BROADCAST, // 广播
ANYCAST // 任播
}
private static SocketType type = SocketType.UNICAST;
static class UdpClient {
protected DatagramSocket socket = null;
private SocketType type;
public UdpClient(int port, SocketType type) {
this.type = type;
byte[] recvBuffer = new byte[1024];
final DatagramPacket recvPacket = new DatagramPacket(recvBuffer, recvBuffer.length);
try {
/*
* 1、本机地址 InetAddress addr = InetAddress.getLocalHost();
* (注意本机地址和环回地址是不一样的。绑定到本机地址的话,本机发送到环回地址收不到,反之亦然)
*
* 2、环回地址 InetAddress addr = InetAddress.getLoopbackAddress(); InetAddress addr
* = InetAddress.getByName("127.0.0.1"); InetAddress addr =
* InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 });
*
* 3、广播地址 InetAddress addr = InetAddress.getByAddress(new byte[] { 255, 255,
* 255, 255 });
*/
switch (type) {
case UNICAST:
case BROADCAST:
socket = new DatagramSocket(port, InetAddress.getLoopbackAddress());
break;
case MULTICAST:
socket = new MulticastSocket(port);
((MulticastSocket) socket).joinGroup(InetAddress.getByName(MULTICAST_ADDRESS));
break;
case ANYCAST:
// 暂时未实现
return;
}
new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
socket.receive(recvPacket);
System.out.println("received packet from " + recvPacket.getAddress().getHostAddress()
+ " : " + recvPacket.getPort());
// 注意由于DatagramPacket的缓冲区复用,本次收到的最后一个字符后并不会补'\0',而是使用一个长度标记
String msg = new String(recvPacket.getData(), recvPacket.getOffset(),
recvPacket.getLength());
System.out.println("received " + msg);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socket != null) {
socket.close();
}
}
}
}).start();
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
}
}
public boolean send(InetAddress addr, int port, String msg) {
byte[] buffer = msg.getBytes();
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
System.out.println("send to " + addr.getHostAddress());
try {
switch (type) {
case UNICAST:
packet.setAddress(addr);
break;
case MULTICAST:
packet.setAddress(InetAddress.getByName(MULTICAST_ADDRESS));
break;
case BROADCAST:
packet.setAddress(
InetAddress.getByAddress(new byte[] { (byte) 255, (byte) 255, (byte) 255, (byte) 255 }));
break;
case ANYCAST:
// 暂时未实现
return false;
}
packet.setPort(port);
socket.send(packet);
return true;
} catch (UnknownHostException e1) {
e1.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
}
static class UdpClientA extends UdpClient {
public UdpClientA() {
super(PORT_A, type);
}
public void startScanner() {
// scanner必须写在线程中,如果阻塞主线程,那么输出将无法打印出来
new Thread(new Runnable() {
@Override
public void run() {
Scanner scanner = new Scanner(System.in);
String line;
InetAddress addr = InetAddress.getLoopbackAddress();
while (scanner.hasNext()) {
line = scanner.nextLine();
if (!send(addr, PORT_B, line)) {
break;
}
if (EXIT.equals(line)) {
break;
}
}
scanner.close();
}
}).start();
}
public void doSchedule() {
InetAddress addr;
try {
addr = InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 });
} catch (UnknownHostException e) {
e.printStackTrace();
return;
}
final InetAddress thisAddr = addr;
new Timer().schedule(new TimerTask() {
@Override
public void run() {
if (!send(thisAddr, PORT_B, "hello world")) {
this.cancel();
}
}
}, 0, 5000);
}
}
static class UdpClientB extends UdpClient {
public UdpClientB() {
super(PORT_B, type);
}
}
public static void main(String[] args) {
UdpClientA clientA = new UdpClientA();
clientA.startScanner();
clientA.doSchedule();
new UdpClientB();
}
}
Java发送组播(多播)数据包
java发送组播(多播)数据包。首先要加入组播组,然后才能向组播组发送组播数据包和接收组播数据包。
以下代码,在局域网中测试有效。
测试代码
public static void main(String[] args)
{
final MulticastSocket socket=createMulticastGroupAndJoin("224.0.0.0",5000); //加入组播组,设置组播组的监听端口为5000
new Thread(new Runnable() {
@Override
public void run() {
sendData(socket,"luanpeng".getBytes(),"224.0.0.0"); //向组播组发送数据
}
}).start();
String message = recieveData(socket,"224.0.0.0");//接收组播组传来的消息
System.out.println(message);
}
创建一个组播组,并加入此组播组,多播地址为D类ip,目的地址为224.0.0.0~239.255.255.255
public static MulticastSocket createMulticastGroupAndJoin(String groupurl,int port) // 创建一个组播组并加入此组的函数
{
try {
InetAddress group = InetAddress.getByName(groupurl); // 设置组播组的地址为239.0.0.0
MulticastSocket socket = new MulticastSocket(port); // 初始化MulticastSocket类并将端口号与之关联
socket.setTimeToLive(1); // 设置组播数据报的发送范围为本地网络
socket.setSoTimeout(10000); // 设置套接字的接收数据报的最长时间
socket.joinGroup(group); // 加入此组播组
return socket;
} catch (Exception e1) {
System.out.println("Error: " + e1); // 捕捉异常情况
return null;
}
}
向组播组发送数据的函数,以UDP形式发送
public static void sendData(MulticastSocket socket,byte[] data,String groupurl) // 向组播组发送数据的函数
{
try {
InetAddress group=InetAddress.getByName(groupurl);
// 存储在数组中
DatagramPacket packet = new DatagramPacket(data, data.length, group, socket.getPort()); // 初始化DatagramPacket
socket.send(packet); // 通过MulticastSocket实例端口向组播组发送数据
util.out("以UDP形式发送组播报文");
} catch (Exception e1) {
System.out.println("Error: " + e1); // 捕捉异常情况
}
}
从组播组接收数据的函数
public static String recieveData(MulticastSocket socket,String groupurl)
{
String message;
try {
InetAddress group=InetAddress.getByName(groupurl);
byte[] data = new byte[512];
DatagramPacket packet=new DatagramPacket(data, data.length,group,socket.getPort());
socket.receive(packet); // 通过MulticastSocket实例端口从组播组接收数据
// 将接受的数据转换成字符串形式
message = new String(packet.getData());
} catch (Exception e1) {
System.out.println("Error: " + e1); // 捕捉异常情况
message = "Error: " + e1;
}
return message;
}
参考文章:
https://www.2cto.com/kf/201610/555014.html
http://blog.sina.com.cn/s/blog_5017ea6c0101bv8l.html
https://blog.csdn.net/luanpeng825485697/article/details/78176319