简介
单播有TCP和UDP两种实现,组播(多播)和广播只有UDP一种实现。单播和广播基本一样,只是广播的数据包IP为广播IP。
单播
DatagramSocket和DatagramPacket
服务端:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPServer {
public static void main(String[] args) throws Exception {
System.out.println("启动UDP单播服务端");
// 构造DatagramSocket实例,指定本地端口6666
try (DatagramSocket datagramSocket = new DatagramSocket(6666)) {
//设置超时时间为10秒
datagramSocket.setSoTimeout(10000);
while (true) {
// 构造DatagramPacket实例,用来接收最大长度为512字节的数据包
byte[] data = new byte[512];
DatagramPacket receivePacket = new DatagramPacket(data, 512);
// 接收报文,此方法在接收到数据报前一直阻塞
datagramSocket.receive(receivePacket);
System.out.println("客户端地址:" + receivePacket.getAddress().getHostAddress());
System.out.println("客户端端口:" + receivePacket.getPort());
System.out.println("接收到的数据长度:" + receivePacket.getLength());
System.out.println("接收到的数据:" + new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8"));
}
}
}
}
客户端:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
public class UDPClient {
public static void main(String[] args) throws Exception {
{
// 构造DatagramSocket实例,指定本地端口8888
try (DatagramSocket datagramSocket = new DatagramSocket(8888)) {
for (int i = 0; i < 5; i++) {
String data = "当前循环:" + i;
// 构造数据报包,用来将data发送到指定主机上的指定端口号。
DatagramPacket sendPacket = new DatagramPacket(data.getBytes("UTF-8"), data.getBytes("UTF-8").length, new InetSocketAddress("10.206.16.67", 6666));
//发送报文
datagramSocket.send(sendPacket);
}
}
}
}
}
服务端打印:
启动UDP单播服务端
客户端地址:10.206.16.67
客户端端口:8888
接收到的数据长度:16
接收到的数据:当前循环:0
客户端地址:10.206.16.67
客户端端口:8888
接收到的数据长度:16
接收到的数据:当前循环:1
客户端地址:10.206.16.67
客户端端口:8888
接收到的数据长度:16
接收到的数据:当前循环:2
客户端地址:10.206.16.67
客户端端口:8888
接收到的数据长度:16
接收到的数据:当前循环:3
客户端地址:10.206.16.67
客户端端口:8888
接收到的数据长度:16
接收到的数据:当前循环:4
Exception in thread "main" java.net.SocketTimeoutException: Receive timed out
at java.net.DualStackPlainDatagramSocketImpl.socketReceiveOrPeekData(Native Method)
at java.net.DualStackPlainDatagramSocketImpl.receive0(DualStackPlainDatagramSocketImpl.java:120)
at java.net.AbstractPlainDatagramSocketImpl.receive(AbstractPlainDatagramSocketImpl.java:144)
at java.net.DatagramSocket.receive(DatagramSocket.java:812)
at UDPServer.main(UDPServer.java:16)
Socket和ServerSocket
服务端:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
public class TCPServer {
public static void main(String[] args) throws Exception {
System.out.println("启动单播TCP服务端");
//构造ServerSocket实例,指定监听的本地端口为6666
try (ServerSocket serverSocket = new ServerSocket(6666)) {
//设置超时时间为10秒
serverSocket.setSoTimeout(10000);
while (true) {
//侦听并接收到此套接字的连接,此方法在连接传入之前一直阻塞。
Socket socket = serverSocket.accept();
TCPServerHandleThread tcpServerHandleThread = new TCPServerHandleThread(socket);
Thread thread = new Thread(tcpServerHandleThread);
thread.start();
}
}
}
}
/**
* 为每个连接进来的请求单独起一个处理线程
*/
class TCPServerHandleThread implements Runnable {
private Socket socket;
public TCPServerHandleThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
try (BufferedInputStream inputStream = new BufferedInputStream(socket.getInputStream()); BufferedOutputStream outputStream = new BufferedOutputStream(socket.getOutputStream())) {
//每次读取的最大字节数,为了演示特地写小一点
byte[] bytes = new byte[8];
//每次读取的有效字节数
int count;
//结果数组
byte[] result = new byte[0];
while ((count = inputStream.read(bytes)) != -1) {
//本次读取的有效字节数组
byte[] temp1 = Arrays.copyOfRange(bytes, 0, count);
//复制结果数组到新数组,新数组长度为结果数组的长度加上本次读取的有效字节数,用0填充
byte[] temp2 = Arrays.copyOf(result, result.length + count);
// 将本次读取的有效字节数组放到新数组里
System.arraycopy(temp1, 0, temp2, result.length, count);
result = temp2;
}
InetSocketAddress inetSocketAddress = (InetSocketAddress) socket.getRemoteSocketAddress();
System.out.println("客户端的ip和端口:" + inetSocketAddress.getAddress() + " " + inetSocketAddress.getPort());
InetSocketAddress inetSocketAddressL = (InetSocketAddress) socket.getLocalSocketAddress();
System.out.println("本地绑定的ip和端口:" + inetSocketAddressL.getAddress() + " " + inetSocketAddressL.getPort());
System.out.println("接收到的数据长度:" + result.length);
System.out.println("接收到的数据:" + new String(result, "UTF-8"));
//关闭此socket输入流
socket.shutdownInput();
outputStream.write("接收完毕".getBytes("UTF-8"));
outputStream.flush();
//关闭此socket输出流
socket.shutdownOutput();
} finally {
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
public class TCPClient {
public static void main(String[] args) throws Exception {
// 构造Socket实例
try (Socket socket = new Socket("10.206.16.67", 6666); BufferedInputStream inputStream = new BufferedInputStream(socket.getInputStream()); BufferedOutputStream outputStream = new BufferedOutputStream(socket.getOutputStream())) {
InetSocketAddress inetSocketAddress = (InetSocketAddress) socket.getRemoteSocketAddress();
System.out.println("服务端的ip和端口:" + inetSocketAddress.getAddress() + " " + inetSocketAddress.getPort());
InetSocketAddress inetSocketAddressL = (InetSocketAddress) socket.getLocalSocketAddress();
System.out.println("本地绑定的ip和端口:" + inetSocketAddressL.getAddress() + " " + inetSocketAddressL.getPort());
outputStream.write("abcdefg测试数据1234567890".getBytes("UTF-8"));
outputStream.flush();
//关闭此socket输出流
socket.shutdownOutput();
byte[] bytes = new byte[1024];
int count;
if ((count = inputStream.read(bytes)) != -1) {
System.out.println(new String(bytes, 0, count, "UTF-8"));
}
//关闭此socket输入流
socket.shutdownInput();
}
}
}
服务端打印:
启动单播TCP服务端
客户端的ip和端口:/10.206.16.67 62414
本地绑定的ip和端口:/10.206.16.67 6666
接收到的数据长度:29
接收到的数据:abcdefg测试数据1234567890
Exception in thread "main" java.net.SocketTimeoutException: Accept timed out
at java.net.DualStackPlainSocketImpl.waitForNewConnection(Native Method)
at java.net.DualStackPlainSocketImpl.socketAccept(DualStackPlainSocketImpl.java:135)
at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:404)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:199)
at java.net.ServerSocket.implAccept(ServerSocket.java:545)
at java.net.ServerSocket.accept(ServerSocket.java:513)
at TCPServer.main(TCPServer.java:17)
客户端打印:
服务端的ip和端口:/10.206.16.67 6666
本地绑定的ip和端口:/10.206.16.67 62414
接收完毕
客户端还可以采用如下方式设置连接超时时间为10秒:
Socket socket = new Socket();
socket.connect(new InetSocketAddress("10.206.16.67", 6666), 10000);
组播(多播)
组播是一对多的通信,允许同时向大量的接收方发送数据包。一个主机可以有多个组播进程,这些进程可以加入到同一个组播组也可以加入到不同的组播组,主机会跟踪记录当前哪些进程属于哪个组播组。进程随时可以要求主机主动告诉路由器加入和退出哪个组播组,而且每隔一段时间(大概1分钟)路由器也会向它所在的LAN发送一个查询数据包,要求主机告诉路由器它自己属于哪个组播组。支持组播的路由器负责向所有组内成员发送数据包,但不确保每个成员一定会收到。目的地址是组播ip的数据包会被路由器转发到对应组播组内的主机。
组播ip地址是D类地址,可以使用224.0.2.0~238.255.255.255这个范围的ip地址,组播进程根据组播ip进行分组。组播进程监听某端口并通过此端口接收数据,当一个组播数据包被转发到主机上的时候,主机会根据数据包的目的端口将数据包交给监听此目的端口的组播进程。
如果主机是多网卡,那么此时就需要注意了,一定要设置用哪个网卡发送和接受数据,因为组播是无法跨网段的,否则会导致数据接收不到。
MulticastSocket继承于DatagramSocket,因此可以发送也可以接收数据包。MulticastSocket绑定的端口是接收和发送数据的,如果数据包目的端口和此端口一致,则这个程序就能接收到数据包。setNetworkInterface方法是用来绑定网卡的。joinGroup告诉主机该程序要加入到哪个组播组,leaveGroup则是退出组播组。其他用法和DatagramSocket基本一致。
发送端:
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
public class MultiCastSender {
public static void main(String[] args) throws Exception {
InetAddress inetAddress = InetAddress.getByName("228.0.0.8");
MulticastSocket multicastSocket = new MulticastSocket();
multicastSocket.setNetworkInterface(NetworkInterface.getByInetAddress(InetAddress.getByName("10.206.16.67")));
multicastSocket.joinGroup(inetAddress);
int count = 0;
while (true) {
if (count == 5) {
multicastSocket.leaveGroup(inetAddress);
return;
}
String message = "时间戳:" + System.currentTimeMillis();
byte[] bytes = message.getBytes("UTF-8");
DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length,
inetAddress, 8888);
multicastSocket.send(datagramPacket);
count++;
}
}
}
接收端:
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
public class MultiCastReceiver {
public static void main(String[] args) throws Exception {
InetAddress inetAddress = InetAddress.getByName("228.0.0.8");
MulticastSocket multicastSocket = new MulticastSocket(8888);
multicastSocket.setNetworkInterface(NetworkInterface.getByInetAddress(InetAddress.getByName("10.206.16.67")));
multicastSocket.joinGroup(inetAddress);
int count = 0;
while (true) {
if (count == 5) {
multicastSocket.leaveGroup(inetAddress);
return;
}
// 构造DatagramPacket实例,用来接收最大长度为512字节的数据包
byte[] data = new byte[512];
DatagramPacket receivePacket = new DatagramPacket(data, 512);
// 接收报文,此方法在接收到数据报前一直阻塞
multicastSocket.receive(receivePacket);
System.out.println("第" + (count + 1) + "次接收");
System.out.println("客户端地址:" + receivePacket.getAddress().getHostAddress());
System.out.println("客户端端口:" + receivePacket.getPort());
System.out.println("接收到的数据长度:" + receivePacket.getLength());
System.out.println("接收到的数据:" + new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8"));
count++;
}
}
}
接收端打印:
第1次接收
客户端地址:10.206.16.67
客户端端口:54214
接收到的数据长度:25
接收到的数据:时间戳:1559133639107
第2次接收
客户端地址:10.206.16.67
客户端端口:54214
接收到的数据长度:25
接收到的数据:时间戳:1559133639108
第3次接收
客户端地址:10.206.16.67
客户端端口:54214
接收到的数据长度:25
接收到的数据:时间戳:1559133639108
第4次接收
客户端地址:10.206.16.67
客户端端口:54214
接收到的数据长度:25
接收到的数据:时间戳:1559133639108
第5次接收
客户端地址:10.206.16.67
客户端端口:54214
接收到的数据长度:25
接收到的数据:时间戳:1559133639108
广播
服务端:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPServer {
public static void main(String[] args) throws Exception {
System.out.println("启动UDP广播服务端");
// 构造DatagramSocket实例,指定本地端口6666
try (DatagramSocket datagramSocket = new DatagramSocket(6666)) {
//设置超时时间为10秒
datagramSocket.setSoTimeout(10000);
while (true) {
// 构造DatagramPacket实例,用来接收最大长度为512字节的数据包
byte[] data = new byte[512];
DatagramPacket receivePacket = new DatagramPacket(data, 512);
// 接收报文,此方法在接收到数据报前一直阻塞
datagramSocket.receive(receivePacket);
System.out.println("客户端地址:" + receivePacket.getAddress().getHostAddress());
System.out.println("客户端端口:" + receivePacket.getPort());
System.out.println("接收到的数据长度:" + receivePacket.getLength());
System.out.println("接收到的数据:" + new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8"));
}
}
}
}
客户端:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
public class UDPClient {
public static void main(String[] args) throws Exception {
{
// 构造DatagramSocket实例,指定本地端口8888
try (DatagramSocket datagramSocket = new DatagramSocket(8888)) {
for (int i = 0; i < 5; i++) {
String data = "当前循环:" + i;
// 构造数据报包,指定数据包的IP为广播地址,端口为6666。
DatagramPacket sendPacket = new DatagramPacket(data.getBytes("UTF-8"), data.getBytes("UTF-8").length, new InetSocketAddress("255.255.255.255", 6666));
//发送报文
datagramSocket.send(sendPacket);
}
}
}
}
}
服务端打印:
启动UDP广播服务端
客户端地址:192.168.56.1
客户端端口:8888
接收到的数据长度:16
接收到的数据:当前循环:0
客户端地址:192.168.56.1
客户端端口:8888
接收到的数据长度:16
接收到的数据:当前循环:1
客户端地址:192.168.56.1
客户端端口:8888
接收到的数据长度:16
接收到的数据:当前循环:2
客户端地址:192.168.56.1
客户端端口:8888
接收到的数据长度:16
接收到的数据:当前循环:3
客户端地址:192.168.56.1
客户端端口:8888
接收到的数据长度:16
接收到的数据:当前循环:4
Exception in thread "main" java.net.SocketTimeoutException: Receive timed out
at java.net.DualStackPlainDatagramSocketImpl.socketReceiveOrPeekData(Native Method)
at java.net.DualStackPlainDatagramSocketImpl.receive0(DualStackPlainDatagramSocketImpl.java:120)
at java.net.AbstractPlainDatagramSocketImpl.receive(AbstractPlainDatagramSocketImpl.java:144)
at java.net.DatagramSocket.receive(DatagramSocket.java:812)
at UDPServer.main(UDPServer.java:17)