一、DatagramSocket类
java.net包中提供了两个类:DatagramSocket和DatagramPacket用来支持UDP通信。这里先介绍一下DatagramSocket类,DatagramSocket用于在程序之间建立传送数据报的通信连接。
DatagramSocket常用的构造方法:
DatagramSocket():创建数据报DatagramSocket对象,并将其绑定到本地主机上任何可用的端 口。
DatagramSocket(int port):创建数据报DatagramSocket对象,并将其绑定到本地主机上的指定端 口。
DatagramSocket(int port, InetAddress laddr):创建数据报DatagramSocket对象,并将其绑定到指定 的本地地址。
DatagramSocket其他的常用方法有:
void send(DatagramPacket p):从发送数据报包。
void receive(DatagramPacket p):接收数据报包。
int getPort():返回DatagramSocket连接到的远程端口。
int getLocalPort():返回DatagramSocket绑定到的本地端口。
InetAddress getInetAddress():返回DatagramSocket连接的地址。
InetAddress getLocalAddress():返回DatagramSocket绑定的本地地址。
boolean isClosed():返回DatagramSocket是否处于关闭状态。
boolean isConnected():返回DatagramSocket是否处于连接状态。
void close():关闭DatagramSocket。
DatagramSocket也实现了AutoCloseable接口,通过自动资源管理技术关闭DatagramSocket。
二、DatagramPacket类
DatagramPacket用来表示数据报包,是数据传输的载体。DatagramPacket实现无连接数据包投递服务, 每投递数据包仅根据该包中信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个 包可能选择不同的路由,也可能按不同的顺序到达,不保证包都能到达目的。
DatagramPacket的构造方法:
DatagramPacket(byte[] buf, int length):构造数据报包,buf包数据,length是接收包数据的长度。
DatagramPacket(byte[] buf, int length, InetAddress address, int port):构造数据报包,包发送到指定主机上的指定端口号。
DatagramPacket(byte[] buf, int offset, int length):构造数据报包,offset是buf字节数组的偏移量。
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port):构造数据报包,包发 送到指定主机上的指定端口号。
DatagramPacket常用的方法:
InetAddress getAddress():返回发往或接收该数据报包相关的主机的IP地址。
byte[] getData():返回数据报包中的数据。
int getLength():返回发送或接收到的数据(byte[])的长度。
int getOffset():返回发送或接收到的数据(byte[])的偏移量。
int getPort():返回发往或接收该数据报包相关的主机的端口号。
三、案例:文件上传工具
服务器端UploadServer代码如下:
import java.io.*; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; /** * @author : 蔡政洁 * @email :caizhengjie888@icloud.com * @date : 2020/2/20 * @time : 3:56 下午 */ //服务器端 public class UploadServer { public static void main(String[] args) { System.out.println("服务器端运行。。。。。"); // 创建一个子线程 Thread thread = new Thread(() ->{ try ( // 创建DatagrameSocket对象,指定端口8080 DatagramSocket socket = new DatagramSocket(8080); // 创建文件输出流,并创建缓冲输出流 FileOutputStream fout = new FileOutputStream("/Users/caizhengjie/Desktop/qq/e/56.txt"); BufferedOutputStream out = new BufferedOutputStream(fout); ){ // 准备一个缓冲区 byte[] buffer = new byte[1024]; // 循环接受数据报包 while (true){ // 创建数据报包对象,用来接收数据 DatagramPacket packet = new DatagramPacket(buffer,buffer.length); // 接收数据包 socket.receive(packet); // 接收数据长度 int len = packet.getLength(); if(len == 3){ // 获得结束标志 String flag = new String(buffer,0,3); // 判断结束标志,如果是bye,则结束接收 if (flag.equals("bye")){ break; } } // 写入数据 out.write(buffer,0,3); } System.out.println("接收完成!"); } catch (SocketException | FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }); // 启动线程 thread.start(); } }
创建一个子线程,因为socket.receive(packet)方法会阻塞主线程了。
客户端UploadClient代码如下:
import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; /** * @author : 蔡政洁 * @email :caizhengjie888@icloud.com * @date : 2020/2/20 * @time : 4:31 下午 */ //客户端 public class UploadCilent { public static void main(String[] args) { System.out.println("客户端运行。。。"); try( // 创建DatagrameSocket对象,由系统分配可以使用的端口 DatagramSocket socket = new DatagramSocket(); FileInputStream fin = new FileInputStream("/Users/caizhengjie/Desktop/qq/123.txt"); BufferedInputStream in = new BufferedInputStream(fin); ){ // 创建远程主机IP地址对象 InetAddress address = InetAddress.getByName("localhost"); // 准备一个缓冲区 byte[] buffer = new byte[1024]; // 首次从文件流读数据 int len = in.read(buffer); while (len != -1){ // 创建数据报包对象 DatagramPacket packet = new DatagramPacket(buffer,len,address,8080); // 发送数据报包 socket.send(packet); // 再次从文件流中读取数据 len = in.read(buffer); } // 创建数据报包对象 DatagramPacket packet = new DatagramPacket("bye".getBytes(),3,address,8080); // 发送结束标志 socket.send(packet); System.out.println("上传完成"); } catch (SocketException | FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
上述是上传文件客户端,发送数据不会堵塞线程,因此没有使用子线程。
四、案例:聊天工具
服务器端ChatServer代码如下:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; /** * @author : 蔡政洁 * @email :caizhengjie888@icloud.com * @date : 2020/2/20 * @time : 7:57 下午 */ //服务器端 public class ChatServer { public static void main(String[] args) { System.out.println("服务器端运行。。。。"); // 创建一个子线程 Thread thread = new Thread(() ->{ try ( // 创建DatagrameSocket对象,指定端口8080 DatagramSocket socket = new DatagramSocket(8080); BufferedReader keyboardIn = new BufferedReader(new InputStreamReader(System.in)); ){ while (true){ // 接收数据报 // 准备一个缓冲区 byte[] buffer = new byte[1024]; DatagramPacket packet = new DatagramPacket(buffer,buffer.length); socket.receive(packet); // 接收数据长度 int len = packet.getLength(); String str = new String(buffer,0,len); // 打印接收的数据 System.out.printf("从客户端接收的数据:【%s】\n",str); // 发送数据 // 从客户端传来的数据包中得到客户端的地址 InetAddress address = packet.getAddress(); // 从客户端传来的数据包中得到客户端端口号 int port = packet.getPort(); // 读取键盘输入的字符串 String keyboardInputString = keyboardIn.readLine(); // 读取键盘的字节数组 byte[] b = keyboardInputString.getBytes(); // 创建 DatagramPacket对象,用于客户端的发送数据 packet = new DatagramPacket(b,b.length,address,port); // 向客户端发送数据 socket.send(packet); } } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }); // 启动线程 thread.start(); } }
创建一个子线程,因为socket.receive(packet)方法会阻塞主线程了。
客户端ChatClient代码如下:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; /** * @author : 蔡政洁 * @email :caizhengjie888@icloud.com * @date : 2020/2/20 * @time : 8:25 下午 */ //客户端 public class ChatClient { public static void main(String[] args) { System.out.println("客户端运行。。。。"); // 创建一个子线程 Thread thread = new Thread(() ->{ try ( // 创建DatagrameSocket对象,由系统自动分配端口 DatagramSocket socket = new DatagramSocket(); BufferedReader keyboardIn = new BufferedReader(new InputStreamReader(System.in)); ){ while (true){ // 发送数据 // 准备一个缓冲区 byte[] buffer = new byte[128]; // 服务器IP地址 InetAddress address = InetAddress.getByName("localhost"); // 服务器端口号 int port = 8080; // 读取键盘输入的端口号 String keyboardInputString = keyboardIn.readLine(); // 退出循环,结束线程 if (keyboardInputString.equals("bye")){ break; } // 读取键盘输入的字节数组 byte[] b = keyboardInputString.getBytes(); // 创建 DatagramPacket对象,用于客户端的发送数据 DatagramPacket packet = new DatagramPacket(b,b.length,address,port); // 发送 socket.send(packet); // 接收数据报 packet = new DatagramPacket(buffer,buffer.length); socket.receive(packet); // 接受数据长度 int len = packet.getLength(); String str = new String(buffer,0,len); // 打印接收数据 System.out.printf("从客户端接收的数据:【%s】\n",str); } } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }); // 启动线程 thread.start(); } }
注意的是ChatClient可 以通过键盘输入bye,退出循环结束线程。
先运行服务器端,再运行客户端
运行结果: