Linux 网络编程实战:HTTP 协议解析与服务器实现
URL 基础
URL(统一资源定位符)是我们访问网页的地址。它通常包含协议、域名、路径和参数等部分。
如果在参数中出现特殊字符如 ? 或 /,系统会自动进行转义处理。例如 + 会被转义为 %2b。理解 URL 的编码规则对于调试网络请求至关重要。
HTTP 协议格式
请求格式
一个标准的 HTTP 请求由三部分组成:
- 请求行:包含方法、URL 和版本号,以
\r\n结尾。 - 请求报头:每行一对键值对,同样以
\r\n结尾。 - 正文:可选部分。如何确定正文大小?通常通过报头中的
Content-Length字段来判断。
如果没有空行分隔报头和正文,服务器将无法正确解析数据。
HTTP 常用方法
常见的请求方法包括 GET 和 POST。GET 用于获取资源,POST 用于提交数据。
响应格式
响应结构与请求类似,包含状态行、响应报头和响应正文。
常见状态码
- 200 (OK): 请求成功。
- 404 (Not Found): 资源未找到。
- 403 (Forbidden): 禁止访问。
- 302 (Redirect): 重定向。
- 504 (Bad Gateway): 网关超时。
HTTP 常见 Header
- Content-Type: 数据类型,如
text/html。 - Content-Length: Body 的长度。
- Host: 客户端告知服务器请求的资源位于哪个主机及端口。
- User-Agent: 声明用户的操作系统和浏览器版本信息。
- Referer: 当前页面是从哪个页面跳转过来的。
- Location: 搭配 3xx 状态码使用,告诉客户端接下来要去哪里访问。
- Cookie: 用于在客户端存储少量信息,通常用于实现会话(Session)功能。
模拟实现 HTTP 服务器
为了深入理解 HTTP 协议,我们尝试用 C++ 模拟一个简单的 HTTP 服务器。项目结构大致如下:
project/
├── socket.hpp
├── log.hpp
├── server.hpp
├── server.cc
└── wwwroot/
├── index.html
└── hello.html
Socket 封装
首先封装一个基础的 Socket 类,处理连接建立、绑定和监听。
// socket.hpp
#pragma once
#include
std;
Log lg;
backlog = ;
{ SocketErr = , BindErr, ListenErr };
{
:
() {}
~() {}
{
sockfd_ = (AF_INET, SOCK_STREAM, );
(sockfd_ < ) {
(Fatal, , (errno), errno);
(SocketErr);
}
}
{
local;
(&local, , (local));
local.sin_family = AF_INET;
local.sin_port = (port);
local.sin_addr.s_addr = INADDR_ANY;
((sockfd_, ( sockaddr*)&local, (local)) < ) {
(Fatal, , (errno), errno);
(BindErr);
}
}
{
((sockfd_, backlog) < ) {
(Fatal, , (errno), errno);
(ListenErr);
}
}
{
peer;
len = (peer);
newfd = (sockfd_, ( sockaddr*)&peer, &len);
(newfd < ) {
(Warning, , (errno), errno);
;
}
ipstr[];
(AF_INET, &peer.sin_addr, ipstr, (ipstr));
*ip = ipstr;
*port = (peer.sin_port);
newfd;
}
{
peer;
(&peer, , (peer));
peer.sin_family = AF_INET;
peer.sin_port = (port);
(AF_INET, ip.(), &(peer.sin_addr));
n = (sockfd_, ( sockaddr*)&peer, (peer));
(n < ) {
cerr << << ip << << port << endl;
;
}
;
}
{ (sockfd_); }
{ sockfd_; }
:
sockfd_;
};


