Linux网络 应用层协议 HTTP

devtools/2025/2/5 14:16:35/

概念

在互联网世界中, HTTP HyperText Transfer Protocol ,超文本传输协议)是一个至关重要的协议。它定义了客户端(如浏览器)与服务器之间如何通信,以交换或传输超文本(如 HTML 文档)。
HTTP 协议是客户端与服务器之间通信的基础。客户端通过 HTTP 协议向服务器发送请求,服务器收到请求后处理并返回响应。HTTP 协议是一个 无连接、无状态 的协议,即每次请求都需要建立新的连接,且服务器不会保存客户端的状态信息。

协议特点

  • 无连接:HTTP协议是无连接的,即每次连接只处理一个请求,服务器处理完客户的请求,并收到客户的应答后,即断开连接。
  • 无状态:HTTP协议是无状态的,即服务器不保留与客户交易时的任何状态。这意味着服务器无法根据之前的交互信息来识别当前的请求是否来自同一客户端,也无法记住之前的交互状态。
  • 基于请求/响应模型:HTTP协议采用请求/响应模型,客户端发送请求,服务器响应请求。客户端向服务器发送一个HTTP请求,请求中包含请求方法、请求资源的路径、协议版本等信息,服务器接收到请求后,根据请求的内容进行处理,并返回一个HTTP响应,响应中包含状态码、响应消息、响应数据等信息。

URL

URL是Uniform Resource Locator的缩写,即统一资源定位符,是用于在互联网上定位和访问资源的地址,平时我们俗称的 "网址" 其实就是说的 URL。https://i-blog.csdnimg.cn/direct/a373b0665b9448ac94bf80fdec9e2be1.png" width="1055" />

urlencode urldecode
/ ? : 等这样的字符 , 已经被 url 当做特殊意义理解了, 因此这些字符不能随意出现。比如, 某个参数中需要带有这些特殊字符 , 就必须先对特殊字符进行转义,即进行 urlencode。
转义的规则如下:
将需要转码的字符转为 16 进制,然后从右到左,取 4 ( 不足 4 位直接处理 ) ,每 2 位做一位,前面加上% ,编码成 %XY 格式。例如:
https://i-blog.csdnimg.cn/direct/06d14101b8bc487ba7fe223a0603294f.png" width="890" />
"+" 被转义成了 "%2B",urldecode 就是 urlencode 的逆过程。以下为进行 urlencode 和 urldecode的工具:urlencode 和 urldecode的工具

HTTP 协议请求与响应格式

请求格式

https://i-blog.csdnimg.cn/direct/be42d751ba834668b44dc21a39202d52.png" width="1551" />

  • 请求行: [方法] + [url] + [版本]
  • Header(请求报头): 请求的属性, 冒号分割的键值对,每组属性之间使用\r\n 分隔,遇到空行表示 Header 部分结束
  • Body(请求正文): 空行后面的内容都是 Body,Body 允许为空字符串,如果 Body 存在, 则在Header 中会有一个 Content-Length 属性来标识 Body 的长度
https://i-blog.csdnimg.cn/direct/8b451e2fc0fd4d3699b32cc5938bdd9c.png" width="735" />
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include <sstream>
using namespace std;
const string Sep = "\r\n";
const string LineSep = " ";
const string HeaderLineSep = ": ";
const string BlankLine = Sep;
const string defaulthomepage = "shop";
const string firstpage = "index.html";
const string page404 = "shop/404.html";class HttpRequest
{
public:bool IsHasArgs(){return _isexec;}// GET /favicon.ico HTTP/1.1\r\n// Host: 8.137.19.140:8080// Connection: keep-alive// User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0// Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8// Referer: http://8.137.19.140:8080/?msg=i_have_sent_a_message_he_he_he_he_he_he_he_he_he_he_he_he_he_he_he_he_he_he// Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6// dnt: 1// sec-gpc: 1//bool SplitString(const string &header, const string &sep, string *key, string *value){auto pos = header.find(sep);if (pos == string::npos)return false;*key = header.substr(0, pos);*value = header.substr(pos + sep.size());return true;}void ParseHeaderkv(){string key, value;for (auto &header : _request_header){if (SplitString(header, HeaderLineSep, &key, &value)){_headerkv[key] = value;}}}bool ParseOneLine(string &str, string *out_str, const string &sep){auto pos = str.find(sep);if (pos == string::npos)return false;*out_str = str.substr(0, pos);str.erase(0, pos + sep.size());return true;}// 处理请求报头bool ParseHeader(string &request_str){string line;while (true){bool ret = ParseOneLine(request_str, &line, Sep);if (ret && !line.empty()){_request_header.push_back(line);}else if (ret && line.empty()){_blank_line = BlankLine;break;}elsereturn false;}ParseHeaderkv();return true;}// 处理请求行//  GET /favicon.ico HTTP/1.1\r\nvoid ParseRequestLine(string &request_line){stringstream ss(request_line);ss >> _method >> _url >> _version;}void Deserialize(string &request_str){if (ParseOneLine(request_str, &_request_line, Sep)){// 提取请求行中的详细字段ParseRequestLine(_request_line);ParseHeader(request_str);_body = request_str;// 分析请求是否含有参数if (_method == "POST"){_isexec = true; // 参数在正文_args = _body;_path = _url;cout << "POST: _path: " << _path << endl;cout << "POST: _args: " << _args << endl;}else if (_method == "GET"){// /login?name=zhang&passwd=123456auto pos = _url.find("?");if (pos != string::npos){_isexec = true;_path = _url.substr(0, pos);_args = _url.substr(pos + 1);cout << "POST: _path: " << _path << endl;cout << "POST: _args: " << _args << endl;}}}}string GetContent(const string &path){// 由于传输的数据可能是文件,图片,视频等// 所以需要通过二进制文件方式获取string content;ifstream in(path, ios::binary);if (!in.is_open())return "";in.seekg(0, in.end);int filesz = in.tellg();in.seekg(0, in.beg);content.resize(filesz);in.read((char *)content.c_str(), filesz);in.close();cout << "content length: " << content.size() << endl;return content;}void Print(){cout << "_method: " << _method << endl;cout << "_url: " << _url << endl;cout << "_version: " << _version << endl;for (auto &kv : _headerkv){cout << kv.first << " # " << kv.second << endl;}cout << "_blank_line: " << _blank_line << endl;cout << "_body: " << _body << endl;}string Url(){return _url;}void SetUrl(const string &newurl){_url = newurl;}string Path(){return _path;}string Args(){return _args;}string Suffix(){auto pos = _path.find(".");if (pos == string::npos)return ".html";elsereturn _path.substr(pos);}private:string _request_line;vector<string> _request_header;string _blank_line = BlankLine;string _body;// 在反序列化中我们需要细化解析出来的字段string _method;string _url; // 如果请求方法为GET,则url中存在路径和参数两部分string _path;string _args;string _version;unordered_map<string, string> _headerkv;bool _isexec = false;
};
响应格式

https://i-blog.csdnimg.cn/direct/d00f836dd9f243e1bf1e41ab38bfeeb9.png" width="1447" />

  • 状态行: [版本号] + [状态码] + [状态码解释]
  • Header(响应报头): 请求的属性, 冒号分割的键值对,每组属性之间使用\r\n 分隔,遇到空行表示 Header 部分结束
  • Body(响应正文): 空行后面的内容都是 Body。Body 允许为空字符串如果 Body 存在, 则在 Header 中会有一个 Content-Length 属性来标识 Body 的长度;如果服务器返回了一个 html 页面, 那么 html 页面内容就是在 body

https://i-blog.csdnimg.cn/direct/6017d868465e4ccfb063040b0c7cbd09.png" width="711" />

class HttpResponse
{
public:void Build(HttpRequest &req){string url = defaulthomepage + req.Path();if (url.back() == '/'){url += firstpage;}cout << "-------客户端正在请求:" << url;req.Print();cout << "---------------------------" << endl;_content = req.GetContent(url);if (_content.empty()){// 用户请求的资源不存在_status_code = 404;_content = req.GetContent(page404);}else{_status_code = 200;}_body = _content;_status_desc = CodeToDesc(_status_code);if (!_content.empty()){SetHeader("Content-Length", to_string(_content.size()));}string mime_type = SuffixToDesc(req.Suffix());SetHeader("Content-Type", mime_type);}void SetHeader(const string &k, const string &v){_header_kv[k] = v;}void SetCode(int code){_status_code = code;_status_desc = CodeToDesc(_status_code);}void SetBody(const string &body){_body = body;}void Serialize(string *response_str){_response_line = _version + LineSep + to_string(_status_code) + LineSep + _status_desc + Sep;for (auto &header : _header_kv){_response_header.push_back(header.first + HeaderLineSep + header.second);}*response_str = _response_line;for (auto &line : _response_header){*response_str += (line + Sep);}*response_str += _blank_line;*response_str += _body;}private:string CodeToDesc(const int &code){switch (code){case 200:return "OK";case 404:return "Not Found";case 301:return "Moved Permanently";case 302:return "Found";default:return "";}}string SuffixToDesc(const string &suffix){if (suffix == ".html")return "/text/html";else if (suffix == ".jpg")return "application/x-jpg";elsereturn "text/html";}private:string _version;int _status_code;string _status_desc;string _content;unordered_map<string, string> _header_kv;// 最终的四部分构建应答string _response_line;vector<string> _response_header;string _blank_line = BlankLine;string _body;
};

HTTP 的方法

https://i-blog.csdnimg.cn/direct/26c2602e55274f3697ad6d4cdd78412f.png" width="1178" />

其中最常用的就是 GET 方法和 POST 方法。
GET 方法
  • 用途:用于请求 URL 指定的资源。
  • 示例:GET /index.html HTTP/1.1
  • 特性:指定资源经服务器端解析后返回响应内容。
string GetContent(const string &path){// 由于传输的数据可能是文件,图片,视频等// 所以需要通过二进制文件方式获取string content;ifstream in(path, ios::binary);if (!in.is_open())return "";in.seekg(0, in.end);int filesz = in.tellg();in.seekg(0, in.beg);content.resize(filesz);in.read((char *)content.c_str(), filesz);in.close();cout << "content length: " << content.size() << endl;return content;}
POST 方法
  • 用途:用于传输实体的主体,通常用于提交表单数据。
  • 示例:POST /submit.cgi HTTP/1.1
  • 特性:可以发送大量的数据给服务器,并且数据包含在请求体中。
PUT 方法
  • 用途:用于传输文件,将请求报文主体中的文件保存到请求 URL 指定的位置。
  • 示例:PUT /example.html HTTP/1.1
  • 特性:不太常用,但在某些情况下,如 RESTful API 中,用于更新资源。
HEAD 方法
  • 用途:与 GET 方法类似,但不返回报文主体部分,仅返回响应头。
  • 示例:HEAD /index.html HTTP/1.1
  • 特性:用于确认 URL 的有效性及资源更新的日期时间等。
DELETE 方法
  • 用途:用于删除文件,是 PUT 的相反方法。
  • 示例:DELETE /example.html HTTP/1.1
  • 特性:按请求 URL 删除指定的资源。
OPTIONS 方法
  • 用途:用于查询针对请求 URL 指定的资源支持的方法。
  • 示例:OPTIONS * HTTP/1.1
  • 特性:返回允许的方法,如 GETPOST 等。

HTTP 的状态码

https://i-blog.csdnimg.cn/direct/bb6d0b14fb474ffca33c4ec1a5873bbc.png" width="1172" />

状态码
含义
应用样例
100
Continue
上传大文件时,服务器告诉客户端可以继续上传
200OK
访问网站首页,服务器返回网页内容
201
Created
发布新文章,服务器返回文章创建成功的信息
204
No Content
删除文章后,服务器返回 无内容 表示操作成功
301
Moved Permanently
网站换域名后,自动跳转到新域名;搜索引擎更新网站链接时使用
302
Found See Other
用户登录成功后,重定向到用户首页
304
Not Modified
浏览器缓存机制,对未修改的资源返回 304 状态码
400
Bad Request
填写表单时,格式不正确导致提交失败
401
Unauthorized
访问需要登录的页面时,未登录或认证失败
403
Forbidden
尝试访问你没有权限查看的页面
404
Not Found
访问不存在的网页链接
500
Internal Server Error
服务器崩溃或数据库错误导致页面无法加载
502
Bad Gateway
使用代理服务器时,代理服务器无法从上游服务器获取有效响应
503
Service
Unavailable
服务器维护或过载,暂时无法处理请求
其中最常见的状态码 , 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 502(Bad Gateway)。对于包含重定向相关状态码,有以下区别:
状态码
含义
是否为临时重定向
应用样例
301
Moved
Permanently
否(永久重定向)
网站换域名后,自动跳转到新域名;
搜索引擎更新网站链接时使用
302
Found See Other
是(临时重定向)
用户登录成功后,重定向到用户首页
307
Temporary
Redirect
是(临时重定向)
临时重定向资源到新的位置(较少使用)
308
Permanent
Redirect
Permanently
否(永久重定向)

永久重定向资源到新的位置(较少使用)

HTTP 状态码 301 (永久重定向)和 302 (临时重定向)都依赖 Location 选项 。以下
是关于两者依赖 Location 选项的详细说明:
  • HTTP 状态码 301(永久重定向)
当服务器返回 HTTP 301 状态码时,表示请求的资源已经被永久移动到新的位置。在这种情况下,服务器会在响应中添加一个 Location 头部,用于指定资源的新位置。这个 Location 头部包含了新的 URL 地址,浏览器会自动重定向到该地址.例如,在 HTTP 响应中,可能会看到类似于以下的头部信息:
HTTP/1.1 301 Moved Permanently\r\n
Location: https://www.new-url.com\r\n
  • HTTP 状态码 302(临时重定向)
当服务器返回 HTTP 302 状态码时,表示请求的资源临时被移动到新的位置。同样地,服务器也会在响应中添加一个 Location 头部来指定资源的新位置。浏览器会暂时使用新的 URL 进行后续的请求,但不会缓存这个重定向。例如,在 HTTP 响应中,可能会看到类似于以下的头部信息:
HTTP/1.1 302 Found\r\n
Location: https://www.new-url.com\r\n

总结:无论是 HTTP 301 还是 HTTP 302 重定向,都需要依赖 Location 选项来指定资源的新位置。这个 Location 选项是一个标准的 HTTP 响应头部,用于告诉浏览器应该将请求重定向到哪个新的 URL 地址。

HTTP 常见 Header

  • Content-Type: 数据类型(text/html )
  • Content-Length: Body 的长度
  • Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
  • User-Agent: 声明用户的操作系统和浏览器版本信息
  • referer: 当前页面是从哪个页面跳转过来的
  • Location: 搭配 3xx 状态码使用, 告诉客户端接下来要去哪里访问
  • Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能
关于 connection 报头
HTTP 中的 Connection 字段是 HTTP 报文头的一部分,它主要用于控制和管理客户端与服务器之间的连接状态。
核心作用
管理持久连接 Connection 字段还用于管理持久连接(也称为长连接)。持久连接允许客户端和服务器在请求/ 响应完成后不立即关闭 TCP 连接,以便在同一个连接上发送多个请求和接收多个响应。
持久连接(长连接)
HTTP/1.1 :在 HTTP/1.1 协议中,默认使用持久连接。当客户端和服务器都不明确指定关闭连接时,连接将保持打开状态,以便后续的请求和响应可以复用同一个连接。
HTTP/1.0 :在 HTTP/1.0 协议中,默认连接是非持久的。如果希望在 HTTP/1.0 上实现持久连接,需要在请求头中显式设置 Connection: keep-alive
语法格式
Connection: keep-alive:表示希望保持连接以复用 TCP 连接。
Connection: close:表示请求 / 响应完成后,应该关闭 TCP 连接。

案例:电商平台服务器

InetAddr.hpp

#pragma once
#include <string>
#include <iostream>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>
using namespace std;
class InetAddr
{
public:InetAddr(){}InetAddr(int port, string ip = ""): _port(port), _ip(ip){bzero(&_sockaddr, sizeof(_sockaddr));_sockaddr.sin_family = AF_INET;_sockaddr.sin_port = htons(_port);if (_ip.empty())_sockaddr.sin_addr.s_addr = INADDR_ANY;else_sockaddr.sin_addr.s_addr = inet_addr(_ip.c_str());}InetAddr(const struct sockaddr_in &sockaddr){_port = ntohs(sockaddr.sin_port);char buf[64];_ip = inet_ntop(AF_INET, &sockaddr.sin_addr, buf, sizeof(buf));}bool operator==(const InetAddr &other){return _ip == other._ip;}InetAddr operator=(const InetAddr &other){_ip = other._ip;_port = other._port;_sockaddr = other._sockaddr;return *this;}struct sockaddr *getSockaddr(){return (struct sockaddr *)&_sockaddr;}int getSockaddrLen(){return sizeof(_sockaddr);}const string &getIp(){return _ip;}int getPort(){return _port;}void SetAddr(const struct sockaddr_in &client){_sockaddr = client;_port = ntohs(client.sin_port);char buf[64];_ip = inet_ntop(AF_INET, &client.sin_addr, buf, sizeof(buf));}private:string _ip;int _port;struct sockaddr_in _sockaddr;
};

Common.hpp

enum
{SOCKET_ERROR=1,BIND_ERROR,LISTEN_ERROR,ACCEPT_ERROR,CONNECT_ERROR
};

Socket.hpp

#pragma once
#include <iostream>
#include <string>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Common.hpp"
#include "InetAddr.hpp"
using namespace std;
class Socket; // 前置声明
using SockPtr = shared_ptr<Socket>;
#define glistensockfd -1
#define gbacklog 8
// 基类,规定创建socket方法
class Socket
{
public:// 创建listensockfdSocket() = default;virtual ~Socket() = default;virtual void SocketOrDie() = 0;virtual void SetSockOpt() = 0;virtual bool BindOrDie(int port) = 0;virtual bool ListenOrDie() = 0;virtual SockPtr Accepter(InetAddr *client) = 0;virtual void Close() = 0;virtual int Recv(string *out_str) = 0;virtual int Send(const string &in_str) = 0;virtual int Fd() = 0;// 创建TcpSocket的固定方法void BuildTcpSocketMethod(int port){SocketOrDie();SetSockOpt();BindOrDie(port);ListenOrDie();}// 创建UdpSocket的固定方法void BuildUdpSocketMethod(int port){SocketOrDie();SetSockOpt();BindOrDie(port);}
};class TcpSocket : public Socket
{
public:TcpSocket() : _listensockfd(glistensockfd){}TcpSocket(int listensockfd): _listensockfd(listensockfd){}virtual ~TcpSocket(){}// 创建listensockfdvirtual void SocketOrDie() override{_listensockfd = socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd < 0){cout << "socket error" << endl;exit(SOCKET_ERROR);}cout << "socket create success" << endl;}virtual void SetSockOpt() override{int opt = 1;// 保证服务器异常断开后可以立即重启,不会有bind问题setsockopt(_listensockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));}virtual bool BindOrDie(int port) override{if (_listensockfd == glistensockfd)return false;// 填充网络信息struct sockaddr_in local;local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = INADDR_ANY;int n = bind(_listensockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){cout << "bind error" << endl;exit(BIND_ERROR);}cout << "bind success" << endl;return true;}virtual bool ListenOrDie() override{if (_listensockfd == glistensockfd)return false;int n = listen(_listensockfd, gbacklog);if (n < 0){cout << "listen error" << endl;exit(LISTEN_ERROR);}cout << "listen success" << endl;return true;}// 1.文件描述符 2.client infovirtual SockPtr Accepter(InetAddr *client) override{if (client == nullptr)return nullptr;struct sockaddr_in peer;socklen_t len = sizeof(peer);int sockfd = accept(_listensockfd, (struct sockaddr *)&peer, &len);if (sockfd < 0){cout << "accep error" << endl;return nullptr;}client->SetAddr(peer);return make_shared<TcpSocket>(sockfd);}virtual void Close() override{if (_listensockfd == glistensockfd)return;close(_listensockfd);}virtual int Recv(string *out_str) override{char buffer[4096*2];int sz = recv(_listensockfd, buffer, sizeof(buffer) - 1, 0);if (sz > 0){buffer[sz] = 0;*out_str = buffer;}return sz;}virtual int Send(const string &in_str) override{int sz = send(_listensockfd, in_str.c_str(), in_str.size(), 0);return sz;}virtual int Fd() override{return _listensockfd;}private:int _listensockfd;
};class UdpSocket : public Socket
{
public:UdpSocket(int sockfd = glistensockfd): _sockfd(sockfd){}// 创建listensockfdvirtual void SocketOrDie() override{_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){cout << "socket error" << endl;exit(SOCKET_ERROR);}cout << "socket create success" << endl;}virtual void SetSockOpt() override{int opt = 1;setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));}virtual bool BindOrDie(int port) override{struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = INADDR_ANY;int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){cout << "bind error" << endl;exit(BIND_ERROR);}cout << "bind success" << endl;return true;}virtual void Close() override{if (_sockfd == glistensockfd)return;close(_sockfd);}virtual int Recv(string *out_str){char buffer[4096];socklen_t len = sizeof(_peer);int sz = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&_peer, &len);if (sz > 0){buffer[sz] = 0;*out_str = buffer;}return sz;}virtual int Send(const string &in_str) override{int sz = sendto(_sockfd, in_str.c_str(), in_str.size(), 0, (struct sockaddr *)&_peer, sizeof(_peer));return sz;}virtual int Fd() override{return _sockfd;}private:int _sockfd;struct sockaddr_in _peer;
};

TcpServer.hpp

#pragma once
#include <iostream>
#include <functional>
#include <memory>
#include "Socket.hpp"
#include "InetAddr.hpp"
#include <unistd.h>
#include <sys/wait.h>
using namespace std;#define BACKLOG 8
using handler_t = function<void(SockPtr, InetAddr)>;
using task_t = function<void()>;
static const uint16_t gport = 8080;// 只负责IO,不对协议进行处理
class TcpServer
{
public:TcpServer(uint16_t port = gport): _port(port), _isrunning(false), _listensockfd(make_unique<TcpSocket>()){}void InitServer(handler_t handler){_handler = handler;_listensockfd->BuildTcpSocketMethod(_port);}void Loop(){_isrunning = true;while (_isrunning){// 1.AcceptInetAddr client;auto sockfd = _listensockfd->Accepter(&client);if (sockfd == nullptr)continue;// 2.IO处理cout << "get a new client,info is: " << client.getIp() << ":" << client.getPort() << endl;// 3.交给孙子进程执行任务pid_t id = fork();if (id == 0){_listensockfd->Close();if (fork() > 0)exit(0);_handler(sockfd, client);exit(0);}sockfd->Close();waitpid(id, nullptr, 0);}_isrunning = false;}void Stop(){_isrunning = false;}~TcpServer(){_listensockfd->Close();}private:// int _listensockfd; // 监听socketunique_ptr<Socket> _listensockfd;uint16_t _port;bool _isrunning;// 处理上层任务的入口handler_t _handler;
};

Daemon.hpp

#pragma once
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>using namespace std;
#define ROOT "/"
#define devnull "/dev/null"
void Daemon(bool ischdir, bool isclose)
{// 守护进程要屏蔽特定的异常信号signal(SIGCHLD, SIG_IGN);signal(SIGPIPE, SIG_IGN);// 成为非组长if (fork() > 0)exit(0);// 建立新会话setsid();// 每一个进程都有自己的CWD,是否将当前进程的CWD更改成为 / 根目录if (ischdir)chdir(ROOT);// 变成守护进程,不需要与用户的输入输出,错误相关联if (isclose){close(0);close(1);close(2);}else{int fd = open(devnull, O_WRONLY);if (fd > 0){// 重定向dup2(fd, 0);dup2(fd, 1);dup2(fd, 2);close(fd);}}
}

HttpProtocol.hpp

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include <sstream>
using namespace std;
const string Sep = "\r\n";
const string LineSep = " ";
const string HeaderLineSep = ": ";
const string BlankLine = Sep;
const string http_version = "HTTP/1.0";
const string defaulthomepage = "shop";
const string firstpage = "page/index.html";
const string page404 = "shop/page/404.html";class HttpRequest
{
public:bool IsHasArgs(){return _isexec;}// GET /favicon.ico HTTP/1.1\r\n// Host: 8.137.19.140:8080// Connection: keep-alive// User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0// Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8// Referer: http://8.137.19.140:8080/?msg=i_have_sent_a_message_he_he_he_he_he_he_he_he_he_he_he_he_he_he_he_he_he_he// Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6// dnt: 1// sec-gpc: 1//bool SplitString(const string &header, const string &sep, string *key, string *value){auto pos = header.find(sep);if (pos == string::npos)return false;*key = header.substr(0, pos);*value = header.substr(pos + sep.size());return true;}void ParseHeaderkv(){string key, value;for (auto &header : _request_header){if (SplitString(header, HeaderLineSep, &key, &value)){_headerkv[key] = value;}}}bool ParseOneLine(string &str, string *out_str, const string &sep){auto pos = str.find(sep);if (pos == string::npos)return false;*out_str = str.substr(0, pos);str.erase(0, pos + sep.size());return true;}// 处理请求报头bool ParseHeader(string &request_str){string line;while (true){bool ret = ParseOneLine(request_str, &line, Sep);if (ret && !line.empty()){_request_header.push_back(line);}else if (ret && line.empty()){_blank_line = BlankLine;break;}elsereturn false;}ParseHeaderkv();return true;}// 处理请求行//  GET /favicon.ico HTTP/1.1\r\nvoid ParseRequestLine(string &request_line){stringstream ss(request_line);ss >> _method >> _url >> _version;}void Deserialize(string &request_str){if (ParseOneLine(request_str, &_request_line, Sep)){// 提取请求行中的详细字段ParseRequestLine(_request_line);ParseHeader(request_str);_body = request_str;// 分析请求是否含有参数if (_method == "POST"){_isexec = true; // 参数在正文_args = _body;_path = _url;cout << "POST: _path: " << _path << endl;cout << "POST: _args: " << _args << endl;}else if (_method == "GET"){// /login?name=zhang&passwd=123456auto pos = _url.find("?");if (pos != string::npos){_isexec = true;_path = _url.substr(0, pos);_args = _url.substr(pos + 1);cout << "POST: _path: " << _path << endl;cout << "POST: _args: " << _args << endl;}}}}string GetContent(const string &path){// 由于传输的数据可能是文件,图片,视频等// 所以需要通过二进制文件方式获取string content;ifstream in(path, ios::binary);if (!in.is_open())return "";in.seekg(0, in.end);int filesz = in.tellg();in.seekg(0, in.beg);content.resize(filesz);in.read((char *)content.c_str(), filesz);in.close();cout << "content length: " << content.size() << endl;return content;}void Print(){cout << "HttpRequest: " << endl;cout << "_method: " << _method << endl;cout << "_url: " << _url << endl;cout << "_version: " << _version << endl;for (auto &kv : _headerkv){cout << kv.first << " # " << kv.second << endl;}cout << "_blank_line: " << _blank_line << endl;cout << "_body: " << _body << endl;}string Url(){return _url;}void SetUrl(const string &newurl){_url = newurl;}string Path(){return _path;}string Args(){return _args;}string Suffix(){auto pos = _path.find(".");if (pos == string::npos)return ".html";elsereturn _path.substr(pos);}private:string _request_line;vector<string> _request_header;string _blank_line = BlankLine;string _body;// 在反序列化中我们需要细化解析出来的字段string _method;string _url; // 如果请求方法为GET,则url中存在路径和参数两部分string _path;string _args;string _version;unordered_map<string, string> _headerkv;bool _isexec = false;
};class HttpResponse
{
public:void Build(HttpRequest &req){string url = defaulthomepage + req.Url();if (url.back() == '/'){url += firstpage;}cout << "-------客户端正在请求:" << url << endl;req.Print();cout << "---------------------------" << endl;_content = req.GetContent(url);if (_content.empty()){// 用户请求的资源不存在_status_code = 404;_content = req.GetContent(page404);}else{_status_code = 200;}_status_desc = CodeToDesc(_status_code);if (!_content.empty()){SetHeader("Content-Length", to_string(_content.size()));}string mime_type = SuffixToDesc(req.Suffix());SetHeader("Content-Type", mime_type);_body = _content;}void SetHeader(const string &k, const string &v){_header_kv[k] = v;}void SetCode(int code){_status_code = code;_status_desc = CodeToDesc(_status_code);}void SetBody(const string &body){_body = body;}void Serialize(string *response_str){_response_line = _version + LineSep + to_string(_status_code) + LineSep + _status_desc + Sep;for (auto &header : _header_kv){_response_header.push_back(header.first + HeaderLineSep + header.second);}*response_str = _response_line;for (auto &line : _response_header){*response_str += (line + Sep);}*response_str += _blank_line;*response_str += _body;}private:string CodeToDesc(const int &code){switch (code){case 200:return "OK";case 404:return "Not Found";case 301:return "Moved Permanently";case 302:return "Found";default:return "";}}string SuffixToDesc(const string &suffix){if (suffix == ".html")return "text/html";else if (suffix == ".jpg")return "application/x-jpg";elsereturn "text/html";}private:string _version = http_version;int _status_code;string _status_desc;string _content;unordered_map<string, string> _header_kv;// 最终的四部分构建应答string _response_line;vector<string> _response_header;string _blank_line = BlankLine;string _body;
};

HttpServer.hpp

#pragma once
#include "TcpServer.hpp"
#include "HttpProtocol.hpp"using http_handler_t = function<void(HttpRequest &, HttpResponse &)>;
class HttpServer
{
public:HttpServer(int port): _tsvr(make_unique<TcpServer>(port)){}void Resgiter(string funcname, http_handler_t func){_route[funcname] = func;}bool SafeCheck(const string &service){auto iter = _route.find(service);return iter != _route.end();}void HandlerHttpRequest(SockPtr sockfd, InetAddr client){cout << "HttpServer: get a new client: " << sockfd->Fd()<< "info: " << client.getIp() << ":" << client.getPort() << endl;string http_request;sockfd->Recv(&http_request);HttpRequest req;req.Deserialize(http_request);HttpResponse resp;// 请求分为两类。1.静态资源 2.携带参数if (req.IsHasArgs()){string service = req.Path();if (SafeCheck(service))_route[service](req, resp);elseresp.Build(req);}else{resp.Build(req);}string resp_str;resp.Serialize(&resp_str);sockfd->Send(resp_str);}void Start(){_tsvr->InitServer([&](SockPtr sockfd, InetAddr client){ this->HandlerHttpRequest(sockfd, client); });_tsvr->Loop();}private:unique_ptr<TcpServer> _tsvr;unordered_map<string, http_handler_t> _route; // 功能路由
};

HttpServermain.cc

#include "HttpServer.hpp"
#include "Daemon.hpp"
#include <functional>
#include <unistd.h>
#include <memory>
void Login(HttpRequest &req, HttpResponse &resp)
{// req.Path(): /login// 根据req,动态构建username=lisi&password=12345cout << "进入登录模块" << req.Path() << ", " << req.Args();// 1. 解析参数格式,得到要的参数// 2. 访问数据库,验证对应的用户是否是合法的用户,其他工作....// 3. 登录成功
}void Register(HttpRequest &req, HttpResponse &resp)
{// 根据req,动态构建respcout << "进入注册模块" << req.Path() << ", " << req.Args();
}void Search(HttpRequest &req, HttpResponse &resp)
{// 根据req,动态构建respcout << "进入搜索模块" << req.Path() << ", " << req.Args();
}void Test(HttpRequest &req, HttpResponse &resp)
{// 根据req,动态构建respcout << "进入测试模块" << req.Path() << ", " << req.Args();
}
int main(int argc, char *argv[])
{if (argc != 2){std::cout << "Usage: " << argv[0] << " port" << std::endl;return 1;}int port = stoi(argv[1]);// 变成守护进程Daemon(false, false);auto server = make_unique<HttpServer>(port);server->Resgiter("/login",Login);server->Resgiter("/register", Register);server->Resgiter("/search", Search);server->Resgiter("/test", Test);server->Start();return 0;
}

makefile

server:HttpServermain.ccg++ -o $@ $^ -std=c++17 -lpthread -ljsoncpp
.PHONY:clean
clean:rm -f server

http://www.ppmy.cn/devtools/156289.html

相关文章

基于Hadoop实现气象分析大屏可视化项目【源码+LW+PPT+解析】

作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验&#xff0c;被多个学校常年聘为校外企业导师&#xff0c;指导学生毕业设计并参与学生毕业答辩指导&#xff0c;…

使用windows笔记本让服务器上网

使用windows笔记本让服务器上网 前言准备工具开始动手实践1.将手机热点打开&#xff0c;让Windows笔记本使用无线网卡连接上网2.使用网线将Windows笔记本的有线网卡和服务器的有线网卡直连3.在Windows笔记本上按winR输入ncpa.cpl打开网卡设置界面4.在Windows笔记本上右键“无线…

Spring AI 与企业级应用架构的结合

随着 AI 技术的不断发展&#xff0c;越来越多的企业开始将 AI 模型集成到其业务系统中&#xff0c;从而提升系统的智能化水平、自动化程度和用户体验。在此背景下&#xff0c;Spring AI 作为一个企业级 AI 框架&#xff0c;提供了丰富的工具和机制&#xff0c;可以帮助开发者将…

深度学习 Pytorch 神经网络的损失函数

本节开始将以分类神经网络为例&#xff0c;展示神经网络的学习和训练过程。在介绍PyTorch的基本工具AutoGrad库时&#xff0c;我们系统地介绍过数学中的优化问题和优化思想&#xff0c;我们介绍了最小二乘法以及梯度下降法这两个入门级优化算法的具体操作&#xff0c;并使用Aut…

半导体器件与物理篇6 MESFET

金属-半导体接触 MESFET与MOSFET的相同点&#xff1a;它们的电压电流特性相似。都有源漏栅三极&#xff0c;强反型&#xff0c;漏极加正向电压&#xff0c;也会经历线性区、夹断点、饱和区三个阶段。 MESFET与MOSFET的不同点&#xff1a;在器件的栅电极部分&#xff0c;MESFE…

程序代码篇---enumratemapraisezipfiter

文章目录 前言第一部分&#xff1a;enumerate基本语法参数iteraablestart返回值 示例使用场景 第二部分&#xff1a;map基本语法functioniterable 示例 第三部分&#xff1a;raise基本语法exceptionvaluetraceback 示例注意事项异常的用途 第四部分&#xff1a;zip基本用法示例…

Linux 信号机制

Linux 信号机制 一、Linux信号的基本概念二、怎么理解Linux信号机制三、Linux信号的用途四、Linux信号的原理五、Linux信号的处理方法1、默认处理2、忽略信号3、自定义信号处理 六、Linux信号处理的细节1、信号掩码2、实时信号3、Linux信号 Alarm 七、Linux信号处理库函数1. si…

深度学习与神经网络

最近看目标识别的文献&#xff0c;许多人工智能的概念把人搞得晕头转向。查了一些资料&#xff0c;总算明白点了。 深度学习泛指深度神经网络&#xff0c;就是有深度的神经网络&#xff0c;需要多层&#xff0c;是从单层、两层神经网络发展而来的。深度学习是机器学习的一种&a…