跳到主要内容 Linux 网络编程:理解 Web 路径与实现简单 HTTP 服务 | 极客日志
C++
Linux 网络编程:理解 Web 路径与实现简单 HTTP 服务 在 Linux 环境下使用 C++ 实现一个简单的 HTTP 服务器的过程。内容涵盖文件结构准备、Makefile 配置、Socket 封装、多线程处理请求以及 HTTP 响应格式编写。重点讲解了 Web 路径的概念,如何通过解析请求 URL 定位 Web 根目录下的资源文件,并提供了完整的代码示例,实现了基于文件的动态网页响应功能。
数字游民 发布于 2026/4/5 更新于 2026/4/13 3 浏览准备文件
首先准备以下文件:
其中 HttpServer.cc 用于运行接收 HTTP 请求的服务,HttpServer.hpp 用于定义 HTTP 请求,Log.hpp 为日志打印组件,Socket.hpp 为套接字组件。使用时直接调用相关接口即可。
Makefile
准备 Makefile 文件:
HttpServer: Httpserver.cc g++ -o $@ $^ -std=c++11 -g -lpthread
.PHONY : clean
clean: rm -rf HttpServer
HttpServer.hpp
类内成员
class HttpServer {
public :
HttpServer (uint16_t port = defaultport) : port_ (port) {}
static void * ThreadRun (void * args) { }
void start () { }
~HttpServer () {};
private :
Socket listensock_;
uint16_t port_;
};
类内成员变量包括端口号 port_,启动服务时输入端口号;listensock_ 用于接收主机请求。在初始化时可结合 accept 进行连接。ThreadRun 是线程执行方法,因需使用线程管理服务,故设为静态成员。
封装 sockfd
对 sockfd 进行封装:
struct ThreadData {
int sockfd;
};
目的是将 指针传给线程,使线程能获取 。由于 必须是静态成员函数,无法直接使用非静态成员 ,因此通过 对象传递。
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown 转 HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
HTML 转 Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online
ThreadData
sockfd
ThreadRun
sockfd
ThreadData*
start start 函数负责服务启动,流程为初始化、绑定、监听,随后循环接收请求。当有请求来时,建立连接获得 sockfd,封装后传给线程处理。
void start () {
listensock_.InitSocket ();
listensock_.Bind (port_);
listensock_.Listen ();
for (;;) {
string clientip;
uint16_t clientport;
int sockfd = listensock_.Accept (&clientip, &clientport);
pthread_t tid;
ThreadData* td = new ThreadData ();
td->sockfd = sockfd;
pthread_create (&tid, nullptr , ThreadRun, td);
}
}
ThreadRun 线程执行过程:创建缓冲区,从 sockfd 读取数据。
static void * ThreadRun (void * args) {
pthread_detach (pthread_self ());
ThreadData* td = static_cast <ThreadData*>(args);
char buffer[10240 ];
ssize_t n = read (td->sockfd, buffer, sizeof (buffer) - 1 );
if (n > 0 ) {
buffer[n] = 0 ;
cout << buffer;
}
close (td->sockfd);
delete td;
return nullptr ;
}
全部代码 #ifndef BE0E1813_421A_4BCD_A33B_77432A3CA8D7
#define BE0E1813_421A_4BCD_A33B_77432A3CA8D7
#include <iostream>
#include "Socket.hpp"
#include "Log.hpp"
#include <pthread.h>
static const int defaultport = 8080 ;
struct ThreadData {
int sockfd;
};
class HttpServer {
public :
HttpServer (uint16_t port = defaultport) : port_ (port) {}
static void * ThreadRun (void * args) {
pthread_detach (pthread_self ());
ThreadData* td = static_cast <ThreadData*>(args);
char buffer[10240 ];
ssize_t n = read (td->sockfd, buffer, sizeof (buffer) - 1 );
if (n > 0 ) {
buffer[n] = 0 ;
cout << buffer;
}
close (td->sockfd);
delete td;
return nullptr ;
}
void start () {
listensock_.InitSocket ();
listensock_.Bind (port_);
listensock_.Listen ();
for (;;) {
string clientip;
uint16_t clientport;
int sockfd = listensock_.Accept (&clientip, &clientport);
pthread_t tid;
ThreadData* td = new ThreadData ();
td->sockfd = sockfd;
pthread_create (&tid, nullptr , ThreadRun, td);
}
}
~HttpServer () {};
private :
Socket listensock_;
uint16_t port_;
};
#endif
运行结果 启动服务后,在浏览器输入服务器 IP 和端口号即可访问。服务端会接收到请求并显示相关信息。
这表明浏览器成功访问了创建的 HTTP 服务。其中的 User-Agent 字段包含了请求方机器的信息。
响应书写 static void HandlerHttp (int sockfd) {
char buffer[10240 ];
ssize_t n = recv (sockfd, buffer, sizeof (buffer) - 1 , 0 );
if (n > 0 ) {
buffer[n] = 0 ;
cout << buffer;
}
close (sockfd);
}
static void * ThreadRun (void * args) {
pthread_detach (pthread_self ());
ThreadData* td = static_cast <ThreadData*>(args);
HandlerHttp (td->sockfd);
delete td;
return nullptr ;
}
HandlerHttp 负责接收请求并返回响应。HTTP 响应包含响应行、报头、空行和正文。
static void HandlerHttp (int sockfd) {
char buffer[10240 ];
ssize_t n = recv (sockfd, buffer, sizeof (buffer) - 1 , 0 );
if (n > 0 ) {
buffer[n] = 0 ;
cout << buffer;
string text = "hello world" ;
string response_line = "HTTP/1.0 200 OK\r\n" ;
string response_header = "Content-Length: " ;
response_header += to_string (text.size ());
response_header += "\r\n" ;
string blank_line = "\r\n" ;
string response;
response += response_line;
response += response_header;
response += blank_line;
response += text;
send (sockfd, response.c_str (), response.size (), 0 );
}
close (sockfd);
}
运行后,浏览器请求可获取字符串资源 "hello world"。
Web 路径 HTTP 服务通常访问网页或图片。正文部分可以是 HTML 文档。
string text = "<html><body>hello world</body></html>" ;
text = "<html><body><h3>hello world</h3></body></html>" ;
访问资源带有路径,根目录为 Web 根目录。仅访问 IP+ 端口默认访问 /(Web 根目录);若加路径则访问对应 Web 路径下的资源。
之前的示例使用字符串作为正文,无论路径如何均返回相同内容。正统网页应为文件。
服务器响应请求过程:编写报头和请求行,将资源目录(Web 目录)拼接到正文。用户根据想要的资源访问相应路径。
Web 根目录可自定义,如当前目录下的 wwwroot 文件夹。
const string wwwroot = "./wwwroot" ;
以后网页、图片都放在该目录下。用户访问时以该目录为根节点,其下任何路径称为 Web 路径。
使用 read 函数读取网页文件,而非硬编码字符串。
static string ReadWebContent (string path) {
ifstream in (path) ;
if (!in.is_open ()) return "404" ;
string content;
string line;
while (getline (in, line)) {
content += line;
}
in.close ();
return content;
}
之后 text 可直接等于 ReadWebContent(某个路径)。路径来自用户请求的 URL。URL 在请求行的第二部分。提取路径后拼接 wwwroot,例如 ./wwwroot/a/b/c。
可创建配置文件 config 指定 Web 根目录,无需修改程序。
class Request {
public :
void Deserialize (string req) {
string tmp;
int pos = 0 ;
while (true ) {
pos = req.find (sep);
if (pos == string::npos) break ;
string temp = req.substr (0 , pos);
if (temp.empty ()) break ;
req_header.push_back (temp);
req.erase (0 , pos + sep.size ());
}
text = req;
DebugPrint ();
}
void DebugPrint () {
cout << "------------------------------------------------------------" << endl;
for (auto & e : req_header) {
cout << e << endl << endl;
}
cout << text << endl;
}
public :
vector<string> req_header;
string text;
};
处理根目录 / 的情况,应访问首页(如 index.html)。
#ifndef BE0E1813_421A_4BCD_A33B_77432A3CA8D7
#define BE0E1813_421A_4BCD_A33B_77432A3CA8D7
#include <iostream>
#include "Socket.hpp"
#include "Log.hpp"
#include <sstream>
#include <pthread.h>
#include <vector>
#include <fstream>
static const int defaultport = 8080 ;
const string wwwroot = "./wwwroot" ;
const string sep = "\r\n" ;
const string homepage = "index.html" ;
class HttpServer ;
struct ThreadData {
ThreadData (int sock) : sockfd (sock) {}
int sockfd;
};
class Request {
public :
void Deserialize (string req) {
string tmp;
int pos = 0 ;
while (true ) {
pos = req.find (sep);
if (pos == string::npos) break ;
string temp = req.substr (0 , pos);
if (temp.empty ()) break ;
req_header.push_back (temp);
req.erase (0 , pos + sep.size ());
}
parse ();
text = req;
DebugPrint ();
}
void DebugPrint () {
cout << "------------------------------------------------------------" << endl;
for (auto & e : req_header) {
cout << e << endl << endl;
}
cout << method << endl << url << endl << http_version << endl << path << endl;
cout << text << endl;
}
void parse () {
stringstream ss (req_header[0 ]) ;
ss >> method >> url >> http_version;
path = wwwroot;
if (url == "/" || url == "/index.html" ) {
path += "/" ;
path += homepage;
} else {
path += url;
}
}
public :
vector<string> req_header;
string text;
string method;
string url;
string http_version;
string path;
};
class HttpServer {
public :
HttpServer (uint16_t port = defaultport) : port_ (port) {}
static string ReadWebContent (string str) {
ifstream in (str) ;
if (!in.is_open ()) return "404" ;
string content;
string line;
while (getline (in, line)) {
content += line;
}
in.close ();
return content;
}
static void HandlerHttp (int sockfd) {
char buffer[10240 ];
ssize_t n = recv (sockfd, buffer, sizeof (buffer) - 1 , 0 );
if (n > 0 ) {
buffer[n] = 0 ;
Request req;
req.Deserialize (buffer);
req.DebugPrint ();
string text = ReadWebContent (req.path);
string response_line = "HTTP/1.0 200 OK\r\n" ;
string response_header = "Content-Length: " ;
response_header += to_string (text.size ());
response_header += "\r\n" ;
string blank_line = "\r\n" ;
string response;
response += response_line;
response += response_header;
response += blank_line;
response += text;
send (sockfd, response.c_str (), response.size (), 0 );
}
close (sockfd);
}
static void * ThreadRun (void * args) {
pthread_detach (pthread_self ());
ThreadData* td = static_cast <ThreadData*>(args);
HandlerHttp (td->sockfd);
delete td;
return nullptr ;
}
void start () {
listensock_.InitSocket ();
listensock_.Bind (port_);
listensock_.Listen ();
for (;;) {
string clientip;
uint16_t clientport;
int sockfd = listensock_.Accept (&clientip, &clientport);
pthread_t tid;
ThreadData* td = new ThreadData (sockfd);
td->sockfd = sockfd;
pthread_create (&tid, nullptr , ThreadRun, td);
}
}
~HttpServer () {};
private :
Socket listensock_;
uint16_t port_;
};
#endif