[Computer Networks]一個http請求的完成的全過程

来源:https://www.cnblogs.com/kirizi/archive/2023/01/03/16994756.html
-Advertisement-
Play Games

摘要 本文主要講述了一個 http request 請求從發出到收到 response 的整個生命周期,希望可以通過對整個流程的一個描述來梳理清楚五層網路協議的定義以及各層之間是如何協作的。 使用Golang發起一個HTTP請求 對於後端來說通過 http 請求來進行遠程調用是再尋常不過的事了,以 ...


摘要

本文主要講述了一個 http request 請求從發出到收到 response 的整個生命周期,希望可以通過對整個流程的一個描述來梳理清楚五層網路協議的定義以及各層之間是如何協作的。

使用Golang發起一個HTTP請求

對於後端來說通過 http 請求來進行遠程調用是再尋常不過的事了,以 Golang 的 resty 包為例,我們通過下麵這個語句來發起一個請求並獲得所請求的伺服器的 response,簡單起見這裡我們使用 GET 方法進行請求:

client := resty.New()
headers := map[string]string{
	"Connection": "Keep-Alive",
}
resp1, _ := client.R().
	EnableTrace().
	SetHeaders(headers).
	Get("https://httpbin.org/get")

fmt.Println("Request Trace Info:")
ti := resp1.Request.TraceInfo()
fmt.Println("  DNSLookup     :", ti.DNSLookup)
fmt.Println("  TCPConnTime   :", ti.TCPConnTime)
fmt.Println("  TLSHandshake  :", ti.TLSHandshake)
fmt.Println("  IsConnReused  :", ti.IsConnReused)
fmt.Println("  RemoteAddr    :", ti.RemoteAddr.String())

我們在應用層發起請求,應用層是用戶的,所以 HTTP 報文的內容都是一些人類可閱讀的 ASCII 碼點,但電腦只懂得二進位,光纖中認識光信號,所以這個 HTTP 報文還需要經過一一些處理才能穿越那些物理鏈路發送到我們的目的伺服器上。首先來講講 HTTP 報文格式
image

在我們這個例子里我們的請求方法是 GET,GET 和 POST 是最常見的 HTTP 方法,除此以外還包括 DELETE、HEAD、OPTIONS、PUT、TRACE。我們沒有傳頭部欄位,也就是 HEADER , HTTP 的頭部可以分為兩種,一種是通用頭部如 Cache-Control、 Connection、Date、Pragma、Transfer-Encoding、Upgrade、Via 等,可以通過它傳遞一些信息,對通用頭部的擴展要求通訊雙方都支持此擴展,如果存在不支持的通用頭部,一般將會作為實體頭部處理。實體頭域包含關於實體的原信息,實體頭包括 Allow、Content-Base、Content-Encoding、Content-Language、Content-Length、Content-Location、Content-MD5、Content-Range、Content-Type、Etag、Expires、Last-Modified、extension-header。extension-header 允許客戶端定義新的實體頭,但是這些域可能無法為接受方識別。
在這個請求里我們也沒有消息體,URL為 https://httpbin.org/get, 是一個很簡單的 GET 請求。
http 響應報文結構和請求差不多,區別在於狀態行,狀態碼(Status-Code)主要用於機器理解,短語(Reason-Phrase)Status-Code 提供一個簡單的文本描述,主要幫助用戶理解:

  • 1xx : 信息響應類,表示接收到請求並且繼續處理
  • 2xx : 處理成功響應類,表示動作被成功接收、理解和接受
  • 3xx : 重定向響應類,為了完成指定的動作,必須接受進一步處理
  • 4xx : 客戶端錯誤,客戶請求包含語法錯誤或者是不能正確執行
  • 5xx : 服務端錯誤,伺服器不能正確執行一個正確的請求

幾個常見的狀態碼和短語:

  • 200 OK 最好的情況,即處理成功
  • 404 Not Found 不希望看到的響應之一,即找不到所請求的資源
  • 500 Internal Server Error 不希望看到的響應之二,服務端發生了錯誤

說完了 HTTP 報文,接下來我們來實踐一下,看看上面那段代碼發起一個 HTTP 請求,它的運行結果如下:
image

可以看到我們這個請求是成功了的,對方伺服器返回了 200, 短語是 OK,意味著目標伺服器成功處理了我們的請求。
輸出的Request Trace Info信息可以幫助我們理解整個請求的過程,我們一行一行地看:

DNSLookup

HTTP 報文里包含了目的伺服器的地址,也就是我們上面輸入的 URL,一個 URL 由協議頭(HTTP、HTTPS、SFTP 等)+ 功能變數名稱 + 資源路徑組成,在我們這個例子里協議頭為https(HTTPS = HTTP + SSL(TLS),它和 HTTP 的區別在於加了一道身份驗證所以更安全),功能變數名稱是 httpbin.org ,資源路徑是 /get,也就是我們以 HTTPS 協議所約定的方式去獲取 httpbin.org 所映射的伺服器上的 /get 路徑下的資源。
功能變數名稱由字元串組成,機器是無法讀懂的,所以我們需要一個服務去將它解析成機器能讀懂的地址,也就是 IP,而這個服務就是 DNS(Domain Name System)功能變數名稱系統,它是用於實現功能變數名稱和IP地址相互映射的一個分散式資料庫,這裡輸出的 DNSLookup 的值就是本次請求里花費在 DNS 解析上的時間。
功能變數名稱解析的過程大致如下:
image
完整的DNS解析過程有以下幾個步驟:
(1)查看瀏覽器緩存(我們這裡是直接通過後端來發起請求,所以沒有這一步)
當用戶通過瀏覽器訪問某功能變數名稱時,瀏覽器首先會在自己的緩存中查找是否有該功能變數名稱對應的 IP 地址(若曾經訪問過該功能變數名稱且沒有清空緩存便存在)。
(2)查看系統緩存
當瀏覽器緩存中無功能變數名稱對應 IP 則會自動檢查用戶電腦系統 Hosts 文件 DNS 緩存是否有該功能變數名稱對應 IP。
(3)查看路由器緩存
當瀏覽器及系統緩存中均無功能變數名稱對應 IP 則進入路由器緩存中檢查,以上三步均為客服端的 DNS 緩存。
(4)查看ISP DNS 緩存
當在用戶客服端查找不到功能變數名稱對應 IP 地址,則將進入 ISP DNS 緩存中進行查詢。比如你用的是電信的網路,則會進入電信的 DNS 緩存伺服器中進行查找。
(5)詢問根功能變數名稱伺服器
當以上均未完成,則進入根伺服器進行查詢。全球僅有 13 台根功能變數名稱伺服器,1 個主根功能變數名稱伺服器,其餘 12 為輔根功能變數名稱伺服器。根功能變數名稱收到請求後會查看區域文件記錄,若無則將其管轄範圍內頂級功能變數名稱(如.com、.cn等)伺服器 IP 告訴本地 DNS 伺服器。
(6)詢問頂級功能變數名稱伺服器
頂級功能變數名稱伺服器收到請求後查看區域文件記錄,若無記錄則將其管轄範圍內權威功能變數名稱伺服器的 IP 地址告訴本地 DNS 伺服器。
(7)詢問權威功能變數名稱(主功能變數名稱)伺服器
權威功能變數名稱伺服器接受到請求後查詢自己的緩存,如果沒有則進入下一級功能變數名稱伺服器進行查找,並重覆該步驟直至找到正確記錄。
(8)保存結果至緩存
本地功能變數名稱伺服器把返回的結果保存到緩存,以備下一次使用,同時將該結果反饋給客戶端,客戶端通過這個 IP 地址即可訪問目標Web伺服器。至此,DNS 遞歸查詢的整個過程結束。
通過功能變數名稱解析服務我們獲得了目標伺服器的 IP,它會在網路層被用到。

TCPConnTime

建立 TCP (Transmission Control Protocol) 連接所花的時間。TCP 屬於傳輸層協議,除了 TCP 外還有 UDP 也是常用的傳輸層協議。本文的傳輸層協議選擇了 TCP, 我們在應用層準備好了 HTTP 報文,然後選擇一個 TCP server 去傳輸這個 HTTP 報文給目標伺服器,TCP server 通過什麼方式進行與目標伺服器的溝通對於應用層來說是透明的(即不可見),應用層只需要等待 TCP server 返回的目標伺服器的應答結果就好了。
TCP 是面向連接的協議,而 UDP 是無連接的,使用 TCP 進行通信的雙方在互相發送消息之前要先建立一個連接,這個連接建立的過程被稱為三次握手。我們先來看看 TCP 報文格式:
image
埠(port),主要分為物理埠邏輯埠。我們一般說的都是邏輯埠,用於區分不同的服務。因為網路中一臺主機只有一個 IP,但是一個主機可以提供多個服務,埠號就用於區分一個主機上的不同服務。一個IP地址的埠通過16bit進行編號,最多可以有65536個埠,標識是從0~65535。
埠號分為系統埠(System Ports)0~ 1023、用戶埠(User Ports)1024~ 49151和動態埠號(Dynamic Ports)49152~65535。我們自己的服務一般都綁定在註冊埠上。
系統埠(0~ 1023):也叫做公認埠(Well Known Ports),它們緊密綁定(binding)於一些服務。通常這些埠的通訊明確表明瞭某種服務的協議。任何TCP/IP實現所提供的服務都用0-1023之間的埠號。我們的私用埠號不應該使用這個區間內的埠,除非你向IANA註冊了。例如:80埠實際上總是 http 通訊、443對應著 https(在本文中我們使用的就是 https 協議,在最後一行輸出的 RemoteAddr 可以看到目標伺服器的埠號就是443)、21對應著 ftp,25對應著 smtp,110對應著 pop3 等。

互聯網號碼分配局(英語:Internet Assigned Numbers Authority,縮寫 IANA),是一家互聯網地址指派機構,管理國際互聯網中使用的 IP 地址、功能變數名稱和許多其它參數的機構。 IP 地址、自治系統成員以及許多頂級和二級功能變數名稱分配的日常職責由國際互聯網註冊中心(IR)和地區註冊中心承擔。IANA 是由 ICANN 管理的。

用戶埠(1024~ 49151):也叫做註冊埠(Registered Ports),從1024到49151。它們鬆散地綁定於一些服務。也就是說有許多服務綁定於這些埠,這些埠同樣用於許多其它目的。例如:許多系統處理動態埠從1024左右開始。
動態埠(49152~65535):也叫做私有或動態埠(Private or Ephemeral Ports),從49152到65535。理論上,不應為服務分配這些埠。實際上,機器通常從1024起分配動態埠。只要運行的程式向系統提出訪問網路的申請,那麼系統就可以從這些埠號中分配一個供該程式使用。比如 49152 埠就是分配給第一個向系統發出申請的程式。在關閉程式進程後,就會釋放所占用的埠號。
所以當 client 準備發出網路請求的時候,client 所在的進程首先要向系統申請一個埠號作為源埠號,系統會隨機從49152~65535中分配一個可用的埠號給這進程,這樣當目標伺服器處理完請求要給我們返回數據的時候才能通過這個源埠號找到發出請求的這個埠所對應的服務並把 response 交給這個服務。可以說 HTTP 報文是面向服務的,它是服務與服務之間的交流,傳輸層是面向進程的,兩個埠號標識了兩個進程,一個進程里可能會有許多個服務(路由,或者說 API)。

序列號

在一個 TCP 連接中傳送的位元組流中的每一個位元組都按順序編號,這個編號就類似於數組的下標,數組裡每個元素都有自己的下標。例如,一報文段的序號是 101,共有 100 位元組的數據。這就表明:本報文段的數據的第一個位元組的序號是 101,最後一個位元組的序號是 200。顯然,下一個報文段的數據序號應當從 201 開始,即下一個報文段的序號欄位值應為 201。

確認號

期望收到對方下一個報文段的第一個數據位元組的序號。若確認號為 N,則表明:到序號 N-1 為止的所有數據都已正確收到。

標誌位欄位

比較常見的標誌位 SYNACKFIN 會在 TCP 連接建立與釋放的時候使用到,也就是我們常說的三次握手四次揮手。先講講三次握手建立連接:
image

三次握手的過程如圖所示,連接的建立一般都是由客戶端主動發起的,客戶端發送一個報文給伺服器,告訴它我想要和你建立一個連接進行數據交換,進行數據交換之前有些事情需要先同步(synchronize,也就是 SYN 標誌位,SYN=1 表示這是一個用於同步信息的報文),雙方得約定好初始序列號(Init Sequense Number,ISN)、視窗大小等信息,連接建立好了之後交換數據的時候才好判斷數據的起始與結束,通過 seq 欄位來告訴對方本報文的序列號。
第一次握手SYN = 1,客戶端告訴伺服器自己的初始序列號是 x (seq = x),伺服器收到了這個報文可以確定客戶端發送正常,自己接收正常。
第二次握手,伺服器端發出報文 SYN = 1, ACK = 1(僅當 ACK=1 時,確認號欄位才有效。TCP規定,在連接建立後所有報文的傳輸都必須把ACK置1),表示這是一個應答報文,並且告訴了客戶端自己的初始序列號是 y (seq = y),以及確認自己收到了客戶端的前 x 個位元組的信息,接下來希望收到 x + 1 的信息(ack = x + 1),但是客戶端剛剛的報文明明沒有攜帶信息,為什麼說收到了前 x 個位元組的信息呢,因為 TCP 規定,SYN報文段(SYN= 1 的報文段)不能攜帶數據,但需要消耗掉一個序號。
第二次握手成功之後客戶端確認了自己發送正常,接收正常,伺服器發送正常,接收正常。伺服器端確認了客戶端發送正常,自己接收正常,所以還需要第三次握手來讓伺服器端確認自己發送正常以及客戶端接收正常。
第三次握手,雙方已經同步完序列號信息了,所以第三次握手不用 SYN 標誌位了,客戶端應答(ACK = 1)伺服器第二次握手時發來的報文,表示自己收到了伺服器端的前 y 個位元組的信息(ACK = y + 1),告訴伺服器端自己這個報文的起始序號是 x + 1(seq = x + 1),當伺服器收到這個報文後伺服器就可以確認自己發送正常以及客戶端接收正常了,連接就建立完成了可以進行信息傳輸了。
如果第三次握手的報文因為各種各樣的原因丟了,伺服器端沒有收到,那麼伺服器就會進行首次重傳,若等待一段時間仍未收到客戶確認包,就進行第二次重傳。如果重傳次數超過系統規定的最大重傳次數,則系統將該連接信息從半連接隊列中刪除。每次重傳等待的時間不一定相同,一般會是指數增長。
說完了三次握手建立連接再來說說四次揮手斷開連接:
image
四次揮手的過程如圖所示。
第一次揮手,客戶端發出一個 FIN 報文,消耗一個序列號,告訴伺服器端自己沒有要發送的數據了,連接可以斷開了。
第二次揮手,伺服器端伺服器端發送一個 ACK 報文表示收到了客戶端要斷開連接的報文(ACK = 1, ack = u + 1),序號 u 之前的數據都以及收到了。此時連接進入半關閉狀態,但是伺服器端可能還有數據沒有發完,所以可以繼續發送數據,直到伺服器端發完了要發的數據,發送第三次揮手的報文。
第三次揮手,伺服器端發送 FIN 報文,消耗一個序列號(seq = w),告訴客戶端自己的數據也發完了,因為前面是伺服器端單向發數據給客戶端,所以 ack 還是為 u + 1。
第四次揮手,客戶端收到了伺服器端要斷開連接的報文,回覆一個 ACK 報文,讓伺服器端知道自己收到了它的 FIN 報文,伺服器端收到報文後立馬斷開了連接。

首部長度

也叫做數據偏移,它指出 TCP 報文段的數據起始處距離 TCP 報文段的起始處有多遠,也就是 TCP 報文段的首部長度。

視窗大小

報文能不能正常被傳輸、接收,不止取決於通信的雙方,還取決於外部環境,也就是網路環境,因為網路鏈路里不止當前的通信雙方在傳輸數據,而是由很多台在發送數據的主機在共同使用。所以數據要正常被傳輸,有兩個問題要解決,一個是發送方與接收方速率匹配,接收方能及時處理髮送方發送過來的數據,使得發送雙方速率匹配的策略我們稱為流量控制;另一個就是需要有一個良好的網路環境,努力使整體網路環境的通暢的策略我們稱為擁塞控制。流量控制與擁塞控制都是通過設置視窗大小來完成的,當然這兩個概念都是針對 TCP 協議來說的,它是一個無私的協議,UDP 可不管網路是否擁塞,它會一直發向網路中發送數據。
在介紹確認號這個首部欄位的時候我們提到了 TCP 採用的是累積確認的方式,下麵我們來具體講解是怎麼個累積確認法。引入累積確認主要是為了提高通信效率,如果沒有累積確認的話,接收方收到一個報文之後回覆一個 ACK 報文,發送方接到這個 ACK 報文才能發送下一個報文,一包一確認的方式並不是很高明,往返時間越長,通信的效率就越低。而使用累積確認,發送方就可以連續發送一批報文,而不需要等待接收方回覆了再發送下一個包。

image

雖然發送方可以一次性發一批報文,但是這個批大小肯定不能是無限大的,得有個規則來約束它,這個大小就是視窗大小。視窗大小以位元組為單位,比如當前視窗大小為1024個位元組,那麼在不需要等待接收方確認的情況下發送發可以連續發送報文直到已發送的報文的長度加起來等於1024個位元組。
流量控制:流量控制的過程就是通過不斷的調整視窗大小來給讓接收方能來得及處理接收到的數據,所以視窗大小是由接收方決定的。在三次握手建立 TCP 連接的時候就已經同步好了視窗大小的信息,並且如果在後續的數據傳輸中因為種種原因需要調整視窗大小也是允許的
擁塞控制:引入擁塞控制這個概念後視窗大小就受到流量控制和擁塞控制這兩個策略共同影響了,通過擁塞控制演算法算出擁塞視窗cwnd 的大小,實際視窗大小 = min(cwndrwndrwnd 就是流量控制中的接收視窗。
擁塞視窗cwnd變化的規則:

  • 只要網路中沒有出現擁塞,cwnd就會增大;
  • 一但網路中出現了擁塞,cwnd就減少;

只要「發送方」沒有在規定時間內接收到 ACK 應答報文,也就是發生了超時重傳,就會認為網路出現了用擁塞。
擁塞控制主要是四個演算法:

  • 慢啟動
  • 擁塞避免
  • 擁塞發生
  • 快速恢復

關於視窗大小的更多細節可以看看這篇博客

校驗和

數據段

segment data,這裡放著 HTTP 請求報文,像這樣:
image
(圖片來自這篇博客

RemoteAddr

這個遠端地址包括了目標伺服器的 IP 和埠號, 埠號在上文我們已經說過了,它會被寫在 TCP 報文里,而 IP 則會被寫在網路層的 IP 數據報里。應用層通過 DNS 服務獲得了 IP,然後通過函數調用傳參的方式來把這個 IP 傳給實現了傳輸層協議的 server,當然傳輸層的的報文里並不需要用到 IP,但是功能變數名稱是屬於應用層的東西,如果沒有在應用層完成解析獲得 IP,後面的層因為沒有功能變數名稱信息就無法獲得 IP 無法正常工作了,所以 IP 從應用層通過參數傳遞的方式傳給傳輸層,傳輸層再通過參數傳遞的方式傳給真正需要用到它的網路層,讓網路層寫進它自己的報文里。Golang 的 net 包的 net.LookupHost() 函數可以完成功能變數名稱解析獲得 IP,對 DNS 解析的過程有興趣的朋友可以看看這篇博客
我們先來看看 IP 數據報的結構:
image

就像 TCP 報文的數據段應用層報文一樣,IP 數據報的數據部分指的就是傳輸層報文,在本文里就是 TCP 報文,如下圖所示:
image

IsConnReused

連接是否復用,說到這個話題又回到了應用層的 HTTP 協議,前面我們說到 HTTP 報文有各種各樣的 HEADER 欄位,其中有一個叫做Connection 的通用 header,它的的取值為Keep-Aliveclose 。當在 header 裡加入Connection: Keep-Alive 意味著開啟長連接,
我們把上面例子里的代碼進行一些小小修改,變成這樣:

client := resty.New()
headers := map[string]string{
	"Connection": "Keep-Alive",
}
resp1, _ := client.R().
	EnableTrace().
	SetHeaders(headers).
	Get("https://httpbin.org/get")

// Explore trace info
fmt.Println("Request Trace Info:")
ti := resp1.Request.TraceInfo()
fmt.Println("  DNSLookup     :", ti.DNSLookup)
fmt.Println("  TCPConnTime   :", ti.TCPConnTime)
fmt.Println("  TLSHandshake  :", ti.TLSHandshake)
fmt.Println("  IsConnReused  :", ti.IsConnReused)
fmt.Println("  RemoteAddr    :", ti.RemoteAddr.String())’

resp2, _ := client.R().
	EnableTrace().
	SetHeaders(headers).
	Get("https://httpbin.org/get")

fmt.Println()
fmt.Println("******** Request with Keep-Alive *******")
fmt.Println()
cname, _ := net.LookupCNAME("httpbin.org")
host, _ := net.LookupHost("httpbin.org")
addr, _ := net.LookupAddr("httpbin.org")
ip, _ := net.LookupIP("httpbin.org")
fmt.Println(cname)
fmt.Println(host)
fmt.Println(addr)
fmt.Println(ip)
// Explore trace info
fmt.Println("Request Trace Info:")
ti2 := resp2.Request.TraceInfo()
fmt.Println("  DNSLookup     :", ti2.DNSLookup)
fmt.Println("  TCPConnTime   :", ti2.TCPConnTime)
fmt.Println("  TLSHandshake  :", ti2.TLSHandshake)
fmt.Println("  IsConnReused  :", ti2.IsConnReused)
fmt.Println("  RemoteAddr    :", ti2.RemoteAddr.String())

這次我們主要想看看 Keep-Alive 的實際效果,所以一些沒啥用的信息就不輸出了,這段代碼的運行效果如下:
image

可以看到在第二次請求的時候 DNSLookup、 TCPConnTime、TLSHandshake 這三個與連接建立有關的時間花費都變成了0啦!IsConnReused 也由 false 變成了 true,說明這次的連接是復用的第一次請求的時候建立的連接,所以不需要再重新建立連接,那些與連接建立的時間花費自然也變成零了(從圖上可以看出來節省了大約2.5s 的時間)。
關於長連接有興趣的朋友可以看看這篇博客

總結

實踐很重要,上學的時候學計網總覺得是一知半解的,書上說分層協作各層解耦,層與層之間是透明的,也就是互相看不見,


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Element Ul快速入門 Element Ul是基於Vue的一套桌面端組件庫,提前封裝好的Ul模板,方便開發者快速搭建一個網站前端界面 官網:https://element.eleme.cn/ 1.Element Ul安裝 在插件中,點擊添加插件 搜索element 安裝成功,界面如下所示 如果 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 短視頻已經無數不在了,但是主體還是使用 app 來承載的。本文講述 H5 如何實現 app 的視頻滑動體驗。 無聲勝有聲,一圖頂百辯,且看下圖: 網址鏈接(需在微信或者手Q中瀏覽) 從上圖可以看到,我們主要實現的功能也是本文要講解的有: ...
  • 時光飛逝,流年似水,讓我們倒帶 2022,迴首這跌宕起伏一年走過的 “升級之路”。 NutUI 表現如何? 成績單等著您打分! ...
  • 最近群里有這樣一個有意思的問題,大家在討論,使用 CSS 3D 能否實現如下所示的效果: 這裡的核心難點在於,如何利用 CSS 實現一個立體的數字?CSS 能做到嗎? 不是特別好實現,但是,如果僅僅只是在一定角度內,利用視覺障眼法,我們還是可以比較完美的還原上述效果的。 利用距離、角度及光影構建不一 ...
  • 在軟體研發過程中,往往隨著為了快速滿足業務要求的壓力,用戶需求的變更,軟體代碼的增多,以及版本的迭代,團隊成員的變化等等因素,導致一個軟體項目隨著時間推移,欠的技術債會越積越多,用戶使用容易出錯,部署流程也變得複雜。 ...
  • 摘要:究竟什麼樣的系統算是高併發系統?今天,我們就一起解密高併發業務場景下典型的秒殺系統的架構。 本文分享自華為雲社區《【高併發】秒殺系統架構解密,不是所有的秒殺都是秒殺(升級版)!!》,作者: 冰 河。 究竟什麼樣的系統算是高併發系統?今天,我們就一起解密高併發業務場景下典型的秒殺系統的架構。 電 ...
  • 流程式控制制 用戶交互Scanner Scanner類可以實現電腦與人的交互。java.util.Scanner 是 Java5 的新特征,我們可以通過 Scanner 類來獲取用戶的輸入。 Scanner 類的 next() 與 nextLine() 方法獲取輸入的字元串,在讀取前我們一般需要 使用 ...
  • 同步和非同步 同步和非同步是指程式的執行方式。在同步執行中,程式會按順序一個接一個地執行任務,直到當前任務完成。而在非同步執行中,程式會在等待當前任務完成的同時,執行其他任務。 同步執行意味著程式會阻塞,等待任務完成,而非同步執行則意味著程式不會阻塞,可以同時執行多個任務。 同步和非同步的選擇取決於你的程式需 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...