UDP協議網路Socket編程(java實現C/S通訊案例)
我的部落格園://www.cnblogs.com/chenzhenhong/p/13825286.html
我的CSDN部落格://blog.csdn.net/Charzous/article/details/109016215
目錄
一、前言:認識UDP
UDP,全稱User Datagram Protocol(用戶數據報協議),是Internet 協議集支援一個無連接的傳輸協議。UDP 為應用程式提供了一種無需建立連接就可以發送封裝的 IP 數據包的方法。
UDP主要用於不要求分組順序到達的傳輸中,分組傳輸順序的檢查與排序由應用層完成,提供面向報文的簡單不可靠資訊傳送服務。UDP 協議基本上是IP協議與上層協議的介面,適用埠分別運行在同一台設備上的多個應用程式。
二、UDP的特點(與TCP相比)
正是UDP提供不可靠服務,具有了TCP所沒有的優勢。無連接使得它具有資源消耗小,處理速度快的優點,所以音頻、影片和普通數據在傳送時經常使用UDP,偶爾丟失一兩個數據包,也不會對接收結果產生太大影響。
-
UDP有別於TCP,有自己獨立的套接字(IP+Port),它們的埠號不衝突。和TCP編程相比,UDP在使用前不需要進行連接,沒有流的概念。
-
如果說TCP協議通訊與電話通訊類似,那麼UDP通訊就與郵件通訊類似:不需要實時連接,只需要目的地址;
-
UDP通訊前不需要建立連接,只要知道地址(ip地址和埠號)就可以給對方發送資訊;
-
基於用戶數據報文(包)讀寫;
-
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+"\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。
成功連接後:
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編程更加有趣!一起學習期待!
我的部落格園://www.cnblogs.com/chenzhenhong/p/13825286.html
我的CSDN部落格://blog.csdn.net/Charzous/article/details/109016215