LINUX網絡基礎 [五] - HTTP協議

?

目錄

HTTP協議

預備知識

認識 URL

認識 urlencode 和 urldecode

HTTP協議格式?

HTTP請求協議格式

HTTP響應協議格式

HTTP的方法

HTTP的狀態碼

?編輯HTTP常見Header

HTTP實現代碼

HttpServer.hpp

HttpServer.cpp

Socket.hpp?

log.hpp?

Makefile?

Web根目錄

HTTPS協議了解

明文 與 密文

對稱加密 與 非對稱加密

數據摘要 &&?數據指紋


HTTP協議

預備知識

HTTP(Hyper Text Transfer Protocol)協議又叫做超文本傳輸協議,是一個簡單的請求-響應協議,HTTP通常運行在TCP之上。

在編寫網絡通信代碼時,我們可以自己進行協議的定制,但實際有很多優秀的工程師早就已經寫出了許多非常成熟的應用層協議,其中最典型的就是HTTP協議。?

日常生活中,為什么不用ip地址和端口呢,而是直接使用域名呢?

因為域名是很容易被記住,ip地址體驗特別差,給你個ip地址你都不知道是什么連接

其實我們平時用的域名,首先會被解析成IP地址的,在進行訪問時,網絡通信真正用到的就是IP地址

我們在瀏覽器進行訪問的時候,大部分默認使用的協議是http協議。他們的端口號一般是要固定下來

其中的網絡服務,這個端口號都要指明出來,不能改,一旦改了,客戶端就找不到你了

認識 URL

URL(Uniform Resource Lacator)叫做統一資源定位符,也就是我們通常所說的網址,是因特網的萬維網服務程序上用于指定信息位置的表示方法。

圖片 音頻...統稱為資源,所有網絡上的資源,都可以用唯一的一個"字符串"標識,并且可以獲取到,我們就稱之為:統一資源

可是,我找到了這臺主機和http服務,但是我想訪問的是什么呢???比如我們平時在搜圖片的時候,這個圖片其實是被存儲在該主機上的,而我們知道Linux一切皆文件,所以每個資源在自己的單機上都有自己的所屬路徑。因此我們還需要有一個帶層次的文件路徑。來標識我們具體想訪問該主機上哪個地方的資源(一般來說可能是從web根目錄開始 也有可能是相對路徑)!/是文件路徑分隔符

我們在上網的時候無非就兩種網絡行為。1.把別人的東西拿下來比如說下載圖片?2.把自己的東西傳上去 比如說登錄注冊

當我們找到了這個資源的路徑,那么這個路徑可能有很多很多格式各種的資源,那么我們繼續需要鎖定其中的一個,就需要有查詢字符串,一般在查詢標示符?的后面,表明該路徑下的唯一資源,而要鎖定唯一資源,就需要傳入一些指定的參數,不同參數之間用&去分隔,讓url支持多參數的提交!?而#后面的是片段標示符,也就是說可能我們想訪問的是該資源的某一個位置,可以用這個去進行標識!?

我們在網絡編程中,還知道要通過端口號來標識這臺主機上的唯一一個服務,而該進程自帶著網絡協議http的解析方法,可是我們普通人使用的時候是沒有端口號這個概念的! 那么瀏覽器怎么知道你這個端口號呢?? 因為瀏覽器會默認使用http協議,所以他必須知道綁定443號端口,所以默認會在請求里給我們添加端口號!

認識 urlencode 和 urldecode

urldecode(編碼)

  • urldecode 函數用于將字符串中的特殊字符轉化為 URL 編碼格式。在 URL 中,某些字符(如空格、&、=、? 等)具有特殊意義因此不能直接出現在 URL 中urldecode 通過將這些字符轉換為百分號編碼(也叫做 URL 編碼)來避免這些問題。

urldecode(解碼)

  • urlencode 函數用于將 URL 編碼(百分號編碼)的字符串恢復為原始的字符串形式。它將 URL 中的 % 后跟的十六進制數還原回原來的字符

?少量的情況,提交或者獲取的數據本身包含和url中特殊的字符沖突的字符,要求BS雙方進行編碼(encode)和解碼的過程(decode),這樣可以保證特殊字符不會在url中出現。瀏覽器會自動執行

為什么要這樣呢?

因為它本身就包含這種特殊符號字符的使用,用戶本身的特殊符號不會造成影響。這個編碼和解碼并不是為了加密什么的,而是為了保證用戶提交的數據和url不發生沖突

urldecode轉義的規則如下:

將需要轉碼的字符轉為16進制,然后從右到左,取4位(不足4位直接處理),每2位做一位,前面加上%,編碼成%XY 格式

urldecode就是urlencode的逆過程:

HTTP協議格式?

介紹

應用層常見的協議有HTTP和HTTPS,傳輸層常見的協議有TCP,網絡層常見的協議是IP,數據鏈路層對應就是MAC幀了。其中下三層是由操作系統或者驅動幫我們完成的,它們主要負責的是通信細節。如果應用層不考慮下三層,在應用層自己的心目當中,它就可以認為自己是在和對方的應用層在直接進行數據交互。

下三層負責的是通信細節,而應用層負責的是如何使用傳輸過來的數據,兩臺主機在進行通信的時候,應用層的數據能夠成功交給對端應用層,因為網絡協議棧的下三層已經負責完成了這樣的通信細節,而如何使用傳輸過來的數據就需要我們去定制協議,這里最典型的就是HTTP協議。

HTTP是基于請求和響應的應用層服務,作為客戶端,你可以向服務器發起request,服務器收到這個request后,會對這個request做數據分析,得出你想要訪問什么資源,然后服務器再構建response,完成這一次HTTP的請求。這種基于request&response這樣的工作方式,我們稱之為cs或bs模式,其中c表示client,s表示server,b表示browser。

由于HTTP是基于請求和響應的應用層訪問,因此我們必須要知道HTTP對應的請求格式和響應格式,這就是學習HTTP的重點。

HTTP請求協議格式

Request請求分析圖

HTTP請求由以下四部分組成:?

  • 請求行:[請求方法]+[url]+[http版本]
  • 請求報頭:請求的屬性,這些屬性都是以key: value的形式按行陳列的。
  • 空行:遇到空行表示請求報頭結束。
  • 請求正文:請求正文允許為空字符串,如果請求正文存在,則在請求報頭中會有一個Content-Length屬性來標識請求正文的長度。

?其中,前面三部分是一般是HTTP協議自帶的,是由HTTP協議自行設置的,而請求正文一般是用戶的相關信息或數據,如果用戶在請求時沒有信息要上傳給服務器,此時請求正文就為空字符串。

你怎么確保正文部分能夠一字不落地讀完呢??
報頭屬性里面有 標注正文部分的長度的屬性,所以他只要根據換行規則去讀取整個報文,然后一直讀到空行就說明報頭讀完了,然后再根據?Content-Length?(length字段) 直接向后讀相應長度的字節 就可以把正文部分也給讀完了!

HTTP響應協議格式

Response響應示意圖

HTTP響應由以下四部分組成:

狀態行:[http版本]+[狀態碼]+[狀態碼描述]
響應報頭:響應的屬性,這些屬性都是以key: value的形式按行陳列的。
空行:遇到空行表示響應報頭結束。
響應正文:響應正文允許為空字符串,如果響應正文存在,則響應報頭中會有一個Content-Length屬性來標識響應正文的長度。比如服務器返回了一個html頁面,那么這個html頁面的內容就是在響應正文當中的。

使用 Telnet 連接遠程主機,來模擬客戶端進行訪問

telnet <主機名> <端口號>

?退出 Telnet 會話

Ctrl+]
telnet> quit

響應結果解析:

fiddle

抓包軟件fiddle

?fiddle為什么能抓包呢

原理:以前是瀏覽器直接發到網絡。fiddle啟動之后,瀏覽器發的請求不會直接發到網絡中了,而是先交給fiddle,它進行包裝下再發給服務器,服務器再把請求響應返回fiddle,它再給我們的瀏覽器

postman

可以通過它發送具體請求

在Linux系統中,recv 函數用于接收通過套接字傳輸的數據。它是一個阻塞函數,通常用于從網絡連接中接收數據。recv 函數是POSIX標準的一部分,可以用來從連接的套接字(如TCP套接字)中接收數據。

ssize_t recv(int sockfd, void *buf, size_t len, int flags);參數說明
//sockfd:要接收數據的套接字描述符。它通常是由 socket()、accept() 或 connect() 等函數創建的套接字。
//buf:接收數據的緩沖區。
//len:緩沖區的大小,指定最多接收多少字節。
//flags:接收數據的選項,可以是以下之一(或它們的組合):
//MSG_OOB:接收帶外數據。
//MSG_PEEK:從隊列中查看數據,但不移除它們。
//MSG_WAITALL:等待接收到指定大小的所有數據。返回值
//如果成功,recv 返回接收到的字節數(即讀取的數據量)。如果接收到的數據量小于指定的 len,可能是因為連接中沒有更多的數據。
//返回值為0,表示對方關閉了連接(TCP連接的正常關閉)。
//返回值為-1,表示出錯,可以通過 errno 獲取錯誤碼。

測試效果?

我們的網頁難道每一次都要通過靜態編碼寫到服務器里面嗎?我們的網頁應該單獨的是一個獨立的文件。需要訪問哪個就通過http訪問就行了

即http響應的正文部分,要放在網頁(index.html)里面。讓內容不要靜態編碼,而是動態的去讓服務器響應給瀏覽器

對index文件修改之后保存,就能動態刷新內容了,不需要重啟服務器?

我們的文件可能不止一個,包括圖片 樣式文件 js文件 各種各樣的文件在Linux存在的情況下一定會存在個目錄結構,這個目錄結構肯定整體能被web訪問

一個Http協議一定有自己的web根目錄,這個目錄也可以由自己去指定。但是這樣無論我們怎么請求只能返回一個同一個文件,等未來你可能需要不同的文件,該怎么辦呢

很明顯將來你不可能把所有東西都放在這個文件下,這樣是不現實的。所以我們現在要實現由客戶端告訴我,它想要什么文件我就給它哪個文件,那該怎么實現呢?

實現的思路就是:我們要提取發來的請求。我們不難發現,Request請求行中的第一行URL就包括了用戶想請求哪個目錄

代碼實現思路:

把指明的路徑拼上前綴,此時就能夠動態的在服務器上找到在wwwroot目錄下指定的文件了。當未來加入圖片等內容的時候,就可以在wwwroot目錄下,用樹狀結構組織好去或許對應的內容了

我們可以定義個config配置文件,把web根目錄放在config里面。當服務器啟動的時候就讀取配置文件里面所需的內容了

如何拿到URL

把發來的請求進行反序列化,拿到請求行和請求報頭(正文內容還沒添加)

在請求行中拿到URL

我們可以發現HTTP協議處理本質上就是文本的處理,跟我們之前講的自定義協議是一樣的

我們可以用一個東西:stringstream 進行分割字符串 用法如下

std::stringstream 在 C++ 中可以用于分割字符串,其基本原理是通過將字符串加載到流中,然后利用流操作符 (>>) 按照空格(或其他分隔符)逐步提取數據。這個過程利用了流的特性,流會根據輸入格式將字符串轉換為不同的數據類型。

分割字符串的原理

std::stringstream 分割字符串的過程可以簡單理解為:

  1. 加載字符串:將需要分割的字符串傳遞給 std::stringstream 對象,這樣就可以使用流操作符讀取字符串中的各個部分。
  2. 使用流提取符提取數據:流操作符 (>>) 會逐個提取數據,并且在遇到空格或其他分隔符時停止提取。
  3. 重復提取直到流為空:你可以反復使用流操作符來從流中提取多個數據,直到流的內容全部被提取完。
#include<sstream>
using namespace std;int main()
{stringstream ss("abc def ghi");string s1, s2, s3;ss >> s1 >> s2 >> s3;cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;return 0;
}

提取URL

如果在訪問的時候不指定目錄,比如說我們要訪問(www.baidu.com,那就會默認去訪問www.baidu.com/)那就是默認會帶一個/,代表著根目錄,只會把網站的首頁給你。也就是index.html。而不會把根目錄下的所有內容給你。

那么我們也要實現這個思路,當你默認訪問的時候設置把首頁給你,當你指定訪問的時候,就去訪問對應的目錄

const std::string homepage = "index.html";//解析之后做判斷
file_path = wwwroot; // ./wwwroot        
if(url == "/" || url == "/index.html") 
{file_path += "/";file_path += homepage; // ./wwwroot/index.html
}
else file_path += url; // /a/b/c/d.html->./wwwroot/a/b/c/d.html

可以看到,當我輸入的url是:/a/b/c.html,服務器自動把路徑給我填充為?./wwwroot/a/b/c/d.html

?當我訪問的是默認的話,服務器也會自動填充為首頁目錄

下一步就是我們新建別的目錄

測試結果:

在下一步:可是對我們來說,我們也不這樣訪問呀,如果我們訪問京東的時候,一般都是上一頁,下一頁,從一個鏈接跳轉到另一個鏈接的去訪問。這個時候我們就需要把URL給維護起來了

此時就需要這個代碼了

<a href="xxxx">到第二張網頁</a>

?讓" "里面的內容具有超鏈接的作用。同樣的,也能跳轉到自己的地址,到第三張網頁...?

重點是理解HTTP的請求,無非就是通過套接字,讀到了HTTP的請求,然后按照格式給解析出來,提取URL,給他響應再把響應返回去。HTTP底層就是TCP

HTTP的方法

同學們您們的請求全部都是get方法那么什么叫做GET方法呢?

但是一般來說,最常用的就是?GET?和?POST?

GET方法一般用于獲取某種資源信息,而POST方法一般用于將數據上傳給服務器。但實際我們上傳數據時也有可能使用GET方法,比如百度提交數據時實際使用的就是GET方法。

我們在日常使用某些網站的時候,是如何把數據提交給服務器的呢?日常又是怎么提交的呢?

其實數據都是通過表單提供的

GET?方法的提交方式

POST?方法的提交方式

GET方法和POST方法都可以帶參:

  • GET方法是通過url傳參的。
  • POST方法是通過正文傳參的。

從GET方法和POST方法的傳參形式可以看出,POST方法能傳遞更多的參數,因為url的長度是有限制的,POST方法通過正文傳參就可以攜帶更多的數據。

此外,使用POST方法傳參更加私密,因為POST方法不會將你的參數回顯到url當中,此時也就不會被別人輕易看到。不能說POST方法比GET方法更安全,因為POST方法和GET方法實際都不安全,要做到安全只能通過加密來完成。

其實提交參數的本質意義就是,fork子進程讓他通過進程替換去執行這個程序,然后我們再通過進程間通信的方法將參數傳遞給他,然后讓他完成相應的功能!!

HTTP的狀態碼

最常見的狀態碼, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)

404表示資源不存在,比如客戶端請求的資源,服務器打開失敗所以產生錯誤,是客戶端的錯誤(所以一般來說我們除了要寫一個網站首頁的文件,還需要寫一個如果請求失敗的一個返回404頁面的網頁)

模擬下404界面的實現

當收到的text為空時,說明沒有找到路徑,那就直接去找err.html提示404 Not Found

403是禁止訪問,一般就是因為你沒有授權然后去訪問了服務端不讓你訪問的信息?

504服務器錯誤,一般來說就是比如連接不上,或者是服務的線程創建失敗

3XX一般是由于我要訪問的服務器可能處于某種原因無法為我提供服務,所以他會傳一個地址告訴客戶端你想要的資源應該去這里查找(通過參數Location),這就是重定向

Redirection詳解

重定向分為永久重定向和臨時重定向?

講個故事:

? ? ? ?比如說你的學校東門有一家火鍋店非常有名,但是他需要長時間的裝修,為了繼續生意,他將生意開到了學校的西門,然后在東門這里貼了一張紙條“正在裝修,請到西門用餐”,這個時候你和你的同學到東門時看到了這個紙條,于是就到西門去吃了了,過了一禮拜你們還打算去的時候,你知道東門那邊只是暫時關閉,所以你還是會先去東門看看,如果還沒開的話才會去西門,這個其實就是?臨時重定向,因為我知道這個店只是暫時開到西門,隨時可能會回來!

? ? ? 而后來東門開了的時候,為了能夠讓之前去西門吃飯的同學知道東門開啟了,他會在之前西門的地方貼上“東門已經裝修完成,以后請都前往東門用餐” 這個時候你們就知道這個店會一直在東門,這其實就是?永久重定向!!

如果某個網站是永久重定向,那么第一次訪問該網站時由瀏覽器幫你進行重定向,但后續再訪問該網站時就不需要瀏覽器再進行重定向了,此時你訪問的直接就是重定向后的網站。而如果某個網站是臨時重定向,那么每次訪問該網站時如果需要進行重定向,都需要瀏覽器來幫我們完成重定向跳轉到目標網站。?所以有的網站時間比較老,做更新的時候需要把域名和網址換了,可是很多老用戶并不知道,所以就會給老網站部署永久性定向服務,讓用戶直接跳轉到新網站!

//構建HTTP響應
string status_line = "http/1.1 307 Temporary Redirect\n"; //狀態行
string response_header = "Location: https://www.csdn.net/\n"; //響應報頭
string blank = "\n"; //空行
string response = status_line + response_header + blank; //響應報文
//響應HTTP請求
send(sock, response.c_str(), response.size(), 0);

當我們用telnet命令登錄我們的服務器時,向服務器發起HTTP請求時,此時服務器給我們的響應就是狀態碼307,響應報頭當中是Location字段對應的就是CSDN首頁的網址?

telnet命令實際上只是一來一回,如果我們用瀏覽器訪問我們的服務器,當瀏覽器收到這個HTTP響應后,還會對這個HTTP響應進行分析,當瀏覽器識別到狀態碼是307后就會提取出Location后面的網址,然后繼續自動對該網站繼續發起請求,此時就完成了頁面跳轉這樣的功能。

此時當瀏覽器訪問我們的服務器時,就會立馬跳轉到CSDN的首頁。

HTTP常見Header

以下是常見的Header

  • Content-Type: 數據類型(text/html等)
  • Content-Length: Body的長度
  • Host: 客戶端告知服務器, 所請求的資源是在哪個主機的哪個端口上;
  • User-Agent: 聲明用戶的操作系統和瀏覽器版本信息;
  • referer: 當前頁面是從哪個頁面跳轉過來的;
  • location: 搭配3xx狀態碼使用, 告訴客戶端接下來要去哪里訪問;
  • Cookie: 用于在客戶端存儲少量信息. 通常用于實現會話(session)的功能;
  • connection:是否支持長連接(是基于服務費和客戶端的版本去協商的)

Content-Type

讀html的時候他是文本文件,那么就可以按照字符串讀,但是如果我們是圖片資源(png),那么就得按照二進制文件的方式來讀!!因此在讀取html之前需要確認一下文件格式,而不同的格式對應的content-type可以到網上搜索對照表。所以讀網頁之前必須確定一下后綴,然后不同的后綴就根據content-type對照表去找。

常見的Content-Type

Host?

Host字段表明了客戶端要訪問的服務的IP和端口,比如當瀏覽器訪問我們的服務器時,瀏覽器發來的HTTP請求當中的Host字段填的就是我們的IP和端口。但客戶端不就是要訪問服務器嗎?為什么客戶端還要告訴服務器它要訪問的服務對應的IP和端口?

因為有些服務器實際提供的是一種代理服務,也就是代替客戶端向其他服務器發起請求,然后將請求得到的結果再返回給客戶端。在這種情況下客戶端就必須告訴代理服務器它要訪問的服務對應的IP和端口,此時Host提供的信息就有效了。

connection

短連接其實就是一次請求響應一個資源,然后就關閉連接

長連接其實就是一次請求連接上之后可以一直服務直到服務結束再關閉連接

為什么要有長連接呢??

因為一個巨大的網頁上的元素是很多的,而每一個元素其實就是一個資源,所以我們在發出http請求申請到網頁資源的時候,同時也需要把網頁上附帶的資源(比如圖片、音頻) 都申請了。比如服務器要獲取100個圖片的網頁,要發起101次HTTP的請求,對我們的服務器來說要一次性創建幾十個線程,而HTTP底層是基于TCP的,TCP是面向連接的,是短連接!所以如果用短連接的話顯然效率是不夠高的!!

長連接和短連接并沒有絕對的優劣,只不過應用場景不一樣!!

? ? ? ? ?但是要注意的是,具體采用長連接還是短連接,是要基于雙方的HTTP版本協商的,其中HTTP1.0其實就是短連接 ?而HTTP1.1其實就是長連接 ? 要支持長連接的話必須要求雙方的版本都是1.1 ? 這樣Connection就會呈現keep-alive表示支持長連接?

Referer

Referer代表的是你當前是從哪一個頁面跳轉過來的。Referer記錄上一個頁面的好處一方面是方便回退,另一方面可以知道我們當前頁面與上一個頁面之間的相關性。

Cookie

HTTP實際上是一種無狀態協議,HTTP的每次請求/響應之間是沒有任何關系的,但你在使用瀏覽器的時候發現并不是這樣的。

比如當你登錄一次CSDN后,就算你把CSDN網站關了甚至是重啟電腦,當你再次打開CSDN網站時,CSDN并沒有要求你再次輸入賬號和密碼,這實際上是通過cookie技術實現的,點擊瀏覽器當中鎖的標志就可以看到對應網站的各種cookie數據。

這些cookie數據實際都是對應的服務器方寫的,如果你將對應的某些cookie刪除,那么此時可能就需要你重新進行登錄認證了,因為你刪除的可能正好就是你登錄時所設置的cookie信息。

Cookie是什么

因為HTTP是一種無狀態協議,如果沒有cookie的存在,那么每當我們要進行頁面請求時都需要重新輸入賬號和密碼進行認證,這樣太麻煩了。?

首先我們要知道,HTTP默認是無狀態的,而他之所以能夠知道你你處在登錄狀態,是因為你之前登錄的時候,在瀏覽器里形成了一個cookie文件,這個文件里存儲著你在這個網站的認證信息,而當你打開這個網站時,瀏覽器向對應服務端發送請求的時候會將你的認證信息(就是用戶名和密碼)放在cookie參數里面帶過去直接認證(認證其實就是拿著你的用戶名和密碼去他后端的數據庫做搜索,所以你想使用的前提是必須得注冊,才能在他的數據庫里留存數據),認證通過之后會直接將你從原先的登錄頁面重定向到目標頁面,這樣你就不需要再次登錄了!!當然這個保存一般是有時間限制的!!

從第一次登錄認證之后,瀏覽器再向該網站發起的HTTP請求當中就會自動包含一個cookie字段,其中攜帶的就是我第一次的認證信息,此后對端服務器需要對你進行認證時就會直接提取出HTTP請求當中的cookie字段,而不會重新讓你輸入賬號和密碼了。也就是在第一次認證登錄后,后續所有的認證都變成了自動認證,這就叫做cookie技術。也叫做HTTP會話保持功能

內存級別&文件級別

cookie就是在瀏覽器當中的一個小文件,文件里記錄的就是用戶的私有信息。cookie文件可以分為兩種,一種是內存級別的cookie文件,另一種是文件級別的cookie文件。

  • 將瀏覽器關掉后再打開,訪問之前登錄過的網站,如果需要你重新輸入賬號和密碼,說明你之前登錄時瀏覽器當中保存的cookie信息是內存級別的。
  • 將瀏覽器關掉甚至將電腦重啟再打開,訪問之前登錄過的網站,如果不需要你重新輸入賬戶和密碼,說明你之前登錄時瀏覽器當中保存的cookie信息是文件級別的。

cookie被盜?

?如果你瀏覽器當中保存的cookie信息被非法用戶盜取了,那么此時這個非法用戶就可以用你的cookie信息,以你的身份去訪問你曾經訪問過的網站,我們將這種現象稱為cookie被盜取了。

比如你不小心點了某個鏈接,這個鏈接可能就是一個下載程序,當你點擊之后它就會通過某種方式把程序下載到你本地,并且自動執行該程序,該程序會掃描你的瀏覽器當中的cookie目錄,把所有的cookie信息通過網絡的方式傳送給惡意方,當惡意方拿到你的cookie信息后就可以拷貝到它的瀏覽器對應的cookie目錄當中,然后以你的身份訪問你曾經訪問過的網站。

所以我們會面臨兩個問題(1)cookie被盜取 (2)個人信息泄露

我們要知道我們小白用戶的防范能力基本為0,他們的電腦在黑客的眼里其實就相當于裸奔,比如你不小心點開了一個病毒,你或許可以通過殺毒軟件去殺毒,但是也很有可能在你清理這個病毒之前你的信息就已經被竊取了!! 所以顯然不能讓客戶端來維護這個安全問題,必須由更專業的服務端來維護!

SessionID

所以引入了session技術,我們輸入用戶名密碼的時候,他會將這個用戶名密碼存在服務端,然后生成一個session id(數字指紋)返回給客戶端存在cookie文件里,然后服務端會將session id管理起來(redis),這樣認證的時候就會用cookie文件里面存儲的session id去服務端做對比,只要通過了就可以直接登錄了!!

?

當我們第一次登錄某個網站輸入賬號和密碼后,服務器認證成功后還會服務端生成一個對應的SessionID,這個SessionID與用戶信息是不相關的。系統會將所有登錄用戶的SessionID值統一維護起來。

此時當認證通過后服務端在對瀏覽器進行HTTP響應時,就會將這個生成的SessionID值響應給瀏覽器。瀏覽器收到響應后會自動提取出SessionID的值,將其保存在瀏覽器的cookie文件當中。后續訪問該服務器時,對應的HTTP請求當中就會自動攜帶上這個SessionID。而服務器識別到HTTP請求當中包含了SessionID,就會提取出這個SessionID,然后再到對應的集合當中進行對比,對比成功就說明這個用戶是曾經登錄過的,此時也就自動就認證成功了,然后就會正常處理你發來的請求,這就是我們當前主流的工作方式。使用SessionID本質上是防止了個人信息泄露了。因為你的個人信息已經是在服務器上維護了,而不是在客戶端上維護了

安全是相對的

?引入SessionID之后,瀏覽器當中的cookie文件保存的是SessionID,此時這個cookie文件同樣可能被盜取。此時用戶的賬號和密碼雖然不會泄漏了,但用戶對應的SessionID是會泄漏的,非法用戶仍然可以盜取我的SessionID去訪問我曾經訪問過的服務器,相當于還是存在剛才的問題。

  • 之前的工作方式就相當于把賬號和密碼信息在瀏覽器當中再保存一份,每次請求時都自動將賬號和密碼的信息攜帶上,但是賬號和密碼一直在網當中發送太不安全了。
  • 因此現在的工作方式是,服務器只有在第一次認證的時候需要在網絡中傳輸賬號和密碼,此后在網絡上發送的都是SessionID。

這種方法雖然沒有真正解決安全問題,但這種方法是相對安全的。但是跟之前最大的區別就是session id是由服務端統一管理的,也就意味著他具備回收、停用、甄別異常等能力(比如說他發現你的ip地址突然發生了很大的變動,察覺異常就會暫時給你停止,等你發現問題了再次申訴的時候,他就會把你之前的session id清理掉然后重新分配) 這其實就是達到了控制客戶端的目的!!?

不過在安全領域有一個準則:如果破解某個信息的成本已經遠遠大于破解之后獲得的收益(說明做這個事是賠本的),那么就可以說這個信息是安全的。

HTTP實現代碼

HttpServer.hpp

#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include <fstream>
#include <vector>
#include <sstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unordered_map>
#include "Socket.hpp"
#include "Log.hpp"const std::string wwwroot="./wwwroot"; // web 根目錄
const std::string sep = "\r\n";
const std::string homepage = "index.html";static const int defaultport = 8082;class HttpServer;class ThreadData
{
public:ThreadData(int fd, HttpServer *s) : sockfd(fd), svr(s){}public:int sockfd;HttpServer *svr;
};class HttpRequest
{
public:void Deserialize(std::string req){while(true){std::size_t pos = req.find(sep);if(pos == std::string::npos) break;std::string temp = req.substr(0, pos);if(temp.empty()) break;req_header.push_back(temp);req.erase(0, pos+sep.size());}text = req;}// .png:image/pngvoid Parse(){std::stringstream ss(req_header[0]);ss >> method >> url >> http_version;file_path = wwwroot; // ./wwwrootif(url == "/" || url == "/index.html") {file_path += "/";file_path += homepage; // ./wwwroot/index.html}else file_path += url; // /a/b/c/d.html->./wwwroot/a/b/c/d.htmlauto pos = file_path.rfind(".");if(pos == std::string::npos) suffix = ".html";else suffix = file_path.substr(pos);}void DebugPrint(){for(auto &line : req_header){std::cout << "--------------------------------" << std::endl;std::cout << line << "\n\n";}std::cout << "method: " << method << std::endl;std::cout << "url: " << url << std::endl;std::cout << "http_version: " << http_version << std::endl;std::cout << "file_path: " << file_path << std::endl;std::cout << text << std::endl;}
public:std::vector<std::string> req_header;std::string text;// 解析之后的結果std::string method;std::string url;std::string http_version;std::string file_path; // ./wwwroot/a/b/c.html 2.pngstd::string suffix;
};class HttpServer
{
public:HttpServer(uint16_t port = defaultport) : _port(port){content_type.insert({".html", "text/html"});content_type.insert({".png", "image/png"});content_type.insert({".jpg", "image/jpeg"});}bool Start(){_listensock.Socket();_listensock.Bind(_port);_listensock.Listen();for (;;){std::string clientip;uint16_t clientport;int sockfd = _listensock.Accept(&clientip, &clientport);if (sockfd < 0)continue;lg(Info, "get a new connect, sockfd: %d", sockfd);pthread_t tid;ThreadData *td = new ThreadData(sockfd, this);pthread_create(&tid, nullptr, ThreadRun, td);}}static std::string ReadHtmlContent(const std::string &htmlpath){// 坑std::ifstream in(htmlpath, std::ios::binary);if(!in.is_open()) return "";in.seekg(0, std::ios_base::end);auto len = in.tellg();in.seekg(0, std::ios_base::beg);std::string content;content.resize(len);in.read((char*)content.c_str(), content.size());//std::string content;//std::string line;//while(std::getline(in, line))//{//    content += line;//}in.close();return content;}std::string SuffixToDesc(const std::string &suffix){auto iter = content_type.find(suffix);if(iter == content_type.end()) return content_type[".html"];else return content_type[suffix];}void HandlerHttp(int sockfd){char buffer[10240];ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0); // bugif (n > 0){buffer[n] = 0;std::cout << buffer << std::endl; // 假設我們讀取到的就是一個完整的,獨立的http 請求HttpRequest req;req.Deserialize(buffer);req.Parse();req.DebugPrint();//std::string path = wwwroot;//path += url; // wwwroot/a/a/b/index.html// 返回響應的過程std::string text;bool ok = true;text = ReadHtmlContent(req.file_path); // 失敗?if(text.empty()){ok = false;std::string err_html = wwwroot;err_html += "/";err_html += "err.html";text = ReadHtmlContent(err_html);}std::string response_line;if(ok)response_line = "HTTP/1.0 200 OK\r\n";elseresponse_line = "HTTP/1.0 404 Not Found\r\n";//response_line = "HTTP/1.0 302 Found\r\n";std::string response_header = "Content-Length: ";response_header += std::to_string(text.size()); // Content-Length: 11response_header += "\r\n";response_header += "Content-Type: ";response_header += SuffixToDesc(req.suffix);response_header += "\r\n";response_header += "Set-Cookie: name=haha&&passwd=12345";response_header += "\r\n";//response_header += "Location: https://www.qq.com\r\n";std::string blank_line = "\r\n"; // \nstd::string response = response_line;response += response_header;response += blank_line;response += text;send(sockfd, response.c_str(), response.size(), 0);}close(sockfd);}static void *ThreadRun(void *args){pthread_detach(pthread_self());ThreadData *td = static_cast<ThreadData *>(args);td->svr->HandlerHttp(td->sockfd);delete td;return nullptr;}~HttpServer(){}private:Sock _listensock;uint16_t _port;std::unordered_map<std::string, std::string> content_type;
};

HttpServer.cpp

#include "HttpServer.hpp"
#include <iostream>
#include <memory>
#include <pthread.h>
#include "Log.hpp"using namespace std;int main(int argc, char *argv[])
{if(argc != 2){exit(1);}uint16_t port = std::stoi(argv[1]);// HttpServer *svr = new HttpServer();// std::unique<HttpServer> svr(new HttpServer());std::unique_ptr<HttpServer> svr(new HttpServer(port));svr->Start();return 0;
}

Socket.hpp?

#pragma once#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"enum
{SocketErr = 2,BindErr,ListenErr,
};// TODO
const int backlog = 10;class Sock
{
public:Sock(){}~Sock(){}public:void Socket(){sockfd_ = socket(AF_INET, SOCK_STREAM, 0);if (sockfd_ < 0){lg(Fatal, "socker error, %s: %d", strerror(errno), errno);exit(SocketErr);}int opt = 1;setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));}void Bind(uint16_t port){struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = INADDR_ANY;if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0){lg(Fatal, "bind error, %s: %d", strerror(errno), errno);exit(BindErr);}}void Listen(){if (listen(sockfd_, backlog) < 0){lg(Fatal, "listen error, %s: %d", strerror(errno), errno);exit(ListenErr);}}int Accept(std::string *clientip, uint16_t *clientport){struct sockaddr_in peer;socklen_t len = sizeof(peer);int newfd = accept(sockfd_, (struct sockaddr*)&peer, &len);if(newfd < 0){lg(Warning, "accept error, %s: %d", strerror(errno), errno);return -1;}char ipstr[64];inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));*clientip = ipstr;*clientport = ntohs(peer.sin_port);return newfd;}bool Connect(const std::string &ip, const uint16_t &port){struct sockaddr_in peer;memset(&peer, 0, sizeof(peer));peer.sin_family = AF_INET;peer.sin_port = htons(port);inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));int n = connect(sockfd_, (struct sockaddr*)&peer, sizeof(peer));if(n == -1) {std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;return false;}return true;}void Close(){close(sockfd_);}int Fd(){return sockfd_;}private:int sockfd_;
};

log.hpp?

#pragma once#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>#define SIZE 1024#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4#define Screen 1
#define Onefile 2
#define Classfile 3#define LogFile "log.txt"class Log
{
public:Log(){printMethod = Screen;path = "./log/";}void Enable(int method){printMethod = method;}std::string levelToString(int level){switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}// void logmessage(int level, const char *format, ...)// {//     time_t t = time(nullptr);//     struct tm *ctime = localtime(&t);//     char leftbuffer[SIZE];//     snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),//              ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,//              ctime->tm_hour, ctime->tm_min, ctime->tm_sec);//     // va_list s;//     // va_start(s, format);//     char rightbuffer[SIZE];//     vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);//     // va_end(s);//     // 格式:默認部分+自定義部分//     char logtxt[SIZE * 2];//     snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);//     // printf("%s", logtxt); // 暫時打印//     printLog(level, logtxt);// }void printLog(int level, const std::string &logtxt){switch (printMethod){case Screen:std::cout << logtxt << std::endl;break;case Onefile:printOneFile(LogFile, logtxt);break;case Classfile:printClassFile(level, logtxt);break;default:break;}}void printOneFile(const std::string &logname, const std::string &logtxt){std::string _logname = path + logname;int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"if (fd < 0)return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}void printClassFile(int level, const std::string &logtxt){std::string filename = LogFile;filename += ".";filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"printOneFile(filename, logtxt);}~Log(){}void operator()(int level, const char *format, ...){time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);// 格式:默認部分+自定義部分char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);// printf("%s", logtxt); // 暫時打印printLog(level, logtxt);}
private:int printMethod;std::string path;
};Log lg;// int sum(int n, ...)
// {
//     va_list s; // char*
//     va_start(s, n);//     int sum = 0;
//     while(n)
//     {
//         sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123);
//         n--;
//     }//     va_end(s); //s = NULL
//     return sum;
// }

Makefile?

HttpServer:HttpServer.ccg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -f HttpServer

Web根目錄

HTTPS協議了解

早期很多公司剛起步的時候,使用的應用層協議都是HTTP,而HTTP無論是用GET方法還是POST方法傳參,都是沒有經過任何加密的,因此早期很多的信息都是可以通過抓包工具抓到的。

為了解決這個問題,于是出現了HTTPS協議,HTTPS實際就是在應用層中加了一層加密層(SSL&TLS),這層加密層本身也是屬于應用層的,它會對用戶的個人信息進行各種程度的加密。HTTPS在交付數據時先把數據交給加密層,由加密層對數據加密后再交給傳輸層。

當然,通信雙方使用的應用層協議必須是一樣的,因此對端的應用層也必須使用HTTPS,當對端的傳輸層收到數據后,會先將數據交給加密層,由加密層對數據進行解密后再將數據交給應用層。

?

此時數據只有在用戶層(應用層)是沒有被加密的,而在應用層往下以及網絡當中都是加密的,這就叫做HTTPS。

明文 與 密文

  • 明文 (Plaintext):指的是未經加密的原始數據或信息。它是人類可以直接讀取的內容,比如普通的文本文件、電子郵件內容、文件內容等。例如,“Hello, how are you?” 這個句子就是明文。

  • 密文 (Ciphertext):指的是通過加密算法將明文轉化后的形式,目的是保護數據的隱私。密文通常是無法直接理解的,只有通過解密操作才能還原成明文。例如,使用加密算法加密后,原來的文本可能變成類似“Xy2#&9kfj23”這樣的內容。

對稱加密 與 非對稱加密

加密的方式可以分為對稱加密和非對稱加密:

  • 采用單鑰密碼系統的加密方法,同一個密鑰可以同時用作信息的加密和解密,這種加密方法稱為對稱加密。
  • 采用公鑰和私鑰來進行加密和解密,用其中一個密鑰進行加密就必須用另一個密鑰進行解密,這種加密方法稱為非對稱加密。

對稱加密?

采用單鑰密碼系統的加密方法,同一個密鑰可以同時用作信息的加密和解密,這種加密方法稱為對稱加密,也稱為單密鑰加密,特征:加密和解密所用的密鑰是相同的。
常見對稱加密算法(了解):DES、3DES、AES、TDEA、Blowfish、RC2 等
特點:算法公開、計算量?、加密速度快、加密效率?

對稱加密其實就是通過同一個 "密鑰"?, 把明文加密成密文, 并且也能把密文解密成明文。

一個簡單的對稱加密, 按位異或:

  • 假設 明文 a = 1234, 密鑰 key = 8888
  • 則加密 a ^ key 得到的密文 b 為 9834。
  • 然后針對密文 9834 再次進行運算 b ^ key, 得到的就是原來的明文 1234. (對于字符串的對稱加密也是同理, 每一個字符都可以表?成一個數字)

非對稱加密?

需要兩個密鑰來進行加密和解密,這兩個密鑰是公開密鑰(public key,簡稱公鑰)和私有密鑰(private key,簡稱私鑰)。
常見非對稱加密算法(了解):RSA,DSA,ECDSA
特點:算法強度復雜、安全性依賴于算法與密鑰但是由于其算法復雜,而使得加密解密速度沒有對稱加密解密的速度快

?? 通過公鑰對明文加密, 變成密文

? 通過私鑰對密文解密, 變成明文

? 通過私鑰對明文加密, 變成密文

? 通過公鑰對密文解密, 變成明文

非對稱加密的數學原理比較復雜, 涉及到一些?數論?相關的知識. 這里舉一個簡單的生活上的例子。

A 要給 B 一些重要的文件, 但是 B 可能不在。

于是 A 和 B 提前做出約定: B 說: 我桌子上有個盒子, 然后我給你一把鎖, 你把文件放盒子里用鎖鎖上, 然后我回頭拿著鑰匙來開鎖取文件。

在這個場景中, 這把鎖就相當于公鑰, 鑰匙就是私鑰。 公鑰給誰都行(不怕泄露), 但是 私鑰只有 B 自己持有。持有私鑰的人才能解密。

所以要注意的是:公鑰不怕泄露,私鑰不能泄露

數據摘要 &&?數據指紋

數據摘要 &&?數據指紋

數據摘要(又被稱為數據指紋),其基本原理是利用單向散列函數(Hash函數)對數據進行運算,生成一串固定長度的數字摘要(散列值)。

摘要的特征:數字摘要并不是一種加密機制,數據摘要具有不可逆性,即無法從摘要推導出原始數據。
摘要的應用:數據摘要用于驗證數據的完整性,確保數據在傳輸或存儲過程中沒有被修改。
摘要的常見算法:有MD5、SHA1、SHA256、SHA512等。

?

數據簽名

數據簽名是在數據摘要的基礎上添加了非對稱的加密操作,用于驗證數據的完整性和真實性。
數據簽名包含了數據摘要、公鑰密碼學算法和數字證書等技術。發送者使用私鑰對摘要進行加密,形成簽名,接收者使用發送者的公鑰對簽名進行解密和驗證。
數據簽名不僅驗證數據的完整性,還驗證發送者的身份,確保數據的真實性和不可否認性。

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

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

相關文章

六十天前端強化訓練之第八天到第十四天——綜合案例:用戶管理系統

歡迎來到編程星辰海的博客講解 看完可以給一個免費的三連嗎&#xff0c;謝謝大佬&#xff01; 目錄 一、知識體系詳解 1. 變量與作用域 2. 箭頭函數特性 3. 數組高階函數 4. DOM操作原理 5. 事件傳播機制 6. 閉包核心原理 7. 原型繼承體系 8. Promise工作流程 二、綜…

技術周總結 03.03 - 03.09 周日(Java監控 SpringAI)

文章目錄 一、03.05 周三二、03.08 周六openAI 的Spring開發 一、03.05 周三 jvisualvm java自帶的監控和故障排除工具 命令行執行后&#xff0c;會出現 JConsole 二、03.08 周六 openAI 的Spring開發 引入 spring-ai-openai-spirng-boot-starter 依賴 Spring AI http…

DeepSeek:中國AGI破局者的技術革命與生態重構

在AI領域被"算力霸權"與"技術壟斷"籠罩的今天&#xff0c;一家來自杭州的初創公司正以顛覆性創新撕開行業鐵幕。DeepSeek&#xff08;深度求索&#xff09;不僅重新定義了AGI技術研發范式&#xff0c;更通過開源生態構建引發全球AI產業格局的深度重構。 一…

manus本地部署使用體驗

manus部署 https://github.com/mannaandpoem/OpenManus git clone https://github.com/mannaandpoem/OpenManus.git 或者手工下載zip包解壓&#xff0c;包很小&#xff0c;只有幾百K。 cd OpenManus-main #創建python環境&#xff0c;有python3的可以用python3 python -m ven…

【統計至簡】【入門測試1】給定數據矩陣X,如何求其質心、中心化數據、標準化數據、格拉姆矩陣、協方差矩陣、相關系數矩陣

給定數據矩陣X&#xff0c;如何求其質心、中心化數據、標準化數據、格拉姆矩陣、協方差矩陣、相關系數矩陣。 ??設數據矩陣 X X X是一個 n p n\times p np的矩陣&#xff0c;其中 n n n是樣本數量&#xff0c; p p p是變量數量&#xff0c; X ( x i j ) X (x_{ij}) X(xij?…

CI/CD—Jenkins、Maven安裝

Jenkins簡介 Jenkins 是一款廣泛使用的開源持續集成和持續交付&#xff08;CI/CD&#xff09;工具&#xff0c;以下是對它的詳細介紹&#xff1a; 基本信息 起源與發展&#xff1a;Jenkins 最早起源于 Hudson 項目&#xff0c;后來從 Hudson 項目中分離出來獨立發展。自 2011 …

抽獎系統測試報告

項目鏈接: 管理員登錄頁面 項目功能: 管理員登錄: 登錄方式分為兩種: 手機號密碼登錄: 正確輸入密碼和手機號登錄 短信驗證碼登錄: 輸入手機號,等待驗證碼,輸入驗證碼登錄 管理員注冊: 登錄頁面點擊注冊按鈕即可注冊管理員身份 人員管理模塊: 人員管理模塊分為注冊…

【高級篇】大疆Pocket 3加ENC編碼器實現無線RTMP轉HDMI進導播臺

【高級篇】大疆Pocket 3加ENC編碼器實現無線RTMP轉HDMI進導播臺 文章目錄 準備工作連接設備RTMP概念ENCSHV2推流地址設置大疆Pocket 3直播設置總結 老鐵們好&#xff01; 很久沒寫軟文了&#xff0c;今天給大家帶了一個干貨&#xff0c;如上圖&#xff0c;大疆Pocket 3加ENC編…

【 <一> 煉丹初探:JavaWeb 的起源與基礎】之 Servlet 與 JSP 的協作:MVC 模式的雛形

<前文回顧> 點擊此處查看 合集 https://blog.csdn.net/foyodesigner/category_12907601.html?fromshareblogcolumn&sharetypeblogcolumn&sharerId12907601&sharereferPC&sharesourceFoyoDesigner&sharefromfrom_link <今日更新> 一、Servl…

【不是廣告】華為昇騰的一小步,Pytorch的一大步

華為昇騰的一小步&#xff0c;Pytorch的一大步 關鍵詞 首個、中國首個、全球第十、最高級別&#xff01;看看這些字眼&#xff0c;就知道事情不簡單&#xff01; 書接上文《Pytorch的一小步&#xff0c;昇騰芯片的一大步》 在2023年10月4日PyTorch 2.1版本的發布博客上&…

python從入門到精通(二十六):python文件操作之Word全攻略(基于python-docx)

python文件操作之word技巧大全 word技巧基礎到高級操作大全A.準備工作1. 安裝python-docx庫2. 導入庫 B.基礎操作1. 創建Word文檔1.1 創建文檔對象1.2 添加word標題1.3 添加word段落1.4 設置段落樣式1.5 創建有序列表1.6 創建無序列表1.7添加word分頁1.8 添加word圖片1.9 添加w…

Debian二次開發一體化工作站:提升科研效率的智能工具

在科研領域&#xff0c;數據處理是實驗成功的關鍵環節之一。隨著實驗數據的復雜性和規模不斷增加&#xff0c;傳統的數據處理方法已經難以滿足科研人員的需求。這時&#xff0c;一體化工作站應運而生&#xff0c;成為科研實驗數據處理的 “智能大腦”。 一體化工作站&#xff…

linux學習(五)(服務器審查,正常運行時間負載,身份驗證日志,正在運行的服務,評估可用內存)

服務器審查 在 Linux 中審查服務器的過程包括評估服務器的性能、安全性和配置&#xff0c;以確定需要改進的領域或任何潛在問題。審查的范圍可以包括檢查安全增強功能、檢查日志文件、審查用戶帳戶、分析服務器的網絡配置以及檢查其軟件版本。 Linux 以其穩定性和安全性而聞名…

Redis- 大key

大key 什么是大key問題大key的危害大key的識別方法大key問題的解決方案數據結構優化與拆分壓縮與序列化優化預防與監控機制 什么是大key問題 大Key問題是指在Redis等內存數據庫中&#xff0c;某個Key對應的value數據結構過大&#xff0c;通常是指單個Key的大小超過10KB甚至達到…

C語言_數據結構總結6:鏈式棧

純c語言代碼&#xff0c;不涉及C 順序棧的實現&#xff0c;歡迎查看這篇文章&#xff1a;C語言_數據結構總結5&#xff1a;順序棧-CSDN博客 0. 結構單元 #include<stdio.h> #include<stdlib.h> typedef int ElemType; typedef struct Linknode { ElemType…

新品速遞 | 多通道可編程衰減器+矩陣系統,如何破解復雜通信測試難題?

在無線通信技術快速迭代的今天&#xff0c;多通道可編程數字射頻衰減器和衰減矩陣已成為測試領域不可或缺的核心工具。它們憑借高精度、靈活配置和強大的多通道協同能力&#xff0c;為5G、物聯網、衛星通信等前沿技術的研發與驗證提供了關鍵支持。從基站性能測試到終端設備校準…

AI自動化應用的影響

生產力的迭代也終將伴隨著一代人的落幕。 2025年是AI應用爆發的開局之年&#xff0c;預計3-5年現有生產關系將出現顛覆性改革。 AI自動化對經濟和就業的影響是一個復雜且多維的問題&#xff0c;其長期影響取決于技術進步、政策調控、社會適應能力等多重因素的綜合作用。以下從技…

潤開鴻重磅首發基于“RISC-V+OpenHarmony+星閃”的“鴻銳”AI開發平臺

潤開鴻重磅首發基于“RISC-VOpenHarmony星閃”的“鴻銳”AI開發平臺 2月28日&#xff0c;2025中國RISC-V生態大會在北京中關村國際創新中心隆重召開。作為領先的鴻蒙生態專業技術公司和終端操作系統發行版提供商&#xff0c;以及不斷推進基于RISC-V與OpenHarmony全棧開源生態構…

Java 深度復制對象:從基礎到實戰

目錄 一、深度復制的概念二、實現深度復制的方法1. 使用序列化2. 手動實現深度復制 三、總結 在 Java 編程中&#xff0c;對象的復制是一個常見的需求。然而&#xff0c;簡單的復制操作&#xff08;如直接賦值&#xff09;只會復制對象的引用&#xff0c;而不是創建一個新的對象…

C++ Primer 交換操作

歡迎閱讀我的 【CPrimer】專欄 專欄簡介&#xff1a;本專欄主要面向C初學者&#xff0c;解釋C的一些基本概念和基礎語言特性&#xff0c;涉及C標準庫的用法&#xff0c;面向對象特性&#xff0c;泛型特性高級用法。通過使用標準庫中定義的抽象設施&#xff0c;使你更加適應高級…