網絡編程初識

注:此博文為本人學習過程中的筆記

1.socket?api

這是操作系統提供的一組api,由傳輸層向應用層提供。

2.傳輸層的兩個核心協議

傳輸層的兩個核心協議分別是TCP協議和UDP協議,它們的差別非常大,編寫代碼的風格也不同,因此socket提供了兩套api。TCP協議是有連接,可靠傳輸,面向字節流,全雙工。UDP協議是無連接,不可靠傳輸,面向數據報,全雙工。

1.有連接/無連接

這里的有無連接是抽象的概念,對于網絡通信來說,物理上(網線)的連接時必須的。這里的連接是指在通信的時候有沒有保存對方的信息。

對于TCP協議來說,A和B通信,會讓A保存B的信息,B保存A的信息,讓它們彼此知道誰是和它建立連接的那一個。

對于UDP協議來說,不保存對方的協議。當然程序員可以在自己的代碼中保存對方的信息,但這不屬于UDP協議的行為。

2.可靠傳輸/不可靠傳輸

網絡上,數據是非常容易出現丟失的情況,用來傳輸的光/電信號很容易受到外界的影響。所以我們不能指望一個數據包發送之后,能百分之一百到達對方。

可靠傳輸的意思不是保證數據包百分之一百到達,而是盡可能提高傳輸成功的概率,如果丟包了,可以感知到。

不可靠傳輸就是指數據包發送之后就不管了。

3.面向字節流/面向數據報

面向字節流是指讀寫數據的時候以字節為單位。它可以靈活地控制讀寫的長度,但是容易出現粘包問題。

面向數據報是指讀寫數據的時候以數據報為單位。一次必須讀取一個數據報的長度,但不容易出現粘包問題。

4.全雙工/半雙工

全雙工是指一個通信鏈路支持雙向通信

半雙工是指一個通信鏈路只支持單向通信

3.使用socket api進行編程

1.UDP服務器

這是操作系統提供的功能,Java進行了封裝。這里我們先講解基于UDP協議的寫法。

1.DatagramSocket

計算機中的文件廣義上還能代指硬件設備,將它們抽象成文件。這里我們將網卡抽象成socket文件,操作網卡的時候和普通的文件差不多,打開(也會在文件描述符表分配一個表項)->讀寫->關閉。直接操作網卡不好操作,把網卡操作轉換成socket文件操作更加方便。

1.構造方法

DatagramSocket()

創建一個UDP數據報套接字的Socket,綁定到本機任意一個隨機端口(一般用于客戶端)

DatagramSocket(int port)

創建一個UDP數據報套接字的Socket,綁定到本機指定的端口(一般用于服務器)

2.DatagramPacket

這個類表示一個完整的UDP數據報

1.構造方法

UDP數據報的載荷可以通過構造方法來指定

DatagramPacket(byte[] buf, int length)

構造一個DatagramPacket用來接收數據報,接收的數據報保存在字節數組,指定長度

DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)

構造一個DatagramPacket用來接收數據報,接收的數據報保存在字節數組,指定數組下標,數組長度,目的IP和端口號

2.receive/send

1.void receive(DatagramPacket p)

接收數據報,沒有接收到會阻塞等待

2.void send(DatagramPacket p)

發送數據報

3.代碼示例

這里我們使用回顯服務器,回顯服務器是指響應和請求都是相同的服務器。我們實現的功能是用戶輸入一個字符串,服務器返回這個字符串。

1.服務器代碼

代碼展示

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;public class UdpEchoServer {private DatagramSocket socket = null;public UdpEchoServer(int port) throws SocketException {//指定了一個固定端口號讓服務器使用socket = new DatagramSocket(port);}public void start() throws IOException {//啟動服務器System.out.println("服務器啟動");while(true) {//循環一次相當處理一次請求//處理請求的過程,典型的服務器分為三個步驟//1.讀取請求并解析//DatagramPacket就表示一個UDP數據報,此處傳入的字節數組保存UDP的載荷部分DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);socket.receive(requestPacket);//把讀取到的二進制數據轉化成字符串,只讀取有效的部分String request = new String(requestPacket.getData(), 0,         requestPacket.getLength());//2.根據請求,計算響應(服務器最關鍵的邏輯)//因為我們寫的是回顯服務器,所以這個步驟省略了String response = process(request);//3.把響應返回給客戶端//根據response構造DatagramPacket返回給客戶端DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), 0, response.getBytes().length, requestPacket.getSocketAddress());socket.send(responsePacket);}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {UdpEchoServer udpEchoServer = new UdpEchoServer(9090);udpEchoServer.start();}
}

解析

1. socket對象代表網卡文件,讀這個文件相當于從網卡收數據,寫這個文件相當于向網卡發數據

2.啟動服務器,在循環中做三件事

3. 讀取請求并解析

a)構造DatagramPacket對象,這個對象就代表UDP數據報,有表頭和載荷(創建字節數組來保存數據)?

b)調用receive接收數據,這里使用的是輸出型參數,所以雖然我們是接收一個UDP數據報,但我們還是創建了一個空的DatagramPacket對象。

c)根據字節數組構造純出一個String

4.根據請求計算響應

5.把響應返回給客戶端

這里構造響應的數據報時,傳入了字節數組作為載荷,指定了數組下標和有效長度,傳入了目的端口和IP。?

6.socket不用close

一個文件是否要關閉,需要考慮這個文件的生命周期,這里的socket對象自始至終都會伴隨整個UDP服務器,如果服務器關閉,會自動釋放PCB的文件描述附表里面的所有資源,所以就不用手動關閉了。

2.客戶端代碼
import java.io.IOException;
import java.net.*;
import java.util.Scanner;public class UdpEchoClient {private DatagramSocket socket = null;//UDP本身不保存對端的信息,所以我們在自己的代碼保存一下private String serverIp;private int serverPort;//和服務器不同,這里的構造方法需要指定訪問服務器的地址public UdpEchoClient(String serverIp, int serverPort) throws SocketException {this.serverIp = serverIp;this.serverPort = serverPort;//這里的DatagramSocket不推薦使用固定端口號,如果客戶端是固定端口,//很可能在這個程序運行的時候指定的端口被其他程序占用了,客戶端在用戶手上,//程序員不能控制socket = new DatagramSocket();}public void start() throws IOException {Scanner scanner = new Scanner(System.in);//1.從控制臺讀取用戶輸入的內容System.out.println("請輸入要發送的內容");String request = scanner.next();//2.把請求放松給服務器,需要構造DatagramPacket對象//構造過程中,不光要構造載荷,還要指定服務器的IP和端口號DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,InetAddress.getByName(serverIp), serverPort);//3.發送數據報socket.send(requestPacket);//4.接收服務器的響應DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);socket.receive(responsePacket);//5.把從服務器讀取出來的數據開始解析,打印出來String response = new String(responsePacket.getData(), 0, responsePacket.getLength());System.out.println(response);}public static void main(String[] args) throws IOException {//127.0.0.1是一個環回ip,非常特殊,無論你的主機是什么,都可以用這個ip表示當前主機,相當于thisUdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1", 9090);udpEchoClient.start();}
}

2.TCP服務器?

因為TCP服務器進行網絡通信的基本單位是字節,所以它不像UDP服務器的那樣有DatagramPacket類作為基本單位,可以直接使用InputStream/OutputStream。

1.ServerSocket

這個類是專門給服務器用的,作為一開始的牽頭人,和客戶端建立連接后,就會使用Socket和客戶端進行通信

ServerSocket(int port)

創建一個服務器端流套接字,并指定端口號

accept()

和客戶端進行連接

2.Socket

這個類服務器和客戶端都會使用

Socket(String host, int port)

這兩個參數指的是需要指定的服務器IP和端口號

InputStream getInputStream()/OutputStream getOutputStream()

這兩個方法是字節流對象

3.代碼解析和修改

初始服務器代碼

public class TcpEchoServer {private ServerSocket serverSocket= null;//這里和Udp服務器類似,也是在構造的時候指定端口號public TcpEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("啟動服務器");while(true) {//對于tcp來說,需要向處理客戶端發過來的連接//通過讀寫clientSocket和客戶端進行通信//如果沒有客戶端發送連接,那么accept就會阻塞Socket clientSocket = serverSocket.accept();processConnection(clientSocket);}}//處理一個客戶端的連接//可能涉及多個客戶端的連接和響應,這里暫不涉及private void processConnection(Socket clientSocket) {try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {Scanner scanner = new Scanner(inputStream);PrintWriter writer = new PrintWriter(outputStream);//處理三個步驟while(true) {//1.讀取請求并解析,可以直接read,也可以借助scannerif(!scanner.hasNext()) {break;}String request = scanner.next();//2.根據請求計算響應String response = procoss(request);//3.返回響應到客戶端writer.println(response);}} catch (IOException e) {e.printStackTrace();}}private String procoss(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);tcpEchoServer.start();}
}

初始客戶端代碼

public class TcpEchoClient {private Socket socket = null;public TcpEchoClient(String serverIp, int port) throws IOException {//這里可以直接使用字符串的ip作為參數socket = new Socket(serverIp, port);}public void start() {Scanner scanner = new Scanner(System.in);try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {//為了方便使用,套殼操作Scanner scannerNet = new Scanner(inputStream);PrintWriter printWriter = new PrintWriter(outputStream);//從控制臺讀取請求String request = scanner.next();//發送給服務器printWriter.println(request);//獲取服務器的響應String response = scannerNet.next();//打印到控制臺System.out.println(response);} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) throws IOException {TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1", 9090);tcpEchoClient.start();}
}

問題1

當我們把客戶端關閉再啟動時,輸入數據服務器沒有響應。原因在于println只是把數據寫入“發送緩沖區”,并沒有真正寫入網卡,此時我們需要用flush方法來屬性緩沖區,讓數據真正寫入網卡。

問題2

這里的pringln里的ln是加上了換行,如果這里我們把ln刪去,那么數據能發送過去,而服務器接收不到。

因為hasNext是以空白符(換行,回車,制表符,翻頁符...)為基準,遇到空白符則是一個完整的next(),否則就會阻塞。?

編寫客戶端代碼的時候是需要約定請求和響應之間的分隔符的,這里我們使用的是\n

問題3

如果沒有客戶端連接,就會阻塞在accept這里

如果客戶端不發送請求, 就會阻塞在hasNext這里

我們的服務器無法同時等待accept和客戶端請求,當我們在等待客戶端發送請求時,如果這時有新的客戶端想要連接進來,就無法連接。

在這個場景下,我們就能引入多線程,讓一個線程專門負責連接服務器。同時可以引入線程池優化效率。

問題4

服務器的socket要記得及時關閉,因為這個socket的生命周期不再是跟隨整個服務器了

問題5

當我們的客戶端多到一定程度時,服務器無法承擔,此時我們就可以使用操作系統中內置的IO多路復用,這個操作本質上是讓一個線程處理多個客戶端的請求。多個客戶端發送數據大概率不是同時的,客戶端很可能在阻塞等待。

優化后服務器代碼

public class TcpEchoServer {private ServerSocket serverSocket= null;//這里和Udp服務器類似,也是在構造的時候指定端口號public TcpEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}public void start() throws IOException {ExecutorService executorService = Executors.newCachedThreadPool();System.out.println("啟動服務器");while(true) {//對于tcp來說,需要向處理客戶端發過來的連接//通過讀寫clientSocket和客戶端進行通信//如果沒有客戶端發送連接,那么accept就會阻塞//主線程負責進行accept,每次accept到一個新客戶端,就創建一個新線程來處理客戶端的請求Socket clientSocket = serverSocket.accept();executorService.submit(() -> {processConnection(clientSocket);});}}//處理一個客戶端的連接//可能涉及多個客戶端的連接和響應,這里暫不涉及private void processConnection(Socket clientSocket) {try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {Scanner scanner = new Scanner(inputStream);PrintWriter writer = new PrintWriter(outputStream);//處理三個步驟while(true) {//1.讀取請求并解析,可以直接read,也可以借助scannerif(!scanner.hasNext()) {break;}String request = scanner.next();//2.根據請求計算響應String response = procoss(request);//3.返回響應到客戶端writer.println(response);writer.flush();}} catch (IOException e) {e.printStackTrace();}}private String procoss(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);tcpEchoServer.start();}
}

優化后客戶端代碼

public class TcpEchoClient {private Socket socket = null;public TcpEchoClient(String serverIp, int port) throws IOException {//這里可以直接使用字符串的ip作為參數socket = new Socket(serverIp, port);}public void start() {Scanner scanner = new Scanner(System.in);try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {//為了方便使用,套殼操作Scanner scannerNet = new Scanner(inputStream);PrintWriter printWriter = new PrintWriter(outputStream);//從控制臺讀取請求String request = scanner.next();//發送給服務器printWriter.println(request);//加上刷新緩沖區操作,才是真正寫入網卡printWriter.flush();//獲取服務器的響應String response = scannerNet.next();//打印到控制臺System.out.println(response);} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) throws IOException {TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1", 9090);tcpEchoClient.start();}
}

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

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

相關文章

【質量管理】現代TRIZ問題識別中的功能分析——功能模型

功能模型的定義 功能模型是對工程系統進行功能分析的一個階段,目的是建立工程系統的功能模型。功能模型描述了工程系統和超系統組件的功能,包括有用功能、性能水平和成本等。 在文章【質量管理】現代TRIZ中問題識別中的功能分析——相互接觸分析-CSDN博客…

廣告事件聚合系統設計

需求背景 廣告事件需要進行統計,計費,分析等。所以我們需要由數據接入,數據處理,數據存儲,數據查詢等多個服務模塊去支持我們的廣告系統 規模上 10000 0000個點擊(10000 00000 / 100k 1wQPS) …

C語言中,sizeof關鍵字(詳細介紹)

目錄 ?1. 基本用法?(1) ?基本數據類型?(2) ?變量?(3) ?數組?(4) ?指針? ?2. 特殊用法?(1) ?結構體與內存對齊?(2) ?動態內存分配?(3) ?表達式? ?3. 注意事項??1)sizeof 與 strlen 的區別?:?2)變長數組(VLA…

ADK 第三篇 Agents (LlmAgent)

Agents 在智能體開發套件(ADK)中,智能體(Agent)是一個獨立的執行單元,旨在自主行動以實現特定目標。智能體能夠執行任務、與用戶交互、使用外部工具,并與其他智能體協同工作。 在ADK中&#x…

【深度學習】典型的 CNN 網絡

目錄 一、LeNet-5 (1)LeNet-5 網絡概覽 (2)網絡結構詳解 (3)關鍵組件與數學原理 3.1 局部感受野與卷積運算 3.2 權重共享 3.3 子采樣(Pooling) 3.4 激活函數 (4…

4.8/Q1,中山大學用NHANES:膳食煙酸攝入量與非酒精性脂肪肝之間的關聯

文章題目:Association between Dietary Niacin Intake and Nonalcoholic Fatty Liver Disease: NHANES 2003-2018 DOI:10.3390/nu15194128 中文標題:膳食煙酸攝入量與非酒精性脂肪肝之間的關聯:NHANES 2003-2018 發表雜志&#xf…

高效管理遠程服務器Termius for Mac 保姆級教程

以下是 Termius for Mac 保姆級教程,涵蓋安裝配置、核心功能、實戰案例及常見問題解決方案,助你高效管理遠程服務器(如Vultr、AWS等)。 一、Termius 基礎介紹 1. Termius 是什么? 跨平臺SSH客戶端:支持Ma…

理解數學概念——支集(支持)(support)

1. 支集(support)的定義 在數學中,一個實函數 f 的支集(support)是函數的不被映射到 0 的元素域(即定義域)的子集。若 f 的(定義)域(domain)是一個拓撲空間(即符合拓撲的集合),則 f 的支集則定義為包含( f 的元素域中)不被映射到0的所有點之最小閉集…

Vue 3 Element Plus 瀏覽器使用例子

Element Plus 是一個基于 Vue 3 的流行開源 UI 庫,提供了一系列的組件,幫助開發者快速構建現代化的用戶界面。它的設計簡潔、現代,包含了許多可定制的組件,如按鈕、表格、表單、對話框等,適合用于開發各種 Web 應用。 …

SSR vs SSG:前端渲染模式終極對決(附 Next.js/Nuxt.js 實戰案例)

一、引言:前端渲染模式的進化之路 隨著互聯網的發展,用戶對于網頁的加載速度和交互體驗要求越來越高。前端渲染技術作為影響網頁性能的關鍵因素,也在不斷地發展和演進。從最初的客戶端渲染(CSR),到后來的服…

算法筆記.分解質因數

代碼實現&#xff1a; #include<iostream> using namespace std; void breakdown(int x) {int t x;for(int i 2;i < x/i;i){if(t%i 0){int counts 0;while(t % i 0){t/i;counts;}cout << i <<" "<< counts<<endl;}}if(t >…

CUDA Error: the provided PTX was compiled with an unsupported toolchain

CUDA程序編譯時生成的PTX代碼與系統上的CUDA驅動版本不兼容 CUDA 編譯器版本&#xff1a; CUDA 12.6 (nvcc 編譯器版本) CUDA 驅動版本&#xff1a; CUDA 12.3 (nvidia-smi 驅動版本) 解決方法&#xff1a; 驅動版本下載參考&#xff1a;Your connected workspace for wiki…

計算機組成原理實驗(7) 堆指令部件模塊實驗

實驗七 堆指令部件模塊實驗 一、實驗目的 1、掌握指令部件的組成方式。 2、熟悉指令寄存器的打入操作&#xff0c;PC計數器的設置和加1操作&#xff0c;理解跳轉指令的實現過程。 二、實驗要求 按照實驗步驟完成實驗項目&#xff0c;掌握數據打入指令寄存器IR1、PC計數器的…

2022 年 6 月大學英語四級考試真題(第 2 套)——閱讀版——仔細閱讀題

&#x1f3e0;個人主頁&#xff1a;fo安方的博客? &#x1f482;個人簡歷&#xff1a;大家好&#xff0c;我是fo安方&#xff0c;目前中南大學MBA在讀&#xff0c;也考取過HCIE Cloud Computing、CCIE Security、PMP、CISP、RHCE、CCNP RS、PEST 3等證書。&#x1f433; &…

磁盤文件系統

磁盤文件系統 一、磁盤結構1.1 認識一下基礎的硬件設備以及真實的機房環境1.2 磁盤物理結構與存儲結構1、磁盤物理結構2、磁盤的存儲結構3、CHS地址定位4、磁盤的邏輯結構&#xff08;LBA&#xff09;5 磁盤真實過程5 CHS && LBA地址 二、理解分區、格式化1 引?"…

基于LangChain 實現 Advanced RAG-后檢索優化(下)-上下文壓縮與過濾

摘要 Advanced RAG 的后檢索優化&#xff0c;是指在檢索環節完成后、最終響應生成前&#xff0c;通過一系列策略與技術對檢索結果進行深度處理&#xff0c;旨在顯著提升生成內容的相關性與質量。在這些優化手段中&#xff0c;上文壓縮與過濾技術是提升檢索結果質量的重要手段。…

為什么 Vite 速度比 Webpack 快?

一、webpack會先進行編譯&#xff0c;再運行&#xff0c;vite會直接啟動&#xff0c;再按需編譯文件。 首先看兩張圖&#xff0c;可以清晰的看到&#xff0c;上面的圖是webpack編譯過的&#xff0c;而下面的圖是vite直接使用工程內文件。 二、區別于Webpack先打包的方式&am…

C# 操作符

C# 操作符 一、操作符概覽二、優先級與運算順序三、各類操作符的實例 一、操作符概覽 操作符&#xff08;運算符&#xff09;的本質是函數的簡記法 操作符不能脫離與它關聯的數據類型 int x 5; int y 4; int z x / y; Console.WriteLine(z);//輸出1double a 5.0; double b…

C++設計模式:面向對象的八大設計原則之四

里氏替換原則&#xff08;Liskov Substitution Principle&#xff0c;LSP&#xff09;是面向對象設計中的一個重要原則&#xff0c;它指出子類必須能夠替換它的基類&#xff0c;并且程序的行為不會發生改變。也就是說&#xff0c;在任何使用基類對象的地方&#xff0c;都可以透…

網絡通信領域的基礎或流行協議

一、TCP(傳輸控制協議) 1. 宏觀介紹 TCP:全稱“Transmission Control Protocol”——傳輸控制協議,是互聯網最基礎的傳輸協議之一。傳輸層協議,提供面向連接、可靠的字節流傳輸服務。它通過三次握手建立連接、四次揮手斷開連接,確保數據有序、完整地傳輸作用:讓兩個設備…