網絡編程 - 2

目錄

UDP 數據報套接字編程

API 介紹

DatagramSocket

DatagramPacket

補充:

代碼示例 - 回顯服務器

服務器端:

客戶端:

補充:

代碼演示

梳理代碼:

下面是一個大概的流程圖~

文字解釋:

圖文并茂解釋:

補充:

完!


UDP 數據報套接字編程

API 介紹

DatagramSocket

DatagramSocket 是 UDP Socket,用于發送和接收 UDP 數據報。

DatagramSocket 構造方法:

DaatGramSocket 方法:

DatagramPacket

DatagramPacket 是 UDP Socket 發送和接收的數據報。

DatagramPacket 構造方法:

DatagramPacket 方法:

構造 UDP 發送的數據報時候,需要傳入 SocketAddress

補充:

UDP socket 中 API 的使用,是 Java 把系統原生的 API 進行了一層封裝。

其中核心的類就是上面的兩個,DatagramSocket 和 DatagramPacket。

DatagramSocket:操作系統中有一類文件,就叫做 socket 文件。(普通文件和目錄文件都是存放在硬盤上的,socket 文件,抽象表示了“網卡”這樣的硬件設備,進行網絡通信最核心的硬件設備就是網卡(通過網卡發送數據,就是寫 socket 文件。通過網卡接收數據,就是讀 socket 文件。)

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

網絡編程中用到的 API 其實并不復雜,真正復雜的是需要理解網絡編程中的基本流程,一個典型的服務器都要做那些事情,一個典型的客戶端都要做那些事情...

代碼示例 - 回顯服務器

服務器端:

回顯服務器,網絡編程中最簡單的程序(相當于之前的“hello world”~),即客戶端發什么請求,就返回什么響應。(沒有什么業務邏輯)

實際開發中,服務器接收到客戶端的請求,返回響應的時候,是要根據業務需要來的~~

我們在這個例子中,研究體會一下:1. socket API 的使用 2. 了解一下 典型的客戶端服務器的基本工作流程。

服務器來說:

第一步,需要先創建 DatagramSocket 對象(接下來要操作網卡,操作網卡都是通過 socket 對象來完成的)

我們的服務器程序一啟動,就需要關聯 / 綁定上操作系統的一個端口號,端口號也是一個整數,用來區分一個主機上進行網絡通信的程序。(在創建 DatagramSocket 對象的時候,手動指定一個端口號(在運行一個服務器程序的時候,通常是會手動指定一個端口 port 的)

(一個主機上的一個端口號只能被一個進程綁定,一個端口已經被進程 1 綁定了,進程 2 如果也想綁定,就會失敗。除非進程 1 釋放這個端口(進程 1 結束了)。反過來,一個進程可以綁定多個端口。端口號和 socket 對象是一一對應的,如果一個進程中多個 socket 對象,自然就能綁定上多個端口了)

上面代碼中,在方法簽名上拋出的異常 SocketException,是網絡編程中常見的異常,通常表示 socket 創建失敗(比如端口號已經被別的進程占用了等待情況~~)

接下來是 strat() 方法

對于服務器來說,需要不停的收到請求,返回響應,收到請求,返回響應。(一個服務器單位時間能處理的請求,能返回的響應越多,服務器水平就越高~)

服務器往往都是 7 * 24 小時運行,即要持續不斷的運行下去,這里的 while(true) 也沒有退出的必要,如果我們確實想重啟服務器,直接 “kill” 進程即可。

當然,我們這里的 while(true) 是非常簡單粗暴的寫法。實際開發中的服務器,很可能要實現“優雅退出”的效果,即確保當前正在進行的請求都做完了之后在進行退出。我們這里先不考慮~~~

在 start 方法中,我們要實現三步:

? ? ? ? 1. 讀取請求并解析。

? ? ? ? 2. 根據請求計算響應(但我們這里只是簡單構造一個回顯服務器,這一步啥都不需要做)

? ? ? ? 3. 把響應返回到客戶端

在第一步讀取請求并解析的時候,需要調用 socket 的 receive 方法:

receive 方法中,需要傳入一個參數是 DatagramPacket 類型的對象,所以我們還需要創建一個 DatagramPacket 對象(此處的 DatagramPacket 參數是“輸出型參數”,實際上,DatagramPacket 內部就會包含一個字節數組)。

第一個參數是 byte[ ] 類型,通過這個字節數據保存消息正文(應用層數據包),也就是 UDP 數據報的載荷部分,第二個參數,表示這個載荷可以有多長。

此處的 receive 就從網卡能讀到一個 UDP 數據包,被放到了 requestPacket 對象中。其中 UDP 數據報的載荷部分就被放到了 requestPacket 內置的字節數組中了。另外報頭部分,也會被 requestPacket 的其他屬性保存。除了 UDP 報頭之外,還有其他信息,比如收到的數據源 IP 是什么等等...即通過 requestPacket 還能知道數據從哪里來的(源 ip? 源端口)

如果執行到 receive 的時候,此時還沒有客戶端發來請求呢?receive 會怎么辦呢? ==》 阻塞等待

我們可以把讀到的字節數組,轉換成 String, 方便后續的邏輯操作

基于字節數組構造出 String,字節數組里面保存的內容也不一定就是二進制數據,也可能是文本數據,如果是文本數據,將其交給 String,也是恰到好處的,如果是二進制的數據,Java 中的 String 也是可以保存的。

第一個參數中,requestPacket 調用 getData() 方法,將上面的 byet[] 中的信息獲取到,然后第二個參數表示:從字節數組的 0 號位置,開始構造 String,第三個參數,requestPacket 調用 getLength() 方法,獲取到字節數組中的有效數據的長度,用這么長的長度,來構造 String。(getLength() 方法中得到的長度是 requestPacket 中字節數組的有效長度,不一定是 4096,4096 是我們設置的最大長度。一定要使用有效長度來構造這里的 String,使用最大長度的話,就會生成一個非常長的 String,后半部分都是空白~~)

補充:

receive 是傳輸層提供的一個 API,傳輸層會給每個 socket 對象分配一個緩沖區(在內核中)每次網卡收到一個數據,都會層層分用,解析好之后,最終才能放到這個緩沖區當中,應用程序調用 receive 就是從緩沖區中拿走一個數據。

?上面就執行完了第一步,讀取請求并解析,我們還將讀取到的字節數組,轉換成了 String 類型,方便我們后續的邏輯處理。

第二步:根據請求計算響應。

這個代碼,要根據請求來構造響應,通過 process 方法來完成這個工作,因為我們這里只是要實現一個回顯服務器,所以就只是單純的 return request 了。(如果是一些具有特定業務的服務器,process 就可以寫其它任何我們想要實現的邏輯了~~)

第三步:把響應返回到客戶端

在 socket 調用 send 方法,把響應返回給客戶端的時候,也需要里面傳一個 DatagramPacket 類型的參數。

當我們構造響應對象的 DatagramPacket 的時候,參數就又不一樣了。

構造響應的 responsPpacket 對象里面的參數,就不是空白的字節數組了。直接把 String 里面包含的字節數組給拉過來了。

第一個參數:response.getBytes() 將我們剛剛轉化成 String 類型的數據,再以字節數組的形式得到

第二個參數:response.getBytes().length 這里是獲取字節數組的長度,是以字節為單位來算長度的,如果直接傳入參數為 response.length() 這里的單位就是字符了~~

第三個參數: requestPacket.getSocketAddress() ,這里調用方法的對象是 requestPacket,這個對象是我們最開始創建的,用那個對象來讀取請求,所以這個對象表示的是客戶端來的數據報,調用 getSocketAddress 方法,調用這個方法,會獲取到一個 INetAddress 對象,這個 INetAddress 對象,就包含了和服務器通信對應的客戶端的 ip 和端口號。(是把請求中的源 ip,源端口,作為響應的目的 ip 和目的端口了,此時就可以把消息返回給客戶端了)

上述代碼中,我們可以看到 UDP 是無連接的通信UDP socket 自身不保存對端(對應的客戶端)的 IP 和端口,而是在每一個數據報中,都有一個對端的 IP 和端口。另外代碼中也沒有“建立連接”和“接受連接”的操作~

而 UDP 特點中的不可靠傳輸,是在代碼中體現不到的。

但是 UDP 中的面向數據報特點,可以看到,在 send 返回響應,和 receive 讀取請求的時候,都是以 DatagramPacket 為單位的~

UDP 中全雙工的特點,在代碼中的體現為:一個 socket 是既可以發送又可以接收的。

我們還可以補充一步打印日志的操作:

補充:第一個參數最后的打印會以點分十進制的形式輸出。

再寫一個 main 方法:

new UdpEchoServer 中傳入的參數是端口號,這個端口號,我們可以指定為任何想要的端口,但是也有范圍,通常來說,使用的端口在 1024 -- 655535 之間~~(但也是有前提的,確保我們當前的機器在這個端口上沒有被其他進程占用(失敗的話換一個端口號即可~))

下面是服務器端的完整代碼:

package network;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 requestPacket = new DatagramPacket(new byte[4096], 4096);socket.receive(requestPacket);// 將讀到的字節數組,轉換成 String 方便后續的邏輯操作String request = new String(requestPacket.getData(), 0, requestPacket.getLength());// 2. 根據請求計算響應(對于回顯服務器來說,這一步什么都不需要做)String response = process(request);// 3. 把響應返回給客戶端// 構造一個 DatagramPacket 作為響應對象DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length, requestPacket.getSocketAddress());socket.send(responsePacket);// 打印日志System.out.printf("[%s:%d] request: %s , response: %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(9090);server.start();}
}

客戶端:

編寫客戶端的代碼,首先也是要創建 socket 對象,但是此處并不需要像服務器端一樣,手動指定端口號了

服務器編寫代碼的時候,要手動指定端口號,但是在客戶端這邊,一般不要手動指定,系統會自動分配一個空閑的端口號。

代碼中手動指定端口號,可以保證端口號始終都是固定的,如果不手動指定,依賴系統自動分配,導致服務器每次重啟之后,端口號可能就變了,一旦變了,客戶端就有可能找不到這個服務器在哪里了,所以我們的服務器代碼,一般要手動指定端口號。

但是客戶端,端口號一般就讓系統隨機分配一個空閑的即可。(主要是無法確保我們手動指定的一個端口號,是可用的(可能被用戶的其他進程占用了))

服務器端口就不會被別人占用嗎?為什么只要擔心客戶端呢?

服務器的機器,是在程序員的手里的,是可控的。程序員事先編寫代碼之前,是能知道服務器上都有那些端口是空閑的。但是,客戶端是在普通用戶的機器上,普通用戶千千萬,上面的環境也是千差萬別,天知道某個用戶會裝什么奇奇怪怪的程序,把我們手動指定的端口號給占用。我們的程序就會因為端口綁定失敗而無法啟動,用戶就會開噴了,辣雞程序,代碼不行,都是bug~~~

上面的構造方法還有重要的一點是,在客戶端的構造方法中,我們還需要指定一下服務器端的 ip 地址和端口。

因為是客戶端需要主動給服務器發起請求,發起請求的前提就是需要知道服務器在哪里~~~(比如說我要去餐廳吃飯,我就需要知道餐廳的位置在哪里,才能去吃飯)

此處,客戶端的目的 ip 和目的端口的 serverIp 和 serverPort 了。而客戶端的源 ip 就是客戶端的本機,源端口就是系統分配的空閑端口~

接下來,我們還是要實現一個 strat 方法,在 start 方法中,我們要做四件事:

? ? ? ? 1. 從控制臺讀取要發送的請求數據

? ? ? ? 2. 構造請求并發送

? ? ? ? 3. 讀取服務器的響應

? ? ? ? 4. 把響應顯示在控制臺上

一進入 start 方法中,我們也可以先打印一句話,啟動,客戶端(原神,啟動~)。

然后因為是要從控制臺讀取要發送的數據,肯定要創建一個 Scanner 對象,這個只需要一個即可,所以就在 while(true) 的外面創建了~

補充:這里在客戶端同樣是 while(true) 的死循環

? ? ? ? 1. 使得客戶端可以持續接收用戶的輸入,實現多次交互

? ? ? ? 2. 由于服務器響應的時間是不確定的,客戶端在發送請求后,需要持續監聽服務器的響應,使用 while(true) 可以讓客戶端不斷的嘗試從套接字中接收數據報。

第一步:從控制臺讀取要發送的請求數據

這里的 if 語句是可以讓用戶在結束輸入的時候,程序可以正確的跳出無限循環,避免程序一直等待用戶輸入~

從控制臺讀取數據的時候,最好使用 scanner 讀取字符串,最好使用 next 而不是 nextLine(如果是文件讀取的話就無所謂了)

如果使用 nextLine 讀取,就需要手動輸入換行符 -- enter 來進行控制。由于 enter 鍵不僅僅會產生 \n 這樣的換行,還會產生其他字符,就會容易導致讀到的內容出問題

使用 next 其實是以“空白符”作為分隔符,包括不限于,換行,回車,空格,制表符...

第二件事:構造請求并發送

同樣的,socket 調用 send 方法的時候,同樣需要傳入一個 DatagramPacket 類型的參數

我們在構造客戶端的 requestPacket 對象的時候,傳入的參數又又有所不同,這次構造傳入的參數需要包括,有內容的字節數組及其有效長度,并且還要指定目的 ip 和 目的端口。

可以看到我們上面的構造是編譯錯誤的,是因為第三個參數中,類型并不能直接傳入 String 類型的 severIP,而是需要調用 InetAddress.getByName(serveIp)

第三步:讀取服務器的響應

這里仍然是 socket 的 receive 方法,同樣參數需要傳入一個 DatagramPacket 類型的對象,此時這個 DatagramPacket 對象因為是搭配 receive 使用,所以構造方法只需要指定空白的字節數組即可

? ? ? ? 第四步:把響應顯示在控制臺上

這里需要將DatagramPacket類型的?responsePacket 轉換成 String 類型,并進行打印

這樣,我們的客戶端代碼也編寫完成,下面實現一個主函數即可:

“127.0.0.1” 表示的是本機的 IP,9090 就是我們剛剛服務器的端口號指定的端口號~

客戶端完整代碼如下:

package network;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 requestPacket = new DatagramPacket(new byte[4096], 4096);socket.receive(requestPacket);// 將讀到的字節數組,轉換成 String 方便后續的邏輯操作String request = new String(requestPacket.getData(), 0, requestPacket.getLength());// 2. 根據請求計算響應(對于回顯服務器來說,這一步什么都不需要做)String response = process(request);// 3. 把響應返回給客戶端// 構造一個 DatagramPacket 作為響應對象DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length, requestPacket.getSocketAddress());socket.send(responsePacket);// 打印日志System.out.printf("[%s:%d] request: %s , response: %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(9090);server.start();}
}

補充:

?在這里我們可以總結一下構造 DatagramPacket 的三種方式

第一種:構造的時候指定空白的字節數組即可:(搭配 receive 進行使用)

第二種:構造的時候指定有內容的字節數組,并且指定 IP 和端口(IP 和端口一起通過 getSocketAddress 發送)(發數據的時候使用)

第三種:構造的時候,指定由內容的字節數組,并且指定 IP 和端口(IP 和端口分開指定)(發數據的時候使用)

代碼演示

上面就完成了客戶端和服務端代碼編寫,下面我們來演示一下可以實現的功能。

先分別將兩個進程啟動

然后在客戶端輸入想要發送的數據

回車之后就可以把我們發送的數據再打印出來,這時候到服務器端的運行窗口:

此時也會得到客戶端的 ip 和端口號信息,也可以得到請求信息 request(即我們在客戶端發送的數據)和返回信息 response(因為我們是回顯服務器,在響應 process 的時候,僅僅是 return 了 request,所以這里 response 和 request 是相同的~)

梳理代碼:

下面是一個大概的流程圖~

文字解釋:

整個過程中,首先一定是服務器先啟動!!!

1. 服務器啟動。啟動之后,打印提示信息后,立即進入 while 循環,執行到 receive,進入阻塞狀態。此時沒有任何客戶端發來請求

2. 客戶端啟動。啟動之后,打印提示信息后,立即進入 while 循環,執行到 hasNext 這里,進入阻塞,此時用戶沒有在控制臺輸入任何內容。

3. 用戶在客戶端的控制臺輸入字符串,按下回車,此時 hasNext 阻塞借出,next 會返回剛才輸入的內容。

基于用戶輸入的內容,構造出一個 DatagramPacket 對象,并進行 send

send 執行完畢之后,客戶端會繼續執行到 receive 操作,等待服務器返回的響應數據(此時服務器還沒有返回響應呢,這里也會阻塞)

4. 服務器收到請求之后,就會從 receive 的阻塞中返回

返回之后,就會根據讀到的 DatagramPacket 對象,構造 String request,通過 process 方法構造一個 String 類型的 response。

再根據這個 response 構造一個 DatagramPacket 表示響應對象,再通過 send 來進行發送給客戶端

(上面服務器在執行的過程中,客戶端始終是在堵塞等待的)

5. 客戶端從 receive 中返回執行,就能夠得到服務器返回的響應,并且執行相應的邏輯(將返回的響應打印到控制臺上)

于此同時,服務器進行到下一次循環,也進入到下一次的 receive 阻塞中,等待下一個客戶端的請求了~

圖文并茂解釋:

1. 服務器啟動之后,進入 receive 阻塞,等待客戶端的請求

2. 客戶端啟動之后,阻塞在 hasNext 等待用戶從控制臺輸入數據

3. 用戶在客戶端輸入之后,客戶端拿到用戶輸入的字符串,構造出請求,發送請求,并且在 receive 處等待響應的返回

4. 服務器收到響應,就從 receive 解除阻塞,繼續往下執行

(這里服務端在執行完上述邏輯之后,就會進入到下次循環中的 receive 阻塞等待中,等待下一個客戶端的請求過來~)

5. 客戶端從 receive 中返回,得到了服務器返回的響應數據,并且把數據打印到控制臺上。

客戶端在打印完畢之后也會進行下一次循環,等待用戶再次從控制臺輸入信息。

補充:

我們可以把寫好的服務器代碼,放到一個云服務器上,然后我們再使用我們的電腦運行客戶端代碼,就可以訪問到云服務器上的程序~~~

完!

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

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

相關文章

【C++深入系列】:模版詳解(上)

🔥 本文專欄:c 🌸作者主頁:努力努力再努力wz 💪 今日博客勵志語錄: 你不需要很厲害才能開始,但你需要開始才能很厲害。 ★★★ 本文前置知識: 類和對象(上) …

java 設計模式之策略模式

簡介 策略模式:策略模式可以定制目標對象的行為,它通過傳入不同的策略實現,來配置目標對象的行為。使用策略模式,就是為了定制目標對象在某個關鍵點的行為。 策略模式中的角色: 上下文類:持有一個策略類…

Perf學習

重要的能解決的問題是這些: perf_events is an event-oriented observability tool, which can help you solve advanced performance and troubleshooting functions. Questions that can be answered include: Why is the kernel on-CPU so much? What code-pa…

「倉頡編程語言」Demo

倉頡編程語言」Demo python 1)# 倉頡語言寫字樓管理系統示例(虛構語法)# 語法規則:中文關鍵詞 類Python邏輯定義 寫字樓管理系統屬性:租戶庫 列表.新建()報修隊列 列表.新建()費用單價 5 # 元/平方米方法 添加租戶(名稱, 樓層, 面積):…

鎖(Mutex)、信號量(Semaphore)與條件量(Condition Variable)

一、同步機制的核心意義 在多線程/多進程編程中,當多個執行流共享資源(如變量、內存、文件)時,可能因操作順序不確定導致數據競爭(Data Race)。同步機制的作用是: 保證原子性:確保…

前端基礎之《Vue(6)—組件基礎(2)》

接上一篇。 七、v-model深入學習 <html> <head><title>組件基礎-4</title><style>.score {display: inline-block;}.score>span {display: inline-block;width: 25px;height: 25px;background: url(./assets/star.png) center center / 25p…

SQL:聚合函數(Aggregate Functions)

目錄 第一性原理出發思考 ——我們為什么需要聚合函數&#xff1f; 什么是聚合函數&#xff1f; 常見聚合函數 實例講解 &#x1f538; 1. COUNT() —— 計數 &#x1f538; 2. MAX() / MIN() —— 最大 / 最小值 &#x1f538; 3. SUM() —— 求和 &#x1f538; 4. …

海關總署廣東:廣東外貿一季度進出口2.14萬億元 同期增長4.2%

大灣區經濟網灣區財經報道&#xff0c;據海關總署廣東分署統計&#xff0c;今年一季度&#xff0c;廣東外貿進出口2.14萬億元&#xff0c;較去年同期&#xff08;下同&#xff09;增長4.2%&#xff0c;增速高于全國2.9個百分點。其中&#xff0c;出口1.34萬億元&#xff0c;增長…

MySQL中高級語法

Mysql高級語法 持續更新中… 1、EXISTS語法 一、基本語法結構 SELECT [列名] FROM [主表] WHERE [條件]AND EXISTS (SELECT 1 -- 子查詢內容無關&#xff0c;僅需占位符&#xff08;如1、*、X等&#xff09;FROM [子查詢表]WHERE [關聯條件] -- 必須與外層查詢關聯&#xf…

SpringBoot 調用deepseek

個人學習心得&#xff0c;僅供參考 軟件環境&#xff1a; JDK 17 你用JDK 11 無法支持SpringBoot 3SpringBoot 3 版本以上才支持spring aimavan 3.6.11.獲取Deepseek官網的API-key 官網&#xff1a;https://platform.deepseek.com/api_keys 2.創建項目 這樣創建 添加依賴…

性能測試面試題的詳細解答

以下是性能測試面試題的詳細解答&#xff1a; 1. 性能測試的流程是怎樣的&#xff1f; 性能測試流程通常包括以下幾個步驟&#xff1a; - **需求分析**&#xff1a;明確測試目標、性能指標&#xff08;如響應時間、吞吐量等&#xff09;。 - **環境搭建**&#xff1a;搭建測試環…

C++程序設計基礎實驗:C++對C的擴展特性與應用

C程序設計基礎實驗&#xff1a;C對C的擴展特性與應用 &#x1f525; 本文詳細講解C基礎實驗&#xff0c;包含C對C語言的擴充與增強特性&#xff0c;從零開始掌握函數重載、引用、指針等核心概念&#xff0c;附詳細代碼分析與運行結果。適合C初學者和有C語言基礎想學習C的同學&a…

量子神經網絡編譯器開發指南:從理論突破到產業落地全景解析

本文深度剖析IBM Qiskit 5.0量子經典混合編譯器的技術架構&#xff0c;詳解如何基于含噪量子處理器實現MNIST手寫數字分類任務&#xff08;準確率達89%&#xff09;。結合本源量子云、百度量子等國內平臺免費配額政策&#xff0c;系統性闡述量子神經網絡開發的技術路線與資源獲…

ESP32之本地HTTP服務器OTA固件升級流程,基于VSCode環境下的ESP-IDF開發(附源碼)

背景知識&#xff1a; 本實驗利用編譯鏈內Python內置的 HTTP 服務器&#xff0c;將升級包通過http發送給設備&#xff0c;實現OTA固件升級。 目錄 背景知識&#xff1a; 1.創建工程 1.1 創建OTA基礎工程 3.編寫、修改代碼 3.1 修改menuconfig配置文件 3.1.1 配置WiFi賬…

BootStrap:進階使用(其一)

今天我要講述的是在BootStrap中進一步使用的方法與代碼舉例; 導航條 作為在應用或網站中作為導航頁頭的響應式基礎組件。導航條在移動設備上可以折疊&#xff08;且可開可關&#xff09;&#xff0c;在視口&#xff08;viewport&#xff09;寬度增加時逐漸變為水平展開模式 …

ffmpeg無損轉格式的命令行

將ffmpeg.exe拖入命令行窗口 c:\users\zhangsan>D:\ffmpeg-2025-03-11\bin\ffmpeg.exe -i happy.mp4 -c:v copy -c:a copy 格式轉換后.mkv -c:v copy 僅做拷貝視頻,不重新編碼 -c:a copy 僅做拷貝音頻 ,不重新編碼

【Linux】深入理解Linux文件系統:從C接口到內核設計哲學

文章目錄 前言一、C語言中的文件接口1. 文件指針&#xff08;句柄&#xff09;FILE*以寫方式打開文件&#xff0c;若文件不存在會新建一個文件W寫入方式&#xff0c;在打開文件之前都會將文件內容全部清空追加寫方式&#xff0c;其用法與寫方法一致&#xff0c;不同在于a方法可…

國產品牌芯洲科技100V降壓芯片系列

SCT2A25采用帶集成環路補償的恒導通時間(COT)模式控制&#xff0c;大大簡化了轉換器的片外配置。SCT2A25具有典型的140uA低靜態電流&#xff0c;采用脈沖頻率調制(PFM)模式&#xff0c;它使轉換器在輕載或空載條件下實現高轉換效率。 芯洲科技100V降壓芯片系列提供豐富的48V系…

ctfshow-大賽原題-web702

因為該題沒有理解到位&#xff0c;導致看wp也一直出錯&#xff0c;特此反思一下。 參考yu22x師傅的文章 &#xff1a;CTFSHOW大賽原題篇(web696-web710)_ctfshow 大賽原題-CSDN博客 首先拿到題目&#xff1a; // www.zip 下載源碼 我們的思路就是包含一個css文件&#xff0c;…

LabVIEW技巧——獲取文件版本信息

獲取可執行文件&#xff08;exe&#xff09;版本信息的幾種方法 方法1. LabVIEW自帶函數 labview自帶了獲取文件版本號的VI&#xff0c;但是沒有開放到程序框圖的函數選板中&#xff0c;在該目錄下可以找到&#xff1a;...\LabVIEW 20xx\vi.lib\Platform\fileVersionInfo.llb…