> 技术文档 > Linux网络-------3.应⽤层协议HTTP

Linux网络-------3.应⽤层协议HTTP


1.HTTP协议

虽然我们说,应⽤层协议是我们程序猿⾃⼰定的.但实际上,已经有⼤佬们定义了⼀些现成的,⼜⾮常好⽤的应⽤层协议,供我们直接参考使⽤.HTTP(超⽂本传输协议)就是其中之⼀。

在互联⽹世界中,HTTP(HyperText Transfer Protocol,超⽂本传输协议) 是⼀个⾄关重要的协议。

它定义了客⼾端(如浏览器)与服务器之间如何通信,以交换或传输超⽂本(如HTML⽂档)。HTTP协议是客⼾端与服务器之间通信的基础。客⼾端通过HTTP协议向服务器发送请求,服务器收到请求后处理并返回响应。HTTP协议是⼀个⽆连接、⽆状态的协议,即每次请求都需要建⽴新的连接,且服务器不会保存客⼾端的状态信息。

Linux网络-------3.应⽤层协议HTTP

3.HTTP回应—Response

Linux网络-------3.应⽤层协议HTTP

4.HTTP request----------------客户端如何打开想要访问的资源

Linux网络-------3.应⽤层协议HTTP
Linux网络-------3.应⽤层协议HTTP
Linux网络-------3.应⽤层协议HTTP

  • 左边就是http协议规定的传输的数据类型,右边则是各个主机中存储的数据
  • 这里只用请求做说明-------说白了就是,左边到右边就是反序列化,右边到左边就是序列化!!!!!!!!!!

5.HTTP状态码

Linux网络-------3.应⽤层协议HTTP

  • 从客户端读取之后,需要设置状态码!!!!!!!

Linux网络-------3.应⽤层协议HTTP

  • 404就是典型的客户端错误码,指客户访问了服务器端没有存储的网页,会显示这个错误

以淘宝·网页举例,淘宝的服务端没有存储a.html 所以会显示无法访问!!!!!
Linux网络-------3.应⽤层协议HTTP

6.HTTP常⻅⽅法

Linux网络-------3.应⽤层协议HTTP

1.GET方法

⽤途:⽤于请求URL指定的资源。
⽰例: GET /index.html HTTP/1.1
特性:指定资源经服务器端解析后返回响应内容。

GET方法详解

2.POST⽅法

⽤途:⽤于传输实体的主体,通常⽤于提交表单数据。
⽰例: POST /submit.cgi HTTP/1.1
特性:可以发送⼤量的数据给服务器,并且数据包含在请求体中。

使用方法

二者对比------以login.html为例

Linux网络-------3.应⽤层协议HTTP

那如果把post改为get呢?

Linux网络-------3.应⽤层协议HTTP

  • 使用get的话,服务端就能拿到登录的数据了,联系数据库,就能做客户注册了!!!!!!!!!!!
  • 使用?做分割符!!!!!!!!

Linux网络-------3.应⽤层协议HTTP

7.代码全览

先看一下格式:

Linux网络-------3.应⽤层协议HTTP
Http.hpp:

// 防止头文件被重复包含#pragma once// 包含必要的头文件#include \"Socket.hpp\" // Socket相关功能#include \"TcpServer.hpp\" // TCP服务器实现#include \"Util.hpp\" // 工具函数#include \"Log.hpp\" // 日志模块#include  // 标准输入输出#include   // 字符串处理#include   // 智能指针#include  // 字符串流#include  // 函数对象#include   // 动态数组#include  // 哈希表// 使用命名空间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: // 构造函数,初始化交互标志为false HttpRequest() : _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); // 处理URI if (_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; } // 获取URI std::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\"; else return \"\"; } // 构建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)//-----------这个才是业务函数! { // 接收HTTP请求 std::string httpreqstr; int n = sock->Recv(&httpreqstr); // 接收请求数据--------把客户端发来的内容(需要反序列化)存入httpreqstr if (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是否存在于中建立的_route {  // 可添加重定向逻辑 } else//如果存在------本文中,如果你在网页中点击login界面时会走这一条路!!!!!!! {  // 调用注册的处理函数  _route[req.Uri()](req, resp);//-----------------------------调用传递的键值对的函数--即业务函数----见main.cc的Login(HttpRequest &req, HttpResponse &resp)函数  std::string response_str = resp.Serialize();//序列化,准备返回给服务器  sock->Send(response_str); // 发送响应-----------------------最后一步,返回给服务器!!!!!! } } else // 处理静态资源请求-----例如.html/.png文件 { resp.SetTargetFile(req.Uri()); if (resp.MakeResponse()) // 构建响应成功 {  std::string response_str = resp.Serialize();  sock->Send(response_str); // 发送响应 } } }// 调试模式下的处理#ifdef DEBUG std::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()//---------------------调用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)//注册服务路由 { 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 portint 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.hpp httpsvr->RegisterService(\"/login\", Login); //  注册服务路由-------->http.hpp,再回到main.cc // httpsvr->RegisterService(\"/register\", Register); // httpsvr->RegisterService(\"/vip_check\", VipCheck); // httpsvr->RegisterService(\"/s\", Search); // httpsvr->RegisterService(\"/\", Login); httpsvr->Start();//开始接收服务--------->http.hpp return 0;}

Util.hpp:

// 防止头文件重复包含的编译指令#pragma once// 包含标准输入输出流库#include // 包含文件流操作库#include // 包含字符串处理库#include // 工具类声明class Util{public: // 静态方法:读取文件内容到字符串中,传递一个文件路径,和一个string,把文件的内容读取到string中 // 参数:filename - 文件名,out - 输出字符串指针 // 返回值:成功返回true,失败返回false static bool ReadFileContent(const std::string &filename /*std::vector*/, std::string *out) { // 获取文件大小 int filesize = FileSize(filename); // FileSize函数用于获取指定文件的大小(以字节为单位)。如果没打开就返回-1!!!! // 检查文件大小是否有效 if (filesize > 0) { // 创建输入文件流对象 std::ifstream in(filename); // ifstream#include  // 方式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或获取失败时返回false return false; } // 读取成功返回true return true; } // 静态方法:从大字符串中读取一行 // 参数:bigstr - 输入大字符串,out - 输出行字符串指针,sep - 行分隔符 // 返回值:成功返回true,失败返回false static bool ReadOneLine(std::string &bigstr, std::string *out, const std::string &sep /*\\r\\n*/) { // 查找分隔符位置 auto pos = bigstr.find(sep); // 如果没有找到分隔符则返回false if (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 // 读取成功返回true return true; } // 静态方法:获取文件大小 // 参数:filename - 文件名 // 返回值:成功返回文件大小(字节),失败返回-1 static 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; }}; // 类定义结束

效果演示

Linux网络-------3.应⽤层协议HTTP
Linux网络-------3.应⽤层协议HTTP

  • 在浏览器中输入115.120.238.130:8081
  • 别忘了要先去云服务器官网开启安全组,这样浏览器才能访问服务端