UDP特点:
UDP编程几个关键的Java类:DatagramSocket、DatagramPacket、MulticastSocket
UDP通信,首先是客户端向服务器发送UDP包,然后服务器就知道了客户端的地址何端口号。一个典型的UDP客户端主要执行以下三步:
不像TCP通信有客户套接字Socket和服务器套接字ServerSocket两种, UDP套接字只有一种DatagramSocket,不区分客户套接字和服务器套接字。UDP套接字扮演的角色就像一个邮箱,从不同地址发来的邮件都可以放到里面。一旦被创建,UDP套接字就可以用来连续向不同的地址发送信息、或从任何地址接收信息。通信过程就需要客户端首先发送信息,服务端收到信息后,就有办法知道远程客户端的地址信息。
(1)UDP套接字创建:
DatagramSocket datagramSocket = new DatagramSocket();
正如前面所述,不需要指定本地的地址和端口号。
(2)UDP套接字的几个重要方法:
用于发送网络数据:
datagramSocket.send(DatagramPacket packet); //发送一个数据包到由IP和端口号指定的地址。
用于接收网络数据:
datagramSocket.receive(DatagramPacket packet);//接收一个数据包,没有数据,则程序会在这里阻塞。
(参数packet属于 DatagramPacket报文类的一个实例对象,用户数据封装在packet中)
指定超时:
datagramSocket.setSoTimeout(int tmeout)
timeout是个整数,表示毫秒数。用来指定上面的receive(DatagramSocket packet)方法的最长阻塞时间。超过这个时长receive方法还没得到响应,就会抛出InterruptedIOException异常,如果你的客户端设置为send一个信息,然后receive等待回应信息,可以用这个方法来设置超时,避免程序无限等待;但如果你的客户端类似TCP的设计方式,开启一个新线程来专门receive信息,那就不要用这个命令,否则在等待的过程中就超时报错了。
TCP发送数据是基于字节流的,而UDP发送数据是基于报文DatagramPacket,网络中传递的UDP数据都要封装在这种自包含(self-contained)的报文中。上面的UDP套接字创建的过程,会发现根本没有指定远程通信方的IP和端口,关键就在send方法的参数:(DatagramPacket packet)。该数据报文除了包含要传输的信息外,每个数据报文实例中还附加了IP地址和端口信息,其具体含义取决于该数据报文是被发送还是被接收。若其是待发送的数据报文,其实例中的地址则指明了目的IP地址和端口号;若其是待接收的数据报文,其实例中的地址则指明了所接收信息的源地址。
(1)UDP数据报文的创建:
DatagramPacket有多个构造方法,对于发送信息,需要明确远程的地址信息,采用下面的构造方法创建:
DatagramPacket(byte[ ] data, int length, InetAddress remoteAddr, int remotePort)
这样UDP套接字的send方法就能把该报文发送到目的地址。
对于接收信息,则采用不用指定地址信息的方式创建:
DatagramPacket(byte[ ] data, int length)
上面的int length表示要读取的数据长度。byte[ ] data就是用于存储报文数据的字节数组缓存。
(2)UDP数据报文DatagramPacket的几个重要方法:
InetAddress getAddress( ):如果是发送的报文,返回的是目标主机的地址信息;如果是要接收的报文,返回的是发送该数据报文的主机地址信息;
int getPort( ):如果是发送的报文,返回的是目标主机的端口;如果是要接收的报文,返回的是发送该数据报文的主机端口;
注意:上面这两个方法主要是服务端使用,服务端可以通过这两个方法获知客户端的地址信息。
byte[ ] getData( ): 从报文中取数据,返回与数据报文相关联的字节数组;
自行创建一个DatagramSocket实例,并指定本地端口,便于客户端初次发消息,短裤如8008。使用send何receive方法来发送和接收报文DatagramPacket的实例,进行通信。
package chapter06;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPClient {
private int remotePort;
private InetAddress remoteIP;
private DatagramSocket socket;//UDP套接字
//用于接收数据的报文字节数组缓存最大容量,字节为单位
private static final int MAX_PACKET_SIZE = 512;
public UDPClient(String remoteIP, String remotePort) throws IOException {
this.remoteIP = InetAddress.getByName(remoteIP);
this.remotePort = Integer.parseInt(remotePort);
//创建一个UDP套接字,系统随机选定一个未使用的UDP端口绑定
socket = new DatagramSocket();
//设置接收数据超时
// socket.setSoTimeout(30000);
}
//定义一个数据的发送方法
public void send(String msg) {
try {
//将待发送的字符串转为字节数组
byte[] outData = msg.getBytes("utf-8");
//构建用于发送的数据报文,构造方法中传入远程通信方(服务器)的ip地址和端口
DatagramPacket outPacket = new DatagramPacket(outData, outData.length, remoteIP, remotePort);
//给UDPServer发送数据报
socket.send(outPacket);
} catch (IOException e) {
e.printStackTrace();
}
}
//定义数据接收方法
public String receive() {
String msg;
//先准备一个空数据报文
DatagramPacket inPacket = new DatagramPacket(
new byte[MAX_PACKET_SIZE], MAX_PACKET_SIZE);
try {
//读取报文,阻塞语句,有数据就装包在inPacket报文中,以装完或装满为止。
socket.receive(inPacket);
//将接收到的字节数组转为对应的字符串
msg = new String(inPacket.getData(),
0, inPacket.getLength(), "utf-8");
} catch (IOException e) {
e.printStackTrace();
msg = null;
}
return msg;
}
}
package chapter06;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class UDPClientFX extends Application {
private Button btnExit = new Button("退出");
private Button btnSend = new Button("发送");
private TextField tfSend = new TextField();
private TextArea taDisplay = new TextArea();
private TextField tfIP = new TextField("202.116.195.71");
private TextField tfPort = new TextField("6868");
private Button btnConnect = new Button("连接");
private chapter06.UDPClient udpClient;
private Thread readThread;
@Override
public void start(Stage primaryStage) throws Exception {
BorderPane mainPane = new BorderPane();
// 顶部连接部件
HBox connHbox = new HBox();
connHbox.setAlignment(Pos.CENTER);
connHbox.setSpacing(10);
connHbox.getChildren().addAll(new Label("IP地址:"), tfIP, new Label("端口:"), tfPort, btnConnect);
mainPane.setTop(connHbox);
// 信息显示区
VBox vBox = new VBox();
vBox.setSpacing(10);
vBox.setPadding(new Insets(10, 20, 10, 20));
// 设置发送信息的文本框
// 自动换行
taDisplay.setWrapText(true);
// 只读
taDisplay.setEditable(false);
vBox.getChildren().addAll(new Label("信息显示区: "), taDisplay, new Label("信息输入区:"), tfSend);
VBox.setVgrow(taDisplay, Priority.ALWAYS);
mainPane.setCenter(vBox);
HBox hBox = new HBox();
hBox.setSpacing(10);
hBox.setPadding(new Insets(10, 20, 10, 20));
hBox.setAlignment(Pos.CENTER_RIGHT);
// 按钮事件绑定
btnConnect.setOnAction(event -> {
String ip = tfIP.getText().trim();
String port = tfPort.getText().trim();
try {
udpClient = new chapter06.UDPClient(ip, port);
// 启用接收信息进程
readThread = new Thread(() -> {
String msg = null;
// 新增线程是否中断条件 解决退出时出现异常问题
while ((msg = udpClient.receive()) != null) {
if (Thread.currentThread().isInterrupted()) {
continue;
}
String msgTemp = msg;
Platform.runLater(() -> {
taDisplay.appendText(msgTemp + "\n");
});
}
});
readThread.start();
} catch (Exception e) {
taDisplay.appendText("服务器连接失败!" + e.getMessage() + "\n");
}
});
btnExit.setOnAction(event -> {
exit();
});
btnSend.setOnAction(event -> {
String sendMsg = tfSend.getText();
// 修改这里可以发送自定义消息
udpClient.send(sendMsg);//向服务器发送一串字符
taDisplay.appendText("客户端发送:" + sendMsg + "\n");
tfSend.clear();
});
hBox.getChildren().addAll(btnSend, btnExit);
mainPane.setBottom(hBox);
Scene scene = new Scene(mainPane, 700, 400);
// 响应窗体关闭
primaryStage.setOnCloseRequest(event -> {
exit();
});
primaryStage.setScene(scene);
primaryStage.show();
}
public void exit() {
if (udpClient != null) {
udpClient.send("bye");
// 系统退出时,单独的读线程没有结束,因此会出现异常。
// 解决方案:在这里通知线程中断,在线程循环中增加条件检测当前线程是否被中断。
readThread.interrupt();
}
System.exit(0);
}
public void sendText() {
String sendMsg = tfSend.getText();
udpClient.send(sendMsg);//向服务器发送一串字符
taDisplay.appendText("客户端发送:" + sendMsg + "\n");
tfSend.clear();
// 发送bye后重新启用连接按钮,禁用发送按钮
if (sendMsg.equals("bye")) {
btnConnect.setDisable(false);
btnSend.setDisable(true);
}
}
}
package chapter06;
import javax.xml.crypto.Data;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.Date;
public class UDPServer {
// UDP套接字类:用于发送信息何接收信息
private DatagramSocket server;
// UDP数据报文类
private DatagramPacket packet;
byte[] buffer = new byte[512];
public UDPServer() throws IOException {
server = new DatagramSocket(8008);
packet = new DatagramPacket(buffer,buffer.length);
System.out.println("服务器开始运行");
}
public void runServer() throws IOException {
while(true){
System.out.println("等待消息");
server.receive(packet);
System.out.println("接收到信息");
String msg = new String(packet.getData(),packet.getOffset(),packet.getLength(),"utf-8");
String remsg = "学号&姓名&"+new Date().toString()+"&"+msg;
byte[] outPutData = remsg.getBytes("utf-8");
DatagramPacket outputPacket = new DatagramPacket(outPutData,outPutData.length,packet.getAddress(),packet.getPort());
server.send(outputPacket);
System.out.println("完成发送");
}
}
public static void main(String[] args) throws IOException {
UDPServer udpServer = new UDPServer();
udpServer.runServer();
}
}