目录
什么是UDP?
UDP(User Datagram Protocol)用户数据报协议,为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据报的方法。
UDP特点
- 非面向连接,即通讯前不需要建立连接
- 高效
- 不可靠,可能存在丢包
- 大小有限制,一般来是数据包大小不要超过60K
- 不存在客户端与服务器的概念,每个端都是平等的
- JAVA编程中,将数据封装到DatagramPacket,指定目标地址进行数据传输
UDP编程核心类
DatagramSocket:该类表示用于发送/接收数据包的套接字
DatagramPacket:该类表示被传输的数据包
UDP编程
UDP编程可以概括为以下几步
发送端
- 创建发送端:DatagramSocket(int port)对象
- 准备数据,转为字节数组
- 将字节数组封装为数据报包:DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)对象
- 发送/接收数据
- 释放资源
接收端
- 创建接收端:DatagramSocket(int port)对象
- 准备容器,封装为DatagramPacket包:DatagramPacket(byte[] buf, int offset, int length)对象
- 阻塞式接收包
- 解析包
- 释放资源
1、基本通信
发送端
public class UdpSender {
public static void main(String[] args) throws Exception {
// 1、指定端口,创建发送端
DatagramSocket client = new DatagramSocket(8888);
// 2、准备数据,转成字节数组
String str = "Hello World!你好";
byte[] datas = str.getBytes();
// 3、将字节数组封装成包,
// 发送端构造方法传递 ——>
// 数据字节数组,起始位置,长度,IP套接字地址(接收端的ip/主机 + 端口)
DatagramPacket packet = new DatagramPacket(datas, 0, datas.length,
new InetSocketAddress("localhost", 9999));
// 4、发送数据
client.send(packet);
// 5、释放资源
client.close();
}
}
接收端
public class UdpReceiver {
public static void main(String[] args) throws Exception {
// 1、指定端口,创建接收端,此处的端口应对应发送端数据包传递的端口
DatagramSocket server = new DatagramSocket(9999);
// 2、准备容器,封装成DatagramPacket包
byte[] contanier = new byte[60*1024]; // 60K
// 接收端的数据包不用传递IP套接字地址
DatagramPacket packet = new DatagramPacket(contanier, 0, contanier.length);
// 3、阻塞式接收包,会一直等待数据包
System.out.println("等待接收");
server.receive(packet);
System.out.println("接收到数据包,进行解析");
// 4、解析包
byte[] datas = packet.getData(); // 字节数组
int len = packet.getLength(); // 长度
System.out.println(new String(datas));
System.out.println(len);
// 5、释放资源
server.close();
}
}
接收端控制台
2、基本类型数据通信
与基本通信相同,唯一需要改动的部分就是发送端的数据转字节数组,接收端的字节数组转数据
发送端
// 1、指定端口,创建发送端
// 2、准备基本类型数据,转成字节数组
// ByteArrayOutputStream 用于将数据转为字节数组
// BufferedOutputStream 用于缓冲,提升效率
// DataOutputStream 用于将基本类型数据写入输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(baos));
dos.writeInt(1);
dos.writeChar('a');
dos.writeBoolean(true);
dos.writeFloat(1.2f);
dos.flush(); // 调用flush()将缓冲区数据输出
byte[] datas = baos.toByteArray();
// 3、将字节数组封装成包
// 4、发送数据
// 5、释放资源
接收端
// 1、指定端口,创建接收端
// 2、准备容器,封装成DatagramPacket包
// 3、阻塞式接收包
// 4、解析基本类型
// ByteArrayInputStream 获取传递过来的输入流
// BufferedInputStream 用于缓冲提升效率
// DataInputStream 用于读取输入流中的基本数据类型
ByteArrayInputStream bais = new ByteArrayInputStream(packet.getData());
DataInputStream dis = new DataInputStream(new BufferedInputStream(bais));
// 取值顺序应和写入顺序一致
System.out.println(dis.readInt());
System.out.println(dis.readChar());
System.out.println(dis.readBoolean());
System.out.println(dis.readFloat());
// 5、释放资源
接收端控制台
3、对象类型数据通信
对象要能够被传输,必须实现Serializable接口,最好提供一个serialVersionUID
Person.java
public class Person implements Serializable {
private static final long serialVersionUID = -3201672726868875942L;
private String name;
private transient int age; // 使用transient修饰的属性不会被序列化传输
public String getName() {
return name;
}
// get/set/constructor方法省略
}
发送端
// 1、指定端口,创建发送端
// 2、准备对象类型数据,转成字节数组
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(baos));
oos.writeObject(new Person("IcyDate", 18));
oos.flush();
byte[] datas = baos.toByteArray();
// 3、将字节数组封装成包
// 4、发送数据
// 5、释放资源
接收端
// 1、指定端口,创建接收端,此处的端口应对应发送端发送数据指定的端口
// 2、准备容器,封装成DatagramPacket包
// 3、阻塞式接收包
// 4、解析对象类型,取值顺序应和写入顺序一致
ByteArrayInputStream bais = new ByteArrayInputStream(packet.getData());
ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(bais));
Object obj = ois.readObject();
Person person = null;
if (obj instanceof Person) {
person = (Person) obj;
}
System.out.println("name----->" + person.getName());
System.out.println("age----->" + person.getAge());
// 5、释放资源
接收端控制台
4、文件类型通信
文件在发送端首先使用FileInputStream读取,再写入ByteArrayOutputStrem并转为字节数组
发送端
// 1、指定端口,创建发送端
// 2、准备基本类型数据,转成字节数组
File file = new File("src/test/1.png"); // 文件大小不超过接收端容器大小
FileInputStream fis = new FileInputStream(file); // 也可直接传递文件地址
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// flush代表每次读取和写入的大小,10K
byte[] flush = new byte[1024*10];
int len = -1;
// 文件输入流读取最多flush.length字节的数据到字节数组,读取完毕返回-1
while ((len = fis.read(flush)) != -1) {
// 往字节输出流中写入读取出的数据
baos.write(flush, 0, len);
}
byte[] datas = baos.toByteArray();
// 3、将字节数组封装成包
// 4、发送数据
// 5、释放资源
接收端
// 1、指定端口,创建接收端,此处的端口应对应发送端发送数据指定的端口
// 2、准备容器,封装成DatagramPacket包
// 3、阻塞式接收包
// 4、解析基本类型,取值顺序应和写入顺序一致
ByteArrayInputStream bais = new ByteArrayInputStream(packet.getData());
File file = new File("src/test/copy.png"); // 需要写入的文件路径
FileOutputStream fos = new FileOutputStream(file);
byte[] flush = new byte[5];
int len = -1;
// 从数据包中的输入流里读取并写入文件输出流
while ((len = bais.read(flush)) != -1) {
fos.write(flush, 0, len);
}
fos.flush();
// 5、释放资源
5、多次通信
之前的例子都是单次的通信,多次通信只需要发送端循环接收控制台输入并发送,接收端循环接收数据包并解析即可
发送端
// 1、指定端口,创建发送端
// 2、准备数据,转成字节数组
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String str = br.readLine(); // 接收控制台输入
byte[] datas = str.getBytes();
// 3、将字节数组封装成包
DatagramPacket packet = new DatagramPacket(datas, 0, datas.length,
new InetSocketAddress("localhost", 9999));
// 4、发送数据
client.send(packet);
if ("bye".equals(str)) {
break;
}
}
// 5、释放资源
接收端
// 1、指定端口,创建接收端,此处的端口应对应发送端发送数据指定的端口
// 2、准备容器,封装成DatagramPacket包
while (true) {
byte[] contanier = new byte[60*1024];
DatagramPacket packet = new DatagramPacket(contanier, 0, contanier.length);
// 3、阻塞式接收包
server.receive(packet);
// 4、解析包
byte[] datas = packet.getData();
int len = packet.getLength();
String msg = new String(datas, 0, len);
System.out.println(msg);
if ("bye".equals(msg)) {
break;
}
}
// 5、释放资源
6、双向通信
双向通信要求每一端,既能发送数据,也能接收数据。那就需要一个线程来执行发送端,一个线程执行接收端。也就需要我们把发送端和接受端封装成两个类并实现Runnable接口
发送端
public class UdpSender implements Runnable{
private DatagramSocket client;
private BufferedReader br;
private String toIP;
private int toPort;
private String sender;
// 用构造方法来指定发送端端口,接收端ip+端口,和发送人
UdpSender(int port, String toIP, int toPort, String sender) {
this.toIP = toIP;
this.toPort = toPort;
this.sender = sender;
try {
client = new DatagramSocket(port);
br = new BufferedReader(new InputStreamReader(System.in));
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
String str = null;
try {
str = sender + ":" + br.readLine();
byte[] datas = str.getBytes();
// 3、将字节数组封装成包
DatagramPacket packet = new DatagramPacket(datas, 0, datas.length,
new InetSocketAddress(toIP, toPort));
// 4、发送数据
client.send(packet);
} catch (IOException e) {
e.printStackTrace();
}
}
// 5、释放资源
client.close();
}
}
接收端
public class UdpReceiver implements Runnable{
private DatagramSocket server;
// 指定接收端端口
UdpReceiver(int port) {
try {
server = new DatagramSocket(port);
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
// 2、准备容器,封装成DatagramPacket包
while (true) {
byte[] contanier = new byte[60*1024];
DatagramPacket packet = new DatagramPacket(contanier, 0, contanier.length);
// 3、阻塞式接收包
try {
server.receive(packet);
// 4、解析包
byte[] datas = packet.getData();
int len = packet.getLength();
String msg = new String(datas, 0, len);
System.out.println(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
// 5、释放资源
server.close();
}
}
创建一个学生类和教师类,分别启动发送和接收端口