您的位置:首页 > 理论基础 > 计算机网络

网络 http服务器-v2-epoll版本

2016-06-30 17:04 549 查看
epoll的基本接口与建立tcp连接的流程 查看:
网络 使用epoll 实现TCP服务器 - 初出茅庐小菜鸟 - 51CTO技术博客
/article/11857294.html

重点:
epoll 遵循的是多路复用的 I/O模型。其内部只实现了对 关注I/O事件的监听,而没有实现具体的操作。因此我们必须自己选择恰当的时机去 读 或者 写。而对于内核而言,对一个socket 文件描述符 的读 、写 事件的监听 是分离的(可读,未必可写)。因此 ,要想达到最高的效率。用户 做的 I/O操作相应的也应该读、写分离。

而如果读写分离的话,对于HTTP服务器而言,server 端 提供的服务有时是需要 客户端 提交的信息的。读、写分离会造成无法有效的 做出响应。所以 我们必须自己维护一段 缓冲区,去将每个socket fd 的有效内容都报春起来。直到进行 写事件 时用到,之后 回收该内存。
每个事件句柄中都有 一个 不长使用的 指针(void*)。正好我们可以用它来指向 我们的缓冲区、


typedef union epoll_data {
void *ptr; //我们可用的 ptr 用来存指向缓冲区
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
//感兴趣的事件和被触发的事件
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
epoll.c

#include "httpd.h"
#include <sys/epoll.h>
#include <malloc.h>
#include <netdb.h>
#include <sys/utsname.h>
#include <net/if.h>
#include <sys/ioctl.h>
#define _MAXFD_ 10
#define ERRORIP -1

int set_non_block(int fd)
{
int old_flag=fcntl(fd,F_GETFL);
if(old_flag<0){
perror("fcntl");
//	exit(-4);
return -1;
}
if(fcntl(fd,F_SETFL,old_flag|O_NONBLOCK)<0){
perror("fcntl");
return -1;
}

return 0;

}

int startup(char* ip,int port)
{
int sock=socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in server;
server.sin_family=AF_INET;
server.sin_addr.s_addr=inet_addr(ip);
server.sin_port=htons(port);
int flag=0;
//	printf("port  %d  %d",port,(htons(port)));
if(bind(sock,(struct sockaddr *)&server,sizeof(server))<0){
perror("bind");
exit(-2);
}
if( setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) == -1)
{
perror("setsockopt");
exit(1);
}
if(listen(sock,50)<0){
perror("listen");
exit(-3);
}
return sock;
}

void usage(char* arg)
{
printf("usage %s [ip] [port]\n",arg);

}

void epollup(int sock)
{
int epoll_fd=epoll_create(256);
if(epoll_fd<0){
perror("epoll");
return;
}
int timeout_num=0;
int done=0;
int timeout=10000;
int i=0;
int ret_num=-1;

struct epoll_event ev;
struct epoll_event event[100];
ev.data.fd=sock;
ev.events=EPOLLIN|EPOLLET;
//	fd_num=1;
//	printf("listen sock%d\n",sock);
if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,sock,&ev)<0){
perror("epoll_ctl");
return ;
}
while(!done){
switch(ret_num=epoll_wait(epoll_fd,event,256,timeout)){
case -1:{
perror("epoll_wait");
break;
}
case 0 :{
if(	timeout_num++>20)
done=1;
printf("time out...\n");
break;
}
default:{
for(i=0;i<ret_num;++i){
if(event[i].data.fd==sock&&event[i].events&EPOLLIN){
int new_sock=-1;
struct sockaddr_in client;
socklen_t len=sizeof(client);
while((new_sock=accept(sock,(struct sockaddr*)&client,&len))){
if(new_sock<0){
//perror("accept");
break;
}
if(set_non_block(new_sock)<0){
echo_error(new_sock,500);
continue;
}
printf(" epoll :%d\n",new_sock);
ev.data.fd=new_sock;
ev.events=EPOLLIN|EPOLLET;
if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&ev)<0){
perror("epoll_ctl");
echo_error(new_sock,503);
continue;
}
}
break;
}
else {
if(event[i].events&EPOLLIN){
int fd=event[i].data.fd;
pev_buf pev=(pev_buf)malloc(sizeof(ev_buf));
event[i].data.ptr=pev;
pev->fd=fd;
if(epoll_recv_http(&event[i])<0){
free(pev);
if(epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,&ev)<0)
perror("read EPOLL_CTL_DEL");
close(fd);
continue;
}
else
event[i].events=EPOLLOUT;
}
if(event[i].events&EPOLLOUT){
int fd=((pev_buf)(event[i].data.ptr))->fd;
epoll_echo_http(event+i);
if(epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,&ev)<0)
perror("out_ctl_del");
free((pev_buf)event[i].data.ptr);
printf("epoll close :%d\n",fd);
close(fd);

}

}
}
break;
}

}
}
}

int main(int argc,char* argv[]){
if(argc!=3){
usage(argv[0]);
exit(-1);
}
int port=atoi(argv[2]);
int listen_sock;
char* ip=NULL;
if(strcmp(argv[1],"any")==0){
int sfd, intr;
struct ifreq buf[16];
struct ifconf ifc;
sfd = socket (AF_INET, SOCK_DGRAM, 0);
if (sfd < 0)
return ERRORIP;
ifc.ifc_len = sizeof(buf);
ifc.ifc_buf = (caddr_t)buf;
if (ioctl(sfd, SIOCGIFCONF, (char *)&ifc))
return ERRORIP;
intr = ifc.ifc_len / sizeof(struct ifreq);
while (intr-- > 0 && ioctl(sfd, SIOCGIFADDR, (char *)&buf[intr]));
close(sfd);
ip= inet_ntoa(((struct sockaddr_in*)(&buf[intr].ifr_addr))-> sin_addr);

printf("%s\n",ip);
listen_sock=startup(ip,port);
}
else

listen_sock=startup( argv[1],port);

//	printf("port %s %d",argv[2],port);

set_non_block(listen_sock);
epollup(listen_sock);
close(listen_sock);
return 0;
}
httpd.h
#ifndef __MYHTTP__

#define __MYHTTP__
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/epoll.h>
#include <sys/sendfile.h>
#define _SIZE_ 1024

typedef struct event_buf{//自己维护的结构体
int fd;
char method[10];//http请求方法
char url[1024];//请求参数
char parameter[1024];//get方法 参数
int cgi;//是否需要用到CGI 方法
}ev_buf,*pev_buf;//重命名

char* get_text(int fd,char* buf);//获取消息正文

int epoll_recv_http(struct epoll_event *ev);//接受消息
int epoll_echo_http(struct epoll_event * ev);//发送xiaoxi
void cgi_action(int fd,char* method,char* url,char* parameter);//CGI接口

int get_line(int sock_fd,char * buf);//从socket fd 读取一行信息

void* http_action(void* client_sock);//

void echo_error(int fd,int _errno);//回显错误信息
void error_all(int fd,int err,char* reason);//所有错误信息
void echo_html(int fd,const char* url,int size );//回显请求网页

#endif
httpd.c

#include "httpd.h"

#define DEFAULT "/default.html"
#define IMG "src_html"
#define CGI "src_cgi"

int times=0;

void echo_error(int fd,int _errno)
{
printf("join err\n");
switch(_errno){
case 400://Bad Request  //客户端请求有语法错误,不能被服务器所理解
break;
case 404:////请求资源不存在,eg:输入了错误的URL
//printf("*******404\n");
error_all(fd,404,"NOT_FIND!!");
break;
case 401://请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
break;
case 403://服务器收到请求,但是拒绝提供服务
break;
case 500:// Internal Server Error //服务器发生不可预期的错误
break;
case 503://Server Unavailable  //服务器当前不能处理客户端的请求,一段时间后可能恢复正常
break;
default:
break;
}
}

void error_all(int fd,int err,char* reason)
{
char buf[_SIZE_]="";
char error[_SIZE_]="";
sprintf(buf,"HTTP/1.0 %d %s\r\n\r\n",err,reason);
sprintf(error," %d %s",err,reason);
printf("err buf:%s\n error:%s",buf,error);
write(fd,buf,strlen(buf));
write(fd,"<html>\n",strlen("<html>\n"));
write(fd,"<head>",strlen("<head>"));
write(fd,"<h1> HELLO PPH!!!</h1>\n",strlen("<h1> HELLO PPH!!!</h1>\n"));
write(fd,"<h2>",strlen("<h2>"));
write(fd,error,strlen(error));
write(fd,"</h2>\n",strlen("</h2>\n"));
write(fd,"</head>\n",strlen("</head>\n"));
write(fd,"</html>",strlen("</html>"));
//	echo_html(fd,"src_html/1.html",102400000);
}
void echo_html(int fd,const char* url,int fd_size ){
char buf[_SIZE_]="HTTP/1.1 200 OK\r\n\r\n";

int rfd=-1;
off_t set=0;
ssize_t size=1;
printf("url:%s\n",url);
if((rfd=open(url, O_RDONLY))<0){
echo_error(fd,500);
printf("e-html %s",strerror(errno));
}
printf("open success\n");
if(write(fd,buf,strlen(buf))<0){
perror(" echo_html_write");
close(rfd);
return;
}
printf("write head success\n");
int i=0;
while(set!=fd_size){
size=sendfile(fd,rfd,&set,fd_size);
if(errno!=EAGAIN&&size<0){
printf("sendfile error %s %d\n",strerror(errno),errno);
break;
}
//	printf("\nsend: size %d all size  %d time %d \n",set,fd_size,++i);
}
close(rfd);
return;
}

int get_line(int sock_fd,char * line){
int index=0;
ssize_t size=1;
char ch=0;
printf("getline start\n");
while(ch!='\n'){
if((size=read(sock_fd,&ch,1))<0){
perror("getline__");
if(errno==EAGAIN)
continue;
else
return -1;
}
if(ch=='\r'){
char tmp=0;
if(recv(sock_fd,&tmp,1,MSG_PEEK)>0){
if(tmp=='\n'){
line[index++]=tmp;
read(sock_fd,&ch,1);
continue;
}
}
}
//printf("index %d\n",index);
if(index==1024){
printf("httpd line full exit\n");
line[1023]=0;
return -2;
}
line[index++]=ch;
}
line[index]=0;
if(strcmp(line,"\n")==0){
return 0;
}
printf("getline  success\n");
return 1;
}

//获取post正文 参数

char* get_length(int fd,char* content_length)
{
int size=1;
int tag=0;
int index=0;
while(size!=0){//通过持续读取一行 直到读到空行结束
size=get_line(fd,content_length);
if(size==-2)
continue;
if(strncasecmp(content_length,"content-length: ",16)==0){
printf(" length success\n");
break;
}
if(size==-1){
printf("get line出错\n");
return NULL;
}

}
content_length[strlen(content_length)-1]=0;
strcpy(content_length,content_length+16);
printf("con end: %s\n",content_length);
return content_length;
}

void cgi_action(int fd,char* method,char* url ,char* parameter)
{
char env[20]="METHOD=";
char par[_SIZE_]="PARAMETER=";
int pwrite[2];
if((pipe(pwrite)<0)){
perror("pipe");
return;
}
strcat(env,method);
strcat(par,parameter);
printf(" act url:%s\n",url);
printf("parameter:%s\n",par);
if(putenv(env)<0){
perror("putenv");
return;
}
if(putenv(par)<0){
perror("putenv par");
return;
}
//	printf("fork qian\n");
pid_t id=fork();
if(id<0){
perror("fork");
return;
}
else if(id==0){//子进程
close(pwrite[0]);
//printf("child\n");
if(dup2(pwrite[1],1)<0){
perror("dup2.1");
return;
}
if(dup2(fd,0)<0){
perror("dup2.2");
return;
}
if(execl(url,NULL)<0){
perror("execl");
printf("exit url:\n",url);
exit(-2);
}

}
else{//父进程
close(pwrite[1]);
char buf[_SIZE_]="";
int count=0;
int i=0;
ssize_t size=1;
while(size>0){
size=read(pwrite[0],buf,_SIZE_);
if(size<0){
echo_error(fd,500);
break;
}
if(size==0)
break;
write(fd,buf,strlen(buf));

}
waitpid(-1,NULL,0);
close(pwrite[0]);
}
}

int epoll_echo_http(struct epoll_event * ev)
{

char* method=((pev_buf)ev->data.ptr)->method;
char* url=((pev_buf)ev->data.ptr)->url;
char* parameter=((pev_buf)ev->data.ptr)->parameter;//get方法 参数
int fd=((pev_buf)ev->data.ptr)->fd;

int cgi=((pev_buf)ev->data.ptr)->cgi;
struct stat stat_buf;
if(cgi==0)
if(stat(url, &stat_buf)<0){
printf("stat <0 \n");
echo_error(fd,404);
return 0;
}

if(strcasecmp("POST",method)==0){

//printf("already cgi\n");
cgi_action(fd,method,url,parameter);
}
else if(strcasecmp("GET",method)==0){
if(cgi==1){
//cgi
//	printf("rev_http: parameter:%s\n",parameter);
cgi_action(fd,method,url,parameter);
printf("ret cgi\n");
}
else{
echo_html(fd,url,stat_buf.st_size);
}

}
if(strcasecmp(method,"POST")==0)
clear_buf(fd);
return 0;
}

int epoll_recv_http(struct epoll_event *ev)
{
if(ev==NULL){
printf("ev error\n");
return -1;
}
int fd=((pev_buf)ev->data.ptr)->fd;

char real_url[128]="src_html";
char line[_SIZE_];
char* method=NULL;
char* version=NULL;
char* url=NULL;
char parameter[_SIZE_]="";//get方法 参数
char  content_length[_SIZE_]="";
if(get_line(fd,line)==-2){
printf("it's a cache request! so can't process!\n");
return 0;
}
int index=strlen(line)-1;
//GET / HTTP/1.1
while(index>0){//提取method url
if(line[index]==' '&&version==NULL){
version=((char*)line)+index+1;
line[index]=0;
}
if(line[index]==' '&&url==NULL){
url=line+index+1;
line[index]=0;
}
--index;
}
method=line;

((pev_buf)ev->data.ptr)->cgi=0;
if(strcasecmp("GET",method)==0){
index=0;
while(url[index]){
if(url[index]=='?'){
((pev_buf)ev->data.ptr)->cgi=1;
strcpy(parameter,url+index+1);
url[index]=0;
((pev_buf)ev->data.ptr)->cgi=1;
break;
}
++index;
}

}
else if(strcasecmp("POST",method)==0){
((pev_buf)ev->data.ptr)->cgi=1;
if(get_length(fd,content_length)==NULL){
echo_error(fd,503);
printf("get len err\n");
clear_buf(fd);
return -1;
}
strcpy(parameter,content_length);
}
if(strcmp(url,"/")==0){
strcat(real_url,DEFAULT);
}
else{
if(((pev_buf)ev->data.ptr)->cgi==1){
strcpy(real_url,CGI);
}
strcat(real_url,url);
}
printf("real_url :%s\n",real_url);

strcpy(((pev_buf)ev->data.ptr)->method,method);
strcpy(((pev_buf)ev->data.ptr)->url,real_url);
strcpy(((pev_buf)ev->data.ptr)->parameter,parameter);
printf(" get connect :%d times !\n",++times);
if(strcasecmp(method,"get")==0)
clear_buf(fd);

return 1;
}

int  clear_buf(int fd)
{
char buf[_SIZE_]="";
ssize_t size=0;
size=read(fd,buf,_SIZE_);
if(size<0){
perror("clear_buf_read");
return -1;
}
buf[size]=0;
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: