基于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("---创建监听结构体失败---\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;
}
用法:
server用法:直接执行就可以
client用法:编译client代码。然后 ./client 127.0.0.1 8083 5(最后一个代表client进程数)
本程序在linux平台下測试成功
免费代码下载地址:http://download.csdn.net/detail/fangjian1204/7575477
基于epoll的简单的httpserver的更多相关文章
- 基于epoll实现简单的web服务器
1. 简介 epoll 是 Linux 平台下特有的一种 I/O 复用模型实现,于 2002 年在 Linux kernel 2.5.44 中被引入.在 epoll 之前,Unix/Linux 平台下 ...
- 基于EPOLL模型的局域网聊天室和Echo服务器
一.EPOLL的优点 在Linux中,select/poll/epoll是I/O多路复用的三种方式,epoll是Linux系统上独有的高效率I/O多路复用方式,区别于select/poll.先说sel ...
- 基于modelsim-SE的简单仿真流程—下
基于modelsim-SE的简单仿真流程—下 编译 在 WorkSpace 窗口的 counter_tst.v上点击右键,如果选择Compile selected 则编译选中的文件,Compile A ...
- 基于modelsim-SE的简单仿真流程—上
基于modelsim-SE的简单仿真流程 编写RTL功能代码 要进行功能仿真,首先得用需要仿真的模块,也就是RTL功能代码,简称待测试的模块,该模块也就是在设计下载到FPGA的电路.一个电路模块想要有 ...
- [置顶] 使用红孩儿工具箱完成基于Cocos2d-x的简单游戏动画界面
[Cocos2d-x相关教程来源于红孩儿的游戏编程之路CSDN博客地址:http://blog.csdn.net/honghaier 红孩儿Cocos2d-X学习园地QQ3群:205100149,47 ...
- 基于IndexedDB实现简单文件系统
现在的indexedDB已经有几个成熟的库了,比如西面这几个,任何一个都是非常出色的. 用别人的东西好处是上手快,看文档就好,要是文档不太好,那就有点尴尬了. dexie.js :A Minimali ...
- .NetCore WebApi——基于JWT的简单身份认证与授权(Swagger)
上接:.NetCore WebApi——Swagger简单配置 任何项目都有权限这一关键部分.比如我们有许多接口.有的接口允许任何人访问,另有一些接口需要认证身份之后才可以访问:以保证重要数据不会泄露 ...
- TOTP 介绍及基于C#的简单实现
TOTP 介绍及基于C#的简单实现 Intro TOTP 是基于时间的一次性密码生成算法,它由 RFC 6238 定义.和基于事件的一次性密码生成算法不同 HOTP,TOTP 是基于时间的,它和 HO ...
- Go Socket实现简单的HttpServer
在上篇博客中写到如何用Python实现一个类似tomcat的简单服务器,接下来用go语言去实现 1. Go本身自己封装实现了非常简单的httpServer package main import ( ...
随机推荐
- uva 10670 Work Reduction(贪心)
题目连接:10670 - Work Reduction 题目大意:有tol的工作量,和要求达到的工作剩余量sur,然后是公司总数,对应每个公司提供两种服务,1.完成一个工作量,2.完成当前未完成工作量 ...
- Java从零开始学二十三(集合Map接口)
一.Map接口 Collection.Set.List接口都属于单值的操作,即:每次只能操作一个对象,而Map与它们不同的是,每次操作的是一对对象,即二元偶对象,Map中的每个元素都使用key à v ...
- Java从零开始学十九(异常)
一.什么是异常 从字面上讲,就是不正常的现实就是异常. 程序中的异常也是要在程序运行中才会偶尔发生.如果程序还没有运行,编译就报错,这种不叫异常,这种叫编译错误,通常是语法上的错误 二.java中异常 ...
- PyQt5教程——组件 Ⅱ(八)
这部分的教程将会继续介绍PyQt5的组件.我们这节教程的内容将包括像素图(QPixmap),单行文本框(QLineEdit)和下拉列表框(QComboBox) 像素图(QPixmap) 像素图(QPi ...
- JVM中类的卸载机制
类的生命周期 当Sample类被载入.连接和初始化后,它的生命周期就開始了. 当代表Sample类的Class对象不再被引用,即不可触及时,Class对象就会结束生命周期.Sample类在方法区内的数 ...
- 解决/usr/lib/libstdc++.so.6: version `GLIBCXX_3.4.15' not found错误的解决
原因是没有GLIBCXX_3..15版本,或是更高的版本. 一.查看并下载 32位系统: [root@localhost ~]# strings /usr/lib/libstdc++.so. | gr ...
- ideaIU-15.0.2 注册码
注册时选择 License server ,填 http://idea.lanyus.com ,然后点击 OK
- Protobuf学习 - 入门(转)
从公司的项目源码中看到了这个东西,觉得挺好用的,写篇博客做下小总结.下面的操作以C++为编程语言,protoc的版本为libprotoc 3.2.0. 一.Protobuf? 1. 是什么? Goo ...
- UVa 1303 - Wall
题目:有非常多点.修一座最短的围墙把素有点围起来,使得全部点到墙的距离不小于l. 分析:计算几何,凸包. 假设.没有距离l的限制.则答案就是凸包的周长了.有了距离限制事实上是添加了2*π*l. 证明: ...
- servlet ServletConfig ServletContext
ServletConfig对象 在Servlet的配置文件中,可以使用一个或者多个<init-param>标签为servlet配置一些初始化参数. 当servlet配置了初始化参数后,we ...