Java Socket API 支持客户端-服务器范式中远程主机之间的网络通信。可以通过三种方式建立通信:一对一通信(客户端-服务器)、一对多通信(广播)和一对多通信(组播)。本文详细阐述了套接字的总体概念,特别是多播,并展示了如何使用 Java 套接字来实现它。
概述:套接字和 IP 地址
套接字基本上是指用于发送和接收数据的目的的网络中的机器之间的指定虚拟端点。网络中的机器由 IP 地址唯一标识。有一种特定的格式和 IP 类别定义了分配给它的网络类型。随着互联网的发展,将更多设备容纳到网络中的需求增加了。结果,使用 32 位寻址并可以支持大约 43 亿台设备的 IPv4 突然显得不那么适应了。因此,需要一个称为 IPv6 的改进版本;它使用64位寻址,可以支持大约3.4e10 4个设备!然而,IPv4 仍在使用并蓬勃发展;因此,我们将只讨论 IPv4。
IPv4 地址格式示例:192.168.17.14(C 类)
第1个字节范围 | 第2个字节范围 | 第3个字节范围 | 第4个字节范围 | |
范围 | (0-255) | (0-255) | (0-255) | (0-255) |
现在,IPv4 支持五类 IP 范围:A 类、B 类、C 类、D 类和 E 类。其中,常用的只有 A、B 和 C。其他有特殊用途。分类的原因是我们可以将我们在网络中大约 43 亿台机器(最坏情况)的大海捞针中搜索机器,限制在特定地址类定义的机器的可管理过滤部分。
类 | 第一个八位组范围 | 网络 (N), 主机 (H) | 子网掩码 | 网络数量 | 每个网络的主机 |
A | 1-126 | NHHH | 255.0.0.0 | 126 | 16777214 |
B | 128-191 | NNHH | 255.255.0.0 | 16382 | 65534 |
C | 192-223 | NNNH | 255.255.255.0 | 2097150 | 254 |
D | 224-239 | 用于多播。 | |||
E | 240-254 | 实验性的;保留用于研究目的。 |
IP 地址 127.xxx 保留为环回地址,称为localhost。IP 地址 255.255.255.255 用于向局域网中的所有主机广播。而且,还有一些私有 IP 地址(在我们讨论的时候不相关)。
客户端和服务器套接字
想象一台服务器正在一台机器上运行。运行本质上意味着服务器进入监听模式;换句话说,循环检查是否有数据到达。现在,数据通过网络通道到达,对吧?但是,数据的类型是由它所遵循的协议定义的。协议定义了数据打包遵循的规范。一些流行的协议是 HTTP、FTP、SMTP 等。(它就像一个包装器,决定了它的公民身份以及服务器如何处理数据。)回想一下,服务器循环地监听套接字(记住,这是一个虚拟的东西)。服务器可能会监听许多套接字。每个套接字由有助于隔离网络中正确机器的 IP 地址和确定服务器专门侦听的机器的套接字或正确端点的端口号唯一标识。
了解差异
- 单播:网络中两台机器之间的消息发送。
- 广播:消息发送到网络中的所有机器。
- 多播:消息发送到网络中的一台或多台机器。
TCP 和 UDP 协议
有两种类型的套接字:面向连接的套接字和无连接的套接字。面向连接的套接字也称为流套接字。在流套接字中,在数据传输之前,通过称为握手的技术建立虚拟的一对一连接,并且数据流在整个虚拟通道中不间断地发送。在无连接套接字中,一次发送一个数据包,并且不建立专用连接。它也称为数据报套接字。传输控制协议 (TCP)是传输层协议,是面向连接的套接字使用最广泛的协议。用户数据报协议 (UDP)也是一种传输层协议,但广泛用于无连接套接字。
简而言之,流套接字是:
- 网络中两台主机之间的点对点专用通道
- 高度可靠的通信
- 以相似顺序发送和接收的数据包
- 即使在传输暂停之间,通道仍然被占用
- 传输中丢失的数据恢复时间长
- 使用 TCP 协议
数据报套接字是:
- 没有专用频道
- 使用 UDP 协议
- 可能不是 100% 可靠的
- 数据发送和接收顺序不一样
- 传输中丢失数据的快速恢复时间
现在,如果您想知道……我们可以使用 TCP 广播或多播吗?? 显然,答案是否定的,因为 TCP 是一种用于在两个端点之间建立连接的协议。创建的虚拟通道是专用的,直到其中一台主机关闭连接。因此,TCP 使用昂贵的连接建立可靠的传输。当一个数据包被发送时,它需要一个确认。确认确定消息已在另一端正确接收;否则,数据包被重传。在整个过程中,通道始终保持一对一的占用。在广播和多播的情况下,没有回复和响应的概念。它创造了一种单向交通。因此,TCP 的数据传输可靠性不能在 UDP 之上实现,顺便说一句,从逻辑上讲是没有意义的。因此,我们使用 UDP 进行多播和广播。
一个简单的例子
java.net 包中定义的MulticastSocket类表示一个多播套接字。一旦MulticastSocket上对象被创建时,joinGroup()方法被调用以使其成员接收的多播消息之一。请注意,组播 IP 地址定义在 224.0.0.0 到 239.255.255.255 的范围内。以下是 IP 多播地址范围和用途。
起始地址 | 结束地址 | 用途 |
224.0.0.0 | 224.0.0.255 | 为特殊的“知名”多播地址保留 |
224.0.1.0 | 238.255.255.255 | 全球范围(互联网范围)的多播地址 |
239.0.0.0 | 239.255.255.255 | 管理范围的(本地)多播地址 |
来源:http : //www.tcpipguide.com/free/t_IPMulticastAddressing.htm
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
public class UDPMulticastClient implements Runnable {
public static void main(String[] args) {
Thread t = new Thread(new UDPMulticastClient());
t.start();
}
public void receiveUDPMessage(String ip, int port) throws IOException {
byte[] buffer = new byte[1024];
MulticastSocket socket = new MulticastSocket(4321);
InetAddress group = InetAddress.getByName("230.0.0.0");
socket.joinGroup(group);
while (true) {
System.out.println("Waiting for multicast message...");
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet);
String msg = new String(packet.getData(), packet.getOffset(), packet.getLength());
System.out.println("[Multicast UDP message received] >> " + msg);
if ("OK".equals(msg)) {
System.out.println("No more message. Exiting : " + msg);
break;
}
}
socket.leaveGroup(group);
socket.close();
}
@Override
public void run() {
try {
receiveUDPMessage("230.0.0.0", 4321);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPMulticastServer {
public static void sendUDPMessage(String message, String ipAddress, int port) throws IOException {
DatagramSocket socket = new DatagramSocket();
InetAddress group = InetAddress.getByName(ipAddress);
byte[] msg = message.getBytes();
DatagramPacket packet = new DatagramPacket(msg, msg.length, group, port);
socket.send(packet);
socket.close();
}
public static void main(String[] args) throws IOException {
sendUDPMessage("This is a multicast messge", "230.0.0.0", 4321);
sendUDPMessage("This is the second multicast messge", "230.0.0.0", 4321);
sendUDPMessage("This is the third multicast messge", "230.0.0.0", 4321);
sendUDPMessage("OK", "230.0.0.0", 4321);
}
}
通过数据报通道进行组播
Java 通过java.nio.channels包中定义的名为DatagramChannel的类支持通过数据报通道进行多播。想要接收多播消息的数据报通道加入多播组。这样,它就成为接收组播消息的组的成员。建立连接后,数据报通道将保持连接状态,直到断开或关闭。因此,我们可以通过调用isConnected()方法来检查数据报通道的状态,如果连接打开则返回布尔值true,否则返回false。
这是一个简单的示例来说明如何在多播中使用数据报通道。
一个简单的例子
使用数据报通道有一些额外的优势,例如我们可以选择仅从选定的源接收多播数据报并阻止其他源。例如,我们可以使用MembershipKey类中的block(InterAddress)方法来阻止多播消息源。有一种方法,称为unblock(InetAddress),可以解锁相同的内容。在这里,我们将实现简单的多播消息发送和接收场景。有关其他 API 信息,请参阅 Java API 文档。
import java.io.IOException;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.MembershipKey;
public class MulticastReceiver {
private static final String MULTICAST_INTERFACE = "eth0";
private static final int MULTICAST_PORT = 4321;
private static final String MULTICAST_IP = "230.0.0.0";
private String receiveMessage(String ip, String iface, int port) throws IOException {
DatagramChannel datagramChannel = DatagramChannel.open(StandardProtocolFamily.INET);
NetworkInterface networkInterface =NetworkInterface.getByInetAddress(InetAddress.getLocalHost());
datagramChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
datagramChannel.bind(new InetSocketAddress(port));
//datagramChannel.setOption(StandardSocketOptions.IP_MULTICAST_IF, networkInterface);
InetAddress inetAddress = InetAddress.getByName(ip);
MembershipKey membershipKey = datagramChannel.join(inetAddress,networkInterface);
System.out.println("Waiting for the message...");
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
datagramChannel.receive(byteBuffer);
byteBuffer.flip();
byte[] bytes = new byte[byteBuffer.limit()];
byteBuffer.get(bytes, 0, byteBuffer.limit());
membershipKey.drop();
return new String(bytes);
}
public static void main(String[] args) throws IOException {
MulticastReceiver mr = new MulticastReceiver();
System.out
.println("Message received : " + mr.receiveMessage(MULTICAST_IP, MULTICAST_INTERFACE, MULTICAST_PORT));
}
}
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
public class MulticastPublisher {
private static final String MULTICAST_INTERFACE = "eth0";
private static final int MULTICAST_PORT = 4321;
private static final String MULTICAST_IP = "230.0.0.0";
public void sendMessage(String ip, String iface, int port, String message) throws IOException {
DatagramChannel datagramChannel = DatagramChannel.open();
datagramChannel.bind(null);
NetworkInterface networkInterface = NetworkInterface.getByInetAddress(InetAddress.getLocalHost());
datagramChannel.setOption(StandardSocketOptions.IP_MULTICAST_IF, networkInterface);
ByteBuffer byteBuffer = ByteBuffer.wrap(message.getBytes());
InetSocketAddress inetSocketAddress = new InetSocketAddress(ip, port);
datagramChannel.send(byteBuffer, inetSocketAddress);
}
public static void main(String[] args) throws IOException {
MulticastPublisher mp = new MulticastPublisher();
mp.sendMessage(MULTICAST_IP, MULTICAST_INTERFACE, MULTICAST_PORT, "Hi there!");
}
}
结论
通过 Java 进行多播是 Java 网络编程范式的一部分。两个远程主机之间的通信实际上通过参考模型或 TCP/IP 模型定义的几个层。沟通的基本原理很复杂。但是,从 Java 编程的角度来看,使用网络库提供的 API 变得简单。尽管 UDP 被称为不太可靠的协议,但实际上它也不是那么不可靠。而且,TCP 资源匮乏。因此,在大多数情况下,UDP 似乎是更好的选择。TCP 几乎可以做 UDP 可以做的所有事情——除了多播和广播只能使用 UDP。