一.前言
在开发和学习过程中,实现一个简单的HTTP服务器是理解网络编程的关键步骤。它不仅可以帮助我们理解HTTP协议,还能让我们深入掌握多线程和高效I/O模型。本篇文章将详细介绍如何用才C/C++实现一个功能基本完善的多线程HTTP服务器。
二.项目概述
本项目的目标是开发一个支持静态文件服务和目录浏览功能的多线程HTTP服务器,主要功能包括:
1. **静态文件服务**:返回HTML、CSS、JavaScript、图片等静态文件。
2. **目录浏览**:当访问目录时,返回该目录下的文件列表。
3. **多线程处理**:为每个客户端请求分配一个线程,提高并发处理能力。
4. **非阻塞I/O**:通过epoll实现高效的I/O多路复用。
5. **错误处理**:针对无效请求或不存在的文件,返回标准的404页面。
三.项目实现
3.1技术选型
在实现过程中,我们使用以下技术和工具:
- **C++ 标准库**:利用`std::thread`实现多线程。
- **Linux 系统调用**:`epoll`、`sendfile`等用于高效的网络I/O操作。
- **HTTP 协议基础**:解析GET请求,返回HTTP响应。
3.2系统架构
服务器采用模块化设计,主要包含以下几个核心模块:
1. **HTTP 请求解析模块**:负责解析客户端的HTTP请求,提取请求方法、路径等信息。
2. **文件处理模块**:根据请求路径,提供文件读取、目录浏览等服务。
3. **网络通信模块**:负责客户端连接的管理和数据的收发。
4. **多线程模块**:每个客户端的请求由独立线程处理,提高并发能力。
四.代码实现
1. 服务器初始化
1. 创建服务器套接字并绑定到指定端口。
2. 设置套接字为非阻塞模式。
3. 初始化epoll实例,用于管理I/O事件。
bool HttpServer::Init()
{
//初始化HttpServer
m_listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (m_listen_fd == -1) {
perror("socket");
return false;
}
//设置端口复用
int opt = 1;
int ret = setsockopt(m_listen_fd, SOL_SOCKET, SO_REUSEADDR,&opt, sizeof(opt));
if(ret == -1)
{
perror("setsockopt");
return false;
}
//设置地址簇
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(m_port);
addr.sin_addr.s_addr = INADDR_ANY;
//绑定套接字
if(bind(m_listen_fd, (struct sockaddr *)&addr,sizeof(addr)) < 0)
{
perror("bind");
return false;
}
//开始监听
if(listen(m_listen_fd, MAX_CLIENTS) < 0)
{
perror("listen");
return false;
}
//创建epoll
m_epfd = epoll_create(MAX_CLIENTS);
if (m_epfd == -1) {
perror("epoll_create");
return false;
}
//添加监听套接字到epoll
struct epoll_event event;
event.data.fd = m_listen_fd;
event.events = EPOLLIN;
if (epoll_ctl(m_epfd, EPOLL_CTL_ADD, m_listen_fd, &event) == -1) {
perror("epoll_ctl");
return false;
}
return true;
}
2.epoll启动,高效i/o复用
void HttpServer::Run()
{
epoll_event evs[MAX_EVENTS];
while(1)
{
int num = epoll_wait(m_epfd,evs,MAX_EVENTS,-1);
for(int i=0;i<num;i++)
{
int lfd = evs[i].data.fd;
if(lfd == m_listen_fd)
{
AcceptClient();//接受客户端连接
}else if(evs[i].events & EPOLLIN)
{