UDP協議網絡Socket編程(java實現C/S通訊案例)

個人博客園:https://www.cnblogs.com/chenzhenhong/p/13825286.htmlhtml

 

個人CSDN博客:https://blog.csdn.net/Charzous/article/details/109016215java

目錄編程

1、前言:認識UDP數組

2、UDP的特色(與TCP相比)緩存

3、UDP網絡Socket編程(Java實現)服務器

  一、建立客戶端網絡

  二、客戶端圖形界面app

  三、建立服務器端socket

4、服務器端和客戶端完整代碼ide

5、效果展現

6、總結


1、前言:認識UDP

UDP,全稱User Datagram Protocol(用戶數據報協議),是Internet 協議集支持一個無鏈接的傳輸協議。UDP 爲應用程序提供了一種無需創建鏈接就能夠發送封裝的 IP 數據包的方法。

UDP主要用於不要求分組順序到達的傳輸中,分組傳輸順序的檢查與排序由應用層完成,提供面向報文的簡單不可靠信息傳送服務。UDP 協議基本上是IP協議與上層協議的接口,適用端口分別運行在同一臺設備上的多個應用程序。

2、UDP的特色(與TCP相比)

正是UDP提供不可靠服務,具備了TCP所沒有的優點。無鏈接使得它具備資源消耗小,處理速度快的優勢,因此音頻、視頻和普通數據在傳送時常用UDP,偶爾丟失一兩個數據包,也不會對接收結果產生太大影響。

  1. UDP有別於TCP,有本身獨立的套接字(IP+Port),它們的端口號不衝突。和TCP編程相比,UDP在使用前不須要進行鏈接,沒有流的概念。

  2. 若是說TCP協議通訊與電話通訊相似,那麼UDP通訊就與郵件通訊相似:不須要實時鏈接,只須要目的地址;

  3. UDP通訊前不須要創建鏈接,只要知道地址(ip地址和端口號)就能夠給對方發送信息;

  4. 基於用戶數據報文(包)讀寫;

  5. UDP通訊通常用於線路質量好的環境,如局域網內,若是是互聯網,每每應用於對數據完整性不是過於苛刻的場合,例如語音傳送等。

以上是對UDP的基本認識,與之前學習的理論相比,接下來的實踐更加有趣,實踐出真知。

3、UDP網絡Socket編程(Java實現)

首先,熟悉java中UDP編程的幾個關鍵類:DatagramSocket(套接字類),DatagramPacket(數據報類),MulticastSocket(組播)。本篇主要使用前兩個。

一、建立客戶端

第一步,實例化一個數據報套接字,用於與服務器端進行通訊。與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();
}

 到這裏,客戶端已所有完成,等待接下來與服務端的通訊...

二、客戶端圖形界面

如今,設計客戶端通訊的簡單界面,一方面能夠更方便的和服務器連續對話通訊,另外一方面,有了圖形界面,體驗感更加!圖形化界面主要使用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+"\n");
 94  
 95             String Rmsg=null;
 96             Rmsg=UDPClient.receive();
 97 //            System.out.println(Rmsg);
 98             taDisplay.appendText(Rmsg+"\n");
 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+"\n");
116  
117  
118                    String Rmsg=null;
119                    Rmsg=UDPClient.receive();
120                    taDisplay.appendText(Rmsg+"\n");
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。

成功鏈接後:

三、建立服務器端

服務器端爲客戶端提供服務,實現通訊。這裏包括了幾個方法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();
        }
    }

4、服務器端和客戶端完整代碼

服務器端:

 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

5、效果展現

這裏的終端打印「創建鏈接」應該是 「通訊成功」,UDP是無鏈接協議。

6、總結

這一篇詳細記錄學習運用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

相關文章
相關標籤/搜索