当前位置: 首页 > news >正文

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

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
  • 别忘了要先去云服务器官网开启安全组,这样浏览器才能访问服务端
http://www.lryc.cn/news/606059.html

相关文章:

  • Java 大视界 -- Java 大数据在智能交通公交客流预测与线路优化中的深度实践(15 城验证,年省 2.1 亿)(373)
  • 快速搭建Node.js服务指南
  • 前端核心技术Node.js(四)——express框架
  • 8,FreeRTOS时间片调度
  • RPA-重塑企业自动化流程的智能引擎
  • 《能碳宝》AI辅助开发系统方案
  • 免费语音识别(ASR)服务深度指南​
  • 深入解析域名并发请求限制与HTTP/2多路复用技术
  • 电脑远程关机的重要性
  • vue3+arcgisAPI4示例:轨迹点模拟移动(附源码下载)
  • 实战教程 ---- Nginx结合Lua实现WAF拦截并可视化配置教程框架
  • 融合数字孪生的智慧能源光伏场站检测系统应用解析
  • 生产管理升级:盘古IMS MES解锁全链路可控可溯,激活制造效率
  • 从 MySQL 迁移到 TiDB:使用 SQL-Replay 工具进行真实线上流量回放测试 SOP
  • 【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 微博评论数据可视化分析-点赞区间折线图实现
  • 保姆级别IDEA关联数据库方式、在IDEA中进行数据库的可视化操作(包含图解过程)
  • 技术速递|GitHub Copilot for Eclipse 迈出重要一步
  • SQL极简函数实战:巧用GREATEST()与LEAST()实现智能数据截断
  • Promise.all Promise.race Promise.any三个对比
  • 【Flask基础②】 | 路由、响应与异常处理
  • 在嵌入式系统或 STM32 平台中常见的外设芯片和接口
  • 《通信原理》学习笔记——第六章
  • 乱删文件,电脑不能开机,怎么办
  • 深入解析 Spring AI 系列:剖析OpenAI接口接入组件
  • 常见的中间件漏洞(tomcat,weblogic,jboss,apache)
  • 微信小程序中进行参数传递的方法
  • 5 种智能策略,从 iQOO 到 iQOO 转移照片
  • Apache RocketMQ 中 Topic 的概念、属性、行为约束和最佳实践
  • 【机器人+相机通讯】宇树科技相机通信
  • ChatGPT的下一站:从“答案引擎”到“思维教练”