基于epoll的简单的http服务器

该http服务器已经可以处理并发连接,支持多个客户端并发访问,每个连接可以持续读写数据,当然,这只是一个简单的学习例子,还有很多bug,发表出来只是希望大家可以互相学习,我也在不断的改进,希望大家有什么意见可以多多指点,谢谢


server.h

/*
 * server.h
 *
 *  Created on: Jun 23, 2014
 *      Author: fangjian
 */


#ifndef SERVER_H_
#define SERVER_H_

#include "epoll_event.h"

struct web_event_t;
struct web_connection_t
{
	int fd;

	int state;//当前处理到哪个阶段
	struct web_event_t* read_event;
	struct web_event_t* write_event;
	char* querybuf;
	int query_start_index;//请求数据的当前指针
	int query_end_index;//请求数据的下一个位置
	int query_remain_len;//可用空间

	char method[8];
	char uri[128];
	char version[16];
	char host[128];
	char accept[128];
	char conn[20];
};

struct server
{
	int epollfd;
};

void web_epoll_ctl(int epollfd,int ctl,int fd,int flag);
int setnonblocking(int fd);
void initConnection(web_connection_t* &conn);
void web_accept(struct web_connection_t* conn);
void read_request( struct web_connection_t* conn );
void process_request_line(struct web_connection_t* conn);
void process_head(struct web_connection_t* conn);
void process_body(struct web_connection_t* conn);
void send_response(struct web_connection_t* conn);
void try_to_enlarge_buffer(struct web_connection_t& conn);
void empty_event_handler(struct web_connection_t* conn);
void close_conn( struct web_connection_t* conn );

#endif /* SERVER_H_ */

server.cpp

/*
 * server.cpp
 *
 *  Created on: Jun 23, 2014
 *      Author: fangjian
 */

#include "server.h"
#include "epoll_event.h"

#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include<signal.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <iostream>
using namespace std;

int main(int argc,char* argv[])
{
	const char* ip = "127.0.0.1";
	int port =  8083;

	signal(SIGPIPE,SIG_IGN);//原因:http://blog.sina.com.cn/s/blog_502d765f0100kopn.html

	int listenfd = socket(AF_INET,SOCK_STREAM,0);
	struct sockaddr_in address;
	bzero(&address,sizeof(address));
	address.sin_family = AF_INET;
	inet_pton(AF_INET,ip,&address.sin_addr);
	address.sin_port = htons(port);
	bind(listenfd,(struct sockaddr*)&address,sizeof(address));
	listen(listenfd,50);

	web_connection_t* conn = NULL;
	epoll_init_event(conn);
	initConnection(conn);//创建一个用于接受连接的结构体
	if(conn == NULL){printf("---创建监听结构体失败---\n");return -1;};//创建监听结构体

	conn->fd = listenfd;
	conn->read_event->handler = web_accept;
	epoll_add_event(conn,EPOLLIN | EPOLLERR);

	setnonblocking(listenfd);

	fork();

	ngx_epoll_process_events();//进入事件循环,等待事件到达
}
void initConnection(web_connection_t* &conn)
{
	conn = (web_connection_t*)malloc(sizeof(web_connection_t));
	conn->read_event = (web_event_t*)malloc(sizeof(web_event_t));
	conn->write_event = (web_event_t*)malloc(sizeof(web_event_t));
	conn->state = ACCEPT;

	conn->querybuf = (char*)malloc(QUERY_INIT_LEN);
	if(!conn->querybuf)
	{
		printf(" malloc error\n");
		return;
	}
	conn->query_start_index = 0;
	conn->query_end_index = 0;
	conn->query_remain_len = QUERY_INIT_LEN;
}

int setnonblocking(int fd)
{
	int old_option = fcntl(fd,F_GETFL);
	int new_option = old_option | O_NONBLOCK;
	fcntl(fd,F_SETFL,new_option);
	return old_option;
}

void web_accept(web_connection_t* conn)
{
	printf("-----------accept-------\n");
	struct sockaddr * client_address;
	socklen_t client_addrlength = sizeof(client_address);
	int connfd = accept(conn->fd,(struct sockaddr*)&(client_address),&client_addrlength);
	if(connfd == -1)
	{
		printf("accept error\n");
		return;
	}
	web_connection_t* new_conn = NULL;
	initConnection(new_conn);//创建一个新的连接结构体
	if(new_conn == NULL){printf("---创建连接结构体失败---\n");return;};

	new_conn->fd = connfd;
	new_conn->state = READ;
	new_conn->read_event->handler = read_request;
	epoll_add_event(new_conn,EPOLLIN | EPOLLERR);

	setnonblocking(connfd);
}
void read_request( struct web_connection_t* conn )
{
	printf("-----------read_begin-------\n");

	int len,fd = conn->fd;
	while(true)
	{
		/* 尝试增加缓冲区空间 */
		try_to_enlarge_buffer(*conn);
		len= recv(fd,conn->querybuf + conn->query_end_index,conn->query_remain_len,0);
		if(len < 0)
		{
			printf("----数据读取完毕-----\n");
			break;//表示当前数据读取完毕,不是出错
		}
		else if(len > 0)
		{
			conn->query_end_index += len;
			conn->query_remain_len-= len;
		}
		else if(len == 0)
		{
			printf("----连接关闭-----\n");
			epoll_del_event(conn);
			close_conn(conn );
			return ;
		}
	}
	cout << "-----客户端的内容是 " << endl;

	cout << conn->querybuf << endl;

	process_request_line(conn);

	return ;
}
void process_request_line(struct web_connection_t* conn)
{
	int len;
	char* ptr = strpbrk(conn->querybuf + conn->query_start_index," \t");
	if( !ptr)
	{
		printf("请求行解析失败\n");
		return;
	}
	len = ptr - conn->querybuf - conn->query_start_index;
	strncpy(conn->method,conn->querybuf + conn->query_start_index,len);
	cout <<"metnod="<<conn->method<<endl;

	conn->query_start_index += (len+1);
	ptr = strpbrk(conn->querybuf + conn->query_start_index," \t");
	if( !ptr)
	{
		printf("请求行解析失败\n");
		return;
	}
	len = ptr - conn->querybuf - conn->query_start_index;
	strncpy(conn->uri,conn->querybuf + conn->query_start_index,len);
	cout << "uri="<<conn->uri<<endl;

	conn->query_start_index += (len+1);
	ptr = strpbrk(conn->querybuf,"\n");//先是回车\r,再是换行\n
	if(!ptr)
	{
		printf("请求行解析失败\n");
		return;
	}
	len = ptr - conn->querybuf - conn->query_start_index;
	strncpy(conn->version,conn->querybuf + conn->query_start_index,len);
	cout << "version="<<conn->version<<endl;
	conn->query_start_index += (len+1);

	cout <<"-----请求行解析完毕----------"<<endl;

	process_head(conn);
}

void process_head(struct web_connection_t* conn)
{
	cout << "-------开始解析首部------" << endl;

	char* end_line;
	int len;

	while(true)
	{
		end_line = strpbrk(conn->querybuf + conn->query_start_index,"\n");
		len = end_line - conn->querybuf - conn->query_start_index;
		if(len == 1)
		{
			printf("解析完毕\n");
			conn->query_start_index += (len +1);
			cout << conn->querybuf + conn->query_start_index << endl;
			break;
		}
		else
		{
			if(strncasecmp(conn->querybuf+conn->query_start_index,"Host:",5) == 0)
			{
				strncpy(conn->host,conn->querybuf+conn->query_start_index + 6,len-6);
				cout << "host="<<conn->host<<endl;
			}
			else if(strncasecmp(conn->querybuf+conn->query_start_index,"Accept:",7) == 0)
			{
				strncpy(conn->accept,conn->querybuf+conn->query_start_index + 8,len-8);
				cout <<"accept="<<conn->accept <<endl;
			}
			else if(strncasecmp(conn->querybuf+conn->query_start_index,"Connection:",11) == 0)
			{
				strncpy(conn->conn,conn->querybuf+conn->query_start_index + 12,len-12);
				cout <<"connection="<<conn->conn <<endl;
			}
			else
			{
			}
			conn->query_start_index += (len +1);
		}
	}
	process_body(conn);
	printf("----首部解析完毕----------\n");
}
void process_body(struct web_connection_t* conn)
{
	if(conn->query_start_index == conn->query_end_index)
	{
		printf("---包体为空----\n");
	}
	else
	{
		printf("---丢体包体-----\n");
	}
	conn->query_start_index = conn->query_end_index = 0;

	conn->state = SEND_DATA;
	conn->write_event->handler = send_response;
	conn->read_event->handler = empty_event_handler;//读事件回调函数设置为空
	epoll_mod_event(conn,EPOLLOUT | EPOLLERR);
}
void send_response(struct web_connection_t* conn)
{
	char path[128] = "http";//根目录下的文件夹
	int len = strlen(conn->uri);
	memcpy(path+4,conn->uri,len);
	len += 4;
	path[len] = '\0';//很重要

	int filefd = open(path,O_RDONLY);
	if(filefd < 0)
	{
		cout << "无法打开该文件" <<endl;
		return ;
	}
	struct stat stat_buf;
	fstat(filefd,&stat_buf);
	sendfile(conn->fd,filefd,NULL,stat_buf.st_size);
	close(filefd);

	//close(conn->fd);//如果不关闭该连接socket,则浏览器一直在加载,如何解决,保持keep-alive?

	conn->state = READ;
	conn->read_event->handler = read_request;
	epoll_mod_event(conn,EPOLLIN | EPOLLERR);

	//sleep(2);
}

void try_to_enlarge_buffer(struct web_connection_t& conn)
{
	if(conn.query_remain_len  < REMAIN_BUFFER)
	{
		int new_size = strlen(conn.querybuf) + QUERY_INIT_LEN;
		conn.querybuf = (char*)realloc(conn.querybuf,new_size);
		conn.query_remain_len  = new_size - conn.query_end_index;
	}
}
void empty_event_handler(struct web_connection_t* conn)
{

}
//关闭一个连接
void close_conn( struct web_connection_t* conn )
{
	static int count = 0;
	count ++;
	printf("关闭第%d个连接\n",count);

    close( conn->fd);
    free(conn->querybuf);
    free(conn->read_event);
    free(conn->write_event);
    free(conn);
}

epoll_event.h

/*
 * event.h
 *
 *  Created on: Jun 25, 2014
 *      Author: fangjian
 */

#ifndef EVENT_H_
#define EVENT_H_

#include <netinet/in.h>
#include "server.h"
#define MAX_EVENT_NUMBER 10000
#define QUERY_INIT_LEN  1024
#define REMAIN_BUFFER  512

/* 以下是处理机的状态 */
#define ACCEPT 1
#define READ 2
#define QUERY_LINE 4
#define QUERY_HEAD 8
#define QUERY_BODY 16
#define SEND_DATA 32

struct web_connection_t;

typedef void (*event_handler_pt)(web_connection_t* conn);

//每一个事件都由web_event_t结构体来表示
struct web_event_t
{
	/*为1时表示事件是可写的,通常情况下,它表示对应的TCP连接目前状态是可写的,也就是连接处于可以发送网络包的状态*/
    unsigned         write:1;
	/*为1时表示此事件可以建立新的连接,通常情况下,在ngx_cycle_t中的listening动态数组中,每一个监听对象ngx_listening_t对应的读事件中
    的accept标志位才会是1*/
    unsigned         accept:1;
    //为1时表示当前事件是活跃的,这个状态对应着事件驱动模块处理方式的不同,例如:在添加事件、删除事件和处理事件时,该标志位的不同都会对应着不同的处理方式
    unsigned         active:1;
    unsigned         oneshot:1;
    unsigned         eof:1;//为1时表示当前处理的字符流已经结束
    unsigned         error:1;//为1时表示事件处理过程中出现了错误

    event_handler_pt  handler;//事件处理方法,每个消费者模块都是重新实现它
    unsigned         closed:1;//为1时表示当前事件已经关闭
};

void epoll_init_event(web_connection_t* &conn);
void epoll_add_event(web_connection_t* conn,int flag);
void epoll_mod_event(web_connection_t* conn,int flag);
void epoll_del_event(web_connection_t* conn);
int ngx_epoll_process_events();
#endif /* EVENT_H_ */

epoll_event.cpp

/*
 * event.cpp
 *
 *  Created on: Jun 25, 2014
 *      Author: fangjian
 */

#include "epoll_event.h"
#include <sys/epoll.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

static int  ep = -1;//epoll对象的描述符,每个进程只有一个

void epoll_init_event(web_connection_t* &conn)
{
	ep = epoll_create(1024);
}

/* 添加事件,conn已经设置好回调函数和fd了 */
void epoll_add_event(web_connection_t* conn,int flag)
{
	epoll_event ee;
	int fd = conn->fd;
	ee.data.ptr = (void*)conn;
	ee.events = flag;
	epoll_ctl(ep,EPOLL_CTL_ADD,fd,&ee);
}

/* 修改事件,event已经设置好回调函数和fd了 */
void epoll_mod_event(web_connection_t* conn,int flag)
{
	epoll_event ee;
	int fd = conn->fd;
	ee.data.ptr = (void*)conn;
	ee.events = flag;
	epoll_ctl(ep,EPOLL_CTL_MOD,fd,&ee);
}

//删除该描述符上的所有事件,若想只删除读事件或写事件,则把相应的事件设置为空函数
void epoll_del_event(web_connection_t* conn)
{
	epoll_ctl( ep, EPOLL_CTL_DEL, conn->fd, 0 );//删除事件最后一个参数为0
}
//事件循环函数
int ngx_epoll_process_events()
{
	epoll_event event_list[MAX_EVENT_NUMBER];
	while(true)
	{
		int number = epoll_wait(ep,event_list,MAX_EVENT_NUMBER,-1);

		printf("number=%d\n",number);
		printf("当前进程ID为: %d \n",getpid());

		int i;
		for(i = 0;i < number;i++)
		{
			web_connection_t* conn = (web_connection_t*)(event_list[i].data.ptr);
			int socket = conn->fd;//当前触发的fd
			//读事件
			if ( event_list[i].events & EPOLLIN )
			{
				conn->read_event->handler(conn);
			}
			//写事件
			else if( event_list[i].events & EPOLLOUT )
			{
				conn->write_event->handler(conn);
			}
			else if( event_list[i].events & EPOLLERR )
			{

			}
		}
	}
	return 0;
}








使用方法:

服务器使用方法:直接运行即可
客户端使用方法:编译客户端代码,然后 ./client 127.0.0.1 8083 5(最后一个代表客户端进程数)
本程序在linux平台下测试成功

免费代码下载地址:http://download.csdn.net/detail/fangjian1204/7575477

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值