1.HTTP協議
雖然我們說,應?層協議是我們程序猿??定的.但實際上,已經有?佬們定義了?些現成的,??常好?的應?層協議,供我們直接參考使?.HTTP(超?本傳輸協議)就是其中之?。
在互聯?世界中,HTTP(HyperText Transfer Protocol,超?本傳輸協議) 是?個?關重要的協議。
它定義了客?端(如瀏覽器)與服務器之間如何通信,以交換或傳輸超?本(如HTML?檔)。HTTP協議是客?端與服務器之間通信的基礎。客?端通過HTTP協議向服務器發送請求,服務器收到請求后處理并返回響應。HTTP協議是?個?連接、?狀態的協議,即每次請求都需要建?新的連接,且服務器不會保存客?端的狀態信息。
3.HTTP回應—Response
4.HTTP request----------------客戶端如何打開想要訪問的資源
- 左邊就是http協議規定的傳輸的數據類型,右邊則是各個主機中存儲的數據
- 這里只用請求做說明-------說白了就是,
左邊到右邊就是反序列化,右邊到左邊就是序列化!!!!!!
!!!!
5.HTTP狀態碼
- 從客戶端讀取之后,需要設置狀態碼!!!!!!!
- 404就是典型的客戶端錯誤碼,指客戶訪問了服務器端沒有存儲的網頁,會顯示這個錯誤
以淘寶·網頁舉例,淘寶的服務端沒有存儲a.html 所以會顯示無法訪問!!!!!
6.HTTP常??法
1.GET方法
?途:?于請求URL指定的資源。
?例: GET /index.html HTTP/1.1
特性:指定資源經服務器端解析后返回響應內容。
GET方法詳解
2.POST?法
?途:?于傳輸實體的主體,通常?于提交表單數據。
?例: POST /submit.cgi HTTP/1.1
特性:可以發送?量的數據給服務器,并且數據包含在請求體中。
使用方法
二者對比------以login.html為例
那如果把post改為get呢?
- 使用get的話,服務端就能拿到登錄的數據了,聯系數據庫,就能做客戶注冊了!!!!!!!!!!!
- 使用?做分割符!!!!!!!!
7.代碼全覽
先看一下格式:
Http.hpp:
// 防止頭文件被重復包含
#pragma once// 包含必要的頭文件
#include "Socket.hpp" // Socket相關功能
#include "TcpServer.hpp" // TCP服務器實現
#include "Util.hpp" // 工具函數
#include "Log.hpp" // 日志模塊
#include <iostream> // 標準輸入輸出
#include <string> // 字符串處理
#include <memory> // 智能指針
#include <sstream> // 字符串流
#include <functional> // 函數對象
#include <vector> // 動態數組
#include <unordered_map> // 哈希表// 使用命名空間
using namespace SocketModule; // Socket模塊命名空間
using namespace LogModule; // 日志模塊命名空間// 定義常量字符串
const std::string gspace = " "; // 空格
const std::string glinespace = "\r\n"; // HTTP換行符
const std::string glinesep = ": "; // 頭部字段分隔符// 定義Web根目錄和默認頁面
const std::string webroot = "./wwwroot"; // 網站根目錄
const std::string homepage = "index.html"; // 默認首頁
const std::string page_404 = "/404.html"; // 404頁面路徑// HTTP請求類
class HttpRequest
{
public:// 構造函數,初始化交互標志為falseHttpRequest() : _is_interact(false){}// 序列化方法(暫未實現)std::string Serialize(){return std::string();}// 解析請求行(如 GET / HTTP/1.1)void ParseReqLine(std::string &reqline){// 使用字符串流分割請求行std::stringstream ss(reqline);ss >> _method >> _uri >> _version; // 分別提取方法、URI和版本--------以空格為分隔符--------這里method是GET,uri是/--但會被自動翻譯為/下的第一個.html文件,verson則是HTTP/1.1}// 反序列化HTTP請求bool Deserialize(std::string &reqstr){// 1. 提取請求行std::string reqline;bool res = Util::ReadOneLine(reqstr, &reqline, glinespace);//把第一行讀入reqline,glinespace是\r\n-----作為一句的尾部LOG(LogLevel::DEBUG) << reqline; // 記錄請求行日志// 2. 解析請求行ParseReqLine(reqline);// 處理URIif (_uri == "/")_uri = webroot + _uri + homepage; // 默認首頁路徑else_uri = webroot + _uri; // 其他資源路徑// 記錄解析結果日志LOG(LogLevel::DEBUG) << "_method: " << _method;LOG(LogLevel::DEBUG) << "_uri: " << _uri;LOG(LogLevel::DEBUG) << "_version: " << _version;// 檢查URI中是否包含參數const std::string temp = "?";auto pos = _uri.find(temp);if (pos == std::string::npos){return true; // 無參數直接返回//----------訪問.html,png等靜態內容時就會直接返回!!!!!!!!!}// 分離參數和URI_args = _uri.substr(pos + temp.size()); // 提取參數部分_uri = _uri.substr(0, pos); // 提取純URI部分_is_interact = true; // 標記為交互請求return true;}// 獲取URIstd::string Uri(){return _uri;}// 檢查是否為交互請求bool isInteract(){ return _is_interact;}// 獲取參數std::string Args(){return _args;}// 析構函數~HttpRequest(){}private:std::string _method; // HTTP方法(GET/POST等)std::string _uri; // 請求資源路徑std::string _version; // HTTP版本std::unordered_map<std::string, std::string> _headers; // 請求頭std::string _blankline; // 空行std::string _text; // 請求體std::string _args; // 請求參數bool _is_interact; // 是否為交互請求標志
};// HTTP響應類
class HttpResponse
{
public:// 構造函數,初始化空行和HTTP版本HttpResponse() : _blankline(glinespace), _version("HTTP/1.0"){}// 序列化HTTP響應std::string Serialize(){// 構建狀態行std::string status_line = _version + gspace + std::to_string(_code) + gspace + _desc + glinespace;// 構建響應頭std::string resp_header;for (auto &header : _headers){std::string line = header.first + glinesep + header.second + glinespace;resp_header += line;}// 組合狀態行、響應頭、空行和響應體return status_line + resp_header + _blankline + _text;}// 設置目標文件void SetTargetFile(const std::string &target){_targetfile = target;}// 設置狀態碼和描述void SetCode(int code){_code = code;switch (_code){case 200:_desc = "OK";break;case 404:_desc = "Not Found";break;case 301:_desc = "Moved Permanently";break;case 302:_desc = "See Other";break;default:break;}}// 添加響應頭void SetHeader(const std::string &key, const std::string &value){auto iter = _headers.find(key);if (iter != _headers.end())return;_headers.insert(std::make_pair(key, value));}// 根據文件后綴確定Content-Type!!!!!!!!//如果要訪問的網頁中還有其他資源如圖片,音頻。。。。。。就需要設置content-type-------可查找mine表!!!!std::string Uri2Suffix(const std::string &targetfile){// 查找最后一個點號auto pos = targetfile.rfind(".");if (pos == std::string::npos){return "text/html"; // 默認返回HTML類型}std::string suffix = targetfile.substr(pos);if (suffix == ".html" || suffix == ".htm")return "text/html";else if (suffix == ".jpg")return "image/jpeg";else if (suffix == "png")return "image/png";elsereturn "";}// 構建HTTP響應bool MakeResponse(){// 忽略favicon.ico請求if (_targetfile == "./wwwroot/favicon.ico"){LOG(LogLevel::DEBUG) << "用戶請求: " << _targetfile << "忽略它";return false;}// 處理重定向測試if (_targetfile == "./wwwroot/redir_test"){SetCode(301);SetHeader("Location", "https://www.qq.com/");return true;}// 讀取文件內容int filesize = 0;bool res = Util::ReadFileContent(_targetfile, &_text);if (!res) // 文件不存在{_text = "";LOG(LogLevel::WARNING) << "client want get : " << _targetfile << " but not found";SetCode(404); // 設置404狀態碼------客戶端訪問了不存在的網頁!!!!!!!_targetfile = webroot + page_404; // filetarget指向404頁面!!!!!!!!filesize = Util::FileSize(_targetfile);Util::ReadFileContent(_targetfile, &_text); // 讀取404頁面內容std::string suffix = Uri2Suffix(_targetfile);SetHeader("Content-Type", suffix); // 設置Content-Type---------------注意這個一定要有,不然沒辦法鏈接到網頁SetHeader("Content-Length", std::to_string(filesize)); // 設置內容長度}else // 文件存在{LOG(LogLevel::DEBUG) << "讀取文件: " << _targetfile;SetCode(200); // 設置200狀態碼filesize = Util::FileSize(_targetfile);std::string suffix = Uri2Suffix(_targetfile);SetHeader("Conent-Type", suffix);// 設置Content-Type---------------注意這個內容類型一定要有,不然沒辦法鏈接到網頁SetHeader("Content-Length", std::to_string(filesize));SetHeader("Set-Cookie", "username=zhangsan;"); // 設置Cookie}return true;}// 設置響應體文本void SetText(const std::string &t){_text = t;}// 反序列化方法(暫未實現)bool Deserialize(std::string &reqstr){return true;}// 析構函數~HttpResponse() {}// 公有成員變量
public:std::string _version; // HTTP版本int _code; // 狀態碼std::string _desc; // 狀態描述std::unordered_map<std::string, std::string> _headers; // 響應頭std::vector<std::string> cookie; // Cookie集合std::string _blankline; // 空行std::string _text; // 響應體std::string _targetfile; // 目標文件路徑
};// 定義HTTP處理函數類型
using http_func_t = std::function<void(HttpRequest &req, HttpResponse &resp)>;// HTTP服務器類
class Http
{
public:// 構造函數,初始化TCP服務器Http(uint16_t port) : tsvrp(std::make_unique<TcpServer>(port))//《2》進一步使用port初始化TcpServer類并返回其指針---------->tcpseerver.hpp{}// 處理HTTP請求void HandlerHttpRquest(std::shared_ptr<Socket> &sock, InetAddr &client)//-----------<13>這個才是業務函數!{// 接收HTTP請求std::string httpreqstr;int n = sock->Recv(&httpreqstr); // 接收請求數據--------<14>把客戶端發來的內容(需要反序列化)存入httpreqstrif (n > 0) // 接收成功{std::cout << "##########################" << std::endl;std::cout << httpreqstr; // 打印原始請求std::cout << "##########################" << std::endl;// 解析請求HttpRequest req;HttpResponse resp;req.Deserialize(httpreqstr);// 處理交互請求if (req.isInteract()){// 檢查路由是否存在------if (_route.find(req.Uri()) == _route.end())//查看uri是否存在于<6>中建立的_route{// 可添加重定向邏輯}else//如果存在------本文中,如果你在網頁中點擊login界面時會走這一條路!!!!!!!{// 調用注冊的處理函數_route[req.Uri()](req, resp);//-----------------------------<15>調用<6>傳遞的鍵值對的函數--即業務函數----見main.cc的Login(HttpRequest &req, HttpResponse &resp)函數std::string response_str = resp.Serialize();//序列化,準備返回給服務器sock->Send(response_str); // 發送響應-----------------------<16>最后一步,返回給服務器!!!!!!}}else // 處理靜態資源請求-----例如.html/.png文件{resp.SetTargetFile(req.Uri());if (resp.MakeResponse()) // 構建響應成功{std::string response_str = resp.Serialize();sock->Send(response_str); // 發送響應}}}// 調試模式下的處理
#ifdef DEBUGstd::string httpreqstr;sock->Recv(&httpreqstr);std::cout << httpreqstr;// 構建簡單響應HttpResponse resp;resp._version = "HTTP/1.1";resp._code = 200;resp._desc = "OK";std::string filename = webroot + homepage;bool res = Util::ReadFileContent(filename, &(resp._text));(void)res;std::string response_str = resp.Serialize();sock->Send(response_str);
#endif}// 啟動HTTP服務器void Start()//---------------------<8>調用TcpServer的start,并傳遞參數是提供服務時用的fd建立的sock類,和客戶傳遞過來的主機地址--------->tcpserver.hpp{tsvrp->Start([this](std::shared_ptr<Socket> &sock, InetAddr &client){ this->HandlerHttpRquest(sock, client); });//業務函數在這呢!!!!!!}// 注冊服務路由void RegisterService(const std::string name, http_func_t h)//<7>注冊服務路由{std::string key = webroot + name; // 構建完整路徑auto iter = _route.find(key);if (iter == _route.end()) // 防止重復注冊{_route.insert(std::make_pair(key, h));}}// 析構函數~Http(){}private:std::unique_ptr<TcpServer> tsvrp; // TCP服務器實例std::unordered_map<std::string, http_func_t> _route; // 路由表
};
Main.cc:
#include"Http.hpp"void Login(HttpRequest &req, HttpResponse &resp)
{// req.Args();LOG(LogLevel::DEBUG) << req.Args() << ", 我們成功進入到了處理數據的邏輯";std::string text = "hello: " + req.Args(); // username=zhangsan&passwd=123456// 登錄認證resp.SetCode(200);resp.SetHeader("Content-Type","text/plain");resp.SetHeader("Content-Length", std::to_string(text.size()));resp.SetText(text);
}
// void Register(HttpRequest &req, HttpResponse &resp)
// {
// LOG(LogLevel::DEBUG) << req.Args() << ", 我們成功進入到了處理數據的邏輯";
// std::string text = "hello: " + req.Args();// resp.SetCode(200);
// resp.SetHeader("Content-Type","text/plain");
// resp.SetHeader("Content-Length", std::to_string(text.size()));
// resp.SetText(text);
// }
// void VipCheck(HttpRequest &req, HttpResponse &resp)
// {
// LOG(LogLevel::DEBUG) << req.Args() << ", 我們成功進入到了處理數據的邏輯";
// std::string text = "hello: " + req.Args();
// resp.SetCode(200);
// resp.SetHeader("Content-Type","text/plain");
// resp.SetHeader("Content-Length", std::to_string(text.size()));
// resp.SetText(text);
// }// void Search(HttpRequest &req, HttpResponse &resp)
// {// }// http port
int main(int argc, char *argv[])
{if(argc != 2){std::cout << "Usage: " << argv[0] << " port" << std::endl;exit(USAGE_ERR);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<Http> httpsvr = std::make_unique<Http>(port);//《1》初始化一個HTTP類,并返回其指針---------》http.hpphttpsvr->RegisterService("/login", Login); // <6> 注冊服務路由-------->http.hpp,再回到main.cc// httpsvr->RegisterService("/register", Register);// httpsvr->RegisterService("/vip_check", VipCheck);// httpsvr->RegisterService("/s", Search);// httpsvr->RegisterService("/", Login);httpsvr->Start();//<7>開始接收服務--------->http.hppreturn 0;
}
Util.hpp:
// 防止頭文件重復包含的編譯指令
#pragma once// 包含標準輸入輸出流庫
#include <iostream>
// 包含文件流操作庫
#include <fstream>
// 包含字符串處理庫
#include <string>// 工具類聲明
class Util
{
public:// 靜態方法:讀取文件內容到字符串中,傳遞一個文件路徑,和一個string,把文件的內容讀取到string中// 參數:filename - 文件名,out - 輸出字符串指針// 返回值:成功返回true,失敗返回falsestatic bool ReadFileContent(const std::string &filename /*std::vector<char>*/, std::string *out){// 獲取文件大小int filesize = FileSize(filename); // FileSize函數用于獲取指定文件的大小(以字節為單位)。如果沒打開就返回-1!!!!// 檢查文件大小是否有效if (filesize > 0){// 創建輸入文件流對象std::ifstream in(filename); // ifstream#include <fstream>// 方式1:先聲明后打開// std::ifstream in; // 創建未關聯文件的流對象// in.open("example.txt"); // 打開文件// 方式2:聲明時直接打開(推薦)// std::ifstream in("example.txt"); // 創建并立即打開文件,不用顯示調用open函數打開文件// 檢查文件是否成功打開if (!in.is_open())return false;// 調整輸出字符串大小以容納文件內容out->resize(filesize);// 將文件內容讀取到字符串中// 注意:這里使用了c_str()獲取字符串底層指針,并進行強制類型轉換in.read((char *)(out->c_str()), filesize);// istream& read(char* s, streamsize n);// 在 C++ 中,out->c_str() 返回的是 const char* 類型指針,而 std::ifstream::read() 需要的是 char* 類型指針,因此需要進行強制類型轉換。// 關閉文件流in.close(); // 記得要關閉!!!}else{// 文件大小為0或獲取失敗時返回falsereturn false;}// 讀取成功返回truereturn true;}// 靜態方法:從大字符串中讀取一行// 參數:bigstr - 輸入大字符串,out - 輸出行字符串指針,sep - 行分隔符// 返回值:成功返回true,失敗返回falsestatic bool ReadOneLine(std::string &bigstr, std::string *out, const std::string &sep /*\r\n*/){// 查找分隔符位置auto pos = bigstr.find(sep);// 如果沒有找到分隔符則返回falseif (pos == std::string::npos) // std::string::npos 是 C++ 標準庫中 std::string 類的一個靜態常量成員,表示"未找到"或"無效位置"的特殊值。它是字符串操作中非常重要的一個標記值。return false;// 提取分隔符前的內容作為一行*out = bigstr.substr(0, pos); // 起始永遠是0// 從原字符串中刪除已讀取的行和分隔符bigstr.erase(0, pos + sep.size()); // 起始必須是0// 讀取成功返回truereturn true;}// 靜態方法:獲取文件大小// 參數:filename - 文件名// 返回值:成功返回文件大小(字節),失敗返回-1static int FileSize(const std::string &filename){// 以二進制模式打開文件std::ifstream in(filename, std::ios::binary); // std::ios::binary 是 C++ 中文件打開模式的一個標志,它的作用是告訴文件流以二進制模式而非文本模式打開文件// 文本模式(默認):// 在某些系統(如 Windows)上,會進行換行符轉換:// 讀取時,\r\n(Windows 換行)會被轉換為 \n(C++ 標準換行)。// 寫入時,\n 會被轉換為 \r\n。// 可能在某些平臺上處理特殊字符(如 EOF)時會有額外行為。// 二進制模式:// 完全按原樣讀寫數據!!!!!,不做任何轉換。// 適合處理非文本文件(如圖片!!!!!!!、音頻、視頻、壓縮包等)。!!!!!!!!!!!!!!!!!!!!!!!// 也適合需要精確控制文件內容的場景(如跨平臺數據交換)。// 檢查文件是否成功打開if (!in.is_open())return -1;// 將文件指針移動到文件末尾--------seekg移動函數!!!!in.seekg(0, in.end);//in.end:基準位置(seek direction),這里是文件末尾(std::ios::end)。// 獲取當前指針位置(即文件大小)int filesize = in.tellg();// 將文件指針移回文件開頭in.seekg(0, in.beg);// 關閉文件流in.close();// 返回文件大小return filesize;}
}; // 類定義結束
效果演示
- 在瀏覽器中輸入115.120.238.130:8081
- 別忘了要先去云服務器官網開啟安全組,這樣瀏覽器才能訪問服務端