前言
使用DatagramSocket代表UDP协议的Socket,DatagramSocket本身只是码头,不维护状态,不能产生IO流,它的唯一作用就是接收和发送数据报,Java使用DatagramPacket来代表数据报,DatagramSocket接收和发送的数据都是通过DatagramPacket对象完成的。
DatagramSocket用于创建发送端和接收端对象,然而在创建发送端和接收端的DatagramSocket对象时,使用的构造方法有所不同,下面对DatagramSocket类中常用的构造方法进行讲解。
● DatagramSocket()
该构造方法用于创建发送端的DatagramSocket对象,在创建DatagramSocket对象时,并没有指定端口号,此时,系统会分配一个没有被其它网络程序所使用的端口号。
● DatagramSocket(int port)
该构造方法既可用于创建接收端的DatagramSocket对象,也可以创建发送端的DatagramSocket对象,在创建接收端的DatagramSocket对象时,必须要指定一个端口号,这样就可以监听指定的端口。
● DatagramSocket(int port,InetAddress addr)
使用该构造方法在创建DatagramSocket时,不仅指定了端口号还指定了相关的IP地址,这种情况适用于计算机上有多块网卡的情况,可以明确规定数据通过哪块网卡向外发送和接收哪块网卡的数据。由于计算机中针对不同的网卡会分配不同的IP,因此在创建DatagramSocket对象时需要通过指定IP地址来确定使用哪块网卡进行通信。
方法声明 | 功能描述 |
---|---|
void receive(DatagramPacket p) | 该方法用于接收DatagramPacket数据报,在接收到数据之前会一直处于阻塞状态,如果发送消息的长度比数据报长,则消息将会被截取 |
void send(DatagramPacket p) | 该方法用于发送DatagramPacket数据报,发送的数据报中包含将要发送的数据、数据的长度、远程主机的IP地址和端口号 |
void close() | 关闭当前的Socket,通知驱动程序释放为这个Socket保留的资源 |
广播/多播(组播)
使用UDP协议进行信息的传输之前不需要建议连接。换句话说就是客户端向服务器发送信息,客户端只需要给出服务器的ip地址和端口号,然后将信息封装到一个待发送的报文中并且发送出去。至于服务器端是否存在,或者能否收到该报文,客户端根本不用管。
通常我们讨论的udp的程序都是一对一的单播程序。
这里将讨论一对多的服务:
- 广播(broadcast)
- 多播(multicast)
对于广播,网络中的所有主机都会接收一份数据副本。
对于多播,消息只是发送到一个多播地址,网络只是将数据分发给哪些表示想要接收发送到该多播地址的数据的主机。
总得来说,只有UDP套接字允许广播或多播。
UDP广播
广播UDP与单播UDP的区别就是IP地址不同,广播使用广播地址255.255.255.255
,将消息发送到在同一广播网络上的每个主机。
值得强调的是:
本地广播信息是不会被路由器转发。当然这是十分容易理解的,因为如果路由器转发了广播信息,那么势必会引起网络瘫痪。这也是为什么IP协议的设计者故意没有定义互联网范围的广播机制。
广播地址通常用于在网络游戏中处于同一本地网络的玩家之间交流状态信息等。
其实广播顾名思义,就是想局域网内所有的人说话,但是广播还是要指明接收者的端口号的,因为不可能接受者的所有端口都来收听广播。
UDP多播
同样的UDP多播也要指明接受者的端口号,而且与广播相似的是多播与单播之间的区别还在于地址。
ipv4中的多播地址范围是:224.0.0.0到239.255.255.255
。
在JAVA中,多播一样十分好实现,要实现多播,就要用到MulticastSocket类,其实该类就是DatagramSocket的子类,在使用时除了多播自己的一些特性外,把它当做DatagramSocket类使用就可以了。
使用Java 的UDP进行多播,要分两步走,首先要加入到广播组地址,其次要建立套接字传输信息
关于多播,涉及到MulticastSocket,他用于接收广播的信息,前提是要将它加入到广播组,
组播的地址是保留的D类地址从224.0.0.0—239.255.255.255
,
IP段 | 作用 | 用户是否可用 |
---|---|---|
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 | 本地管理组播地址,可供组织内部使用,类似于私有 IP 地址,不能用于 Internet,可限制多播范围 | 是 |
这里我们就选取230.0.0.1作为我们的广播地址。
UDP广播
发送端
代码语言:javascript复制import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.Date;
public class DSServer {
@SuppressWarnings("InfiniteLoopStatement")
public static void main(String[] args) throws IOException, InterruptedException {
String broadcast_address = "255.255.255.255";
int port = 55666;
while (true) {
DatagramSocket datagramSocket = new DatagramSocket();
datagramSocket.setBroadcast(true);
datagramSocket.connect(InetAddress.getByName(broadcast_address), port);
String sendStr = new Date().toLocaleString();
sendMsg(datagramSocket,sendStr);
datagramSocket.close();
Thread.sleep(2000);
}
}
/**
* 发送消息
* @param datagramSocket
* @param sendStr
* @throws IOException
*/
public static void sendMsg(DatagramSocket datagramSocket, String sendStr) throws IOException {
System.out.println("发送:" sendStr);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] bytes = sendStr.getBytes(StandardCharsets.UTF_8);
byteArrayOutputStream.write(bytes);
DatagramPacket datagramPacket = new DatagramPacket(
byteArrayOutputStream.toByteArray(),
byteArrayOutputStream.size()
);
datagramSocket.send(datagramPacket);
}
}
接收端
代码语言:javascript复制import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class DSClient {
@SuppressWarnings("InfiniteLoopStatement")
public static void main(String[] args) throws IOException {
int port = 55666;
while (true) {
//创建数据包传输对象DatagramSocket 绑定端口号
DatagramSocket datagramSocket = new DatagramSocket(port);
//创建字节数组
byte[] data = new byte[1024 * 50];
//创建数据包对象,传递字节数组
DatagramPacket datagramPacket = new DatagramPacket(data, data.length);
//调用ds对象的方法receive传递数据包
datagramSocket.receive(datagramPacket);
String msg = new String(datagramPacket.getData(), 0, datagramPacket.getLength());
System.out.println(msg);
//关闭
datagramSocket.close();
}
}
}
UDP广播(处理消息字节)
发送端
代码语言:javascript复制import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.Date;
public class DSServer {
@SuppressWarnings("InfiniteLoopStatement")
public static void main(String[] args) throws IOException, InterruptedException {
String broadcast_address = "255.255.255.255";
int port = 55666;
while (true) {
DatagramSocket datagramSocket = new DatagramSocket();
datagramSocket.setBroadcast(true);
datagramSocket.connect(InetAddress.getByName(broadcast_address), port);
String sendStr = new Date().toLocaleString();
sendMsg(datagramSocket,sendStr);
datagramSocket.close();
Thread.sleep(2000);
}
}
/**
* 发送消息
* @param datagramSocket
* @param sendStr
* @throws IOException
*/
public static void sendMsg(DatagramSocket datagramSocket, String sendStr) throws IOException {
System.out.println("发送:" sendStr);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] bytes = sendStr.getBytes(StandardCharsets.UTF_8);
byteArrayOutputStream.write(toByte(bytes.length));
byteArrayOutputStream.write(bytes);
DatagramPacket datagramPacket = new DatagramPacket(
byteArrayOutputStream.toByteArray(),
byteArrayOutputStream.size()
);
datagramSocket.send(datagramPacket);
}
//整数转字节
public static byte[] toByte(int n) {
byte[] b = new byte[4];
b[0] = (byte) (n & 0xff);
b[1] = (byte) (n >> 8 & 0xff);
b[2] = (byte) (n >> 16 & 0xff);
b[3] = (byte) (n >> 24 & 0xff);
return b;
}
}
接收端
代码语言:javascript复制import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class DSClient {
@SuppressWarnings("InfiniteLoopStatement")
public static void main(String[] args) throws IOException {
int port = 55666;
while (true) {
//创建数据包传输对象DatagramSocket 绑定端口号
DatagramSocket datagramSocket = new DatagramSocket(port);
//创建字节数组
byte[] data = new byte[1024 * 50];
//创建数据包对象,传递字节数组
DatagramPacket datagramPacket = new DatagramPacket(data, data.length);
//调用ds对象的方法receive传递数据包
datagramSocket.receive(datagramPacket);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
byte[] lenByte = new byte[4];
byteArrayInputStream.read(lenByte);
int len = toInt(lenByte);
byte[] strByte = new byte[len];
byteArrayInputStream.read(strByte);
System.out.println(new String(strByte));
//关闭
datagramSocket.close();
}
}
//字节转整数
public static int toInt(byte[] b) {
int res = 0;
for (int i = 0; i < b.length; i ) {
res = (b[i] & 0xff) << (i * 8);
}
return res;
}
}
字节操作
字符传转字节及拼接
代码语言:javascript复制ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] bytes = sendStr.getBytes(StandardCharsets.UTF_8);
byteArrayOutputStream.write(toByte(bytes.length));
byteArrayOutputStream.write(bytes);
byteArrayOutputStream.toByteArray()
整数转字节
代码语言:javascript复制//整数转字节
public static byte[] toByte(int n) {
byte[] b = new byte[4];
b[0] = (byte) (n & 0xff);
b[1] = (byte) (n >> 8 & 0xff);
b[2] = (byte) (n >> 16 & 0xff);
b[3] = (byte) (n >> 24 & 0xff);
return b;
}
字节截取及转字符串
代码语言:javascript复制ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
byte[] lenByte = new byte[4];
byteArrayInputStream.read(lenByte);
int len = toInt(lenByte);
byte[] strByte = new byte[len];
byteArrayInputStream.read(strByte);
System.out.println(new String(strByte));
字节转整数
代码语言:javascript复制//字节转整数
public static int toInt(byte[] b) {
int res = 0;
for (int i = 0; i < b.length; i ) {
res = (b[i] & 0xff) << (i * 8);
}
return res;
}
UDP多播
发送端
代码语言:javascript复制import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.Date;
public class DSServer {
private static final String broadcast_address = "239.8.8.8";
static int port = 55666;
@SuppressWarnings("InfiniteLoopStatement")
public static void main(String[] args) throws IOException, InterruptedException {
while (true) {
DatagramSocket socket = new DatagramSocket();
String sendStr = new Date().toLocaleString();
sendMsg(socket, sendStr);
socket.close();
Thread.sleep(5000);
}
}
/**
* 发送消息
*
* @param datagramSocket
* @param sendStr
* @throws IOException
*/
public static void sendMsg(DatagramSocket datagramSocket, String sendStr) throws IOException {
System.out.println("发送:" sendStr);
byte[] bytes = sendStr.getBytes(StandardCharsets.UTF_8);
DatagramPacket datagramPacket = new DatagramPacket(
bytes,
bytes.length,
InetAddress.getByName(broadcast_address),
port
);
datagramSocket.send(datagramPacket);
}
}
接收端
默认网卡监听
代码语言:javascript复制import java.net.*;
import java.util.List;
public class DSClient {
static int port = 55666;
@SuppressWarnings("InfiniteLoopStatement")
public static void main(String[] args) throws Exception {
String broadcast_address = "239.8.8.8";
while (true) {
//创建数据包传输对象DatagramSocket 绑定端口号
MulticastSocket datagramSocket = new MulticastSocket(port);
List<NetworkInterface> addressList = NetworkUtil.getNetworkInterfaces();
datagramSocket.joinGroup(InetAddress.getByName(broadcast_address));
//创建字节数组
byte[] data = new byte[1024 * 50];
//创建数据包对象,传递字节数组
DatagramPacket datagramPacket = new DatagramPacket(data, data.length);
//调用ds对象的方法receive传递数据包
datagramSocket.receive(datagramPacket);
String receiveMsg = new String(datagramPacket.getData(), 0, datagramPacket.getLength());
System.out.println("接收:" receiveMsg);
//关闭
datagramSocket.close();
}
}
}
所有网卡监听
代码语言:javascript复制import java.net.DatagramPacket;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.util.List;
public class DSClient {
static int port = 55666;
@SuppressWarnings("InfiniteLoopStatement")
public static void main(String[] args) throws Exception {
String broadcast_address = "239.8.8.8";
while (true) {
//创建数据包传输对象DatagramSocket 绑定端口号
MulticastSocket datagramSocket = new MulticastSocket(port);
List<NetworkInterface> addressList = NetworkUtil.getNetworkInterfaces();
//端口不冲突就行
int tempport = 10009;
for (NetworkInterface networkInterface : addressList) {
InetSocketAddress inetSocketAddress = new InetSocketAddress(broadcast_address, tempport);
//将有效网卡加入组播
datagramSocket.joinGroup(inetSocketAddress, networkInterface);
tempport ;
}
//创建字节数组
byte[] data = new byte[1024 * 50];
//创建数据包对象,传递字节数组
DatagramPacket datagramPacket = new DatagramPacket(data, data.length);
//调用ds对象的方法receive传递数据包
datagramSocket.receive(datagramPacket);
String receiveMsg = new String(datagramPacket.getData(), 0, datagramPacket.getLength());
System.out.println("接收:" receiveMsg);
//关闭
datagramSocket.close();
}
}
}
UDP多播(互发)
接收端要占用端口,所以下面的这种情况都是发送端和接收端,就要在两台机器上运行。
发送端接收回发
代码语言:javascript复制import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.*;
import java.nio.charset.StandardCharsets;
public class DSServer {
private static String broadcast_address = "230.0.0.1";
static int port = 55666;
static DatagramSocket socket;
@SuppressWarnings("InfiniteLoopStatement")
public static void main(String[] args) throws IOException, InterruptedException {
while (true) {
socket = new DatagramSocket(port);
new Thread(new MReceiver()).start();
InetAddress addr = InetAddress.getLocalHost();
String msg = addr.getHostAddress() "@" addr.getHostName();
sendMsg(socket, msg);
socket.close();
Thread.sleep(5000);
}
}
/**
* 发送消息
*
* @param datagramSocket
* @param sendStr
* @throws IOException
*/
public static void sendMsg(DatagramSocket datagramSocket, String sendStr) throws IOException {
System.out.println("发送:" sendStr);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] bytes = sendStr.getBytes(StandardCharsets.UTF_8);
byteArrayOutputStream.write(toByte(bytes.length));
byteArrayOutputStream.write(bytes);
DatagramPacket datagramPacket = new DatagramPacket(
byteArrayOutputStream.toByteArray(),
byteArrayOutputStream.size(),
InetAddress.getByName(broadcast_address),
port
);
datagramSocket.send(datagramPacket);
}
static class MReceiver implements Runnable { // 新建的线程,用于侦听
public void run() {
DatagramPacket inPacket;
String message;
while (true) {
try {
inPacket = new DatagramPacket(new byte[1024], 1024);
socket.receive(inPacket); // 接收广播信息并将信息封装到inPacket中
message = new String(inPacket.getData(), 0, inPacket.getLength());
System.out.println("Server 接收到消息:" message);
} catch (Exception e) {
System.out.println("线程出错 " e);
}
}
}
}
//整数转字节
public static byte[] toByte(int n) {
byte[] b = new byte[4];
b[0] = (byte) (n & 0xff);
b[1] = (byte) (n >> 8 & 0xff);
b[2] = (byte) (n >> 16 & 0xff);
b[3] = (byte) (n >> 24 & 0xff);
return b;
}
}
接收端回发消息
代码语言:javascript复制import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.*;
public class DSClient {
static int port = 55666;
@SuppressWarnings("InfiniteLoopStatement")
public static void main(String[] args) throws IOException {
String broadcast_address = "230.0.0.1";
String localhost_address = "192.168.3.23";
while (true) {
//创建数据包传输对象DatagramSocket 绑定端口号
MulticastSocket datagramSocket = new MulticastSocket(port);
NetworkInterface nic = NetworkInterface.getByInetAddress(InetAddress.getByName(localhost_address));
datagramSocket.joinGroup(new InetSocketAddress(broadcast_address, port),nic);
//创建字节数组
byte[] data = new byte[1024 * 50];
//创建数据包对象,传递字节数组
DatagramPacket datagramPacket = new DatagramPacket(data, data.length);
//调用ds对象的方法receive传递数据包
datagramSocket.receive(datagramPacket);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
byte[] lenByte = new byte[4];
byteArrayInputStream.read(lenByte);
int len = toInt(lenByte);
byte[] strByte = new byte[len];
byteArrayInputStream.read(strByte);
String receiveMsg = new String(strByte);
System.out.println("接收:" receiveMsg);
String ip = receiveMsg.split("@")[0];
returnUserMsg(ip, "你好:" ip);
//关闭
datagramSocket.close();
}
}
//回发消息
static void returnUserMsg(String ip, String msg) {
System.out.printf("回发消息:%sn", msg);
try {
DatagramSocket sender = new DatagramSocket();
byte[] b = ("retn@" msg).getBytes();
DatagramPacket packet = new DatagramPacket(b, b.length, InetAddress.getByName(ip), port);
sender.send(packet);
} catch (Exception e) {
System.out.println("*****发送返还信息失败*****");
}
}
//字节转整数
public static int toInt(byte[] b) {
int res = 0;
for (int i = 0; i < b.length; i ) {
res = (b[i] & 0xff) << (i * 8);
}
return res;
}
}
验证网卡是否加入多播组
windows: 执行
代码语言:javascript复制netsh interface ipv4 show joins
Linux: 执行
代码语言:javascript复制netstat -g
获取网卡信息
代码语言:javascript复制import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.List;
public class WK {
public static void main(String[] args) throws SocketException {
Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
while (en.hasMoreElements()) {
NetworkInterface ni = en.nextElement();
if(!ni.isVirtual() && !ni.isLoopback() && ni.isUp()){
System.out.println("-------------------------------------------");
System.out.println(" Name = " ni.getName());
System.out.println(" Display Name = " ni.getDisplayName());
System.out.println(" Support multicast = " ni.supportsMulticast());
System.out.println(" Is point to point = " ni.isPointToPoint());
System.out.println(" Hardware address = " getMac(ni.getHardwareAddress()));
System.out.println(" MTU = " ni.getMTU());
System.out.println("nList of Interface Addresses:");
List<InterfaceAddress> list = ni.getInterfaceAddresses();
for (InterfaceAddress ia : list) {
if(ia.getNetworkPrefixLength()<=32){
System.out.println(" Address = " ia.getAddress());
System.out.println(" Broadcast = " ia.getBroadcast());
System.out.println(" Network prefix length = " ia.getNetworkPrefixLength());
System.out.println("");
}
}
}
}
}
public static String getMac(byte[] hardwareAddress){
if (hardwareAddress != null) {
StringBuilder stringBuffer = new StringBuilder();
for (int i = 0; i < hardwareAddress.length; i ) {
if (i != 0) {
stringBuffer.append(":");
}
int tmp = hardwareAddress[i] & 0xff; // 字节转换为整数
String str = Integer.toHexString(tmp);
if (str.length() == 1) {
stringBuffer.append("0").append(str);
} else {
stringBuffer.append(str);
}
}
return stringBuffer.toString().toUpperCase();
}
return "";
}
}
工具类
代码语言:javascript复制import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
public class NetworkUtil {
private final static String LOCAL_IP = "127.0.0.1";
/**
* 获取本机有效所有网卡地址
*
* @return List<NetworkInterface> 网卡列表
* @throws
*/
public static List<NetworkInterface> getNetworkInterfaces() throws Exception {
List<NetworkInterface> localIPlist = new ArrayList<NetworkInterface>();
Enumeration<NetworkInterface> interfs = NetworkInterface.getNetworkInterfaces();
if (interfs == null) {
return null;
}
while (interfs.hasMoreElements()) {
NetworkInterface interf = interfs.nextElement();
Enumeration<InetAddress> addres = interf.getInetAddresses();
while (addres.hasMoreElements()) {
InetAddress in = addres.nextElement();
if (in instanceof Inet4Address) {
if (!LOCAL_IP.equals(in.getHostAddress())){
localIPlist.add(interf);
}
}
}
}
return localIPlist;
}
public static void main(String[] args) throws Exception {
List<NetworkInterface> networkInterfaces = getNetworkInterfaces();
assert networkInterfaces != null;
for (NetworkInterface networkInterface : networkInterfaces) {
System.out.println(networkInterface.getName());
System.out.println(networkInterface.getInetAddresses().nextElement().getHostAddress());
}
}
}
根据IP获取网卡
代码语言:javascript复制String localhost_address = "192.168.3.95";
NetworkInterface nic = NetworkInterface.getByInetAddress(InetAddress.getByName(localhost_address));
System.out.println(nic.getName());
获取默认
代码语言:javascript复制InetAddress addr = InetAddress.getLocalHost();
String msg = addr.getHostAddress() "@" addr.getHostName();