HTTP协议工作原理与生产环境服务器搭建实战 - 详解

news/2025/9/25 14:41:36/文章来源:https://www.cnblogs.com/tlnshuju/p/19111189

HTTP协议工作原理与生产环境服务器搭建实战 - 详解

2025-09-25 14:40  tlnshuju  阅读(0)  评论(0)    收藏  举报

前言:在浏览器中点击一个链接或输入一个网址时,背后是HTTP协议在默默地协调着客户端与服务器之间的每一次对话。作为应用层最核心的协议之一,HTTP定义了Web通信的基本规则。本文将深入解析HTTP协议的报文格式、关键机制,并通过实践搭建一个简单的HTTP服务器,来揭示网页从请求到展现的完整过程。

文章目录

  • 一、HTTP协议简介
  • 二、HTTP请求报文格式
  • 三、HTTP响应报文格式
  • 四、HTTP服务搭建
    • 4.1 uri资源获取
    • 4.2 头部字段
    • 4.3 状态码与状态描述
    • 4.4 GET和POST
  • 五、源码

一、HTTP协议简介

  HTTP协议是应用层一个重要的协议。数据本质是二进制,也可以把它们看做是字符,而要完成各种业务逻辑需要通信双方约定一个固定的数据格式,完成业务的分用,像传输层、网络层、数据链路层等一样。不过应用层没有标准的协议规定,而是让程序员根据具体的需求自行设计。不过已经有大佬们设计出了很多优秀的协议,我们可以直接使用,比如HTTP协议。
  HTTP协议全称超文本传输协议,是客户端与服务器之间通信的基础。客户端通过HTTP协议向服务器发送请求,服务器收到请求后处理并返回响应。

  • 注1:HTTP协议是⼀个⽆连接、⽆状态的协议,即每次请求都需要建⽴新的连接,且服务器不会保存客户端的状态信息。
  • 注2:HTTP协议的客户端通常不用我们自己写,直接使用浏览器即可。

认识URL
在这里插入图片描述
  要在全网内访问某个唯一进程,需要知道该进程所在服务器的 IP 地址和端口号(port),如上http就代表了80端口,https代表了443端口。而www.example.jp是域名,到时候会被转化为服务器端的ip地址。(域名+协议即ip地址+端口号)

在上网时事实上只有两种行为,即:

  • 从远端获取资源
  • 把本地资源上传到远端

那么什么是资源呢?
资源在远端服务器里,以文件形式存在。如上/dir/index.html就是linux路径下的一个文件。
在这里插入图片描述

如上在url中像@?等字符已经被用作特殊含义,如果在搜索框中出现,最后在url中会被转义,转义规则:

  • 将需要转义的字符转为16进制,然后从右往左,取4位(不足4位直接处理),每2位前加上%,编码成%XY格式

二、HTTP请求报文格式

HTTP请求报头格式如下:
在这里插入图片描述

请求行

头部字段:

空行:

请求正文:

注意:

  1. HTTP 协议的序列化与反序列化,是通过特殊字符(如 \r\n、空格)进行字段拼接实现的,不依赖任何第三方库。
  2. HTTP是基于TCP协议的。

如何让报头与有效载荷分离?通过空行。
如何让报文与报文之间分离?在头部字段中有一个参数Content-Length标记正文部分的长度,方便我们进行报文分离。

接下来我们快速搭建一个TCP服务,接收并打印一个HTTP请求报头:

快速搭建TCP服务
目录结构:

test
├── main.cc
├── server.hpp
└── Makefile

server.hpp文件

#include <iostream>#include <string>#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <netinet/ip.h>#include <arpa/inet.h>class Server{public:Server(int port): _port(port), _isrunning(false){/*打开套接字*/_listenfd = socket(AF_INET, SOCK_STREAM, 0);/*绑定端口*/sockaddr_in server_addr;server_addr.sin_family = AF_INET;// 设置为IPv4// 主机序列->网络序列server_addr.sin_port = htons(_port);server_addr.sin_addr.s_addr = INADDR_ANY;int n = bind(_listenfd, (sockaddr *)&server_addr, sizeof(server_addr));/*打开监听*/static const int backlog = 10;// 允许10个客户端连接n = listen(_listenfd, backlog);}void Run(){_isrunning = true;while (_isrunning){sockaddr_in client_addr;socklen_t len = sizeof(client_addr);int socketfd = accept(_listenfd, (sockaddr *)&client_addr, &len);pid_t pid = fork();if (pid == 0){close(_listenfd);if (fork() >0)exit(0);work(socketfd);exit(0);}elseclose(socketfd);}_isrunning = false;}void work(int sockfd){while (true){char buffer[1024];int n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);if (n <= 0){close(sockfd);return;}else{buffer[n] = '\0';std::cout <<"Client Say@ \n" << buffer;}}}~Server(){}private:uint16_t _port;int _listenfd;bool _isrunning;};

注意:以上代码只列出来核心部分,省略了返回值的有效性判断,在实战开发中不可省。

#include "server.hpp"
#include <memory>int main(int argc,char* argv[]){if(argc!=2){std::cout<<"Usage "<<argv[0]<<" port"<<std::endl;return 1;}std::unique_ptr<Server>sv(new Server(std::stoi(argv[1])));sv->Run();return 0;}

Makefile:

server:main.cc
g++ -o $@ $^ -std=c++17
.PHONY:clean
clean:
rm -rf server

在这里插入图片描述
  这里可以不用makefile工具,直接执行指令g++ -o server main.cc -std=c++17,服务启动后到浏览器搜索网址:服务器ip:端口号,如:117.199.66.239:7171
因为在以上函数work中我们将从客户端的请求接收后进行输出,所以在终端我们可以看到这样的结果:
在这里插入图片描述

三、HTTP响应报文格式

在这里插入图片描述

状态行:

  • HTTP版本:客户端会与服务器做HTTP版本交换,服务器方便给客户端提供对应版本的服务。
  • 状态码:标记请求的处理情况,比如200表示成功、404表示找不到资源…
  • 状态码描述:描述请求的处理情况,比如“Success”"Not Found"

后面的响应报头,空行和响应正文的作用和HTTP请求报文相同。
以上是HTTP协议请求和应答的报文格式,接下来我们通过程序设计的过程,详细了解里面的每一个元素:

四、HTTP服务搭建

  在上面TCP服务的基础上添加文件http.hpp,用来做协议相关的类方法的实现。接下来的操作都是在该文件中完成。
首先准备一个类或结构体用来储存报文中的各个元素:

typedef struct httpRequest //请求报文
{
//请求行
string _method;
string _uri;
string _versions;
//头部字段
unordered_map<string, string> _head_data;//请求正文string _text;} httpRequest;typedef struct httpResponse //响应报文{//状态行string _versions;int _state_code;string _state;//头部字段unordered_map<string, string> _head_data;//响应正文string _text;} httpResponse;

接下来封装http类,主要的结构如下:

class Http
{
public:
bool Deserilize(string src) //解包(反序列化)
{
}
string Serilize() //封包(序列化)
{
}
//报头各个元素的设置方法和获取方法.....
httpRequest _req;
// 请求报头
httpResponse _rsp;
// 响应报头
};

解包(反序列化):

const string blank = " ";
const string mark = "\r\n";
const string kv_mark = ": ";
inline bool GetOneline(string &out, string &src)
{
int pos = src.find(mark);
if (pos == string::npos)
return false;
out = src.substr(0, pos);
src = src.erase(0, pos + mark.size());
return true;
}
bool Deserilize(string src) // 传入一个完整的http报文
{
// 提取请求行
string out;
GetOneline(out, src);
// 解包请求行
stringstream ss(out);
ss >> _req._method >> _req._uri >> _req._versions;
// 提取并解包头部字段
out.clear();
while (out != "")
{
int pos = out.find(kv_mark);
if (pos == string::npos)
return false;
_req._head_data[out.substr(0, pos)] = out.substr(pos + kv_mark.size());
out.clear();
GetOneline(out, src);
}
// 提取请求正文
_req._text = src;
return true;
}

封包(序列化)

string Serilize()
{
// 响应行
string out;
out += _rsp._versions + blank + to_string(_rsp._state_code) + blank + _rsp._state;
out += mark;
// 头部字段
for (auto [key, val] : _rsp._head_data)
out += key + kv_mark + val + mark;
out += mark;
// 空行
// 响应正文
out += _rsp._text;
return out;
}

4.1 uri资源获取

  我们从请求报文中提取到uri后需要做的是把uri对应下的文件资源读取并发送给客户端。该资源通常是.html格式的一个网页信息,或者图片,或视频。
  访问服务器时若未指明具体 uri,则默认 uri 为‘/’;注意此处的‘/’不指向 Linux 系统的根目录,而是指向我们自定义的 Web 根目录(即存放网页资源的目录)。

如果用户访问一个不存在的uri资源呢?该情况我们通常会返回给用户一个404页面,表示该资源找不到。

const string webroot = "./wwwroot";
//设置web根目录,这里设为当前目录下的wwwroot目录
const string home_page = "index.html";
//把这个当做网页首页(即默认资源)
const string login_page = "login.html";
const string register_page = "register.html";
const string errofile = "404.html";

目录结构:

.
├── http.hpp
├── Makefile
├── server.cc
├── server.hpp
└── wwwroot
├── 404.html
├── index.html
├── login.html
└── register.html

这些页面我们只是拿来做测试,直接让AI生成即可。
  业务处理的本质,就是构建并填写 HTTP 响应报文,接下来我们分别做出HTTP协议的各字段的函数。
  下面会给出响应正文的设置逻辑,关于HTTP版本我们默认使用HTTP/1.1,状态码仅考虑200和404,而关于头部字段和其他状态码在后文进行讲解和设置。

string setText()
{
string file;
if (_req._uri == "/")
file = webroot + _req._uri + homepage;
else
file = webroot + _req._uri;
// 检查uri文件是否存在
ifstream in(file);
if (!in.is_open())
{
//不存在则返回给用户404页面
file = webroot + '/' + errofile;
_req._uri = errofile;
ifstream in(file);
if(!in.is_open()) return;
}
// 将文件内容写入响应正文
in.seekg(0, in.end);
int size = in.tellg();
in.seekg(0, in.beg);
_rsp._text.resize(size);
in.read((char *)(_rsp._text.c_str()), size);
in.close();
return file;
}
void setVersion(string ver = "HTTP/1.1")
{
_rsp._versions = ver;
}
void setCode(int code)
{
_rsp._state_code = code;
if (code == 200)
_rsp._state = "Success";
else
_rsp._state = "Not Found";
}

新增文件task.hpp用来做任务分配:

#include "http.hpp"
#include <iostream>#include <unistd.h>class Task{public:Task(int clientfd): _clientfd(clientfd){}void task(){while (true){// 接收报文char buffer[1024];int n = recv(_clientfd, buffer, sizeof(buffer)-1, 0);if (n <= 0){// 客户端退出或读取错误close(_clientfd);return;}else{buffer[n] = '\0';std::cout <<"Client Request@ \n" << buffer;_recvBuffer += buffer;}// 报文完整性检查,并提取一个完整报文// 省略......// 解包,这里应该从_recvBuffer中提取单个完整的报文,这里就省略_http.Deserilize(_recvBuffer);// 业务分发处理(本质填_http._rsp成员)// ......_http.setVersion("HTTP/1.1");string file = _http.setText();if(_http._req._uri == errofile) _http.setCode(404);else _http.setCode(200);// 封包string out = _http.Serilize();_sendBuffer += out;// 发送报文std::cout <<"\n" <<"Server Response@ \n" << _sendBuffer << std::endl;send(_clientfd, _sendBuffer.c_str(), _sendBuffer.size(), 0);}}private:Http _http;// 发送缓冲区string _sendBuffer;// 接收缓冲区string _recvBuffer;int _clientfd;};

其他类型的文件
在这里插入图片描述

4.2 头部字段

Content-Length
Content-Length:用来标记正文部分的长度,通常都要被设置,方便报文之间分离。上文中没有设置也能被浏览器解析,是因为浏览器太强大了。
函数设计:

void setHeader(string key, string val)
{
_rsp._head_data.insert({key, val
});
}

设置Content-Length字段:

  • _http.setHeader("Content-Length",to_string(_http._rsp._text.size()));

Content-Type
现在我们尝试让网页显示图片,把本地图片上传的服务器,然后在index.html中添加相应的代码。
目录结构变化:

.
├── http.hpp
├── main.cc
├── Makefile
├── server.hpp
├── task.hpp
└── wwwroot
├── 404.html
├── image
│     ├── 1.png
│     └── 2.jpg
├── index.html
├── login.html
└── register.html

添加标签:
在这里插入图片描述
重新启动服务,再次访问网页,可以发现图片是无法正常显示的。
主要有两点:

  1. 图片属于二进制文件(而非文本文件),读取时需以二进制模式打开文件;TCP 协议本身是面向字节流的,传输时不区分文本与二进制。
  2. 在浏览器解析响应正文时默认把它当做文本进行解析。所以需要告诉客户端这个属于什么类型的资源,是图片,文本,还是视频,这样客户端就能做出正确的解析。

  以上代码使用read函数读取文件字节流,恰好满足二进制文件的传输需求;资源类型是通过文件名后缀来确定的,如.png.jpg是图片,.html是文本。然后通过Content-Type字段来告诉客户端,如果没有该字段,默认为文本资源即text/html

Content-Type对照表:https://tool.oschina.net/commons
部分截图:
在这里插入图片描述
判断资源类型:

std::string UriSuffix(std::string &targetfile)
{
int pos = targetfile.rfind(".");
if (pos == string::npos)
return "text/html";
string str = targetfile.substr(pos);
if (str == ".html" || str == ".htm")
return "text/html";
else if (str == ".txt")
return ".txt";
else if (str == ".png")
return "image/png";
else if (str == ".jpg" || str == ".jpeg")
return "image/jpeg";
//......
else
return "";
}

在实战开发中需要把整个对应表填写到代码中,这里只是提取了一部分。
设置响应正文后设置资源类型:

  • string file = _http.setText();
  • _http.setHeader("Content-Type", _http.UriSuffix(file));

效果:
在这里插入图片描述

这样就能正常访问图片,视频等非文本资源了。

  • 注意:在访问一个网页中如果有图片或视频等特殊资源,浏览器会再次为此单独发起请求。例如一个网页中有3张图片,那么浏览器会向服务器发起4次请求。

Connection
  在早期HTTP协议中,客户端与服务器使用短连接(短服务)的形式进行交互的,也就是完成一次交互(请求)就会断开连接,如果要发起新的请求,就需要重新建立连接。如果网页有10张图片,那么就需要进行11次三次握手,和11次四次挥手。那么有更多图片或其他资源呢?
  可以看出来短连接的交互方式非常繁琐。不过在HTTP/1.1版本的默认交互方式变成了
长连接
,也可以通过字段Connection来表示是否支持长连接。

  • Connection:keep-alive表示支持长连接
  • Connection:close表示不支持长连接

  对于服务器来说是否支持长连接是我们自己决定的,在拿到客户端套接字后,给他分配执行流等待循环等待客户端请求到来不断开就是长连接。拿到客户端套接字后,处理完一次请求就断开连接,这种方式就是短连接。

  • 注意:HTTP协议是一个无连接,无状态的协议,即每次请求都要建立新的理解,服务器不会记录客户端状态信息。所以某个资源无论你是否已经获取到了,只有你继续访问服务器还是会返回给你。

这种无连接,无状态的特点也会给用户带来困扰,比如一个网站每次访问都要输入账号和密码或验证码,很繁琐。如何解决呢?
虽然HTTP协议无状态,但浏览器不是,浏览器可以把你频繁访问的资源进行缓存,不用每次都去访问服务器,也提供了一种cookie-session功能,会帮你记录某个网站的登录状态信息,不至于你每次访问都需要输入账号和密码。

关于cookie-session也需要是通过头部字段来完成信息交互的,这里就不扩展,我们继续往下学习:
Referer

在这里插入图片描述
  浏览器的“后退 / 前进”按钮功能,是通过维护历史访问的 uri 记录实现的,当浏览器从该页面点击访问服务器时,请求报文的头部字段会带上Referer:当前uri路径来告诉服务器该请求是来源于那个网页的。

User-Agent

  通常客户端请求会携带User-Agent字段,该字段包含了客户端浏览器和版本信息、引擎信息、操作系统信息、设备类型等。服务器可能会据此提供不同的服务,比如服务器识别你的设备是手机就给你app下载包,而不是windows下载包。

  • 注意:一些钓鱼网站可能会利用这些信息,要注意防范。

最简单的请求仅需一个请求行即可完成,这意味着在减少信息暴露的前提下,可通过客户端实现网页抓取。

  • 爬虫的本质:模拟浏览器行为,获取指定连接下的网页。
  • 反爬机制:部分服务器会校验 HTTP 请求的完整性(如是否携带User-AgentReferer等字段),若请求不完整(例如缺少User-Agent),则会判定为非法请求并拒绝响应。
  • 绕过防爬机制:伪造User-Agent等信息。

搜索引擎本质就是爬虫,把首页连接和里面的部分信息留下来呈现给用户。

Location
该字段用来做重定向地址,在下文讲解

4.3 状态码与状态描述

在状态码中1开头,2开头,…,它们代表着不同的类别,如下:

-类别原因短语
1XXInformational(信息性状态码)接收的请求正在处理
2XXSuccess(成功状态码)请求正常处理完毕
3XXRedirection(重定向状态码)需要进行附加操作以完成请求
4XXClient Error(客户端错误状态码)服务器无法处理请求
5XXServer Error(服务器错误状态码)服务器处理请求出错

状态码对照表:https://tool.oschina.net/commons?type=5
部分截图:在这里插入图片描述
常用状态码即含义:

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

优化setCode函数

void setCode(int code)
{
_rsp._state_code = code;
switch (code)
{
case 200:
_rsp._state = "Success";
break;
case 404:
_rsp._state = "Not Found";
break;
// 永久重定向
case 301:
_rsp._state = "Moved Permanently";
// 临时重定向
case 302:
_rsp._state = "See Other";
//......
default:
_rsp._state = "None";
break;
}
}

这里我们重点来学习一下重定向功能:

  • 临时重定向:这个地址临时改变,以后还会回来。如注册登录或csdn等各app平台上刚点进去就进行广告跳转。下一次访问还是去原地址。
  • 永久重定向:地址永久改变。第一次访问进行跳转,再次访问就直接去改变后的地址。

临时重定向和永久重定向在用户使用上是感受不出来区别的。那么区分两种重定向的意义在于什么?永久重定向最大的价值就是用来给搜索引擎(浏览器)更新网址,下一次搜索时,搜索引擎就会直接提供新地址的链接。
换一个角度来说,临时重定向主要作用于用户,永久重定向主要作用于浏览器。
  重定向操作只需要我们将响应报文的状态码设置为301302,然后添加头部字段Location:新uri来提供新地址。但客户端收到报文后检查状态码,发现是3xx后会提取Location字段,用新地址再次发起请求。因此重定向的响应报文通常都没有正文部分。
  客户端发起一个无效的uri资源路径请求时,我们就不用做哪些复杂的操作,直接重定向到404页面即可。

  • 注意1:一个浏览器页面就像以首页为根的多叉树,请求的再次发起通常是通过点击页面链接,而不是直接输入uri地址,所以404情况很少出现。
  • 注意2:在部分开发场景中,状态码的规范使用可能未被严格重视,存在不按标准填写的情况,因此排查网页问题时,不能仅以状态码作为唯一依据,还需结合请求日志、响应内容等进一步分析。而且5开头的状态码会很少见,尽管确实是服务器出问题。因为这会暴露自己服务器短板,被黑客利用。

4.4 GET和POST

  • GET:从远端获取内容,也可以把资源上传到远端。
  • POST:把资源上传到远端。

如上,GETPOST都能把资源上传到远端,那么它们的区别是什么,POST的意义何在?
事实上它们上传的方式是不同的,GET通过uri上传,而POST通过请求正文上传。所以GET上传不了大的资源,通常是登陆和注册的时候用。

测试:
接下来我们往首页里添加登陆和注册功能,完善login.html文件做一个提交表单。(这一部交给AI即可)
在这里插入图片描述
访问网站,跳转到登陆页面,提交登陆信息:
在这里插入图片描述
POST方法:
在这里插入图片描述
GET方法:
在这里插入图片描述
  关于action参数这里填写了login,也可以填资源地址,取决于自己怎么处理。示例login相当于一个微服务,怎么理解呢?
  可以做一个用来管理各个服务的数据结构(类似函数指针管理),然后提前注册一个login服务(把函数添加到管理结构中),主要做账号密码与数据库比对,然后根据不同的结果重定向到不同的资源路径。
  服务器接收到请求后提取到login,判断不是资源路径,然后去匹配服务调用对应的接口。register等服务也是类似的操作。

  • 注意:如上图片,用GET请求方法时,账号和密码是回显在搜索窗口的,HTTP协议并不安全,相当于数据在网络中裸奔。基本上没人用了,而是使用HTTPS协议HTTPSHTTP 的安全升级版,其本质是在 HTTP 下层加入了一个安全层(SSL/TLS协议),为数据传输提供加密、认证和完整性校验。

HTTPS协议学习:
从明文裸奔到密钥长城:HTTPS加密全链路攻防与CA信任锚点构建

非常感谢您能耐心读完这篇文章。倘若您从中有所收获,还望多多支持呀!在这里插入图片描述

五、源码

  1. http.hpp
#include <unordered_map>#include <string>#include <sstream>#include <fstream>#include <sys/socket.h>using namespace std;const string blank = " ";const string mark = "\r\n";const string kv_mark = ": ";const string webroot = "./wwwroot";const string homepage = "index.html";const string errofile = "404.html";typedef struct httpRequest{string _method;string _uri;string _versions;unordered_map<string, string> _head_data;string _text;} httpRequest;typedef struct httpResponse{string _versions;int _state_code;string _state;unordered_map<string, string> _head_data;string _text;} httpResponse;class Http{public:// 解包(反序列化)inline bool GetOneline(string &out, string &src){int pos = src.find(mark);if (pos == string::npos)return false;out = src.substr(0, pos);src = src.erase(0, pos + mark.size());return true;}bool Deserilize(string src) // 只有一个完整的http报头{// 提取请求行string out;GetOneline(out, src);// 解包请求行stringstream ss(out);ss >> _req._method >> _req._uri >> _req._versions;// 提取并解包请求报头out.clear();while (out != ""){int pos = out.find(kv_mark);if (pos == string::npos)return false;_req._head_data[out.substr(0, pos)] = out.substr(pos + kv_mark.size());out.clear();GetOneline(out, src);}// 提取请求正文_req._text = src;return true;}// 封包(序列化)string Serilize(){// 响应行string out;out += _rsp._versions + blank + to_string(_rsp._state_code) + blank + _rsp._state;out += mark;// 响应报头for (auto [key, val] : _rsp._head_data){out += key + kv_mark + val + mark;}out += mark;// 空行// 响应正文out += _rsp._text;return out;}std::string UriSuffix(std::string &targetfile){int pos = targetfile.rfind(".");if (pos == string::npos)return "text/html";string str = targetfile.substr(pos);if (str == ".html" || str == ".htm")return "text/html";else if (str == ".txt")return ".txt";else if (str == ".png")return "image/png";else if (str == ".jpg" || str == ".jpeg")return "image/jpeg";else//......return "";}string setText(){string file;if (_req._uri == "/")file = webroot + _req._uri + homepage;elsefile = webroot + _req._uri;// 检查uri文件是否存在ifstream in(file);// if (!in.is_open())// {// file = webroot + '/' + errofile;// _req._uri = errofile;// ifstream in(file);// if(!in.is_open()) return "";// }if (!in.is_open())return "";// 将文件内容写入响应正文in.seekg(0, in.end);int size = in.tellg();in.seekg(0, in.beg);_rsp._text.resize(size);in.read((char *)(_rsp._text.c_str()), size);in.close();return file;}void setVersion(string ver = "HTTP/1.1"){_rsp._versions = ver;}void setCode(int code){_rsp._state_code = code;switch (code){case 200:_rsp._state = "Success";break;case 404:_rsp._state = "Not Found";break;// 永久重定向case 301:_rsp._state = "Moved Permanently";// 临时重定向case 302:_rsp._state = "See Other";//......default:_rsp._state = "None";break;}}void setHeader(string key, string val){_rsp._head_data.insert({key, val});}httpRequest _req;// 请求报头httpResponse _rsp;// 响应报头};
  1. main.cc
#include "server.hpp"
#include <memory>int main(int argc,char* argv[]){if(argc!=2){std::cout<<"Usage "<<argv[0]<<" port"<<std::endl;return 1;}std::unique_ptr<Server>sv(new Server(std::stoi(argv[1])));sv->Run();return 0;}
  1. Makefile
server:main.cc
g++ -o $@ $^ -std=c++17
.PHONY:clean
clean:
rm -rf server
  1. server.hpp
#include <iostream>#include <string>#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <netinet/ip.h>#include <arpa/inet.h>#include "task.hpp"class Server{public:Server(int port): _port(port), _isrunning(false){/*打开套接字*/_listenfd = socket(AF_INET, SOCK_STREAM, 0);/*绑定端口*/sockaddr_in server_addr;server_addr.sin_family = AF_INET;// 设置为IPv4// 主机序列->网络序列server_addr.sin_port = htons(_port);server_addr.sin_addr.s_addr = INADDR_ANY;int n = bind(_listenfd, (sockaddr *)&server_addr, sizeof(server_addr));/*打开监听*/static const int backlog = 10;// 允许10个客户端连接n = listen(_listenfd, backlog);}void Run(){_isrunning = true;while (_isrunning){sockaddr_in client_addr;socklen_t len = sizeof(client_addr);int socketfd = accept(_listenfd, (sockaddr *)&client_addr, &len);pid_t pid = fork();if (pid == 0){close(_listenfd);if (fork() >0)exit(0);Task tk(socketfd);tk.task();//work(socketfd);exit(0);}elseclose(socketfd);}_isrunning = false;}// void work(int sockfd)// {// while (true)// {// char buffer[1024];// int n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);// if (n <= 0)// {// close(sockfd);// return;// }// else// {// buffer[n] = '\0';// std::cout << "Client Say@ \n" << buffer;// }// }// }~Server(){}private:uint16_t _port;int _listenfd;bool _isrunning;};
  1. task.hpp
#include "http.hpp"
#include <iostream>#include <unistd.h>class Task{public:Task(int clientfd): _clientfd(clientfd){}void task(){while (true){// 接收报文char buffer[1024];int n = recv(_clientfd, buffer, sizeof(buffer) - 1, 0);if (n <= 0){// 客户端退出或读取错误close(_clientfd);return;}else{buffer[n] = '\0';std::cout <<"Client Request@ \n" << buffer;_recvBuffer += buffer;}// 报文完整性检查,并提取一个完整报文// 省略......// 解包,这里应该从_recvBuffer中提取单个完整的报文,这里就省略_http.Deserilize(_recvBuffer);// 业务分发处理(本质填_http._rsp成员)// ......_http.setVersion("HTTP/1.1");string file = _http.setText();// if(_http._req._uri == errofile) _http.setCode(404);// else _http.setCode(200);if(file.empty()){_http.setHeader("Location", "/404.html");_http.setCode(302);}//else if......else _http.setCode(200);_http.setHeader("Content-Length", _http.UriSuffix(file));_http.setHeader("Content-Type",to_string(_http._rsp._text.size()));// 封包string out = _http.Serilize();_sendBuffer += out;// 发送报文std::cout <<"\n" <<"Server Response@ \n" << _sendBuffer << std::endl;send(_clientfd, _sendBuffer.c_str(), _sendBuffer.size(), 0);}}private:Http _http;// 发送缓冲区string _sendBuffer;// 接收缓冲区string _recvBuffer;int _clientfd;};
  1. wwwroot/404.html
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>404 Not Found</title><script src="https://cdn.tailwindcss.com"></script><link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"></head><body class="bg-gray-50 min-h-screen flex flex-col items-center justify-center text-gray-800"><h1 class="text-9xl font-bold mb-4">404</h1><p class="text-xl mb-8">Page not found</p></a></body></html>
  1. wwwroot/index.html
<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>简单网页</title><style>body {font-family: Arial, sans-serif;max-width: 800px;margin: 0 auto;padding: 20px;background-color: #f0f8ff;color: #333;line-height: 1.6;}h1 {color: #2c3e50;text-align: center;border-bottom: 2px solid #3498db;padding-bottom: 10px;}.auth-links {text-align: right;margin-bottom: 20px;}.auth-links a {margin-left: 15px;color: #3498db;text-decoration: none;}.auth-links a:hover {text-decoration: underline;}.content {background: white;padding: 20px;border-radius: 8px;box-shadow: 0 0 10px rgba(0,0,0,0.1);margin-top: 20px;}.footer {text-align: center;margin-top: 30px;color: #7f8c8d;font-size: 0.9em;}</style></head><body><div class="auth-links"><a href="login.html">登录</a><a href="#">注册</a></div><h1>欢迎访问我的网页</h1><div class="footer"><p>© 2025 csdn敲上瘾</p></div></body></html>
  1. wwwroot/login.html
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>登录</title><style>body {font-family: Arial, sans-serif;max-width: 400px;margin: 50px auto;padding: 20px;background-color: #f0f8ff;}.login-box {background: white;padding: 30px;border-radius: 8px;box-shadow: 0 0 10px rgba(0,0,0,0.1);}h2 {text-align: center;color: #2c3e50;}.input-group {margin-bottom: 15px;}label {display: block;margin-bottom: 5px;}input {width: 100%;padding: 8px;box-sizing: border-box;border: 1px solid #ddd;border-radius: 4px;}button {width: 100%;padding: 10px;background-color: #3498db;color: white;border: none;border-radius: 4px;cursor: pointer;}button:hover {background-color: #2980b9;}.links {text-align: center;margin-top: 15px;}a {color: #3498db;text-decoration: none;}</style></head><body><div class="login-box"><h2>用户登录</h2><form action="/login" method="post"><div class="input-group"><label for="username">用户名</label><input type="text" id="username" name="username" required></div><div class="input-group"><label for="password">密码</label><input type="password" id="password" name="password" required></div><button type="submit">登录</button><div class="links"><a href="#">忘记密码?</a> | <a href="#">注册账号</a></div></form></div></body></html>
  1. image/
    省略

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/917088.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网站建设课程简介枣庄手机网站建设公司

超精密光学3D测量仪器具有高精度、自动化程度高、实时反馈和范围广等优势。它能够实现微米级别的精确测量&#xff0c;能够精确测量产品的尺寸、形状和表面粗糙度等&#xff0c;具有广泛的应用价值和重要意义。 超精密光学3D测量仪器配备多种传感器、控制器和计算机系统&#…

超快轻量级离线翻译服务器MTranServer在腾讯云轻量应用服务器上的全流程部署指南 - 实践

超快轻量级离线翻译服务器MTranServer在腾讯云轻量应用服务器上的全流程部署指南 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important…

微算法科技(NASDAQ: MLGO)利用高级 Blowfish 加密标准实现区块链集成信息共享

在当今数字化时代,信息的安全与高效共享愈发重要。区块链技术的兴起为分布式数据存储与共享提供了可靠的架构,但数据安全始终是核心关注点。微算法科技(NASDAQ: MLGO)敏锐洞察到此需求,鉴于传统加密方式存在一定局…

专业讲解大模型登记(纯干货)

专业讲解大模型登记(纯干货)一、为什么要做大模型上线登记? 对通过API接口或其他方式直接调用已备案大模型能力,且面向境内公众提供具有舆论属性或者社会动员能力的生成式人工智能服务开展登记工作。 二、 我应该做…

Spring / Spring Boot 常用注解 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

实用指南:【Cesium 开发实战教程】第六篇:三维模型高级交互:点击查询、材质修改与动画控制

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

中国做网站的公司有哪些优化的网站做域名跳转

引子?最近&#xff0c;忽然想起曾在 WebGL 基础系列 文章中立下 flag&#xff1a;“后续还打算出 《ThreeJS 源码剖析》 系列”(特意翻出原话?)&#xff0c;项目忙了一阵后&#xff0c;便决定开始写此系列&#xff0c;更新周期不固定&#xff0c;毕竟项目排期“天晓得”。此系…

转载 - Heterogeneous Memory Management (HMM) - (待翻译)

转载 - Heterogeneous Memory Management (HMM) - (待翻译)Heterogeneous Memory Management (HMM) Provide infrastructure and helpers to integrate non-conventional memory (device memory like GPU on board me…

Docker常用命令速查

一、镜像相关命令命令 说明docker build -t <镜像名>:<标签> . 根据 Dockerfile 构建镜像docker images 或 docker image ls 查看本地镜像列表docker rmi <镜像ID或名字> 删除本地镜像docker pull &…

MX 练石 2025 NOIP #9

, Rank .继续怒砍 25pts!2025 --【炼石计划 NOIP】-- 第九套 链接: 题解: 时间:4.5h (2025.09.25 07:40~12:10) 题目数:4 难度:估分:20 + 0 + 5 + ? = 25 + ? 得分: Rank:场祭 读题。 草咋这么难。 A,推了…

深入解析:gpt-4o+deepseek+R生成热力图表

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

PostgreSQL 的索引Ooracle、Mysql索引的类型对比和说明

PostgreSQL 的索引Ooracle、Mysql索引的类型对比和说明 目录PostgreSQL 的索引Ooracle、Mysql索引的类型对比和说明一、索引类型对比概览通用索引类型特定数据库索引类型二、索引类型对比总览三、使用案例PostgreSQLOr…

Docker打包CMake项目镜像操作步骤

步骤 1:创建项目目录结构 HelloWorldProject/ ├── CMakeLists.txt ├── Dockerfile └── HelloWorld.cpp步骤 2:编写 HelloWorld.cpp 在 HelloWorld.cpp 中写: #include <iostream>int main() {std::c…

Linux dmesg 内核日志查看工具详解

Linux dmesg 内核日志查看工具详解dmesg 是 CentOS 系统中一个非常重要的内核日志查看工具,它直接读取内核的环形缓冲区,为你提供了系统底层运作的“第一手资料”。下面这个表格汇总了它的核心应用场景和排查问题的基…

网站的基础建设项目大型定制网站最贵建设多少钱

VTK下载地址&#xff1a;https://vtk.org/download/ cmake下载地址&#xff1a;https://cmake.org/download/ 版本对应方面&#xff0c;如果你的项目对版本没有要求&#xff0c;就不用在意。我就是自己随机搭建的&#xff0c;VTK选择最新版本吧&#xff0c;如果后面其他的库不…

专业营销网站计算机网络技专业术网站开发

问题 使用qtdesigner设计好大体的软件结构&#xff0c;需要使用代码进行批量修改控件样式,self.ui.x 会被解释为访问 self.ui 中名为 x 的属性&#xff0c;而不是将 x 作为变量名来解析&#xff0c;此时需要通过字符串动态访问 self.ui 中的按钮对象 for i in range(20):x f…

【智慧】 gym104385

qwq题目注意到 \(b_i\leq n\),所以对于每个询问 \(a\) 只用暴力看对称轴在 \([a-\sqrt{n},a+\sqrt{n}]\) 范围内的抛物线。

__repr__魔术方法

__repr__ 是 Python 中一个非常重要的魔术方法(Magic Method),也常被称为双下划线方法 (Dunder Method)。它的主要作用是为对象提供一个官方的、明确的、面向开发者的字符串表示形式(Representation)。 简单来说,…

基于萤火虫算法(FA)优化支持向量机(SVM)参数的分类实现

一、算法原理 1. 萤火虫算法(FA)核心机制亮度计算:萤火虫亮度与目标函数值(SVM分类准确率)成正比(I0为初始亮度,γ为光吸收系数,ri为当前解与最优解的距离)位置更新:(β为吸引度,rij为萤火虫i与j的距离,α…

OSS cp(下载文件)

alfolder/ --only-current-dir -r下载文件夹(指定时间戳) 当某一个文件夹(包含子目录)下的文件修改时间在北京时间2023年10月31日10:09:18至2023年10月31日12:55:58内时,才会被下载。示例如下: ossutil cp -r os…