zoukankan      html  css  js  c++  java
  • 基于epoll的简单的httpserver

    该httpserver已经能够处理并发连接,支持多个client并发訪问,每一个连接能够持续读写数据。当然。这仅仅是一个简单的学习样例。还有非常多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("---创建监听结构体失败---
    ");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
    ");
    		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-------
    ");
    	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
    ");
    		return;
    	}
    	web_connection_t* new_conn = NULL;
    	initConnection(new_conn);//创建一个新的连接结构体
    	if(new_conn == NULL){printf("---创建连接结构体失败---
    ");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-------
    ");
    
    	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("----数据读取完成-----
    ");
    			break;//表示当前数据读取完成,不是出错
    		}
    		else if(len > 0)
    		{
    			conn->query_end_index += len;
    			conn->query_remain_len-= len;
    		}
    		else if(len == 0)
    		{
    			printf("----连接关闭-----
    ");
    			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," 	");
    	if( !ptr)
    	{
    		printf("请求行解析失败
    ");
    		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," 	");
    	if( !ptr)
    	{
    		printf("请求行解析失败
    ");
    		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,"
    ");//先是回车
    ,再是换行
    
    	if(!ptr)
    	{
    		printf("请求行解析失败
    ");
    		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,"
    ");
    		len = end_line - conn->querybuf - conn->query_start_index;
    		if(len == 1)
    		{
    			printf("解析完成
    ");
    			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("----首部解析完成----------
    ");
    }
    void process_body(struct web_connection_t* conn)
    {
    	if(conn->query_start_index == conn->query_end_index)
    	{
    		printf("---包体为空----
    ");
    	}
    	else
    	{
    		printf("---丢体包体-----
    ");
    	}
    	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] = '';//非常重要
    
    	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个连接 ",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
    ",number);
    		printf("当前进程ID为: %d 
    ",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;
    }
    
    
    
    
    
    
    
    

    用法:

    server用法:直接执行就可以
    client用法:编译client代码。然后 ./client 127.0.0.1 8083 5(最后一个代表client进程数)
    本程序在linux平台下測试成功

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

  • 相关阅读:
    【译】StackExchange.Redis 中文文档(十)性能分析
    【译】StackExchange.Redis 中文文档(九)服务器相关命令
    【译】StackExchange.Redis 中文文档(八)流
    【译】StackExchange.Redis 中文文档(七)推送/订阅消息顺序
    【译】StackExchange.Redis 中文文档(六)事件
    【译】StackExchange.Redis 中文文档(五)事务
    查看供应商2086报表
    创建内部供应商
    创建客户前台配置
    创建客户后台配置-spro
  • 原文地址:https://www.cnblogs.com/yangykaifa/p/6722879.html
Copyright © 2011-2022 走看看