准备文件
首先准备以下文件:
- HttpServer.cc:用于运行接收 HTTP 请求的服务。
- HttpServer.hpp:定义 HTTP 请求相关类。
- Log.hpp:打印日志的小组件。
- Socket.hpp:套接字组件。
直接调用相关接口即可(Log.hpp 和 Socket.hpp 的实现细节不在此讲解)。
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_ 用于接收主机请求。ThreadRun 是线程执行的方法,用于管理服务。
封装 sockfd
为了将 sockfd 传给线程,使用 ThreadData 结构体封装:
struct ThreadData {
int sockfd;
};
ThreadRun 必须是静态成员函数才能作为线程执行方法。通过传递 ThreadData* 指针给线程,使其能访问 sockfd。
start
start 函数负责服务启动流程:初始化、绑定、监听,然后循环接收请求。建立连接后获取 sockfd,封装后传给线程处理。
void start() {
listensock_.InitSocket(); // 初始化 sockfd
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;
}
响应书写
可以将响应单独封装。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);
}
Web 路径
Web 根目录可以自定义,例如当前目录下的 wwwroot 文件夹。用户访问的 URL 路径对应服务器上的文件路径。
定义 Web 根目录:
const string wwwroot = "./wwwroot";
读取网页内容:
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;
}
解析请求路径:
class Request {
public:
void Deserialize(string req) {
string tmp;
int pos = 0;
const string sep = "\r\n";
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 parse() {
stringstream ss(req_header[0]);
ss >> method >> url >> http_version;
path = wwwroot;
if (url == "/" || url == "/index.html") {
path += "/";
path += homepage;
} else {
path += url;
}
}
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;
}
public:
vector<string> req_header;
string text;
string method;
string url;
string http_version;
string path;
};
完整代码示例:
#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";
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 parse() {
stringstream ss(req_header[0]);
ss >> method >> url >> http_version;
path = wwwroot;
if (url == "/" || url == "/index.html") {
path += "/";
path += homepage;
} else {
path += url;
}
}
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;
}
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 /* BE0E1813_421A_4BCD_A33B_77432A3CA8D7 */
通过以上步骤,我们实现了基于 C++ 的简单 HTTP 服务器,能够根据 Web 路径解析请求并返回对应的静态资源。

