当前位置: 首页 > news >正文

VC++ 打造小型HTTP服务器

一、概述

使用VC++实现一个轻量级HTTP服务器,支持基本的HTTP/1.1协议,能够处理静态文件请求、目录列表和简单的CGI脚本执行。服务器采用多线程架构,支持并发连接,适合作为嵌入式设备或小型应用的Web服务器。

二、系统架构

2.1 整体架构

graph TDA[客户端浏览器] -->|HTTP请求| B[主服务器]B -->|分发请求| C[工作线程1]B -->|分发请求| D[工作线程2]B -->|分发请求| E[工作线程3]C -->|文件请求| F[文件系统]D -->|CGI请求| G[CGI处理器]E -->|目录请求| H[目录列表生成器]

2.2 核心组件

  1. 主服务器模块:监听端口,接受连接,管理线程池
  2. 请求处理模块:解析HTTP请求,路由到相应处理器
  3. 文件服务模块:提供静态文件服务
  4. CGI处理器:执行简单脚本
  5. 目录列表模块:生成目录浏览页面
  6. 日志模块:记录访问日志和错误日志

三、核心源代码实现

3.1 主服务器类 (HttpServer.h)

#if !defined(AFX_HTTPSERVER_H__)
#define AFX_HTTPSERVER_H__#include <winsock2.h>
#include <ws2tcpip.h>
#include <vector>
#include <map>
#include <string>
#include <process.h>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <direct.h>#pragma comment(lib, "ws2_32.lib")#define DEFAULT_PORT 8080
#define MAX_CONNECTIONS 100
#define BUFFER_SIZE 4096
#define THREAD_POOL_SIZE 4class CHttpServer {
public:CHttpServer();~CHttpServer();bool Start(unsigned short port = DEFAULT_PORT);void Stop();private:static unsigned __stdcall WorkerThread(void* param);void ProcessRequest(SOCKET clientSocket);void SendResponse(SOCKET clientSocket, const std::string& status, const std::string& contentType, const std::string& content);void SendFile(SOCKET clientSocket, const std::string& filePath);void SendDirectoryListing(SOCKET clientSocket, const std::string& path);void ExecuteCGI(SOCKET clientSocket, const std::string& scriptPath);std::string GetContentType(const std::string& extension);std::string UrlDecode(const std::string& str);void LogRequest(const std::string& method, const std::string& path, const std::string& protocol, int statusCode);private:SOCKET m_listenSocket;bool m_running;unsigned short m_port;std::map<std::string, std::string> m_mimeTypes;
};#endif // AFX_HTTPSERVER_H__

3.2 主服务器实现 (HttpServer.cpp)

#include "HttpServer.h"
#include <iostream>
#include <algorithm>
#include <ctime>CHttpServer::CHttpServer() : m_listenSocket(INVALID_SOCKET), m_running(false), m_port(DEFAULT_PORT) {// 初始化MIME类型映射m_mimeTypes[".html"] = "text/html";m_mimeTypes[".htm"] = "text/html";m_mimeTypes[".txt"] = "text/plain";m_mimeTypes[".css"] = "text/css";m_mimeTypes[".js"] = "application/javascript";m_mimeTypes[".jpg"] = "image/jpeg";m_mimeTypes[".jpeg"] = "image/jpeg";m_mimeTypes[".png"] = "image/png";m_mimeTypes[".gif"] = "image/gif";m_mimeTypes[".ico"] = "image/x-icon";m_mimeTypes[".pdf"] = "application/pdf";m_mimeTypes[".zip"] = "application/zip";m_mimeTypes[".json"] = "application/json";m_mimeTypes[".xml"] = "application/xml";
}CHttpServer::~CHttpServer() {Stop();
}bool CHttpServer::Start(unsigned short port) {WSADATA wsaData;if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {std::cerr << "WSAStartup failed." << std::endl;return false;}m_port = port;m_listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (m_listenSocket == INVALID_SOCKET) {std::cerr << "Socket creation failed: " << WSAGetLastError() << std::endl;WSACleanup();return false;}// 设置SO_REUSEADDR选项int opt = 1;setsockopt(m_listenSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));sockaddr_in serverAddr;serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = INADDR_ANY;serverAddr.sin_port = htons(m_port);if (bind(m_listenSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {std::cerr << "Bind failed: " << WSAGetLastError() << std::endl;closesocket(m_listenSocket);WSACleanup();return false;}if (listen(m_listenSocket, SOMAXCONN) == SOCKET_ERROR) {std::cerr << "Listen failed: " << WSAGetLastError() << std::endl;closesocket(m_listenSocket);WSACleanup();return false;}std::cout << "HTTP Server started on port " << m_port << std::endl;std::cout << "Press Ctrl+C to stop..." << std::endl;m_running = true;// 创建工作线程for (int i = 0; i < THREAD_POOL_SIZE; i++) {_beginthreadex(NULL, 0, WorkerThread, this, 0, NULL);}// 主线程接受连接while (m_running) {sockaddr_in clientAddr;int clientAddrSize = sizeof(clientAddr);SOCKET clientSocket = accept(m_listenSocket, (sockaddr*)&clientAddr, &clientAddrSize);if (clientSocket == INVALID_SOCKET) {if (m_running) {std::cerr << "Accept failed: " << WSAGetLastError() << std::endl;}continue;}// 将客户端套接字传递给工作线程处理// 这里简化处理:直接在工作线程池中处理// 实际应用中应使用队列和线程池管理ProcessRequest(clientSocket);closesocket(clientSocket);}closesocket(m_listenSocket);WSACleanup();return true;
}void CHttpServer::Stop() {m_running = false;closesocket(m_listenSocket);
}unsigned __stdcall CHttpServer::WorkerThread(void* param) {CHttpServer* pServer = (CHttpServer*)param;while (pServer->m_running) {// 简化处理:实际应使用连接队列Sleep(100);}return 0;
}void CHttpServer::ProcessRequest(SOCKET clientSocket) {char buffer[BUFFER_SIZE];int bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE - 1, 0);if (bytesReceived <= 0) {return;}buffer[bytesReceived] = '\0';std::string request(buffer);// 解析请求行size_t pos1 = request.find(' ');size_t pos2 = request.find(' ', pos1 + 1);if (pos1 == std::string::npos || pos2 == std::string::npos) {SendResponse(clientSocket, "400 Bad Request", "text/plain", "Invalid request format");return;}std::string method = request.substr(0, pos1);std::string path = request.substr(pos1 + 1, pos2 - pos1 - 1);std::string protocol = request.substr(pos2 + 1);// 解码URLpath = UrlDecode(path);// 记录请求LogRequest(method, path, protocol, 200);// 处理请求if (method == "GET") {// 检查是否是CGI脚本if (path.find("/cgi-bin/") == 0) {ExecuteCGI(clientSocket, "." + path.substr(8)); // 去掉/cgi-bin/前缀} // 检查是否是目录else if (path == "/" || path.empty()) {SendDirectoryListing(clientSocket, ".");} else {// 规范化路径if (path[0] == '/') {path = "." + path;}// 检查路径是否存在DWORD attr = GetFileAttributes(path.c_str());if (attr == INVALID_FILE_ATTRIBUTES) {SendResponse(clientSocket, "404 Not Found", "text/html", "<html><body><h1>404 Not Found</h1></body></html>");return;}// 如果是目录if (attr & FILE_ATTRIBUTE_DIRECTORY) {SendDirectoryListing(clientSocket, path);} // 如果是文件else {SendFile(clientSocket, path);}}} else {SendResponse(clientSocket, "501 Not Implemented", "text/plain", "Method not supported");}
}void CHttpServer::SendResponse(SOCKET clientSocket, const std::string& status, const std::string& contentType, const std::string& content) {std::stringstream response;response << "HTTP/1.1 " << status << "\r\n";response << "Server: VC++ HTTP Server\r\n";response << "Content-Type: " << contentType << "\r\n";response << "Content-Length: " << content.length() << "\r\n";response << "Connection: close\r\n";response << "\r\n";response << content;send(clientSocket, response.str().c_str(), response.str().length(), 0);
}void CHttpServer::SendFile(SOCKET clientSocket, const std::string& filePath) {std::ifstream file(filePath, std::ios::binary);if (!file.is_open()) {SendResponse(clientSocket, "404 Not Found", "text/html", "<html><body><h1>404 Not Found</h1></body></html>");return;}// 获取文件扩展名size_t dotPos = filePath.find_last_of('.');std::string extension = (dotPos != std::string::npos) ? filePath.substr(dotPos) : "";std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);// 获取内容类型std::string contentType = GetContentType(extension);if (contentType.empty()) {contentType = "application/octet-stream";}// 读取文件内容std::stringstream contentStream;contentStream << file.rdbuf();std::string content = contentStream.str();// 发送响应SendResponse(clientSocket, "200 OK", contentType, content);
}void CHttpServer::SendDirectoryListing(SOCKET clientSocket, const std::string& path) {std::stringstream html;html << "<html><head><title>Directory Listing</title>";html << "<style>body { font-family: Arial, sans-serif; margin: 20px; }";html << "table { border-collapse: collapse; width: 100%; }";html << "th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }";html << "tr:hover { background-color: #f5f5f5; }";html << "</style></head><body>";html << "<h1>Directory Listing: " << path << "</h1>";html << "<table><tr><th>Name</th><th>Size</th><th>Modified</th></tr>";// 添加上级目录链接if (path != ".") {html << "<tr><td><a href=\"..\">[Parent Directory]</a></td><td>-</td><td>-</td></tr>";}// 遍历目录WIN32_FIND_DATAA findData;HANDLE hFind = FindFirstFile((path + "\\*").c_str(), &findData);if (hFind != INVALID_HANDLE_VALUE) {do {if (strcmp(findData.cFileName, ".") == 0 || strcmp(findData.cFileName, "..") == 0) {continue;}std::string fileName = findData.cFileName;std::string filePath = path + "\\" + fileName;// 获取文件属性DWORD attrs = findData.dwFileAttributes;bool isDirectory = (attrs & FILE_ATTRIBUTE_DIRECTORY);// 格式化文件大小std::string sizeStr;if (isDirectory) {sizeStr = "-";} else {DWORD fileSize = findData.nFileSizeLow;std::stringstream ss;ss << fileSize << " bytes";sizeStr = ss.str();}// 格式化修改时间SYSTEMTIME stUTC, stLocal;FileTimeToSystemTime(&findData.ftLastWriteTime, &stUTC);SystemTimeToTzSpecificLocalTime(NULL, &stUTC, &stLocal);char timeStr[20];sprintf_s(timeStr, "%04d-%02d-%02d %02d:%02d:%02d", stLocal.wYear, stLocal.wMonth, stLocal.wDay,stLocal.wHour, stLocal.wMinute, stLocal.wSecond);// 添加到HTMLhtml << "<tr>";html << "<td>" << (isDirectory ? "[DIR]" : "") << "<a href=\"" << fileName << (isDirectory ? "/" : "") << "\">" << fileName << "</a></td>";html << "<td>" << sizeStr << "</td>";html << "<td>" << timeStr << "</td>";html << "</tr>";} while (FindNextFileA(hFind, &findData));FindClose(hFind);}html << "</table></body></html>";SendResponse(clientSocket, "200 OK", "text/html", html.str());
}void CHttpServer::ExecuteCGI(SOCKET clientSocket, const std::string& scriptPath) {// 简化处理:实际应使用更复杂的CGI处理std::ifstream script(scriptPath);if (!script.is_open()) {SendResponse(clientSocket, "404 Not Found", "text/html", "<html><body><h1>CGI Script Not Found</h1></body></html>");return;}std::stringstream contentStream;contentStream << script.rdbuf();std::string content = contentStream.str();// 替换模板变量(示例)size_t pos = content.find("${DATE}");if (pos != std::string::npos) {time_t now = time(NULL);char* dt = ctime(&now);content.replace(pos, 7, dt);}SendResponse(clientSocket, "200 OK", "text/html", content);
}std::string CHttpServer::GetContentType(const std::string& extension) {auto it = m_mimeTypes.find(extension);if (it != m_mimeTypes.end()) {return it->second;}return "";
}std::string CHttpServer::UrlDecode(const std::string& str) {std::string result;for (size_t i = 0; i < str.length(); i++) {if (str[i] == '%' && i + 2 < str.length()) {int hexValue;if (sscanf_s(str.substr(i + 1, 2).c_str(), "%x", &hexValue) == 1) {result += static_cast<char>(hexValue);i += 2;} else {result += str[i];}} else if (str[i] == '+') {result += ' ';} else {result += str[i];}}return result;
}void CHttpServer::LogRequest(const std::string& method, const std::string& path, const std::string& protocol, int statusCode) {time_t now = time(NULL);char* dt = ctime(&now);dt[strlen(dt) - 1] = '\0'; // 去掉换行符std::cout << "[" << dt << "] " << method << " " << path << " " << protocol << " - " << statusCode << std::endl;
}

3.3 主程序入口 (main.cpp)

#include "HttpServer.h"
#include <iostream>int main() {CHttpServer server;// 设置控制台标题SetConsoleTitle(L"VC++ HTTP Server");// 启动服务器if (!server.Start(8080)) {std::cerr << "Failed to start server." << std::endl;return 1;}return 0;
}

四、项目配置与编译

4.1 环境要求

  • Windows 7/10/11
  • Visual Studio 2010或更高版本
  • Winsock2库

4.2 编译步骤

  1. 创建新的Win32控制台应用程序项目
  2. 添加上述三个源文件(HttpServer.h, HttpServer.cpp, main.cpp)
  3. 配置项目属性:
    • 链接器 → 输入 → 附加依赖项:添加 ws2_32.lib
    • C/C++ → 预处理器 → 预处理器定义:添加 _CRT_SECURE_NO_WARNINGS
  4. 编译并运行

4.3 目录结构

HTTP Server/
├── HttpServer.h       # 服务器类声明
├── HttpServer.cpp     # 服务器类实现
├── main.cpp           # 程序入口
├── wwwroot/           # 网站根目录
│   ├── index.html     # 默认首页
│   ├── css/           # CSS样式表
│   ├── js/            # JavaScript文件
│   ├── images/        # 图片资源
│   └── cgi-bin/       # CGI脚本目录
│       └── hello.cgi  # 示例CGI脚本
└── logs/              # 日志目录(运行时创建)

参考代码 VC打造小型HTTP服务器 www.youwenfan.com/contentcnt/122432.html

五、功能扩展

5.1 添加POST请求支持

// 在ProcessRequest函数中添加
else if (method == "POST") {// 解析Content-Lengthsize_t contentLengthPos = request.find("Content-Length:");if (contentLengthPos != std::string::npos) {size_t endPos = request.find("\r\n", contentLengthPos);std::string lenStr = request.substr(contentLengthPos + 15, endPos - contentLengthPos - 15);int contentLength = atoi(lenStr.c_str());// 读取POST数据std::string postData = request.substr(request.length() - contentLength);// 处理表单数据// ...}
}

5.2 添加MIME类型自动检测

// 使用Windows API检测文件类型
std::string GetContentTypeFromFile(const std::string& filePath) {HANDLE hFile = CreateFile(filePath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (hFile == INVALID_HANDLE_VALUE) {return "application/octet-stream";}DWORD fileSize = GetFileSize(hFile, NULL);if (fileSize == INVALID_FILE_SIZE || fileSize == 0) {CloseHandle(hFile);return "application/octet-stream";}BYTE buffer[256];DWORD bytesRead;ReadFile(hFile, buffer, min(256, fileSize), &bytesRead, NULL);CloseHandle(hFile);// 简单的魔数检测if (bytesRead >= 4 && buffer[0] == 0xFF && buffer[1] == 0xD8) {return "image/jpeg";}if (bytesRead >= 8 && buffer[0] == 0x89 && buffer[1] == 0x50 && buffer[2] == 0x4E && buffer[3] == 0x47) {return "image/png";}if (bytesRead >= 6 && buffer[0] == 'G' && buffer[1] == 'I' && buffer[2] == 'F' && buffer[3] == '8') {return "image/gif";}return "application/octet-stream";
}

5.3 添加HTTPS支持

// 使用OpenSSL添加HTTPS支持
#include <openssl/ssl.h>
#include <openssl/err.h>class CSSLWrapper {
public:CSSLWrapper(SOCKET socket) : m_socket(socket), m_ssl(NULL) {SSL_library_init();SSL_CTX* ctx = SSL_CTX_new(SSLv23_server_method());SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM);SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM);m_ssl = SSL_new(ctx);SSL_set_fd(m_ssl, m_socket);SSL_accept(m_ssl);}int Send(const char* data, int length) {return SSL_write(m_ssl, data, length);}int Recv(char* buffer, int length) {return SSL_read(m_ssl, buffer, length);}~CSSLWrapper() {if (m_ssl) {SSL_shutdown(m_ssl);SSL_free(m_ssl);}}private:SOCKET m_socket;SSL* m_ssl;
};

六、使用示例

6.1 创建测试网页

在wwwroot目录下创建index.html:

<!DOCTYPE html>
<html>
<head><title>VC++ HTTP Server</title><style>body { font-family: Arial, sans-serif; margin: 40px; }h1 { color: #0066cc; }.container { max-width: 800px; margin: 0 auto; }</style>
</head>
<body><div class="container"><h1>Welcome to VC++ HTTP Server</h1><p>This is a simple HTTP server built with Visual C++.</p><ul><li><a href="/images/">View Images</a></li><li><a href="/docs/">View Documents</a></li><li><a href="/cgi-bin/hello.cgi">Run CGI Script</a></li></ul></div>
</body>
</html>

6.2 创建CGI脚本

在wwwroot/cgi-bin目录下创建hello.cgi:

#!/usr/bin/perl
print "Content-type: text/html\n\n";
print "<html><body>";
print "<h1>Hello from CGI!</h1>";
print "<p>Current date and time: ";
print scalar localtime;
print "</p>";
print "</body></html>";

6.3 运行服务器

  1. 编译并运行HTTP服务器程序
  2. 打开浏览器访问:http://localhost:8080
  3. 浏览网站内容,测试各种功能

七、性能优化建议

7.1 使用IOCP异步I/O

// 使用IOCP提高并发性能
HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);// 将套接字关联到完成端口
CreateIoCompletionPort((HANDLE)m_listenSocket, hCompletionPort, (ULONG_PTR)m_listenSocket, 0);// 使用AcceptEx异步接受连接

7.2 添加缓存机制

// 简单的内存缓存实现
std::map<std::string, std::pair<std::string, time_t>> m_cache;std::string GetCachedContent(const std::string& path) {auto it = m_cache.find(path);if (it != m_cache.end()) {// 检查缓存是否过期(5分钟)if (time(NULL) - it->second.second < 300) {return it->second.first;}m_cache.erase(it);}// 读取文件内容并缓存std::ifstream file(path);std::stringstream buffer;buffer << file.rdbuf();std::string content = buffer.str();m_cache[path] = std::make_pair(content, time(NULL));return content;
}

7.3 启用Gzip压缩

// 添加Gzip压缩支持
#include <zlib.h>std::string CompressString(const std::string& str) {z_stream zs;memset(&zs, 0, sizeof(zs));if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK) {return "";}zs.next_in = (Bytef*)str.data();zs.avail_in = str.size();int ret;char outbuffer[32768];std::string outstring;do {zs.next_out = reinterpret_cast<Bytef*>(outbuffer);zs.avail_out = sizeof(outbuffer);ret = deflate(&zs, Z_FINISH);if (outstring.size() < zs.total_out) {outstring.append(outbuffer, zs.total_out - outstring.size());}} while (ret == Z_OK);deflateEnd(&zs);if (ret != Z_STREAM_END) {return "";}return outstring;
}

八、项目总结

本VC++ HTTP服务器实现了以下功能:

  1. 核心HTTP协议支持

    • GET请求处理
    • 静态文件服务
    • 目录列表生成
    • 简单CGI脚本执行
  2. 关键技术点

    • Winsock网络编程
    • 多线程并发处理
    • MIME类型识别
    • URL解码
    • HTTP响应生成
  3. 扩展能力

    • 支持HTTPS(通过OpenSSL)
    • 支持POST请求
    • 支持Gzip压缩
    • 支持缓存机制
  4. 实际应用

    • 嵌入式设备Web管理界面
    • 本地文件共享服务器
    • 开发测试服务器
    • 物联网设备控制接口
http://www.jsqmd.com/news/645051/

相关文章:

  • 终极指南:如何用novideo_srgb实现硬件级显示器色彩校准,解决宽色域显示器色彩过饱和问题
  • GetQzonehistory:你的QQ空间记忆守护者,永久保存青春时光
  • DETR模型训练AP=0?别慌!手把手教你排查自定义数据集常见问题
  • linux启动关闭java程序
  • GD32F103实战指南(3)——从零搭建Keil工程模板
  • 告别Windows系统管理烦恼:WinUtil一站式解决方案指南
  • 打造个人AI助手:通义千问2.5-7B+WebUI,免费商用全教程
  • 2026年旅游管理论文降AI工具推荐:市场调研和旅游策略部分 - 还在做实验的师兄
  • 无实体公司在巴西如何雇人?一文读懂Safeguard Global名义雇主EOR服务 - 品牌2026
  • 保姆级教程:用Python脚本将Cornell抓取数据集PCD文件批量转成TIFF(附避坑指南)
  • Ozon订单同步设置教程:新ERP对接与数据迁移全流程! - 跨境小媛
  • Termux安卓设备通过内网穿透搭建SFTP服务器实现跨设备文件共享
  • 如何快速部署OPC UA Client:面向开发者的完整配置教程
  • 2026实验室水质检测仪精选:COD氨氮总磷总氮检测仪口碑推荐 - 品牌推荐大师
  • 深度解析MOFA:5个核心优势掌握多组学因子分析
  • Verilog实战:用全加器搭建进位保存加法器(CSA)的完整流程
  • Midjourney提示词实战:从零到商业级插画的5个关键步骤
  • 16进制+数字并返回16进制字符串
  • 医学图像分割刷点秘籍:拆解Polyp-PVT中的注意力模块与特征融合‘骚操作’
  • 突破性创新:用ESP32构建你的第一台智能小车,3小时实现自动避障
  • 如何通过本地化网盘直链解析工具解决下载速度瓶颈问题
  • USBCopyer:Windows平台U盘自动备份工具完整使用指南
  • 永辉超市购物卡如何变现?最全攻略来了! - 团团收购物卡回收
  • 栋察宇宙(五十):C语言数据类型
  • AdaIN在StyleGAN中的应用:从风格迁移到图像生成的进阶之路
  • 原神玩家必备:胡桃工具箱完整使用指南与实战技巧
  • Word排版救星:用‘分节符’5分钟搞定混合页面方向,告别复制粘贴到新文档的笨办法
  • 不停车判断锅炉、换热器等系统设备结垢与腐蚀的方法及需要注意的5个相关问题
  • Ubuntu 22.04~24.04 自定义GDM登录背景的完整指南
  • 无实体公司在香港如何雇人?一文读懂Safeguard Global名义雇主EOR服务 - 品牌2026