1. 前言
基于传输层协议的UDP的网络通信是不可靠的、无序的、无差错控制的。使用DatagramSocket类表示UDP通信节点的套接字,使用DatagramPacket表示节点之间发送和接收的数据报。基于UDP通信的节点之间不需要建立任何连接。组播也是一种基于UDP的通信。
2. DatagramSocket 类
每个DatagramSocket对象会绑定本地IP地址和一个UDP端口号,它可以和任意其他DatagramSocket对象之间有通信行为,但不会建立实时的网络连接。
2.1 构造DatagramSocket
- DatagramSocket():创建一个DatagramSocket对象,但没有设置绑定的端口号,相当于绑定了本地的任意一个可用的端口
- DatagramSocket(int port):创建一个DatagramSocket对象,并绑定本地端口号port。
- DatagramSocket(int port,InetAddress addr):创建一个DatagramSocket对象,并绑定本地地址addr和端口号port。IP地址为网络接口地址,如果设置为0.0.0.0,就是通配地址
- DatagramSocket(SocketAddress bindaddr):创建一个DatagramSocket对象,并绑定套接字地址bindaddr
- void bind(SocketAddress addr):如果使用无参数构造方法创建一个DatagramSocket对象,可以进行一些选项的设置,之后使用bind方法绑定IP地址和端口
2.2 DatagramSocket 类的常用方法
2.2.1 发送数据
DatagramSocket的send方法负责发送一个数据报,该方法的定义:void send(DatagramPacket p),其中参数p包含要发送的数据、数据长度、目的IP地址和端口。
//1234本地端口号
DatagramSocket ds = new DatagramSocket(1234);
InetAddress receiver = InetAddress.getLocalHost();
byte[] b = "send a message".getBytes();
//5678目的端口号
DatagramPacket dp = new DatagramPacket(b, b.length, receiver, 5678);
ds.send(dp)
2.2.2 接收数据
void receive(DatagramPacket p): DatagramSocket类的receive方法用于接收消息。消息并不是以返回值的形式得到的,而是存在于p缓冲区中,p中还包括发送者的IP地址和端口信息。
receive方法是一个阻塞的方法,调用receive的时候,如果没有收到数据报会一直阻塞,直到收到一个数据报。
byte[] b = new byte[100];
DatagramPacket dp = new DatagramPacket(b,100);
DatagramSocket ds = new DatagramSocket(1234);
ds.receive(dp);
2.2.3 建立固定通信关系
UDP的节点之间是不建立实时连接的,但是却可以建立这样一种固定关系:一个节点的DatagramSocket,只能同另一个固定的节点(由IP地址和端口号确定)进行通信
DatagramSocket ds = new DatagramSocket();
ds.connect(InetSocketAddress.createUnresolved("www.foo.com",1234));
2.2.4 解除固定通信关系
DatagramSocket ds = new DatagramSocket(1234);
...
ds.disconnect();
2.2.5 关闭 DatagramSocket
void close():关闭,并释放所有相关的资源。
...
ds.close();
补充:
- isBound(): 判断DatagramSocket对象的绑定状态
- isConnected():判断DatagramSocket对象是否处于固定通信连接状态
- isClosed():判断DatagramSocket对象是否关闭
2.3 设置 DatagramSocket 的选项
选项 | 含义 |
---|---|
SO_BROADCAST | 广播地址 |
SO_TIMEOUT | 设定接收数据报的等待超时时间 |
SO_RECBUF | 表示接收数据缓冲区的大小 |
SO_REUSEADDR | 表示是否允许重用DatagramSocket所绑定的本地地址 |
3. DatagramPacket 类
3.1 DatagramPacket 类的构造方法
- 发送数据的DatagramPacket对象
- DatagramPacket(byte[] buf, int length, InetAddress address, int port):用于发送的DatagramPacket对象,包括目的节点的IP地址address和端口号port。
- DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port):设置了发送数据的起始位置为:data[offset]
- DatagramPacket(byte[] buf, int offset, int length, SocketAddress address):将目的地址和端口号合并在了一起
- DatagramPacket(byte[] buf, int length, SocketAddress address)
- 接收数据的DatagramPacket对象
- DatagramPacket(byte[] buf, int length):用于接收数据,只需设置接收缓冲区buf,并指定读取的字节数length
- DatagramPacket(byte[] buf, int offset, int length):设置了接收缓冲区buf的起始位置偏移量
3.2 DatagramPacket 类的常用方法
3.2.1 查询 DatagramPacket
- InetAddress getAddress():对于发送数据报,返回目的节点的主机IP地址。对于接收数据报,返回的是数据的来源主机IP地址。总之,返回的是远程主机的IP地址
- int getPort():返回的是远程主机的UDP端口号。
- byte[] getData():对于发送数据报,返回的是发送缓冲区从offset开始的数据,对于接收数据,返回的是接收缓冲区的数据。
- int getOffset():返回的是发送或者接收缓冲区的数据偏移量offset
- int getLength():返回的是发送或者接收缓冲区中数据的长度
- SocketAddress getSocketAddress():返回的是远程主机IP地址和UDP端口号
3.2.2 设置 DatagramPacket
- void setData(byte[] buf):设置数据报的缓冲区数据
- void setData(byte[] data, int offset, int length):数据从buf[offset]开始,长度为length
- void setAddress(InetAddress iaddr):发送数据报时,使用参数iaddr设置目的主机的IP地址
- void setPort(int iport):发送数据报时,使用参数iport设置目的主机的UDP端口号。
- void setSocketAddress(SocketAddress address):发送数据报时,使用参数iaddr设置目的主机的套接字地址
- void setLength(int length):设置数据报的长度
4. 程序实例
4.1 UDPService
package udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.text.SimpleDateFormat;
import java.util.Date;
public class UDPService {
DatagramSocket ds = null;
public UDPService() throws Exception{
ds = new DatagramSocket(5678);
System.out.println("服务启动");
}
public void service(){
new Thread(){
public void run(){
while(true){
try {
byte[] b = new byte[100];
DatagramPacket dp = new DatagramPacket(b, b.length);
ds.receive(dp);
String msg = new String(b, 0, dp.getLength());
System.out.println("从" + dp.getAddress() + ":" + dp.getPort() + "收到:" + msg);
if(msg.equalsIgnoreCase("date")){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年mm月dd日");
dp.setData(("date:" + sdf.format(new Date())).getBytes());
}else if(msg.equalsIgnoreCase("time")){
SimpleDateFormat sdf = new SimpleDateFormat("HH:MM:SS");
dp.setData(("time:"+ sdf.format(new Date())).getBytes());
}
ds.send(dp);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
}
public static void main(String[] args) throws Exception {
new UDPService().service();
}
}
4.2 UDPClient
package udp;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.*;
public class UDPClient {
public static void main(String[] args) {
try {
InetAddress server = InetAddress.getByName("localhost");
DatagramSocket ds = new DatagramSocket();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String msg = null;
while((msg = br.readLine())!= null){
byte[] b = msg.getBytes();
DatagramPacket dp = new DatagramPacket(b, b.length, server, 5678);
ds.send(dp);
DatagramPacket sp = new DatagramPacket(new byte[100], 100);
ds.receive(sp);
msg = new String(sp.getData(),0, sp.getLength());
if(msg.equalsIgnoreCase("bye")){
break;
}
System.out.println("服务器:" + msg);
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
}
}
}
4.3 运行截图
5. 组播Socket
组播也叫多播,组播组内的所有主机共享同一个D类IP地址,这种地址称为组播地址。一台主机可以自由决定何时加入或离开一个组播组。组播地址是范围在244.0.0.0~239.255.255.255之间的IP地址
5.1 MulticastSocket类
MulticastSocket类实际上是DatagramSocket类的子类,包含了DatagramSocket类的所有域和方法,还额外定义了与组播有关的一些方法。
5.2 构造 MulticastSocket
- MulticastSocket():创建MulticastSocket对象
- MulticastSocket(int port):创建绑定到端口port的MulticastSocket对象
- MulticastSocket(SocketAddress bindAddress):创建绑定到端口套接字地址bindAddress的MulticastSocket对象。
5.3 MulticastSocket 的常用方法
5.3.1 加入组播组
- void joinGroup(InetAddress mcastaddr) 其中mcastaddr是D类组播IP地址,如果要接收发送到组播组的数据,就必须要加入组播组
- void joinGroup(SocketAddress mcastaddr, NetworkInterface netIf) 其中netIf参数设置了使用哪个网络接口加入到组播组
5.3.2 离开组播组
- void leaveGroup(InetAddress mcastaddr) 其中,mcastaddr是D类组播IP地址,调用leaveGroup之前,MulticastSocket对象应该已经加入了某个组播组。
- void leaveGroup(SocketAddress mcastaddr, NetworkInterface netIf) 其中netIf设置了哪个网络接口离开组播组。
5.3.3 设置网络接口
- void setInterface(InetAddress inf) 假设主机有多个网络接口,通过该方法设置究竟是哪一个接口参与组播操作。
5.3.4 查询网络接口
- InetAddress getInterface() 此方法返回用于组播的网路接口地址
6. 程序实例
6.1 MulticastSender
package udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;
public class MulticastSender {
public static void main(String[] args) throws IOException {
InetAddress group = InetAddress.getByName("226.0.0.1");
MulticastSocket ms = new MulticastSocket();
ms.joinGroup(group);
String msg = "Hello, everybody!";
byte[] b = msg.getBytes();
DatagramPacket dp = new DatagramPacket(b, b.length, group, 5678);
ms.send(dp);
System.out.println("发送问候给:"+ group + ":" + 5678);
ms.close();
}
}
6.2 MulticastReceiver
package udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
public class MulticastReceiver {
public static void main(String[] args) throws IOException {
InetAddress group = InetAddress.getByName("226.0.0.1");
MulticastSocket ms = new MulticastSocket(5678);
ms.joinGroup(group);
byte[] b = new byte[100];
DatagramPacket dp = new DatagramPacket(b, b.length);
ms.receive(dp);
String str = new String(dp.getData(), 0, dp.getLength());
System.out.println("从" + dp.getAddress().toString() + ":"+dp.getPort()+"收到消息");
System.out.println(str);
ms.leaveGroup(group);
ms.close();
}
}
6.3 运行截图