JavaFx -- chapter06(UDPSocket)

chapter06(UDPSocket)

UPD的特点

  1. UDP有独立的套接字(IP + PORT),与TCP使用相同端口号不会冲突。
  2. UDP在使用前不需要进行连接,没有流的概念。
  3. UDP通信类似于邮件通信:不需要实时连接,只需要目的地址。
  4. UDP通信前只需知道对方的IP地址和端口号即可发送信息。
  5. 基于用户数据报文(包)进行读写。
  6. UDP通信通常用于线路质量好的环境,如局域网。如果在互联网上,通常用于对数据完整性要求不高的场合,例如语音传送等。

UDP 编程关键Java类

  • DatagramSocket
  • DatagramPacket
  • MulticastSocket

1.创建 UDPClient.java 程序

UDP 客户端的主要步骤
  1. 创建 DatagramSocket 实例

    • 可以选择对本地地址和端口号进行设置,但一般不需要指定。
    • 不指定时程序将自动选择本地地址和可用的端口。
  2. 发送和接收数据

    • 使用 DatagramSocket 类来发送和接收 DatagramPacket 类的实例进行通信。
  3. 关闭套接字

    • 通信完成后,使用 DatagramSocket 类的 close() 方法销毁该套接字。
注意事项
  • Socket 类不同,创建 DatagramSocket 实例时并不需要指定目的地址,这也是 TCP 协议和 UDP 协议的最大不同点之一。

UDP 套接字类: DatagramSocket

概述
  • UDP通信没有客户套接字 (Socket用于通信) 和服务器套接字 (ServerSocket服务器端用于接收连接请求) 之分,UDP套接字只有一种:DatagramSocket
  • UDP套接字的角色类似于邮箱,可以从不同地址接收邮件,并向不同地址发送信息。
  • UDP编程不严格区分服务端和客户端,通常将固定IP和固定端口的机器视为服务器。
创建 UDP 套接字
DatagramSocket datagramSocket = new DatagramSocket();
  • 创建时不需要指定本地的地址和端口号。
UDP 套接字的重要方法
  1. 发送网络数据

    datagramSocket.send(DatagramPacket packet);
    
    • 发送一个数据包到由IP和端口号指定的地址。
  2. 接收网络数据

    datagramSocket.receive(DatagramPacket packet);
    
    • 接收一个数据包。如果没有数据,程序会在此调用处阻塞。
  3. 指定超时

    datagramSocket.setSoTimeout(int timeout);
    
    • timeout 是一个整数,表示毫秒数,用于指定 receive(DatagramPacket packet) 方法的最长阻塞时间。
    • 超过此时限后,如果没有响应,将抛出 InterruptedIOException 异常。
注意事项
  • 如果客户端通过 send 发送信息并等待响应,则可以设置超时,避免程序无限等待。
  • 如果采用类似TCP的设计,开启新线程接收信息,则不应使用超时设置,以避免在等待过程中导致超时错误。

UDP 数据报文类: DatagramPacket

概述
  • TCP发送数据是基于字节流的,而UDP发送数据是基于DatagramPacket报文。
  • 网络中传递的UDP数据都封装在自包含(self-contained)的报文中。
发送数据的过程
  • 创建UDP套接字时,没有指定远程通信方的IP和端口,而send方法的参数 (DatagramPacket packet) 是关键。
  • 每个数据报文实例除了包含要传输的信息外,还附加了IP地址和端口信息,这些信息的含义取决于数据报文是被发送还是被接收。
数据报文的创建
  1. 发送信息的构造方法

    DatagramPacket(byte[] data, int length, InetAddress remoteAddr, int remotePort);
    
  • 需要明确远程地址信息,以便将报文发送到目的地址。
  1. 接收信息的构造方法

    DatagramPacket(byte[] data, int length);
    
    • 不需要指定地址信息,length 表示要读取的数据长度,data 是用于存储报文数据的字节数组缓存。
UDP 数据报文的几个重要方法
  1. 获取目标主机IP地址

    InetAddress getAddress();
    
    • 如果是发送的报文,返回目标主机的IP地址;如果是接收的报文,返回发送该数据报文的主机IP地址。
  2. 获取目标主机端口

    int getPort();
    
    • 如果是发送的报文,返回目标主机的端口;如果是接收的报文,返回发送该数据报文的主机端口。
  3. 获取与报文相关联的数据

    byte[] getData();
    
    • 从报文中取出数据,返回与数据报文相关联的字节数组。
注意事项
  • 上述两个方法 (getAddress()getPort()) 主要供服务端使用,服务端可以通过这些方法获知客户端的地址信息。

2.创建UDPClientFX.java客户端窗体程序

创建 UDPServer.java 程序
概述
  • 类似TCP服务器,UDP服务器的工作是建立一个通信终端,并被动等待客户端发起连接。
  • 由于UDP是无连接的,因此没有TCP中建立连接的步骤。
  • UDP通信通过客户端的数据报文进行初始化。
典型的UDP服务器步骤
  1. 创建UDP套接字

    • 创建一个DatagramSocket实例,并指定一个本地端口(端口号范围在1024-65535之间选择)。
    DatagramSocket datagramSocket = new DatagramSocket(port);
    
  • 服务器准备好从任何客户端接收数据报文。
  • UDP服务器为所有客户端使用同一个套接字(与TCP不同,TCP服务器为每个成功的accept方法调用创建新的套接字)。
  1. 接收UDP报文

    • 使用DatagramSocket实例的receive方法接收一个DatagramPacket实例。
    datagramSocket.receive(datagramPacket);
    
    • receive方法返回时,数据报文将包含客户端的地址信息,从而使服务器知道该消息的来源,以便进行回复。
  2. 通信过程

    • 使用套接字的sendreceive方法来发送和接收DatagramPacket的实例进行通信。
注意事项
  • 服务端需要循环调用receive方法接收消息。

  • 如果使用同一个报文实例来接收消息,在下一个receive方法调用之前,需要调用报文实例的setLength(缓存数组.length)方法,以确保兼容性,避免数据丢失的BUG。

    datagramPacket.setLength(缓存数组.length);
    
  • 每次receive接收到的报文会修改内部消息的长度值。如果接收到的消息是10字节,下一次receive接收超出10字节的内容将会被丢弃。因此,务必重置长度值以防数据丢失。

UDP 服务器处理方法

注意事项
  • 与TCP不同,小负荷的UDP服务器通常不采用多线程方式
  • 由于UDP使用同一个套接字对应多个客户端,UDP服务器可以简单地使用顺序迭代的方式处理请求,而无需创建多个线程。
处理模式
  • UDP服务器的工作模式可以直接按照以下步骤进行:
// 省略...... 
byte[] buffer = new byte[MAX_PACKET_SIZE]; // 创建数据缓存区
DatagramPacket inPacket = new DatagramPacket(buffer, buffer.length); // 创建接收数据报文
// 省略..... while (true) { // 等待客户端请求serverSocket.receive(inPacket); // 阻塞等待,来了哪个客户端就服务哪个客户端 // 处理请求String receivedData = new String(inPacket.getData(), 0, inPacket.getLength()); // 读取客户端发送的数据System.out.println("收到来自客户端的消息: " + receivedData);// 发送响应数据String response = "服务器已收到: " + receivedData;byte[] responseData = response.getBytes();DatagramPacket outPacket = new DatagramPacket(responseData, responseData.length, inPacket.getAddress(), inPacket.getPort());serverSocket.send(outPacket); // 发送响应给客户端// 每次调用前,重置报文内部消息长度为缓冲区的实际长度inPacket.setLength(buffer.length); 
}
工作流程
  1. 创建缓冲区:在服务器启动时,创建一个字节数组作为数据缓存区,以存放接收到的UDP数据报文。
  2. 进入处理循环:服务器进入无限循环,等待客户端的请求。
  3. 接收数据:当客户端请求到达时,通过serverSocket.receive(inPacket)方法阻塞等待,直到有数据到达。
  4. 处理请求:从inPacket中读取客户端发送的数据,处理相应的业务逻辑。
  5. 发送响应:
    • 根据处理结果创建响应数据,并将其封装到新的DatagramPacket中。
    • 使用serverSocket.send(outPacket)将响应发送回客户端。
  6. 重置长度:在每次接收数据之前,调用inPacket.setLength(buffer.length),以确保能够正确接收下一次数据,避免出现数据丢失。
优势
  • 这种单线程顺序处理方法简单易懂,适用于负载较轻的场景,可以有效减少服务器资源的占用。
  • 与多线程相比,能避免上下文切换和线程管理带来的额外开销。

预习版本代码

UDPServer
package server;import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.Date;public class UDPServer {private final int port = 8888;private DatagramSocket socket;public UDPServer() {try {socket = new DatagramSocket(port);System.out.println("Server started on port " + port);} catch (Exception e) {e.printStackTrace();}}public void Service(){while (true) {byte[] buffer = new byte[1024];DatagramPacket packet = new DatagramPacket(buffer, buffer.length);try {socket.receive(packet);String message = new String(packet.getData(), 0, packet.getLength());System.out.println("Received message: " + message);String response = "20221003174&徐彬&"+ new Date() + "&" + message;byte[] responseBytes = response.getBytes();// 返回响应DatagramPacket responsePacket = new DatagramPacket(responseBytes, responseBytes.length, packet.getAddress(), packet.getPort());socket.send(responsePacket);} catch (Exception e) {e.printStackTrace();}}}public static void main(String[] args) {new UDPServer().Service();}
}
UDPClient
package client;import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;public class UDPClient {private final int remotePort;private final InetAddress remoteIP;private final DatagramSocket socket; // UDP套接字//用于接收数据的报文字节数组缓存最大容量,字节为单位private static final int MAX_PACKET_SIZE = 512;// private static final int MAX_PACKET_SIZE = 65507;public UDPClient(String remoteIP, String remotePort) throws IOException {this.remoteIP = InetAddress.getByName(remoteIP);this.remotePort = Integer.parseInt(remotePort);// 创建UDP套接字,系统随机选定一个未使用的UDP端口绑定socket = new DatagramSocket(); // 其实就是创建了一个发送datagram包的socket//设置接收数据超时// socket.setSoTimeout(30000);}public void send(String msg) {try {//将待发送的字符串转为字节数组byte[] outData = msg.getBytes(StandardCharsets.UTF_8);//构建用于发送的数据报文,构造方法中传入远程通信方(服务器)的ip地址和端口DatagramPacket outPacket = new DatagramPacket(outData, outData.length, remoteIP, remotePort);// 给UDPServer发送数据报文socket.send(outPacket);} catch (IOException e) {throw new RuntimeException(e);}}// 定义数据接收方法public String receive() {String msg = null;// 先准备一个空数据报文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(), StandardCharsets.UTF_8);} catch (IOException e) {e.printStackTrace();}return msg;}
}
UDPClientFx
package client;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.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;import java.io.IOException;public class UDPClientFx extends Application {private UDPClient client;private final Button btnInit = new Button("初始");private final Button btnExit = new Button("退出");private final Button btnSend = new Button("发送");private final TextField IpAdd_input = new TextField();private final TextField Port_input = new TextField();private final TextArea OutputArea = new TextArea();private final TextField InputField = new TextField();public void start(Stage primaryStage) {BorderPane mainPane = new BorderPane();VBox mainVBox = new VBox();HBox hBox = new HBox();hBox.setSpacing(10);//各控件之间的间隔//HBox面板中的内容距离四周的留空区域hBox.setPadding(new Insets(20, 20, 10, 20));hBox.getChildren().addAll(new Label("IP地址: "), IpAdd_input, new Label("端口: "), Port_input, btnInit);hBox.setAlignment(Pos.TOP_CENTER);//内容显示区域VBox vBox = new VBox();vBox.setSpacing(10);//各控件之间的间隔//VBox面板中的内容距离四周的留空区域vBox.setPadding(new Insets(10, 20, 10, 20));vBox.getChildren().addAll(new Label("信息显示区:"), OutputArea, new Label("信息输入区"), InputField);//设置显示信息区的文本区域可以纵向自动扩充范围VBox.setVgrow(OutputArea, Priority.ALWAYS);// 设置文本只读和自动换行OutputArea.setEditable(false);OutputArea.setStyle("-fx-wrap-text: true; /* 实际上是默认的 */ -fx-font-size: 18px;");InputField.setOnKeyPressed(event -> {if (event.getCode() == KeyCode.ENTER) {btnSend.fire();}});//底部按钮区域HBox hBox2 = new HBox();hBox2.setSpacing(10);hBox2.setPadding(new Insets(10, 20, 10, 20));hBox2.setAlignment(Pos.CENTER_RIGHT);hBox2.getChildren().addAll(btnSend, btnExit);mainVBox.getChildren().addAll(hBox, vBox, hBox2);mainPane.setCenter(mainVBox);VBox.setVgrow(vBox, Priority.ALWAYS);Scene scene = new Scene(mainPane, 800, 600);IpAdd_input.setText("127.0.0.1");Port_input.setText("8888");btnInit.setOnAction(event -> {try {String ip = IpAdd_input.getText().trim();String port = Port_input.getText().trim();client = new UDPClient(ip, port);client.send("Hello, Server!");new Thread(() -> {while (true) {String message = client.receive();if (message != null && !message.isEmpty()) {Platform.runLater(() -> OutputArea.appendText(message + "\n"));}}}).start(); // 启动接收线程} catch (IOException e) {e.printStackTrace();Platform.runLater(() -> OutputArea.appendText("连接服务器失败: " + e.getMessage() + "\n"));}});btnExit.setOnAction(event -> {//TODO 退出程序System.exit(0);});btnSend.setOnAction(event -> {//TODO 发送消息String message = InputField.getText().trim();if (message.isEmpty()) {return;}client.send(message);InputField.clear();});// 添加滚轮事件OutputArea.setOnScroll(event -> { // event滚轮事件,从底层的gestureEvent中继承,里面定义了controlDown变量,表示是否按下了ctrl键if (event.isControlDown()) {if (event.getDeltaY() > 0) {OutputArea.setStyle("-fx-font-size: " + (OutputArea.getFont().getSize() + 1) + "px;");} else {OutputArea.setStyle("-fx-font-size: " + (OutputArea.getFont().getSize() - 1) + "px;");}}});OutputArea.setWrapText(true);primaryStage.setScene(scene);primaryStage.show();}public static void main(String[] args) {launch(args);}
}

拓展练习一: 组播程序设计

  • 组播是指在一群用户范围内发送和接收信息,该信息具有共享性。UDP具有组播功能,而TCP不具有。

  • 组播地址范围为224.0.0.0 --- 239.255.255.255。组播地址号唯一标示一群用户(一定网络范围内,仅限于局域网络内或有些自治系统内支持组播)。但有很多组播地址默认已经被占用,建议在225.0.0.0到238.255.255.255之间随机选择一个组播地址使用。(默认组播只能同一网段,不能跨子网,除非设置了TTL值,且有配置组播路由器。另外同一个子网内如果也出现部分主机组播无效,可能是vmware的虚拟网卡影响,可先临时禁用这些命名为Vmnet*的虚拟网卡,并关闭防火墙)

  • 只要大家加入同一个组播地址,就能全体收取信息。在Java中,使用组播套接字MulticastSocket来组播数据,其是DatagramSocket 的一个子类,使用方式也与DatagramSocket 十分相似:将数据放在DatagramPacket对象中,然后通过MulticastSocket收发DatagramPacket对象。

组播套接字类MulticastSocket及其几个重要的方法:

路由器、交换机一般只转发和终端机一致IP地址和广播地址数据,终端机如何知道要接收组内信息?

  • 要先声明加入或退出某一组播组,其方法是:

    MulticastSocket ms = new MulticastSocket(8900);  
    ms.joinGroup(groupIP);
    

    该方法表示加入groupIP 组,groupIP 是 InetAddress 类型的组播地址。

    其作用是:告知自己的网络层该IP地址的包要收;转告上联的路由器这样的IP地址包要转发。

    ms.leaveGroup(groupIP);
    

    该方法表示退出 groupIP 组

  • 组内接收和发送信息的方法同UDP单播,也是以下两个方法:

    ms.send(DatagramPacket packet);
    ms.receive(DatagramPacket packet); 
    
  • 独立完成组播程序Multicast.java(供参考的源代码见附录)和窗体界面MulticastFX.java,组播套接字为225.0.0.1:8900,在组内发言要求以 "From IP 地址 学号 姓名:"为信息头。

  • 其效果如图6.3所示,要求每位同学都能看到组内其他同学的留言。

在这里插入图片描述

Multicast.java
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.nio.charset.StandardCharsets;
public class Multicast {InetAddress groupIP;int port = 8900;MulticastSocket ms = null;byte[] inBuff = new byte[1024]; // 1MB数据byte[] outBuff = new byte[1024];public Multicast() throws IOException {groupIP = InetAddress.getByName("225.0.0.1");// 开启一个组播端口ms = new MulticastSocket(port);// 告诉网卡这样的 IP 地址数据包要接收ms.joinGroup(groupIP);}public void send(String msg) {try {outBuff = ("From/" + InetAddress.getLocalHost().toString() + " " + "20221003xxx xx" + msg).getBytes(StandardCharsets.UTF_8);DatagramPacket outPacket = new DatagramPacket(outBuff, outBuff.length, groupIP, port);ms.send(outPacket);} catch (Exception e) {e.printStackTrace();}}public String receive() {try {DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length);ms.receive(inPacket);String msg = new String(inPacket.getData(), 0, inPacket.getLength(), StandardCharsets.UTF_8);return "From " + inPacket.getAddress().getHostAddress() + " " + msg + "\n";} catch (Exception e) {e.printStackTrace();return null;}}public void close() {try {ms.leaveGroup(groupIP);ms.close();} catch (Exception e) {e.printStackTrace();}}
}
MulticastFx.java
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.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;public class MulticastFx extends Application {private Multicast multicast;private final Button btnExit = new Button("退出");private final Button btnSend = new Button("发送");private final TextArea OutputArea = new TextArea();private final TextField InputField = new TextField();public void start(Stage primaryStage) throws IOException {BorderPane mainPane = new BorderPane();VBox mainVBox = new VBox();//内容显示区域VBox vBox = new VBox();vBox.setSpacing(10);//各控件之间的间隔//VBox面板中的内容距离四周的留空区域vBox.setPadding(new Insets(10, 20, 10, 20));vBox.getChildren().addAll(new Label("信息显示区:"), OutputArea, new Label("信息输入区"), InputField);//设置显示信息区的文本区域可以纵向自动扩充范围VBox.setVgrow(OutputArea, Priority.ALWAYS);// 设置文本只读和自动换行OutputArea.setEditable(false);OutputArea.setStyle("-fx-wrap-text: true; /* 实际上是默认的 */ -fx-font-size: 18px;");InputField.setOnKeyPressed(event -> {if (event.getCode() == KeyCode.ENTER) {btnSend.fire();}});//底部按钮区域HBox hBox2 = new HBox();hBox2.setSpacing(10);hBox2.setPadding(new Insets(10, 20, 10, 20));hBox2.setAlignment(Pos.CENTER_RIGHT);hBox2.getChildren().addAll(btnSend, btnExit);mainVBox.getChildren().addAll(vBox, hBox2);mainPane.setCenter(mainVBox);VBox.setVgrow(vBox, Priority.ALWAYS);Scene scene = new Scene(mainPane, 800, 600);multicast = new Multicast();Thread receiveThread = new Thread(() -> {while (true) {try {String msg = multicast.receive();Platform.runLater(() -> {OutputArea.appendText(msg + "\n");});} catch (Exception e) {e.printStackTrace();}}}, "receiveThread");receiveThread.start();btnExit.setOnAction(event -> {//TODO 退出程序System.exit(0);});btnSend.setOnAction(event -> {//TODO 发送消息String message = InputField.getText().trim();if (message.isEmpty()) {return;}multicast.send(message);try {OutputArea.appendText("From/" + InetAddress.getLocalHost().toString() + " " + "20221003xxx xx" + message + "\n");} catch (UnknownHostException e) {e.printStackTrace();}InputField.clear();});// 添加滚轮事件OutputArea.setOnScroll(event -> { // event滚轮事件,从底层的gestureEvent中继承,里面定义了controlDown变量,表示是否按下了ctrl键if (event.isControlDown()) {if (event.getDeltaY() > 0) {OutputArea.setStyle("-fx-font-size: " + (OutputArea.getFont().getSize() + 1) + "px;");} else {OutputArea.setStyle("-fx-font-size: " + (OutputArea.getFont().getSize() - 1) + "px;");}}});OutputArea.setWrapText(true);primaryStage.setScene(scene);primaryStage.show();}public static void main(String[] args) {launch(args);}
}

扩展练习二:UDP局域网聊天程序

  • 与TCP不同,UDP其实不真正区分服务端和客户端,一个程序其实可以身兼二职,尝试写一个不区分服务端和客户端UDP局域网聊天程序UDPChatFX.java

  • 为了能够彼此通信,使用一个约定的固定端口号,例如9527,界面可参考图 6.4。在同一局域网网段的机器运行该程序,可以互相

    发消息及发送广播消息。 程序应该提供一个下拉组合框来显示在线的用户IP地址,选中地址即可以给该用户发送消息;如果下拉组

    合框的内容为空,则给所有用户发送广播消息。发送广播消息可以简单的给广播地址"255.255.255.255"发送报文来实现。

在这里插入图片描述

  • 下拉组合框可以使用泛型方式的private ComboBox ipComboBox = new ComboBox<>()ipComboBox.setEditable(true)将组合框设置成可编辑,ipComboBox.getValue()可获取组合框中选定的内容, ipComboBox.getItems().add(ipString)可以添加 IP 地址到组合框,ipComboBox.getItems().clear()可以清空组合框,具体其他用法可以自行搜索查询。
  • 关于在线IP地址列表的获得方法,可以给广播地址发送一个约定的探测信息,收到该特定探测信息的用户就回发一个约定的信息报文,这样就可以从该报文中取出IP地址,加入到下拉组合框中。例如可以约定:点击"刷新在线用户"按钮时,向广播地址"255.255.255.255"发送特定的字符串"detect",而收到"detect"信息时,回发"echo"。通过这种统一的约定就可以找到在线用户。
UDPChat.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;public class UDPChat {private final int port = 8118;private DatagramSocket socket;public InetAddress broadcastAddress; // 广播地址private Thread refreshThread; // 接收线程byte[] inBuff = new byte[512]; // 512字节 = 512Bbyte[] outBuff = new byte[512];// 创建一个数组private final HashSet<String> onlineUsers = new HashSet<>();public UDPChat() {try {socket = new DatagramSocket(port);socket.setBroadcast(true);broadcastAddress = InetAddress.getByName("255.255.255.255");//            startRefreshThread(onlineUsers);} catch (Exception e) {e.printStackTrace();}}public void send(String msg, int type, InetAddress address) {try {if (type == 1) { // 群播outBuff = ("From/" + InetAddress.getLocalHost().toString() + " " + "20221003xxx xx " + msg).getBytes(StandardCharsets.UTF_8);DatagramPacket outPacket = new DatagramPacket(outBuff, outBuff.length, broadcastAddress, port);socket.send(outPacket);} else if (type == 2) { // 单播System.out.println("单播消息:" + msg);outBuff = ("单播消息:" + msg).getBytes(StandardCharsets.UTF_8);DatagramPacket outPacket = new DatagramPacket(outBuff, outBuff.length, address, port);socket.send(outPacket);} else if (type == 3) { // 刷新`在线用户`outBuff = (msg).getBytes(StandardCharsets.UTF_8);DatagramPacket outPacket = new DatagramPacket(outBuff, outBuff.length, InetAddress.getByName("255.255.255.255"), port);socket.send(outPacket);}} catch (Exception e) {e.printStackTrace();}}public String receive() {try {DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length);socket.receive(inPacket);String msg = new String(inPacket.getData(), 0, inPacket.getLength(), StandardCharsets.UTF_8);if (msg.equals("detect")) {// 发送检测请求send("echo", 3, null);return "detect";}return "receive: " + msg;} catch (Exception e) {e.printStackTrace();return null;}}public boolean isClosed() {return socket.isClosed();}public void close() {// 关闭套接字socket.close();System.out.println("Socket closed.");}//    public void startRefreshThread(HashSet<String> onlineUsers) {//        // 接收刷新在线用户响应//       this.refreshThread = new Thread(() -> {//            while (true) {//                try {//                    DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length);//                    socket.receive(inPacket);//                    String msg = new String(inPacket.getData(), 0, inPacket.getLength(), StandardCharsets.UTF_8);//                    if (msg.equals("echo")) {//                        String usrAddr = inPacket.getAddress().toString().substring(1);//                        onlineUsers.add(usrAddr);//                    }//                } catch (Exception e) {//                    e.printStackTrace();//                } finally {//                    // 加入适当的休眠时间//                    try {//                        Thread.sleep(5000); // 100毫秒//                    } catch (InterruptedException e) {//                        Thread.currentThread().interrupt(); // 恢复中断状态//                    }//                }//            }//        }, "RefreshThread");//        refreshThread.start();//    }public void RefreshUsers( HashSet<String> onlineUsers ) {long startTime = System.currentTimeMillis();long endTime = startTime + 5000; // 设置结束时间为当前时间加上5秒ExecutorService executor = Executors.newSingleThreadExecutor();Future<?> future = executor.submit(() -> {while (System.currentTimeMillis() < endTime) {try {DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length);socket.receive(inPacket);String msg = new String(inPacket.getData(), 0, inPacket.getLength(), StandardCharsets.UTF_8);if ("echo".equals(msg)) {String usrAddr = inPacket.getAddress().toString().substring(1);onlineUsers.add(usrAddr);}} catch (Exception e) {e.printStackTrace();}}});try {// 等待任务完成或者超时future.get(5, java.util.concurrent.TimeUnit.SECONDS);} catch (Exception e) {e.printStackTrace();} finally {executor.shutdownNow(); // 尝试立即停止所有正在执行的任务try {if (!executor.awaitTermination(5, java.util.concurrent.TimeUnit.SECONDS)) {executor.shutdownNow(); // 再次尝试强制停止}} catch (InterruptedException ex) {executor.shutdownNow();Thread.currentThread().interrupt(); // 恢复中断状态}}}public HashSet<String> refreshOnlineUsers() {// 发送刷新在线用户请求send("detect", 3, null);// 刷新在线用户RefreshUsers(onlineUsers);return onlineUsers;}
}
UDPChatFx.java
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashSet;import static javafx.scene.input.KeyCode.ENTER;public class UDPChatFx extends Application {private final UDPChat chat = new UDPChat();private final TextArea Output = new TextArea();private final TextField Input = new TextField();private final Button refreshButton = new Button("刷新在线用户");private final Button sendButton = new Button("发送");private final Button closeButton = new Button("关闭");private final ComboBox<String> ipComboBox = new ComboBox<>();public void start(Stage primaryStage) {BorderPane mainPane = new BorderPane();mainPane.setPadding(new Insets(10));ipComboBox.resize(150, 20);ipComboBox.setEditable(true);ipComboBox.getItems().add("所有用户");ipComboBox.getSelectionModel().select("所有用户");// 设置对话框区域VBox vbox = new VBox(10);vbox.getChildren().addAll(new Label("对话框"), Output);VBox.setVgrow(Output, Priority.ALWAYS);Output.setEditable(false);// 设置输入区域HBox hbox = new HBox(10);hbox.setAlignment(Pos.CENTER);HBox.setHgrow(Input, Priority.ALWAYS);hbox.getChildren().addAll(ipComboBox, refreshButton, Input, sendButton, closeButton);VBox mainVBox = new VBox(10);mainVBox.getChildren().addAll(vbox, hbox);mainPane.setCenter(mainVBox);VBox.setVgrow(vbox, Priority.ALWAYS);Thread ReceiveThread = new Thread(() -> {// 退出线程while (!chat.isClosed()) {String msg = chat.receive();System.out.println("接收到消息: " + msg);Platform.runLater(() -> Output.appendText(msg + "\n"));}}, "ReceiveThread");ReceiveThread.start();// 设置关闭按钮事件closeButton.setOnAction(e -> {chat.close();System.exit(0);});// 设置刷新按钮事件refreshButton.setOnAction(e -> {System.out.println("刷新在线用户列表");HashSet<String> onlineUsers = chat.refreshOnlineUsers();ipComboBox.getItems().clear();ipComboBox.getItems().add("所有用户");ipComboBox.getItems().addAll(onlineUsers);ipComboBox.getSelectionModel().select("所有用户");});Input.setOnKeyPressed(e -> {if (e.getCode() == ENTER) {sendButton.fire();}});// 设置发送按钮事件sendButton.setOnAction(e -> {String msg = Input.getText();if (msg.isEmpty()) {return;}if (ipComboBox.getSelectionModel().getSelectedItem().equals("所有用户")) {System.out.println("群发: " + msg);chat.send(msg, 1, null); // 默认群发} else {InetAddress ip = null;try {ip = InetAddress.getByName(ipComboBox.getSelectionModel().getSelectedItem());} catch (UnknownHostException ex) {throw new RuntimeException(ex);}chat.send(msg, 2, ip); // 指定用户群发}Output.appendText("我: " + msg + "\n");Input.clear();});primaryStage.setScene(new Scene(mainPane, 760, 450));primaryStage.setTitle("UDP Chat Application");primaryStage.show();}public static void main(String[] args) {launch(args);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1451.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

爬虫学习2

数据解析 正则表达式 量词&#xff1a; import re#searcch只会匹配到第一次匹配的内容#result re.search(r"\d","今年32")#print(result.group()) #result re.findall(r"\d","我是一个abcdeafg") #print(result)#search只会匹配到第…

radio astronomy 2

地球上的电离层会被太阳风影响。

服务器作业(2)

架设一台NFS服务器&#xff0c;并按照以下要求配置 关闭防火墙 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 配置文件设置&#xff1a; [rootlocalhost ~]# vim /etc/exports 1、开放/nfs/shared目录&#xff0c;供所有用户查询资料 共享…

基于MATLAB多参数结合火焰识别系统

一、课题介绍 本设计为基于MATLAB的火焰烟雾火灾检测系统。传统的采用颜色的方法&#xff0c;误识别大&#xff0c;局限性强。结合火焰是实时动态跳跃的&#xff0c;采用面积增长率&#xff0c;角点和圆形度三个维度相结合的方式判断是否有火焰。该设计测试对象为视频&#xf…

云轴科技ZStack在CID大会上分享VF网卡热迁移技术

近日&#xff0c;2024中国云计算基础架构开发者大会&#xff08;以下简称CID大会&#xff09;在北京举行。此次大会集中展示了云计算基础架构技术领域最前沿的科创成果&#xff0c;汇聚众多的技术专家和行业先锋&#xff0c;共同探讨云计算基础设施的最新发展和未来趋势。云轴科…

阿里云 K8S ACK服务 创建使用教程

目录 1.1 阿里云容器服务ACK介绍和创建 1.1.1 什么是容器服务Kubernetes版? 1.1.2 创建专有版Kubernetes集群 1.1.3 访问专有版Kubernetes集群 1.1.4 在专有版ACK集群创建资源并访问 通过百度网盘分享的文件&#xff1a;第12章-阿里云托管k8s集群ACK创建和使用 链接&…

H5测试点总结

一、概述 1.1 什么是H5 H5 即 HTML5&#xff0c;是最新的 Web 端开发语言版本&#xff0c;现如今&#xff0c;大多数手机 APP 页面会用 H5 实现&#xff0c;包括 PC Web 站点也会用它开发实现。所以 Web 的通用测试点和方法基本都可以适用于它。H5其实就是&#xff1a;移动端…

TapData 发布官方性能测试报告,针对各流行数据源,在多项指标中表现拔群

近日&#xff0c;TapData 官方发布了最新的性能测试报告&#xff0c;该报告详细展示了 TapData v3.5.13 在各种数据源下的性能表现&#xff0c;包括全量同步、增量同步、读写延迟等关键性能指标。 随着企业对实时数据集成和处理能力需求的提升&#xff0c;TapData 凭借其高效、…

小红书发布IP与实际不一样?揭秘背后的原因与应对策略

在小红书这个充满活力的社交平台上&#xff0c;用户们经常分享着各自的生活点滴、购物心得、美食体验等丰富内容。然而&#xff0c;有时你可能会发现&#xff0c;小红书上显示的IP地址与你的实际所在地并不一致&#xff0c;这不禁让人心性疑惑。那么&#xff0c;小红书发布IP与…

Java8 新特性 —— Stream API 详解

本文涉及到的知识点有Lambda表达式以及函数式接口&#xff0c;有不了解的小伙伴可以先学习上一篇文章&#xff1a; Java8 新特性 —— Lambda 表达式、函数接口以及方法引用详解 文章目录 引言Stream API 的使用1、创建 Stream2、中间操作&#xff08;1&#xff09;筛选与切片…

Linux历史命令history增加执行时间显示

Centos系统默认历史命令显示如下 为了更好的溯源&#xff0c;获取执行命令的准确时间&#xff0c;需要增加一些配置 设置环境变量 vim /etc/profile 在最下面添加以下环境配置 export HISTTIMEFORMAT"%Y-%m-%d %H:%M:%S " 立即刷新该环境变量 source /etc/pro…

【测试平台】【前端VUE】工具页面学习记录

背景&#xff1a; 这个我4年半以前刚接手记录&#xff0c;测试工具页面一般比较简单&#xff0c;不需要复杂东西&#xff0c;剩下就是维护。 工程安装 npm install 1.执行nmp install前先确认一下自己的node版本&#xff0c;这个项目需要是node12才可以&#xff0c;否则会出…

mysq-B+Treel(一)

介绍 MySQL是一个关系型数据库管理系统&#xff0c;由瑞典 MySQL AB 公司开发&#xff0c;属于 Oracle 旗下产品。MySQL是最流行的关系型数据库管理系统之一&#xff0c;在 WEB 应用方面&#xff0c;MySQL是最好的RDBMS (Relational Database Management System&#xff0c;关系…

解决使用netstat查看端口显示FIN_WAIT的问题

解决使用netstat查看端口显示FIN_WAIT的问题 1. 理解`FIN_WAIT`状态2. 检查应用程序3. 检查网络延迟和稳定性4. 更新和修补系统5. 调整TCP参数6. 使用更详细的工具进行分析7. 咨询开发者或技术支持8. 定期监控和评估结论在使用 netstat查看网络连接状态时,如果发现大量连接处…

微服务实战系列之玩转Docker(十八)

导览 前言Q&#xff1a;如何保障容器云环境下etcd集群的数据安全一、安全机制身份认证必学必看1. 启动参数2. 授权命令3. 开启认证 二、应用实践1. 访问容器2. 查看认证是否开启3. 查看是否已创建用户4. 创建用户5. 开启认证6. 验证是否开启7. 验证数据 结语系列回顾 前言 etc…

畅享云边大模型!火山引擎 x 地瓜机器人,大模型网关能力免费开放

前期&#xff0c;火山引擎官宣与地瓜机器人达成了合作&#xff0c;实现了火山引擎边缘智能-大模型网关与地瓜机器人软硬件通用底座“云-边-端”的全面打通&#xff0c;拓展机器人的无限智能化潜能。地瓜 RDK X5 机器人开发套件集成了火山引擎边缘智能-大模型网关能力&#xff0…

计算机性能监控体系:Quark2.0

一、背景 在过去的IT日常支持场景中&#xff0c;因为服务的用户、终端、系统等等因业务而异&#xff0c;往往会遇到以下类似这些问题或需求&#xff1a; IT工程师定位终端问题跨越不同的平台或系统&#xff0c;低效繁琐用户想要获取一些个人相关的IT环境信息&#xff0c;只能…

【新闻转载】“假冒 LockBit”来袭:勒索软件借助 AWS S3 偷窃数据,威胁升级

关键要点 Trend团队发现了一些利用 Amazon S3&#xff08;简单存储服务&#xff09;传输加速功能的 Golang 勒索软件样本&#xff0c;用于窃取受害者的文件并上传至攻击者控制的 S3 存储桶。 这些样本中硬编码的 Amazon Web Services (AWS) 凭证被用于追踪与恶意活动关联的 AW…

python之数据结构与算法(数据结构篇)-- 栈

一、栈的概念 这里我们不去了解教科书上面的“教条概念”&#xff0c;其实“栈”的概念和古代的时候的“客栈”是有异曲同工之妙的。 在这里我们把客栈看成“栈”&#xff0c;旅客看作“栈元素” 1.当旅客进来住店时&#xff0c;叫做“入栈”&#xff1b; 2.当旅客退房时&#…

【银河麒麟高级服务器操作系统】虚拟机lvm分区丢失现象分析及解决建议

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer.kylinos.cn 文档中心&#xff1a;https://documentkylinos.cn 环境描述 系统环境 物理机/虚拟机/云/容器 虚拟…