UDP套接字程序设计

一、简介

UDP特点:

  • UDP与TCP不同,有自己独立的套接字(IP+PORT),所以UDP何TCP使用相同的端口号不会冲突
  • UDP在使用前不需要进行连接,没有流的概念,不需要实时连接,只需要目的地址(TCP:电话通讯;UDP:邮件通信)
  • 基于用户数据报文(包)读写
  • 适用于对数据完整性不是过于苛刻的场合

UDP编程几个关键的Java类:DatagramSocket、DatagramPacket、MulticastSocket

二、程序设计

1、创建UDPClient.java

UDP通信,首先是客户端向服务器发送UDP包,然后服务器就知道了客户端的地址何端口号。一个典型的UDP客户端主要执行以下三步:

  • 创建一个DatagramSocket,不需要指定目的地址
  • 使用DatagramSocket类来发送和接收DatagramPacket类的实例,进行通信
  • 通信完成后,使用DatagramSocket类的close()销毁该套接字
1.1 UDP套接字类: DatagramSocket

不像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信息,那就不要用这个命令,否则在等待的过程中就超时报错了。

1.2 UDP数据报文类: DatagramPacket

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( ): 从报文中取数据,返回与数据报文相关联的字节数组;

2、创建UDPClientFX.java

UDP套接字程序设计_第1张图片

3、创建UDPServer.java

自行创建一个DatagramSocket实例,并指定本地端口,便于客户端初次发消息,短裤如8008。使用send何receive方法来发送和接收报文DatagramPacket的实例,进行通信。

4、运行结果

UDP套接字程序设计_第2张图片

三、完整代码

UDPClient.java

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;
    }
}

UDPClientFX.java

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);
        }
    }
}

UDPServer.java

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();
    }
}

你可能感兴趣的:(互联网程序设计,udp,网络,tcp/ip)