「網絡編程」基于 UDP 協議實現回顯服務器

🎇個人主頁:Ice_Sugar_7
🎇所屬專欄:計網
🎇歡迎點贊收藏加關注哦!

實現回顯服務器

  • 🍉socket api
  • 🍉回顯服務器
    • 🍌實現
      • 🥝服務器
      • 🥝客戶端

🍉socket api

操作系統給我們提供的進行網絡編程的 api 稱為 socket api(網絡編程套接字),具體到傳輸層,有兩個重要的協議的 api —— UDP apiTCP api,本文我們介紹的是 UDP api

UDP 有四個特點:無連接、不可靠傳輸、面向數據報、全雙工。這在后文中會解釋

Java 對系統原生的 api 進行了封裝,UDP socket 有兩個核心的類

  1. DatagramSocket

操作系統中有一類文件,叫作 socket 文件,它和我們之前所說的“文件”不太一樣,我們平時所說的普通文件、目錄文件位于硬盤上,而 socket 文件則是抽象表示了網卡這樣的硬件設備(網卡是網絡通信中的核心硬件設備),也就是把網卡等硬件視為一種文件。通過網卡發送數據就是寫 socket 文件;接收數據就是讀 socket 文件
說回 DatagramSocket,它負責讀寫 socket 文件,也就是借助網卡發送或接收數據

它有兩個構造方法:

構造方法說明
DatagramSocket()創建一個 UDP 數據報套接字的 Socket,綁定到本機任意一個隨機端口(一般用于客戶端)
DatagramSocket(int port)創建一個 UDP 數據報套接字的 Socket,綁定到本機指定的端口,即 port(一般用于服務器)

負責發送和接收的方法如下:

方法說明
void reveive(DatagramPacket p)讓 p 接收數據報(如果沒接收到數據報,這個方法就會阻塞等待)(注意這里的參數是輸出型參數,實際上 DatagramPacket 內部包含了一個字節數組
void send(DatagramPacket p)從 p 發送數據報(直接發送出去,不會阻塞)

  1. DatagramPacket

DatagramPacket 表示一個 UDP 數據報。UDP 面向數據報,每次發送、接收數據的基本單位就是一個 UDP 數據報


🍉回顯服務器

這是網絡編程中最簡單的程序,相當于 hello world,不過還是有一定的難度
服務器在接收客戶端的請求后會返回響應,具體返回什么響應,要根據實際的業務場景分析。對于回顯服務器,它沒有業務邏輯,客戶端發什么請求,服務器就返回什么響應

🍌實現

接下來我們通過 UDP 協議來實現一個回顯服務器

🥝服務器

首先要創建一個 DatagramSocket 對象,然后要通過這個 socket 對象來操作網卡

public class UdpEchoServer {DatagramSocket socket = null;public UdpEchoServer(int port) throws SocketException {socket = new DatagramSocket(port); //在運行一個服務器程序時,通常會手動指定端口}
}

補充:這里的 SocketException 是網絡編程中一個常見的異常,通常表示 socket 創建失敗,比如端口號已經被別的進程占用了

接下來服務器主要做三件事

①讀取請求并解析
②根據請求計算響應。對于回顯服務器來說,這一步啥都不用做
③把響應返回到客戶端

要讀取請求得先創建一個 DatagramPacket 接收請求

DatagramPacket requestPacket = new DatagramPacket(new byte[1024],1024);
socket.receive(requestPacket);
String request = new String(requestPacket.getData(),0,requestPacket.getLength());

在這里插入圖片描述
使用字節數組構造字符串的方法一定要記住

在這里插入圖片描述
調用 receive 涉及到緩沖區,下面通過圖示補充一下:

在這里插入圖片描述
第二步是根據請求計算響應,雖然回顯服務器這一步不用做什么,不過為了邏輯完整,我們寫一個 process 方法,它只返回 request
(如果是具有特定業務的服務器,process 中就寫其他你想要的邏輯)

public String process(String request) {return request;
}

最后就是把響應返回給客戶端,這一步要用到 send 方法

//3.把響應返回到客戶端
//構造一個 DatagramPacket 作為響應對象
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),0,response.getBytes().length,requestPacket.getSocketAddress());
socket.send(responsePacket);

在這里插入圖片描述
接下來在主方法中啟動服務器

在這里插入圖片描述
服務器的代碼如下:

public class UdpEchoServer {DatagramSocket socket = null;public UdpEchoServer(int port) throws SocketException {socket = new DatagramSocket(port);}//服務器的啟動邏輯public void start() throws IOException {System.out.println("服務器啟動");while(true) {//每次循環就是處理一個請求,然后返回響應的過程DatagramPacket requestPacket = new DatagramPacket(new byte[1024],1024);//1.讀取請求并解析socket.receive(requestPacket);//填充字節數組后,將其轉為 String 方便后續處理邏輯//getData 方法獲取到 DatagramPacket 內部的字節數組String request = new String(requestPacket.getData(),0,requestPacket.getLength());//2.根據請求計算響應String response = process(request);//3.把響應返回到客戶端//構造一個 DatagramPacket 作為響應對象DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),0,response.getBytes().length,requestPacket.getSocketAddress());socket.send(responsePacket);//打印日志System.out.printf("[%s:%d] req:%s, resp:%s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);}}public String process(String request) {return request;}public static void main(String[] args) throws IOException {UdpEchoServer server = new UdpEchoServer(7000);server.start();}
}

🥝客戶端

接下來編寫客戶端的代碼
首先要創建 socket 對象,注意客戶端這里不需要手動指定端口號

在這里插入圖片描述

1.在代碼中手動指定端口號,可以保證端口號始終固定;如果不手動指定,那就是系統自動分配,這樣的話服務器每次重啟之后端口號可能就變了,一旦變了,客戶端就可能找不到服務器在哪兒了,所以服務器需要手動指定
2.而對于客戶端,因為無法確保手動指定的端口是可用的(可能被其他進程占用了),這就可能導致程序因為端口綁定失敗而無法啟動,所以讓系統隨機分配一個空閑的端口就 ok 了

接下來客戶端要做四件事

1.從控制臺讀取要發送的請求數據
2.構造請求并發送
3.讀取服務器的響應
4.把響應顯示到控制臺上

第一步就是先創建一個 Scanner 對象來讀取字符串
這里補充一點,使用 Scanner 從控制臺讀取字符串的話最好使用 next,因為如果用 nextLine 讀取需要手動輸入換行符 enter,但是 enter 鍵除了產生 \n 還會產生其他字符,這就會導致讀取到的內容容易出問題;而如果從文件讀取的話那就用哪個都行

第二步構造請求就用接收的字符串來構造一個 DatagramPacket 對象,然后發送

DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,InetAddress.getByName(serverIp),serverPort);
socket.send(requestPacket);

到這里我們已經了解了三種構造 DatagramPacket 對象的方法,總結一下:

//第一種:搭配 receive 使用。構造的時候指定空白的字節數組
DatagramPacket requestPacket = new DatagramPacket(new byte[1024],1024);//第二種:發數據時使用。構造時指定有內容的字節數組,并指定 IP 和端口
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
requestPacket.getSocketAddress());//第三種:發數據時使用。構造時指定有內容的字節數組,并指定 IP 和 端口,這兩者分開指定
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
InetAddress.getByName(serverIp),serverPort);

回到正題,第三步是讀取服務器響應

DatagramPacket responsePacket = new DatagramPacket(new byte[1024],1024);
socket.receive(responsePacket);

最后就是把它顯示到控制臺:

String response = new String(responsePacket.getData(),0,responsePacket.getLength()); //再次強調,這里的 getLength 方法得到的是有效長度
System.out.println(response);

客戶端代碼如下:

public class UdpEchoClient {DatagramSocket socket;String serverIp;int serverPort;public UdpEchoClient(String serverIp,int serverPort) throws SocketException {this.serverIp = serverIp;this.serverPort = serverPort;socket = new DatagramSocket();}public void start() throws IOException {System.out.println("客戶端啟動");Scanner scanner = new Scanner(System.in);while(true) {if(!scanner.hasNext()) break;//1. 從控制臺讀取要發送的請求數據String request = scanner.next();//2.構造請求并發送DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,InetAddress.getByName(serverIp),serverPort);socket.send(requestPacket);//3.讀取服務器的響應DatagramPacket responsePacket = new DatagramPacket(new byte[1024],1024);socket.receive(responsePacket);//4.把響應顯示到控制臺上String response = new String(responsePacket.getData(),0,responsePacket.getLength());System.out.println(response);}}public static void main(String[] args) throws IOException {UdpEchoClient client = new UdpEchoClient("127.0.0.1",7000);client.start();}
}

接下來運行一下看看效果:
在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/22447.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/22447.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/22447.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

純血鴻蒙實戰開發—如何添加頂部tab頁面

1.Tabs組件 Tabs組件的頁面組成包含兩個部分,分別是TabContent和TabBar。TabContent是內容頁,TabBar是導航頁簽欄. 根據不同的導航類型,布局會有區別,可以分為底部導航、頂部導航、側邊導航,其導航欄分別位于底部、頂…

react基礎學習 JSX

JSX的測試網站 Babel Babel 可以測試代碼的效果 JSX實現map列表 注意 key不一樣(使用遍歷的時候) 簡單條件渲染 復雜條件渲染 綁定事件 function App() {const colorse (e)>{console.log("測試點擊",e);}const colorse1 (name)>{…

代碼隨想錄訓練營第六十天 | 84.柱狀圖中最大的矩形

84.柱狀圖中最大的矩形 題目鏈接:. - 力扣(LeetCode) 文檔講解:代碼隨想錄 視頻講解:單調棧,又一次經典來襲! LeetCode:84.柱狀圖中最大的矩形_嗶哩嗶哩_bilibili 狀態:未…

地理信息科學中的大數據挑戰

在信息化爆炸的時代,地理信息科學(GIScience)正經歷著前所未有的變革,其中,地理空間大數據的涌現為科學研究與應用帶來了前所未有的機遇與挑戰。作為地理信息與遙感領域的探索者,本文旨在深入剖析地理空間大…

揭秘HubSpot集客營銷:如何吸引并轉化全球潛在客戶

隨著全球數字化浪潮的推進,企業出海已經成為許多公司擴大市場、增加品牌曝光度的重要戰略。HubSpot集客營銷作為一種以客戶為中心、數據驅動的營銷策略,為企業在海外市場的成功提供了強有力的支持。作為HubSpot亞太地區的合作伙伴,NetFarmer將…

[AIGC] 自定義Spring Boot中BigDecimal的序列化方式

在很多場景下,我們需要對BigDecimal類型的數據進行特殊處理,比如保留三位小數。Spring Boot使用Jackson作為默認的JSON序列化工具,我們可以通過自定義Jackson的序列化器(Serializer)來實現,下面將詳細介紹實…

力扣2730.找到最長的半重復子字符串

力扣2730.找到最長的半重復子字符串 找到相鄰的相同字母后same 再雙指針找到前一組相同字母位置 class Solution {public:int longestSemiRepetitiveSubstring(string s) {int res1,n s.size(),same0;for(int i1,j0;i<n;i){if(s[i] s[i-1] && same > 1)for…

godot的安裝和使用 1

今天是第一節&#xff0c;因此呢先做godot的安裝&#xff0c;其實很簡單 godot官網&#xff1a;https://godotengine.org/ 進入官網&#xff0c; 安裝好之后呢&#xff0c;會有兩個文件 打開第一個就是可視化界面的&#xff0c;進入后是這個樣子 說明安裝成功了

【ArcGIS微課1000例】0115:字段數據類型案例詳解

文章目錄 一、ArcGIS數據類型概述二、案例1. 數字2. 文本3. 日期4. BLOB5. 對象標識符6. 全局標識符一、ArcGIS數據類型概述 創建要素類和表時,需要為各字段選擇數據類型。可用的類型包括多種數字類型、文本類型、日期類型、二進制大對象 (BLOB) 或全局唯一標識符 (GUID)。選…

python高級面試題

1. Python 中的 GIL (Global Interpreter Lock) 是什么? 解答: Python 的 GIL 是全局解釋器鎖,限制了在 CPython 解釋器中同時執行多個線程。GIL 確保在任意時刻只有一個線程執行 Python 字節碼。這是為了保護訪問 Python 對象的內部數據結構免受并發問題的影響。盡管 GIL 會…

鴻蒙開發接口安全:【@ohos.abilityAccessCtrl (訪問控制管理)】

訪問控制管理 說明&#xff1a; 本模塊首批接口從API version 8開始支持。后續版本的新增接口&#xff0c;采用上角標單獨標記接口的起始版本。 導入模塊 import abilityAccessCtrl from ohos.abilityAccessCtrlabilityAccessCtrl.createAtManager createAtManager(): AtMan…

LeeCode 1787 DP

題意 傳送門 LeeCode 1787 使所有區間的異或結果為零 題解 任一個元素都至多對 k k k個長度為 k k k的區間產生影響&#xff0c;故難以直接依次處理每一個元素。 觀察到滿足條件的數組中模 k k k意義下索引相等的各個元素相同&#xff0c;故可以依次處理每一個同余類。 d p…

OpenCV學習(4.1) 改變顏色空間

1.目標 在本教程中&#xff0c;你將學習如何將圖像從一個色彩空間轉換到另一個&#xff0c;像BGR?灰色&#xff0c;BGR?HSV等除此之外&#xff0c;我們還將創建一個應用程序&#xff0c;以提取視頻中的彩色對象你將學習以下功能&#xff1a;cv2.cvtColor&#xff0c;**cv2.i…

更適合工程師和研究僧的FPGA專項培訓課程

各位編程精英er~ 社區打造的FPGA工程師培訓班上線后&#xff0c;有不少同學后臺私信詢問&#xff1a;“能不能出個那種專門針對某個知識點的課程呢&#xff1f;我想針對自己的薄弱點深入學習。” 貼心如我&#xff0c;當然會滿足大家的學習需求啦。本周&#xff0c;社區FPGA專…

數學學習基本理念與方法

公理&#xff1a;不證自明的命題&#xff0c;一定條件下都認同的正確的結論 定理&#xff1a;在公理基礎上由嚴謹的數學邏輯獲得&#xff08;為證明的&#xff0c;叫猜想&#xff09; 推論&#xff1a;由某個定理推導出來&#xff0c;相對定理約束條件更多&#xff0c;重要程度…

面試題:說說你對 JS 中 this 指向的了解

面試題&#xff1a;說說你對 JS 中 this 指向的了解 JS 的代碼執行環境分為嚴格模式和非嚴格模式&#xff0c;可以通過 use strict 打開嚴格模式&#xff0c;此時 JS 在語法檢查上會更加嚴格。要討論 JS 中的 this 指向問題&#xff0c;也要分為嚴格模式和非嚴格模式進行討論。…

VRRP簡介

一、VRRP 定義概念 VRRP “Virtual Router Redundancy Protocol”即虛擬路由器冗余協議。 一種將多個物理路由器組合成一個虛擬路由器的協議。為了提供網關的冗余備份&#xff0c;提高網絡的可靠性。虛擬路由器擁有虛擬 IP 地址和虛擬 MAC 地址。虛擬信息作為終端設備訪問網絡…

Nextjs使用教程

一.手動創建項目 建議看這個中文網站文檔,這個里面的案例配置都是手動的,也可以往下看我這個博客一步步操作 1.在目錄下執行下面命令,初始化package.json文件 npm init -y2.安裝react相關包以及next包 yarn add next react react-dom // 或者 npm install --save next react…

使用Python操作Redis

大家好&#xff0c;在當今的互聯網時代&#xff0c;隨著數據量和用戶量的爆發式增長&#xff0c;對于數據存儲和處理的需求也日益增加。Redis作為一種高性能的鍵值存儲數據庫&#xff0c;以其快速的讀寫速度、豐富的數據結構支持和靈活的應用場景而備受青睞。本文將介紹Redis數…

貓頭虎分享已解決Bug || Error: ‘fetch‘ is not defined

原創作者&#xff1a; 貓頭虎 作者微信號&#xff1a; Libin9iOak 作者公眾號&#xff1a; 貓頭虎技術團隊 更新日期&#xff1a; 2024年6月6日 博主貓頭虎的技術世界 &#x1f31f; 歡迎來到貓頭虎的博客 — 探索技術的無限可能&#xff01; 專欄鏈接&#xff1a; &…