目錄
什麼是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();
}
}
創建一個學生類和教師類,分別啓動發送和接收端口