Linux网络 应用层协议 HTTP

server/2025/2/4 16:02:02/

概念

在互联网世界中, 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/server/164921.html

相关文章

k8s二进制集群之负载均衡器高可用部署

Haproxy 和 Keepalived安装Haproxy配置文件准备Keepalived配置及健康检查启动Haproxy & Keepalived服务继续上一篇文章《K8S集群架构及主机准备》,下面介绍负载均衡器搭建过程 Haproxy 和 Keepalived安装 在负载均衡器两个主机上安装即可 apt install haproxy keepalived…

Notepad++消除生成bak文件

设置(T) ⇒ 首选项... ⇒ 备份 ⇒ 勾选 "禁用" 勾选禁用 就不会再生成bak文件了 notepad怎么修改字符集编码格式为gbk 如图所示

小程序的协同工作与发布

1.小程序API的三大分类 2.小程序管理的概念&#xff0c;以及成员管理两个方面 3.开发者权限说明以及如何维护项目成员 4.小程序版本

pytorch实现变分自编码器

人工智能例子汇总&#xff1a;AI常见的算法和例子-CSDN博客 变分自编码器&#xff08;Variational Autoencoder, VAE&#xff09;是一种生成模型&#xff0c;属于深度学习中的无监督学习方法。它通过学习输入数据的潜在分布&#xff08;Latent Distribution&#xff09;&…

亚博microros小车-原生ubuntu支持系列:19 nav2 导航

开始小车测试之前&#xff0c;先补充下背景知识 nav2 Navigation2具有下列工具&#xff1a; 加载、提供和存储地图的工具&#xff08;地图服务器Map Server&#xff09; 在地图上定位机器人的工具 (AMCL) 避开障碍物从A点移动到B点的路径规划工具&#xff08;Nav2 Planner&a…

数据结构的队列

一.队列 1.队列&#xff08;Queue&#xff09;的概念就是先进先出。 2.队列的用法&#xff0c;红色框和绿色框为两组&#xff0c;offer为插入元素&#xff0c;poll为删除元素&#xff0c;peek为查看元素红色的也是一样的。 3.LinkedList实现了Deque的接口&#xff0c;Deque又…

【优先算法】专题——前缀和

目录 一、【模版】前缀和 参考代码&#xff1a; 二、【模版】 二维前缀和 参考代码&#xff1a; 三、寻找数组的中心下标 参考代码&#xff1a; 四、除自身以外数组的乘积 参考代码&#xff1a; 五、和为K的子数组 参考代码&#xff1a; 六、和可被K整除的子数组 参…

一文讲解Java中的ArrayList和LinkedList

ArrayList和LinkedList有什么区别&#xff1f; ArrayList 是基于数组实现的&#xff0c;LinkedList 是基于链表实现的。 二者用途有什么不同&#xff1f; 多数情况下&#xff0c;ArrayList更利于查找&#xff0c;LinkedList更利于增删 由于 ArrayList 是基于数组实现的&#…