用 Linux epoll 实现高性能 HTTP 服务器

用 Linux epoll 实现高性能 HTTP 服务器

为了代码的整洁性,本文章所介绍功能将使用 C++ 实现。实际使用中可转为 C 语言使用。
此项目只能在Linux下使用,windows请绕道。

项目概括

本项目是使用 Linux epoll 实现的一个简单的 HTTP 服务器。仅支持 HTTP 1.0、GET 和 HEAD 方法,对 HTTP 请求报文仅使用正则表达式进行解析。在实际使用中,请使用词法和语法分析来实现请求报文的内容解析。

什么是 epoll

epoll 是 Linux 内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

准备编译环境

本程序需要 gcc,make 相关(make 不是必须)。可通过以下命令安装:

$ sudo apt-get install gcc
$ sudo apt-get install make

项目代码

项目配套代码已上传到 GitHub,项目地址 https://github.com/ZiFung/epoll-http-server/

将项目源码下载下来后,用 terminal 打开,输入

$ make

后输入

$ ./my_http [port]

即可运行

项目成果

本项目经过 GitHub 上的开源项目 wrk 进行压力测试,在测试虚拟机上测得11000并发。

完整代码

项目结构

  • epoll_http/
    • html/
      • index.html
    • main.cpp
    • HttpServer.hpp
    • HttpServer.cpp
    • HttpResponse.hpp
    • HttpResponse.cpp

代码

// main.cpp
#include "HttpServer.hpp"

#include <stdlib.h>
#include <string.h>
#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
	// 端口处理
	unsigned short port = 80;
	if (argc > 3)
	{
		cerr << "At most two arguments." << endl;
		return -1;
	}
	if (argc == 2)
		port = atoi(argv[1]);
	else if (argc == 3)
	{
		if (strcmp(argv[1], "--port") != 0)
		{
			cerr << "Unknown command \"" << argv[1] << "\"" << endl;
			return -2;
		}
		port = atoi(argv[2]);
	}
	
	// 新建服务器
	HttpServer *server = new HttpServer();
	
	if (!server->init_server(port))
	{
		cerr << "Failed in initializing server!" << endl;
		return -3;
	}
	
	// 启动服务器
	server->start_serving();
	delete server;
	return 0;
}
// HttpServer.hpp
#ifndef HTTPSERVER_HPP
#define HTTPSERVER_HPP

#include "HttpResponse.hpp"
#include <fstream>
#include <sys/epoll.h>

#define MAX_SOCK_SIZE		1024
#define FD_SIZE			1000
#define EPOLLEVENTS_SIZE	1000
#define BUFFER_SIZE		1024 * 1024

using namespace std;

class HttpServer
{
public:
	HttpServer();
	~HttpServer();
	
	bool init_server(unsigned short port);
	void start_serving();

private:
	void add_event(int fd, int state);
	void modify_event(int fd, int state);
	void delete_event(int fd);
	
private:
	int listen_fd = -1;
	int epfd = -1;
	struct epoll_event events[EPOLLEVENTS_SIZE];

	HttpResponse client_data[MAX_SOCK_SIZE];
};

#endif // HTTPSERVER_HPP
// HttpServer.cpp
#include "HttpServer.hpp"

#include <iostream>
#include <string>

#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>

using namespace std;

HttpServer::HttpServer()
{
}

HttpServer::~HttpServer()
{
	if (listen_fd > 0)
	{
		close(listen_fd);
		listen_fd = -1;
	}
}

bool HttpServer::init_server(unsigned short port)
{
	listen_fd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == listen_fd)
	{
		cerr << "Error: failed to create socket" << endl;
		return false;
	}
	
	bool opt = false;
	setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(&opt));
	
	sockaddr_in  saddr;
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(port);
	saddr.sin_addr.s_addr = htonl(0);
	
	if (::bind(listen_fd, (sockaddr*)&saddr, sizeof(saddr)) != 0)
	{
		cerr << "Error: failed to bind port (" << port << ")!" << endl;
		return false;
	}
	
	if (listen(listen_fd, 5) < 0)
	{
		cerr << "Error: failed to listen!!!" << endl;
		return false;
	}
	
	epfd = epoll_create(FD_SIZE);
	add_event(listen_fd, EPOLLIN);
	
	return true;
}

void HttpServer::start_serving()
{
	for (;;)
	{
		int ret = epoll_wait(epfd, events, EPOLLEVENTS_SIZE, -1);
		for (int i = 0; i < ret; i++)
		{
			if (events[i].data.fd == listen_fd)
			{
				sockaddr_in caddr;
				socklen_t len = sizeof(caddr);
				int client = accept(listen_fd, (sockaddr*)&caddr, &len);
				if (client <= 0)
				{
					cerr << "==> Accept client failed!!!" << endl;
					continue;
				}
				add_event(client, EPOLLIN);
			}
			else
			{
				int client = events[i].data.fd;
				if (events[i].events & EPOLLIN)
				{
					bool re = false;
					
					if (!re)
					{
						char buf[BUFFER_SIZE] = { 0 };
						int revclen = recv(client, buf, BUFFER_SIZE, 0);
						if (revclen <= 0)
						{
							close(client);
							delete_event(client);
							client_data[client].request = "";
							continue;
						}
						client_data[client].request += buf;
						re = BUFFER_SIZE != revclen;
						if (!re)
							continue;
					}
					if (!client_data[client].parse_request())
					{
						close(client);
						delete_event(client);
						client_data[client].request = "";
						continue;
					}
					
					modify_event(client, EPOLLOUT);
				}
				else if (events[i].events & EPOLLOUT)
				{
					char buf[BUFFER_SIZE] = { 0 };
					int data_read = client_data[client].read_data(buf, BUFFER_SIZE);
					int data_sent = send(client, buf, data_read, MSG_NOSIGNAL);
					if (data_sent == 0)
						continue;
					if (data_sent < 0)
					{
						delete_event(client);
						client_data[client].close();
						close(client);
						continue;
					}
					client_data[client].forward(data_sent);
					
					if (client_data[client].is_reading_finished())
					{
						delete_event(client);
						client_data[client].close();
						close(client);
					}
				}
			}
		}
	}
	close(listen_fd);
}

void HttpServer::add_event(int fd, int state)
{
	struct epoll_event ev;
	ev.data.fd = fd;
	ev.events = state;
	epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
}

void HttpServer::modify_event(int fd, int state)
{
	struct epoll_event ev;
	ev.data.fd = fd;
	ev.events = state;
	epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
}

void HttpServer::delete_event(int fd)
{
	epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
}
// HttpSponse.hpp
#ifndef HTTPRESPONSE_HPP
#define HTTPRESPONSE_HPP

#include <string>
#include <fstream>

using namespace std;

class HttpResponse
{
public:
	HttpResponse();
	~HttpResponse();

	string request = "";
	bool parse_request();
	
	int read_data(char *buf, int buf_size);
	void forward(int offset);
	
	bool is_reading_finished();
	
	void close();

private:
	ifstream *in_file = NULL;
	
	string buffer = "";
	
};

#endif // HTTPRESPONSE_HPP
// HttpResponse.cpp
#include "HttpResponse.hpp"

#include <iostream>
#include <string.h>

using namespace std;

HttpResponse::HttpResponse()
{
}

HttpResponse::~HttpResponse()
{
}

bool HttpResponse::parse_request()
{
	string type = "GET";
	string path = "/";
	
	if (request[3] == ' ')
	{
		int i;
		for (i = 4; request[i] != '?' && request[i] != ' '; i++);
		path = request.substr(4, i - 4);
	}
	else
		type = "HEAD";
	
	buffer += "HTTP/1.1 200 OK\r\n";
	
	unsigned long file_size = 0;
	
	if (type == "GET")
	{
		string filename = path;
		if(path == "/")
			filename = "/index.html";
		
		string filepath = "html";
		filepath += filename;
		
		in_file = new ifstream(filepath, ios::in | ios::binary);
		if(!in_file->is_open())
		{
			in_file = new ifstream("html/404.html", ios::in | ios::binary);
			if(!in_file->is_open())
			{
				cerr << "Open file html/404.html failed!!!" << endl;
				return false;
			}
		}
		// Get the length of the file required.
		in_file->seekg(0, ios::end);
		file_size = in_file->tellg();
		in_file->seekg(0, ios::beg);
		
		buffer += "Content-Length: ";
		buffer += to_string(file_size);
		buffer += "\r\n";
	}
	buffer += "\r\n";
	request = "";
	return true;
}
	
int HttpResponse::read_data(char *buf, int buf_size)
{
	if ((unsigned int)buf_size >= buffer.size() && buffer.size() != 0)
	{
		strcpy(buf, buffer.c_str());
		if (in_file == NULL)
			return (int)buffer.size();
		in_file->read(buf + buffer.size(), buf_size - (int)buffer.size());
		int data_read = in_file->gcount();
		in_file->seekg(-1 * data_read, ios::cur);
		return (int)buffer.size() + data_read;
	}
	else if (buffer.size() > 0)
	{
		string temp = buffer.substr(0, buf_size);
		strcpy(buf, temp.c_str());
		return buf_size;
	}
	else
	{
		if (in_file == NULL)
			return -1;
		in_file->read(buf, buf_size);
		int data_read = in_file->gcount();
		in_file->seekg(-1 * data_read, ios::cur);
		return data_read;
	}
}

void HttpResponse::forward(int offset)
{
	if (buffer.size() > 0 && offset > (int)buffer.size())
	{
		offset -= (int)buffer.size();
		buffer = "";
		in_file->seekg(offset, ios::cur);
	}
	else if (buffer.size() > 0)
		buffer = buffer.substr(offset - 1, buffer.size() - offset);
	else
		in_file->seekg(offset, ios::cur);
}

bool HttpResponse::is_reading_finished()
{
	if (in_file != NULL)
		return in_file->peek() == EOF;
	return buffer.size() == 0;
}

void HttpResponse::close()
{
	if (in_file != NULL)
		in_file->close();
	in_file = NULL;
	buffer = "";
}

猜你喜欢

转载自blog.csdn.net/ZiFung_Yip/article/details/84645761