協議是通信雙方必須遵守的規則,確保數據能夠正確傳輸和解析,它規定了數據格式、傳輸順序、錯誤處理等細節。應用層的協議一般都是我們自己進行定義的,但是有很多程序員前輩已經寫出來了很哇塞的協議,我們直接進行學習和使用即可
HTTP協議
認識URL
初識URL
URL(Uniform Resource Locator,統一資源定位符)就是我們口中的網址,是用來標識和定位互聯網上的資源的字符串,這里的資源包括文本、圖片視頻等等。
URL的結構
服務器地址其實就是IP地址,IP地址標定主機的唯一性,通過IP地址和特定端口號就可以和服務器進行建立鏈接,通過文件路徑就可以申請訪問服務器特定路徑下的資源信息
http就是通過http協議進行訪問服務器的資源,我們能夠在網絡上能夠進行看到的就是資源,既然是資源就需要進行存儲,服務器中的資源當然也不例外,服務器中的資源當然存在于服務器的磁盤上,所以要想進行訪問服務器中的資源就是要找到資源的特定路徑進行訪問。
urlencode和urldecode
enlencode
在URL中 ‘ / ’和 ‘ ?’是有特殊含義的分隔符,對于斜杠“/”,應該說明它在路徑中的分層作用,比如如何表示資源的位置結構,還可能提到根目錄和相對路徑的區別;關于問號“?”,需要明確它是查詢參數的起始符,后面跟著鍵值對,多個參數用“&”連接。當我們需要進行使用這些特殊符號進行當作我們的內容怎么辦?豈不是會將這個內容當作分隔符進行處理然后造成內容出現差錯??當然不會,URL會進行將特殊字符進行轉義。
轉義的規則如下: 將需要轉碼的字符轉為16進制,然后從右到左,取4位(不足4位直接處理),每2位做一位,前面加上%,編碼成%XY
URL 中某些字符(如空格、中文)需轉義為?%
?后跟十六進制值(稱為?百分號編碼)。
示例:空格 →?%20
,中文字符“你” →?%E4%BD%A0
。
目的是確保 URL 在傳輸中無歧義,兼容所有系統和協議。
urldecode
http請求和響應的宏觀格式
http以行為單位的協議
http 請求的宏觀格式
- 請求行中的數據:請求的方法? 請求的URL? 請求的版本(1.0? 1.1? 2.0)
- 請求報頭:存放各種屬性(多行)大多數是key -- val的格式
- http請求的空行
- 請求正文,請求正文是可以為空的
上述結構整體稱為http的報頭
http響應的宏觀格式
- 狀態行:響應的版本? 狀態碼(請求的正確性) 狀態碼描述(404->資源不存在)
- 響應報頭:存放服務器的各種屬性
- 空行:
- 響應正文:一般是服務器中的資源
基于宏觀格式的細節問題?
- http請求和響應是如何進行通信的?
????????http請求是通過tcp進行建立鏈接,然后向服務器進行發送請求;http響應也是通過tcp進行鏈接socket,向客戶端進行返回響應。
- 請求和響應是如何保證應用層讀取完畢了呢??
? ? ? ? 寫個讀一行的函數
? ? ? ? 通過while循環,進行讀取請求行+請求報頭,知道讀取到空行為止
? ? ? ? 分析讀取報頭中的屬性信息,其中有一條屬性信息是正文內容的長度,
????????根據解析出來的正文內容,進行讀取正文中的屬性信息
- 請求和響應是如何做到序列化和反序列化的??
? ? ? ? 這并不需要我們自己進行實現,這是http自己進行實現的,請求行/狀態行+請求報頭/響應報頭只需要進行按照\r\n進行1->即可!
進行編碼實現驗證
HTTP服務端代碼的封裝
//HTTP_server.hpp#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <functional>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include "protocol.hpp"
#include "util.hpp"static int gbacklog = 5;
int sockid = -1;
#define SIZE 1024
typedef std::function<bool(const HttpRequest &req, HttpResponse &resp)> func_t;class HttpServer
{
public:HttpServer(func_t func,uint16_t port): _listen_sockid(-1), _port(port),_func(func){}void InitServer(){// 進行創建套接字_listen_sockid = socket(AF_INET, SOCK_STREAM, 0);if (_listen_sockid < 0){exit(1);}std::cout << "listen_sockid:" << _listen_sockid << std::endl;// 進行bind綁定struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_addr.s_addr = INADDR_ANY;local.sin_port = htons(_port);socklen_t len = sizeof(local);int n = bind(_listen_sockid, (struct sockaddr *)&local, len);if (bind < 0){exit(2);}// 設置socket為監聽狀態 //哪些客戶端申請進行訪問“我”(服務器)int m = listen(_listen_sockid, gbacklog);if (m < 0){exit(3);};}void StartServer(){for (;;){// 進行獲取鏈接struct sockaddr_in client;memset(&client, 0, sizeof(client));socklen_t len = sizeof(client);sockid = accept(_listen_sockid, (struct sockaddr *)&client, &len);if (sockid < 0){exit(4);}// 進行通信// 通過創建多進程的方式進行通信pid_t id = fork();if (id == 0){// 子進程close(_listen_sockid);if (fork() > 0){exit(0);}// http處理方法httpHandler(sockid);close(sockid);// exit(0);}close(sockid);waitpid(id, nullptr, 0);}}void httpHandler(int sockid){// 進行獲取完整的數據// 進行反序列化// 將反序列化的數據進行處理// 將處理的數據進行序列化// 發送完整的數據// 定義一個緩沖區將數據進行存儲char buffer[4096];int n = recv(sockid, buffer, sizeof(buffer) - 1, 0);if (n <= 0){if (n == 0){std::cerr << "對方關閉寫通道" << std::endl;}else{std::cerr << "讀取數據失敗" << std::endl;}}else{buffer[n]=0;HttpRequest req;HttpResponse resp;req.inbuffer = buffer;req.prase();_func(req, resp);send(sockid,resp.outbuffer.c_str(),resp.outbuffer.size(),0);}}~HttpServer(){if (sockid > 0){close(sockid);}}private:uint16_t _port;int _listen_sockid;func_t _func;};
HTTP服務端的調用
//HTTP_server.cpp#include "HTTP_server.hpp"
#include <memory>void Usage(std::string proc)
{std::cerr << "Usage:\n\t" << proc << " port\r\n\r\n";
}//通過suffix和Content-Type進行補充描述信息
std::string suffixToDesc(const std::string suffix)
{std::string ret="Content-Type: ";if(suffix==".html"){ret+="text/html";}else if(suffix==".jpg"){ret += "application/x-jpg";}ret+="\r\n";return ret;
}bool Get(const HttpRequest &req, HttpResponse &resp)
{// for teststd::cout << "----------------------http request start---------------------------" << std::endl;std::cout << req.inbuffer << std::endl;std::cout << "method: " << req.method << std::endl;std::cout << "url: " << req.url << std::endl;std::cout << "httpversion: " << req.httpversion << std::endl;std::cout << "path: " << req.path << std::endl;std::cout << "suffix: " << req.suffix << std::endl;std::cout << "size: " << req.size << std::endl;std::cout << "----------------------http request end---------------------------" << std::endl;std::string respline = "HTTP/1.1 200 OK\r\n";// 這里也不想進行硬編碼// std::string respheader = "Content-Type: text/html\r\n";std::string respheader = suffixToDesc(req.suffix);std::string respblank = "\r\n";// 不要進行硬編碼// std::string body = "<html lang=\"en\"><head><meta charset=\"UTF-8\"><title>for test</title><h1>hello world</h1></head><body><p>北京交通廣播《一路暢通》“交通大家談”節目,特邀北京市交通委員會地面公交運營管理處處長趙震、北京市公安局公安交通管理局秩序處副處長 林志勇、北京交通發展研究院交通規劃所所長 劉雪杰為您解答公交車專用道6月1日起社會車輛進出公交車道須注意哪些?</p></body></html>";// 從文件中進行獲得std::string body;body.resize(req.size+1);if (Util::readFile(req.path, (char*)body.c_str(),body.size()) == 0){Util::readFile(error_html, (char*)body.c_str(),body.size());}// 進行填充responseresp.outbuffer += respline;resp.outbuffer += respheader;resp.outbuffer += respblank;std::cout << "----------------------http response start---------------------------" << std::endl;std::cout << resp.outbuffer << std::endl;std::cout << "----------------------http response end---------------------------" << std::endl;resp.outbuffer += body;return true;
}int main(int args, char *argv[])
{if (args != 2){Usage(argv[0]);exit(0);}uint16_t port = atoi(argv[1]);std::unique_ptr<HttpServer> hs(new HttpServer(Get, port));hs->InitServer();hs->StartServer();return 0;
}
HTTP協議有關的封裝
//prococal.hpp#pragma once
#include <iostream>
#include <sstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>const std::string sep = "\r\n";
const std::string default_root = "./wwwroot";
const std::string home_page = "index.html";
const std::string error_html = "./wwwroot/404.html";
#include "util.hpp"
class HttpRequest
{
public:HttpRequest(){}~HttpRequest(){}void prase(){// 1. 從inbuffer中拿到第一行,分隔符\r\nstd::string line = Util::getOneLine(inbuffer, sep);if (line.empty())return;// 2. 從請求行中提取三個字段// std::cout << "line: " << line << std::endl;std::stringstream ss(line);ss >> method >> url >> httpversion;// 3. 添加web默認路徑path = default_root; // ./wwwroot,path += url; //./wwwroot/a/b/c.html, ./wwwroot/if (path[path.size() - 1] == '/')path += home_page;// 4.進行后綴的查詢auto pos = path.rfind(".");if (pos == std::string::npos){suffix = ".html";}else{suffix = path.substr(pos);}// 5.進行獲取資源大小struct stat st;int n = stat(path.c_str(), &st);if (n == 0)size = st.st_size;elsesize = -1;}public:std::string inbuffer;// std::string reqline;// std::vector<std::string> reqheader;// std::string body;std::string method;std::string url;std::string httpversion;std::string path;std::string suffix;int size;
};class HttpResponse
{
public:std::string outbuffer;
};
其他有關函數的封裝
//util.hpp#pragma once #include<iostream>
#include<string>
#include<fstream>class Util
{
public:static std::string getOneLine(std::string& buffer,const std::string& sep){auto pos=buffer.find(sep);if(pos==std::string::npos){return "";}std::string ret=buffer.substr(0,pos);return ret;}static bool readFile(const std::string path,char* buffer,int size){//以二進制的形式進行打開讀取pathstd::ifstream in(path);if(!in.is_open()){return false;}//error:// std::string line;// while(std::getline(in,line))// {// body+=line;// }in.read(buffer,size);in.close();return true;}
};
通過上面的代碼我們可以清晰的得知,網頁資源其實是通過多個資源進行整合而成的,我們要進行訪問一整個網頁資源,需要進行發送若干次的網絡請求。
結合代碼加強對宏觀結構的影響
HTTP方法
http請求不但可以進行獲取資源還是可以進行資源的交互的。我們通過http進行向服務端進行數據的上傳時,本質事通過form表單進行提交的,瀏覽器會自動將form表單中的內容轉化成http方法進行請求,http常用的兩種方法分別是POST/GET方法
POST/GET兩種方法的區別
- GET方法是將用戶通過HTTP進行提交的數據直接進行拼接到URL上,也就是說GET方法是通過URL進行傳遞參數
- POST方法是將用戶通過HTTP進行提交的數據進行放到正文部分,通過正文進行參數的提交
說明:
GET方法進行提交參數由于是通過URL進行提交的,所以說我們通過URL就可以看到我們進行通過HTTP提交的參數,POST由于是通過正文進行提交參數的,所以一般用戶是看不到的,私密性正好,但是不敢說通過POST就比通過GET方法安全性更高,我們通過抓包工具都是可以進行看到參數的明文的,要想進行提高安全性,必須進行加密,https就是經過加密之后的。
HTTP狀態碼
最常見的狀態碼, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)
3XX是重定向狀態碼,重定向在網絡中的是如何進行理解的呢?
上述這種先進行訪問服務器,然后服務器通過返回一個3XX的錯誤碼,并且附帶一個新的URL,客戶端通過新的URL進行訪問的方式稱為重定向
重定向分為臨時重定向和永久重定向
臨時重定向:當我們進行訪問某個網站的時候,首先自動為我們進行跳轉到廣告商金主爸爸那里,之所以稱為臨時重定向是因為這種跳轉URL并不是唯一的,當金主爸爸換人之后進行跳轉的鏈接也就不一樣了
永久重定向:與臨時重定向不同的是,永久重定向進行跳轉的目標每次都是唯一的,例如一個網站慢慢壯大之后由于服務器配置不夠用了,需要進行將一些用戶進行引流到新的網站,當老用戶進行登陸后,通過URL 進行識別,然后跳轉新的服務器進行使用,這種方式就是永久重定向。
長鏈接的概念
通過http進行請求網絡資源,由于http網頁中的資源是由若干部分進行組成的,按道理也就是說客戶端需要多次通過http請求才能完整的獲取網頁中的資源,但是http協議是應用層的協議是基于傳輸層的TCP的,TCP是面向鏈接的,會出現頻繁創建鏈接的問題。
所以說client如果和server都支持長鏈接,直接通過"一條長鏈接進行讀取大份資源。"進行獲取大份資源的理解其實本質上是通過一個TCP連接進行傳輸多個HTTP請求和響應。
Connection: keep-alive????????支持長鏈接
Connection: close? ? ? ? ? ? ? ? 不支持長連接
會話保持
會話保持的認識
當我們通過瀏覽器進行通過URL進行訪問指定服務器的資源時,首次進行訪問時需要進行信息的認證才可以進行訪問服務器中的資源,當我們短時間內再次進行訪問網站,繼續需要用戶認證,這對于用戶來說體驗感是非常差的;當服務器的資源并不是完全部署在一臺服務器上的,而是網站文件放在一臺服務器,資源(如圖片、視頻)存放在另一臺服務器上,當我們進行訪問資源時,每進行訪問一個資源就首先開始進行用戶認證登錄,這是非常非常難受的。
由于HTTP本身是無狀態的,為了解決上述用戶體驗問題,讓用戶進行登錄后就可以按照自己的身份(因為有些資源是有權限的,例如VIP視頻)進行隨意訪問,這種方式就叫做會話保持(保證用戶長時間在線)。
會話保持的實現
老方法
cookie文件中存儲的信息一般包括用戶標識信息(用戶ID:唯一標識用戶的編號或字符串。用戶名:用戶的登錄名或顯示名。會話ID:用于識別用戶當前會話的唯一標識符。認證信息(登錄狀態:標記用戶是否已登錄,令牌:用于身份驗證的令牌)。偏好設置(用戶選擇的語言、用戶選擇的界面主題等等)。瀏覽行為信息(記錄用戶瀏覽歷史)
補充:cookie是分為cookie文件級別和cookie內存級別,如何進行區分cookie是文件級別還是內存級別,通過以瀏覽器進行為例,當我們通過進行將瀏覽器進行訪問特定服務器,當我們將瀏覽器這個進程進行殺掉后,在進行重新打開瀏覽器進行訪問特定的服務器如果不需要重新進行認證登錄則cookie就是內存級的,怎么理解呢??如果這個cookie是內存級別的,則這個cookie是存在在進程中的,當進程被殺掉后,則這個cookie就消失了,所以說需要進行重新認證登錄。而文件級別的Cookie則被寫入到用戶電腦的某個文件夾中,比如在Windows系統里可能是特定的目錄下。cookie技術分為cookie文件級別(關閉瀏覽器)和內存級別? ----cookie是內存級別還是文件級別可以在瀏覽器中進行配置。
老方法可能存在的問題
當一些黑客通過某些方式進行將木馬散播出去(例如陌生郵件中的鏈接等等),當誤點進入這個鏈接中時,木馬就會浸入我們的瀏覽器中進行盜取我們瀏覽器中cookie文件中的信息,例如訪問某些特定服務器中的身份信息(用戶ID,賬號密碼等等),黑客通過拿到這些cookie信息直接模擬用戶的身份驗證,直接進行訪問服務器。
新方法
新方法通過將用戶的信息及使用緩存痕跡不在瀏覽器中進行存儲了,直接進行放到服務端中session文件中,當用戶首次進行申請該服務器中的資源時,服務器接收到申請后進行返回唯一的session id作為身份驗證的信息,在會話保持中通過session id進行身份驗證進行訪問服務器中的資源。即使黑客進行拿到我們的cookie文件,cookie文件中只是驗證信息并沒有用戶的數據,很大程度上減少了用戶數據泄漏的風險。
新方法可能存在的問題
當黑客同樣將木馬進行散播進入我們的服務器中進行竊取我們的cookie文件,雖然減少了用戶數據泄漏的風險,但還是無法進行阻止別人通過cookie文件中的信息進行繞開服務器中的驗證進行訪問服務器,但是這種方案極易配合其他策略進行增加安全性,例如當服務器進行檢測到用戶的IP發生短時間的長距離跨越時,會進行向綁定的設備進行推送警告信息,甚至進行強制下線。
HTTPS協議
通過上面兩個工具的使用,我們可以看到通過HTTP進行數據的傳輸,無論GET方法還是POST方法,數據無異于進行裸奔,HTTPS就是通過加密的方式進行解決這種問題
簡單認識HTTPS的加密過程
HTTP通過SSL/TLS握手過程中進行實現將HTTP的明文數據進行加密形成的密文數據通過協議棧進行傳輸到另一臺主機,然后通過將數據進行解密進行拿到發送方進行網絡通信的內容
數據指紋
數據指紋又稱為數據摘要,其基本原理是利用單向散列函數(Hash函數)對信息進行運算,生成一串固定長度的字符串。數據指紋并不是一種加密機制,但是可以用來進行判斷數據有沒有被篡改。
常見的摘要算法:MD5,SHA1,SHA256,SHA512等,算法將無線映射成有限,因此也是有可能出現哈希碰撞,但是概率是非常非常的低的。
數據指紋是不屬于加密過程的,因為加密過程是可逆的能夠進行解密,但是數據指紋是通過映射性成的,不能進行可逆的解密過程。
應用:
進行判斷兩份數據是否是相同的,如果兩份數據通過相同的算法進行哈希映射,將映射后的數據進行比對,如果相同則則是同一份數據,反之則是不同的數據
百度網盤的秒傳功能就是通過這種方式進行實現的,當用戶A進行傳入某種資源后,當用戶B在進行上傳相同的資源時,通過查詢數據摘要是否存在,如果數據存在,百度網盤并不會再將相同的資源進行重新上傳一份,而是通過建立通過建立軟連接的形式進行讓用戶B進行訪問資源.
數據庫中的表單行列中的密碼行列也是通過某種算法經過Hash映射進行形成數據摘要,即使數據庫進行泄漏也較大程度保護用戶的隱私.
HTTPS的加密過程
在進行網絡通信的過程中,往往會出現數據安全問題,例如數據被監聽,數據被篡改,只有通過加密的方式解決這幾種問題,通過下面幾種方式進行理解對稱加密和非對稱加密,并且分析各種各種方案的優劣,找出最優加密方式
只采用對稱加密
客戶端在進行推送數據之前首先將自己密鑰進行送,但是在進行推動密鑰的過程中,由于密鑰是沒有進行機密的,容易被中間人進行拿到并拷貝,當服務端在進行通過密鑰進行機密數據,讓加密的數據在網絡中進行傳遞時,中間人也是有密鑰的,直接可以對數據進行監聽和修改.
只采用非對稱加密
采用非對稱加密雖然能夠繼續寧確保客戶端到服務端的數據安全,因為即使中間人進行竊取客戶端向服務端的數據,但是無法對加密的數據進行解密,服務端到客戶端的數據不安全由于服務端在進行推送自己的公鑰給客戶端時容易被中間人拿到并拷貝,導致服務端后續進行向客戶端進行發送數據的時候容易被篡改,導致后續進行數據傳輸的時候造成數據不安全問題。?
雙方使用非對稱加密
雙方都采用非堆成的加密方式,每一方都將自己的自己的公鑰進行推送給對方,之后在進行數據傳輸的過程中,都是用對方的公鑰進行加密,這樣只能通過對應的私鑰才能進行解密,即使中間人拿到雙發的公鑰也無法進行解密,確保數據的安全。
但是這種方式加解密過程依賴復雜的數學運算(如大數分解或橢圓曲線運算),相比對稱加密速度慢數百倍,且加密后的數據體積更大,不適合處理大量傳輸數據。而對稱加密算法(如 AES)結構簡單、加解密速度快、資源消耗低,適合高效的數據傳輸
采用非對稱加密+對稱加密
?這種方式先通過非對稱加密進行將客戶端的對稱密鑰進行安全的交給服務端,然后后續直接通過對稱加密即可,這種方式并沒有將加密的對稱密鑰進行暴露給中間人,又可以進行高效傳輸。這種方式兼顧了安全性與性能。