• UDP协议网络Socket编程(java实现C/S通信案例)


    我的博客园:https://www.cnblogs.com/chenzhenhong/p/13825286.html

    我的CSDN博客:https://blog.csdn.net/Charzous/article/details/109016215

    目录

    一、前言:认识UDP

    二、UDP的特点(与TCP相比)

    三、UDP网络Socket编程(Java实现)

      1、创建客户端

      2、客户端图形界面

      3、创建服务器端

    四、服务器端和客户端完整代码

    五、效果展示

    六、总结


    一、前言:认识UDP

    UDP,全称User Datagram Protocol(用户数据报协议),是Internet 协议集支持一个无连接的传输协议。UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。

    UDP主要用于不要求分组顺序到达的传输中,分组传输顺序的检查与排序由应用层完成,提供面向报文的简单不可靠信息传送服务。UDP 协议基本上是IP协议与上层协议的接口,适用端口分别运行在同一台设备上的多个应用程序。

    二、UDP的特点(与TCP相比)

    正是UDP提供不可靠服务,具有了TCP所没有的优势。无连接使得它具有资源消耗小,处理速度快的优点,所以音频、视频和普通数据在传送时经常使用UDP,偶尔丢失一两个数据包,也不会对接收结果产生太大影响。

    1. UDP有别于TCP,有自己独立的套接字(IP+Port),它们的端口号不冲突。和TCP编程相比,UDP在使用前不需要进行连接,没有流的概念。

    2. 如果说TCP协议通信与电话通信类似,那么UDP通信就与邮件通信类似:不需要实时连接,只需要目的地址;

    3. UDP通信前不需要建立连接,只要知道地址(ip地址和端口号)就可以给对方发送信息;

    4. 基于用户数据报文(包)读写;

    5. UDP通信一般用于线路质量好的环境,如局域网内,如果是互联网,往往应用于对数据完整性不是过于苛刻的场合,例如语音传送等。

    以上是对UDP的基本认识,与以前学习的理论相比,接下来的实践更加有趣,实践出真知。

    三、UDP网络Socket编程(Java实现)

    首先,熟悉java中UDP编程的几个关键类:DatagramSocket(套接字类),DatagramPacket(数据报类),MulticastSocket(组播)。本篇主要使用前两个。

    1、创建客户端

    第一步,实例化一个数据报套接字,用于与服务器端进行通信。与TCP不同,UDP中只有DatagramSocket一种套接字,不区分服务端和客户端,创建的时候并不需要指定目的地址,这也是TCP协议和UDP协议最大的不同点之一。

    public UDPClient(String remoteIP,String remotePort) throws IOException{
            this.remoteIP=InetAddress.getByName(remoteIP);
            this.remotePort=Integer.parseInt(remotePort);
            //创建UDP套接字,系统随机选定一个未使用的UDP端口绑定
            socket=new DatagramSocket();
    }

    第二步, 创建UDP数据报,实现发送和接收数据的方法。UDP发送数据是基于报文DatagramPacket,网络中传递的UDP数据都要封装在这种自包含的报文中。

    实现DatagramPacket发送数据的方法:

    //定义一个数据的发送方法
    public void send(String msg){
        try {
            //将待发送的字符串转为字节数组
            byte[] outData=msg.getBytes("utf-8");
            //构建用于发送的数据报文,构造方法中传入远程通信方(服务器)的ip地址和端口
            DatagramPacket outPacket=new DatagramPacket(outData,outData.length,remoteIP,remotePort);
            //给UDP发送数据报
            socket.send(outPacket);
        }catch (IOException e){
                e.printStackTrace();
         }
    }

     DatagramPacket接收数据的方法:

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

    可以看到,发送和接收数据中使用DatagramSocket的实例的send和receive方法,这就是数据报套接字的两个重要方法。

    通信结束,销毁Socket的方法如下:

    public void close(){
        if (socket!=null)
            socket.close();
    }

     到这里,客户端已全部完成,等待接下来与服务端的通信...

    2、客户端图形界面

    现在,设计客户端通信的简单界面,一方面可以更方便的和服务器连续对话通信,另一方面,有了图形界面,体验感更加!图形化界面主要使用JavaFX实现,代码容易看懂。

      1 import javafx.application.Application;
      2 import javafx.event.EventHandler;
      3 import javafx.geometry.Insets;
      4 import javafx.geometry.Pos;
      5 import javafx.scene.Scene;
      6 import javafx.scene.control.*;
      7 import javafx.scene.input.KeyCode;
      8 import javafx.scene.input.KeyEvent;
      9 import javafx.scene.layout.BorderPane;
     10 import javafx.scene.layout.HBox;
     11 import javafx.scene.layout.Priority;
     12 import javafx.scene.layout.VBox;
     13 import javafx.stage.Stage;
     14  
     15 import java.io.IOException;
     16  
     17  
     18 public class UDPClientFX extends Application {
     19  
     20     private Button btnExit=new Button("退出");
     21     private Button btnSend = new Button("发送");
     22  
     23     private TextField tfSend=new TextField();//输入信息区域
     24  
     25     private TextArea taDisplay=new TextArea();//显示区域
     26     private TextField ipAddress=new TextField();//填写ip地址
     27     private TextField tfport=new TextField();//填写端口
     28     private Button btConn=new Button("连接");
     29     private UDPClient UDPClient;
     30  
     31     private String ip;
     32     private String port;
     33  
     34  
     35     @Override
     36     public void start(Stage primaryStage) {
     37         BorderPane mainPane=new BorderPane();
     38  
     39         //连接服务器区域
     40         HBox hBox1=new HBox();
     41         hBox1.setSpacing(10);
     42         hBox1.setPadding(new Insets(10,20,10,20));
     43         hBox1.setAlignment(Pos.CENTER);
     44         hBox1.getChildren().addAll(new Label("ip地址:"),ipAddress,new Label("端口:"),tfport,btConn);
     45         mainPane.setTop(hBox1);
     46  
     47         VBox vBox=new VBox();
     48         vBox.setSpacing(10);
     49  
     50         vBox.setPadding(new Insets(10,20,10,20));
     51         vBox.getChildren().addAll(new Label("信息显示区"),taDisplay,new Label("信息输入区"),tfSend);
     52  
     53         VBox.setVgrow(taDisplay, Priority.ALWAYS);
     54         mainPane.setCenter(vBox);
     55  
     56  
     57         HBox hBox=new HBox();
     58         hBox.setSpacing(10);
     59         hBox.setPadding(new Insets(10,20,10,20));
     60         hBox.setAlignment(Pos.CENTER_RIGHT);
     61         hBox.getChildren().addAll(btnSend,btnExit);
     62         mainPane.setBottom(hBox);
     63  
     64         Scene scene =new Scene(mainPane,700,500);
     65         primaryStage.setScene(scene);
     66         primaryStage.show();
     67  
     68         //连接服务器之前,发送bye后禁用发送按钮,禁用Enter发送信息输入区域,禁用下载按钮
     69         btnSend.setDisable(true);
     70         tfSend.setDisable(true);
     71  
     72         //连接按钮
     73         btConn.setOnAction(event -> {
     74             ip=ipAddress.getText().trim();
     75             port=tfport.getText().trim();
     76  
     77             try {
     78                 UDPClient = new UDPClient(ip,port);
     79                 //连接服务器之后未结束服务前禁用再次连接
     80                 btConn.setDisable(true);
     81                 //重新连接服务器时启用输入发送功能
     82                 tfSend.setDisable(false);
     83                 btnSend.setDisable(false);
     84             } catch (IOException e) {
     85                 e.printStackTrace();
     86             }
     87         });
     88  
     89         //发送按钮事件
     90         btnSend.setOnAction(event -> {
     91             String msg=tfSend.getText();
     92             UDPClient.send(msg);//向服务器发送一串字符
     93             taDisplay.appendText("客户端发送:"+msg+"
    ");
     94  
     95             String Rmsg=null;
     96             Rmsg=UDPClient.receive();
     97 //            System.out.println(Rmsg);
     98             taDisplay.appendText(Rmsg+"
    ");
     99  
    100             if (msg.equals("bye")){
    101                 btnSend.setDisable(true);//发送bye后禁用发送按钮
    102                 tfSend.setDisable(true);//禁用Enter发送信息输入区域
    103                 //结束服务后再次启用连接按钮
    104                 btConn.setDisable(false);
    105             }
    106             tfSend.clear();
    107         });
    108         //对输入区域绑定键盘事件
    109         tfSend.setOnKeyPressed(new EventHandler<KeyEvent>() {
    110             @Override
    111             public void handle(KeyEvent event) {
    112                if(event.getCode()==KeyCode.ENTER){
    113                    String msg=tfSend.getText();
    114                    UDPClient.send(msg);//向服务器发送一串字符
    115                    taDisplay.appendText("客户端发送:"+msg+"
    ");
    116  
    117  
    118                    String Rmsg=null;
    119                    Rmsg=UDPClient.receive();
    120                    taDisplay.appendText(Rmsg+"
    ");
    121  
    122                    if (msg.equals("bye")){
    123                        tfSend.setDisable(true);//禁用Enter发送信息输入区域
    124                        btnSend.setDisable(true);//发送bye后禁用发送按钮
    125                        //结束服务后再次启用连接按钮
    126                        btConn.setDisable(false);
    127                    }
    128                    tfSend.clear();
    129                 }
    130             }
    131         });
    132         
    133         btnExit.setOnAction(event -> {
    134             try {
    135                 exit();
    136             } catch (InterruptedException e) {
    137                 e.printStackTrace();
    138             }
    139  
    140         });
    141         //窗体关闭响应的事件,点击右上角的×关闭,客户端也关闭
    142         primaryStage.setOnCloseRequest(event -> {
    143             try {
    144                 exit();
    145             } catch (InterruptedException e) {
    146                 e.printStackTrace();
    147             }
    148         });
    149  
    150  
    151         //信息显示区鼠标拖动高亮文字直接复制到信息输入框,方便选择文件名
    152         //taDispaly为信息选择区的TextArea,tfSend为信息输入区的TextField
    153         //为taDisplay的选择范围属性添加监听器,当该属性值变化(选择文字时),会触发监听器中的代码
    154         taDisplay.selectionProperty().addListener(((observable, oldValue, newValue) -> {
    155             //只有当鼠标拖动选中了文字才复制内容
    156             if(!taDisplay.getSelectedText().equals(""))
    157                 tfSend.setText(taDisplay.getSelectedText());
    158         }));
    159     }
    160  
    161     private void exit() throws InterruptedException {
    162         if (UDPClient!=null){
    163             //向服务器发送关闭连接的约定信息
    164             UDPClient.send("bye");
    165             UDPClient.close();
    166         }
    167         System.exit(0);
    168     }
    169  
    170     
    171     public static void main (String[] args) {
    172             launch(args);
    173     }
    174 }
    View Code

    重点在各个控件的事件处理逻辑上,需避免要一些误操作导致异常抛出,如:连接服务器前禁用发送按钮在连接服务器成功后禁用连接按钮,禁用输入区等。另外,实现了回车发送的快捷功能,详见代码的键盘事件绑定部分。(注:这里的UDP连接应该视为初始化,不属于连接服务器,相当于我们发邮件时候填写对方的地址一样)

    还有,约定发送"bye"或者退出按钮结束通信关闭Socket。

    成功连接后:

    3、创建服务器端

    服务器端为客户端提供服务,实现通信。这里包括了几个方法Service(),udpSend()和udpReceive().

    首先,我将UDP数据报发送和接收写成一个方法,作为整体方便多次调用。

    public DatagramPacket udpReceive() throws IOException {
        DatagramPacket receive;
        byte[] dataR = new byte[1024];
        receive = new DatagramPacket(dataR, dataR.length);
        socket.receive(receive);
        return receive;
    }
     
    public void udpSend(String msg,InetAddress ipRemote,int portRemote) throws IOException {
        DatagramPacket sendPacket;
        byte[] dataSend = msg.getBytes();
        sendPacket = new DatagramPacket(dataSend,dataSend.length,ipRemote,portRemote);
        socket.send(sendPacket);
    }

    与TCP的Socket通信不同,需要将数据转化成字节数据形式,封装成数据报进行传输,接收时解析数据为字节,再进行读取。

    服务器端核心部分为Service()方法,实例化一个DatagramSocket类套接字,实现循环与客户端的通信。

    与客户端约定的结束标志"bye"进行处理,结束对话。

        public void Service() throws IOException {
            try {
                socket = new DatagramSocket(port);
                System.out.println("服务器创建成功,端口号:" + socket.getLocalPort());
     
                while (true) {
     
                    //服务器接收数据
                    String msg=null;
                    receivePacket = udpReceive();
                    InetAddress ipR = receivePacket.getAddress();
                    int portR = receivePacket.getPort();
                    msg = new String(receivePacket.getData(), 0, receivePacket.getLength(), "utf-8");
     
    //                System.out.println("收到:"+receivePacket.getSocketAddress()+"内容:"+msg);
     
                    if (msg.equalsIgnoreCase("bye")) {
                        udpSend("来自服务器消息:服务器断开连接,结束服务!",ipR,portR);
                        System.out.println(receivePacket.getSocketAddress()+"的客户端离开。");
                        continue;
                    }
                    System.out.println("建立连接:"+receivePacket.getSocketAddress());
     
                    String now = new Date().toString();
                    String hello = "From 服务器:&" + now + "&" + msg;
                    udpSend(hello,ipR,portR);
     
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    四、服务器端和客户端完整代码

    服务器端:

     1 //UDPServer.java
     2 import java.io.IOException;
     3 import java.net.DatagramPacket;
     4 import java.net.DatagramSocket;
     5 import java.net.InetAddress;
     6 import java.util.Date;
     7  
     8 public class UDPServer {
     9     private DatagramSocket socket = null;
    10     private int port = 8888;
    11     private DatagramPacket receivePacket;
    12  
    13     public UDPServer() throws IOException {
    14         System.out.println("服务器启动监听在" + port + "端口...");
    15     }
    16  
    17     public void Service() throws IOException {
    18         try {
    19             socket = new DatagramSocket(port);
    20             System.out.println("服务器创建成功,端口号:" + socket.getLocalPort());
    21  
    22             while (true) {
    23  
    24                 //服务器接收数据
    25                 String msg=null;
    26                 receivePacket = udpReceive();
    27                 InetAddress ipR = receivePacket.getAddress();
    28                 int portR = receivePacket.getPort();
    29                 msg = new String(receivePacket.getData(), 0, receivePacket.getLength(), "utf-8");
    30  
    31 //                System.out.println("收到:"+receivePacket.getSocketAddress()+"内容:"+msg);
    32  
    33                 if (msg.equalsIgnoreCase("bye")) {
    34                     udpSend("来自服务器消息:服务器断开连接,结束服务!",ipR,portR);
    35                     System.out.println(receivePacket.getSocketAddress()+"的客户端离开。");
    36                     continue;
    37                 }
    38                 System.out.println("建立连接:"+receivePacket.getSocketAddress());
    39  
    40                 String now = new Date().toString();
    41                 String hello = "From 服务器:&" + now + "&" + msg;
    42                 udpSend(hello,ipR,portR);
    43  
    44             }
    45         } catch (IOException e) {
    46             e.printStackTrace();
    47         }
    48     }
    49  
    50     public DatagramPacket udpReceive() throws IOException {
    51         DatagramPacket receive;
    52         byte[] dataR = new byte[1024];
    53         receive = new DatagramPacket(dataR, dataR.length);
    54         socket.receive(receive);
    55         return receive;
    56     }
    57  
    58     public void udpSend(String msg,InetAddress ipRemote,int portRemote) throws IOException {
    59         DatagramPacket sendPacket;
    60         byte[] dataSend = msg.getBytes();
    61         sendPacket = new DatagramPacket(dataSend,dataSend.length,ipRemote,portRemote);
    62         socket.send(sendPacket);
    63     }
    64  
    65     public static void main(String[] args) throws IOException {
    66         new UDPServer().Service();
    67     }
    68 }
    View Code

    客户端:

     1 //UDPClient.java
     2  
     3 import java.io.IOException;
     4 import java.net.DatagramPacket;
     5 import java.net.DatagramSocket;
     6 import java.net.InetAddress;
     7  
     8 public class UDPClient {
     9     private int remotePort;
    10     private InetAddress remoteIP;
    11     private DatagramSocket socket;
    12     //用于接收数据的报文字节数组缓存最最大容量,字节为单位
    13     private static final int MAX_PACKET_SIZE=512;
    14  
    15     public UDPClient(String remoteIP,String remotePort) throws IOException{
    16         this.remoteIP=InetAddress.getByName(remoteIP);
    17         this.remotePort=Integer.parseInt(remotePort);
    18         //创建UDP套接字,系统随机选定一个未使用的UDP端口绑定
    19         socket=new DatagramSocket();
    20  
    21     }
    22  
    23     //定义一个数据的发送方法
    24     public void send(String msg){
    25         try {
    26             //将待发送的字符串转为字节数组
    27             byte[] outData=msg.getBytes("utf-8");
    28             //构建用于发送的数据报文,构造方法中传入远程通信方(服务器)的ip地址和端口
    29             DatagramPacket outPacket=new DatagramPacket(outData,outData.length,remoteIP,remotePort);
    30             //给UDP发送数据报
    31             socket.send(outPacket);
    32         }catch (IOException e){
    33             e.printStackTrace();
    34         }
    35     }
    36  
    37     public String receive(){
    38         String msg;
    39         //准备空的数据报文
    40         DatagramPacket inPacket=new DatagramPacket(new byte[MAX_PACKET_SIZE],MAX_PACKET_SIZE);
    41         try {
    42             //读取报文,阻塞语句,有数据就装包在inPacket报文中,以装完或装满为止
    43             socket.receive(inPacket);
    44             //将接收到的字节数组转为对应的字符串
    45             msg=new String(inPacket.getData(),0,inPacket.getLength(),"utf-8");
    46         } catch (IOException e) {
    47             e.printStackTrace();
    48             msg=null;
    49         }
    50         return msg;
    51     }
    52  
    53     public void close(){
    54         if (socket!=null)
    55             socket.close();
    56     }
    57 }
    View Code

    五、效果展示

    这里的终端打印“建立连接”应该是 “通信成功”,UDP是无连接协议。

    六、总结

    这一篇详细记录学习运用java进行网络编程,基于UDP套接字(Socket)实现服务器与客户端间的通信,在实战案例中更深刻理解UDP的实现原理,掌握UDP实践应用步骤。

    起初完成UDP通信时,遇到了几个问题,相比较TCP的实现,确实体会到数据传输的过程的不同,UDP服务和客户端共用了一个DatagramSocket,另外需要DatagramPacket数据报的协作。另外,UDP没有数据流的概念,所以读写不同于TCP,需要以字节数据进行读取。

    接下来将继续探索网络编程,基于TCP的Socket编程更加有趣!一起学习期待!


    我的博客园:https://www.cnblogs.com/chenzhenhong/p/13825286.html

    我的CSDN博客:https://blog.csdn.net/Charzous/article/details/109016215

  • 相关阅读:
    Python 中的 __str__ 与 __repr__ 到底有什么差别
    02 LeetCode---链表相加
    01 LeetCode---两数之和
    python 数据结构一 之 线性表
    来自C++的"Const式"傲娇
    string 与 char 字符串区别-1
    超级有爱的并查集入门
    多项式求解
    竞码编程-蓝桥杯模拟赛4-D题
    树的直径-蓝桥杯大臣的旅费
  • 原文地址:https://www.cnblogs.com/chenzhenhong/p/13825286.html
Copyright © 2020-2023  润新知