java實現基於UDP協議網路Socket程式設計(C/S通訊)
一、前言:認識UDP
UDP,全稱UserDatagramProtocol(使用者資料報協議),是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 void send(String msg){ try { //將待發送的字串轉為位元組陣列 byte[] outData=msg.getBytes("utf-8"); //構建用於傳送的資料報文,構造方法中傳入遠端通訊方(伺服器)的ip地址和埠 DatagramPacket outPacket=new DatagramPacket(outData,remotePort); //給UDP傳送資料報 socket.send(outPacket); }catch (IOException e){ e.printStackTrace(); } }
可以看到,傳送和接收資料中使用DatagramSocket的例項的send和receive方法,這就是資料報套接字的兩個重要方法。
通訊結束,銷燬Socket的方法如下:
public void close(){ if (socket!=null) socket.close(); }
到這裡,客戶端已全部完成,等待接下來與服務端的通訊...
2、客戶端圖形介面
現在,設計客戶端通訊的簡單介面,一方面可以更方便的和伺服器連續對話通訊,另一方面,有了圖形介面,體驗感更加!圖形化介面主要使用JavaFX實現,程式碼容易看懂。
import javafx.application.Application; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; 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 Button btnExit=new Button("退出"); private Button btnSend = new Button("傳送"); private TextField tfSend=new TextField();//輸入資訊區域 private TextArea taDisplay=new TextArea();//顯示區域 private TextField ipAddress=new TextField();//填寫ip地址 private TextField tfport=new TextField();//填寫埠 private Button btConn=new Button("連線"); private UDPClient UDPClient; private String ip; private String port; @Override public void start(Stage primaryStage) { BorderPane mainPane=new BorderPane(); //連線伺服器區域 HBox hBox1=new HBox(); hBox1.setSpacing(10); hBox1.setPadding(new Insets(10,20,10,20)); hBox1.setAlignment(Pos.CENTER); hBox1.getChildren().addAll(new Label("ip地址:"),ipAddress,new Label("埠:"),tfport,btConn); mainPane.setTop(hBox1); VBox vBox=new VBox(); vBox.setSpacing(10); vBox.setPadding(new Insets(10,20)); 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)); hBox.setAlignment(Pos.CENTER_RIGHT); hBox.getChildren().addAll(btnSend,btnExit); mainPane.setBottom(hBox); Scene scene =new Scene(mainPane,700,500); primaryStage.setScene(scene); primaryStage.show(); //連線伺服器之前,傳送bye後禁用傳送按鈕,禁用Enter傳送資訊輸入區域,禁用下載按鈕 btnSend.setDisable(true); tfSend.setDisable(true); //連線按鈕 btConn.setOnAction(event -> { ip=ipAddress.getText().trim(); port=tfport.getText().trim(); try { UDPClient = new UDPClient(ip,port); //連線伺服器之後未結束服務前禁用再次連線 btConn.setDisable(true); //重新連線伺服器時啟用輸入傳送功能 tfSend.setDisable(false); btnSend.setDisable(false); } catch (IOException e) { e.printStackTrace(); } }); //傳送按鈕事件 btnSend.setOnAction(event -> { String msg=tfSend.getText(); UDPClient.send(msg);//向伺服器傳送一串字元 taDisplay.appendText("客戶端傳送:"+msg+"\n"); String Rmsg=null; Rmsg=UDPClient.receive(); // System.out.println(Rmsg); taDisplay.appendText(Rmsg+"\n"); if (msg.equals("bye")){ btnSend.setDisable(true);//傳送bye後禁用傳送按鈕 tfSend.setDisable(true);//禁用Enter傳送資訊輸入區域 //結束服務後再次啟用連線按鈕 btConn.setDisable(false); } tfSend.clear(); }); //對輸入區域繫結鍵盤事件 tfSend.setOnKeyPressed(new EventHandler<KeyEvent>() { @Override public void handle(KeyEvent event) { if(event.getCode()==KeyCode.ENTER){ String msg=tfSend.getText(); UDPClient.send(msg);//向伺服器傳送一串字元 taDisplay.appendText("客戶端傳送:"+msg+"\n"); String Rmsg=null; Rmsg=UDPClient.receive(); taDisplay.appendText(Rmsg+"\n"); if (msg.equals("bye")){ tfSend.setDisable(true);//禁用Enter傳送資訊輸入區域 btnSend.setDisable(true);//傳送bye後禁用傳送按鈕 //結束服務後再次啟用連線按鈕 btConn.setDisable(false); } tfSend.clear(); } } }); btnExit.setOnAction(event -> { try { exit(); } catch (InterruptedException e) { e.printStackTrace(); } }); //窗體關閉響應的事件,點選右上角的×關閉,客戶端也關閉 primaryStage.setOnCloseRequest(event -> { try { exit(); } catch (InterruptedException e) { e.printStackTrace(); } }); //資訊顯示區滑鼠拖動高亮文字直接複製到資訊輸入框,方便選擇檔名 //taDispaly為資訊選擇區的TextArea,tfSend為資訊輸入區的TextField //為taDisplay的選擇範圍屬性新增監聽器,當該屬性值變化(選擇文字時),會觸發監聽器中的程式碼 taDisplay.selectionProperty().addListener(((observable,oldValue,newValue) -> { //只有當滑鼠拖動選中了文字才複製內容 if(!taDisplay.getSelectedText().equals("")) tfSend.setText(taDisplay.getSelectedText()); })); } private void exit() throws InterruptedException { if (UDPClient!=null){ //向伺服器傳送關閉連線的約定資訊 UDPClient.send("bye"); UDPClient.close(); } System.exit(0); } public static void main (String[] args) { launch(args); } }
重點在各個控制元件的事件處理邏輯上,需避免要一些誤操作導致異常丟擲,如:連線伺服器前禁用傳送按鈕,在連線伺服器成功後禁用連線按鈕,禁用輸入區等。另外,實現了回車傳送的快捷功能,詳見程式碼的鍵盤事件繫結部分。
還有,約定傳送"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 DatagramPacket udpReceive() throws IOException { DatagramPacket receive; byte[] dataR = new byte[1024]; receive = new DatagramPacket(dataR,portRemote); socket.send(sendPacket); }
四、伺服器端和客戶端完整程式碼
伺服器端:
public DatagramPacket udpReceive() throws IOException { DatagramPacket receive; byte[] dataR = new byte[1024]; receive = new DatagramPacket(dataR,portRemote); socket.send(sendPacket); }
客戶端:
//UDPClient.java 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; //用於接收資料的報文位元組陣列快取最最大容量,位元組為單位 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(); } //定義一個數據的傳送方法 public void send(String msg){ try { //將待發送的字串轉為位元組陣列 byte[] outData=msg.getBytes("utf-8"); //構建用於傳送的資料報文,構造方法中傳入遠端通訊方(伺服器)的ip地址和埠 DatagramPacket outPacket=new DatagramPacket(outData,remotePort); //給UDP傳送資料報 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(),inPacket.getLength(),"utf-8"); } catch (IOException e) { e.printStackTrace(); msg=null; } return msg; } public void close(){ if (socket!=null) socket.close(); } }
五、效果展示
六、總結
這一篇詳細記錄學習運用java進行網路程式設計,基於UDP套接字(Socket)實現伺服器與客戶端間的通訊,在實戰案例中更深刻理解UDP的實現原理,掌握UDP實踐應用步驟。
起初完成UDP通訊時,遇到了幾個問題,相比較TCP的實現,確實體會到資料傳輸的過程的不同,UDP服務和客戶端共用了一個DatagramSocket,另外需要DatagramPacket資料報的協作。另外,UDP沒有資料流的概念,所以讀寫不同於TCP,需要以位元組資料進行讀取。
到此這篇關於java實現基於UDP協議網路Socket程式設計(C/S通訊)的文章就介紹到這了,更多相關java UDP協議Socket程式設計內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!