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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络 http服务器-v2-epoll版本相关的知识,希望对你有一定的参考价值。

epoll的基本接口与建立tcp连接的流程 查看:

网络 使用epoll 实现TCP服务器 - 初出茅庐小菜鸟 - 51CTO技术博客

http://shaungqiran.blog.51cto.com/10532904/1784410


重点:

        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;
}


以上是关于网络 http服务器-v2-epoll版本的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段14——Vue的axios网络请求封装

VSCode自定义代码片段14——Vue的axios网络请求封装

VSCode自定义代码片段14——Vue的axios网络请求封装

Flutter 报错 DioError [DioErrorType.DEFAULT]: Bad state: Insecure HTTP is not allowed by platform(代码片段

代码片段 - Golang 实现简单的 Web 服务器

如何管理在每个 git 版本中添加私有代码片段?