epoll与libevent实现web服务器开发

功能介绍:使用浏览器作为客户端访问web服务器

epoll开发流程:

1 创建socket

2 设置端口复用

3 绑定

4 监听

5 创建epoll树

6 将监听文件描述符上epoll树

7

while(1)
  {
  	nready = epoll_wait();
  	if(nready<0)
  	{
  	

  	}
  	
  	for(i=0; i<nready; i++)
  	{
  	  	//若是有新的客户端连接到来,则接受新的客户端连接, 然后上epoll树
  	  	if(lfd==events[i].data.fd)
  	  	{
  	  		cfd = accept();
  	  		epoll_ctl(epfd, cfd, EPOLL_CTL_ADD, &ev);
  	  	}
  	  	else
  	  	{ 	
  	  		http_request(cfd);
  	  	}
  	}

  }	
 //处理客户端的请求
 int http_request(int cfd)
 {
 	//读取数据
 	//先读取一行, 可以获得浏览器想请求的文件
 	//然后循环将剩余的数据读完
 	

 	//查看本地是否有这个文件
 		---若没有这个文件, 则组织应答信息并且将404.jpg文件内容发送给浏览器
 		---若有个这个文件, 则判断文件类型
 			----若是普通文件, 则组织应答消息并且将文件内容发送给浏览器
 			----若是目录, 则组织应答消息, 响应正文部分将目录下的所有文件组织
 			成文件列表的形式生成html文件(每个文件都是一个超连接), 然后发送给浏览器 

 }

read函数读通信文件描述符是阻塞的, 所以在while中循环读的时候若读完数据了会引起阻塞; 所以应该将通信文件描述符设置为非阻塞.

遍历目录的函数:

1 opendir readdir closedir

2 int scandir(const char dirp, struct dirent ***namelist,

int (filter)(const struct dirent ), int (compar)(const struct dirent , const struct dirent ));


webserver.c

//web服务端程序--使用epoll模型
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
#include <dirent.h>

#include "pub.h"
#include "wrap.h"

int http_request(int cfd, int epfd);

int main()
{
	//若web服务器给浏览器发送数据的时候, 浏览器已经关闭连接, 
	//则web服务器就会收到SIGPIPE信号
	struct sigaction act;
	act.sa_handler = SIG_IGN;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGPIPE, &act, NULL);
	
	//改变当前进程的工作目录
	char path[255] = {0};
	sprintf(path, "%s/%s", getenv("HOME"), "webpath");
	chdir(path);
	
	//创建socket--设置端口复用---bind
	int lfd = tcp4bind(9999, NULL);
	
	//设置监听
	Listen(lfd, 128);

	//创建epoll树
	int epfd = epoll_create(1024);
	if(epfd<0)
	{
		perror("epoll_create error");
		close(lfd);
		return -1;
	}
	
	//将监听文件描述符lfd上树
	struct epoll_event ev;
	ev.data.fd = lfd;
	ev.events = EPOLLIN;
	epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
	
	int i;
	int cfd;
	int nready;
	int sockfd;
	struct epoll_event events[1024];
	while(1)
	{
		//等待事件发生
		nready = epoll_wait(epfd, events, 1024, -1);
		if(nready<0)
		{
			if(errno==EINTR)
			{
				continue;
			}
			break;
		}
		
		for(i=0; i<nready; i++)
		{
			sockfd = events[i].data.fd;
			//有客户端连接请求
			if(sockfd==lfd)
			{
				//接受新的客户端连接
				cfd = Accept(lfd, NULL, NULL);
				
				//设置cfd为非阻塞
				int flag = fcntl(cfd, F_GETFL);
				flag |= O_NONBLOCK;
				fcntl(cfd, F_SETFL, flag);
				
				//将新的cfd上树
				ev.data.fd = cfd;
				ev.events = EPOLLIN;
				epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
			}
			else 
			{
				//有客户端数据发来
				http_request(sockfd, epfd);
			}			
		}		
	}
}

int send_header(int cfd, char *code, char *msg, char *fileType, int len)
{
	char buf[1024] = {0};
	sprintf(buf, "HTTP/1.1 %s %s\r\n", code, msg);
	sprintf(buf+strlen(buf), "Content-Type:%s\r\n", fileType);
	if(len>0)
	{
		sprintf(buf+strlen(buf), "Content-Length:%d\r\n", len);
	}
	strcat(buf, "\r\n");
	Write(cfd, buf, strlen(buf));
	return 0;
}

int send_file(int cfd, char *fileName)
{
	//打开文件
	int fd = open(fileName, O_RDONLY);
	if(fd<0)
	{
		perror("open error");
		return -1;
	}
	
	//循环读文件, 然后发送
	int n;
	char buf[1024];
	while(1)
	{
		memset(buf, 0x00, sizeof(buf));
		n = read(fd, buf, sizeof(buf));
		if(n<=0)
		{
			break;
		}
		else 
		{
			Write(cfd, buf, n);
		}
	}
}

int http_request(int cfd, int epfd)
{
	int n;
	char buf[1024];
	//读取请求行数据, 分析出要请求的资源文件名
	memset(buf, 0x00, sizeof(buf));
	n = Readline(cfd, buf, sizeof(buf));
	if(n<=0)
	{
		//printf("read error or client closed, n==[%d]\n", n);
		//关闭连接
		close(cfd);
		
		//将文件描述符从epoll树上删除
		epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
		return -1;	
	}
	printf("buf==[%s]\n", buf);
	//GET /hanzi.c HTTP/1.1
	char reqType[16] = {0};
	char fileName[255] = {0};
	char protocal[16] = {0};
	sscanf(buf, "%[^ ] %[^ ] %[^ \r\n]", reqType, fileName, protocal);
	//printf("[%s]\n", reqType);
	printf("--[%s]--\n", fileName);
	//printf("[%s]\n", protocal);
	
	char *pFile = fileName;
	if(strlen(fileName)<=1)
	{
		strcpy(pFile, "./");
	}
	else 
	{
		pFile = fileName+1;
	}
	
	//转换汉字编码
	strdecode(pFile, pFile);
	printf("[%s]\n", pFile);
	
	//循环读取完剩余的数据,避免产生粘包
	while((n=Readline(cfd, buf, sizeof(buf)))>0);
	
	//判断文件是否存在
	struct stat st;
	if(stat(pFile, &st)<0)
	{
		printf("file not exist\n");
		
		//发送头部信息
		send_header(cfd, "404", "NOT FOUND", get_mime_type(".html"), 0);
		
		//发送文件内容
		send_file(cfd, "error.html");	
	}
	else //若文件存在
	{
		//判断文件类型
		//普通文件
		if(S_ISREG(st.st_mode))
		{
			printf("file exist\n");
			//发送头部信息
			send_header(cfd, "200", "OK", get_mime_type(pFile), st.st_size);
			
			//发送文件内容
			send_file(cfd, pFile);
		}
		//目录文件
		else if(S_ISDIR(st.st_mode))
		{
			printf("目录文件\n");
			
			char buffer[1024];
			//发送头部信息
			send_header(cfd, "200", "OK", get_mime_type(".html"), 0);	
			
			//发送html文件头部
			send_file(cfd, "html/dir_header.html");	
			
			//文件列表信息
			struct dirent **namelist;
			int num;

			num = scandir(pFile, &namelist, NULL, alphasort);
			if (num < 0)
			{
			   perror("scandir");
			   close(cfd);
			   epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
			   return -1;
			   
			}
			else 
			{
			   while (num--) 
			   {
			       printf("%s\n", namelist[num]->d_name);
			       memset(buffer, 0x00, sizeof(buffer));
			       if(namelist[num]->d_type==DT_DIR)
			       {
			       		sprintf(buffer, "<li><a href=%s/>%s</a></li>", namelist[num]->d_name, namelist[num]->d_name);
			       }
			       else
			       {
			       		sprintf(buffer, "<li><a href=%s>%s</a></li>", namelist[num]->d_name, namelist[num]->d_name);
			       }
			       free(namelist[num]);
			       Write(cfd, buffer, strlen(buffer));
			   }
			   free(namelist);
			}
			//发送html尾部
			sleep(10);
			send_file(cfd, "html/dir_tail.html");		
		}
	}
	
	return 0;
}

 ip地址是自己的,端口是9999

 

 libevent实现Web服务器

//通过libevent编写的web服务器
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "pub.h"
#include <event.h>
#include <event2/listener.h>
#include <dirent.h>

#define _WORK_DIR_ "%s/webpath"
#define _DIR_PREFIX_FILE_ "html/dir_header.html"
#define _DIR_TAIL_FILE_ "html/dir_tail.html"

int copy_header(struct bufferevent *bev,int op,char *msg,char *filetype,long filesize)
{
    char buf[4096]={0};
    sprintf(buf,"HTTP/1.1 %d %s\r\n",op,msg);
    sprintf(buf,"%sContent-Type: %s\r\n",buf,filetype);
    if(filesize >= 0){
        sprintf(buf,"%sContent-Length:%ld\r\n",buf,filesize);
    }
    strcat(buf,"\r\n");
    bufferevent_write(bev,buf,strlen(buf));
    return 0;
}
int copy_file(struct bufferevent *bev,const char *strFile)
{
    int fd = open(strFile,O_RDONLY);
    char buf[1024]={0};
    int ret;
    while( (ret = read(fd,buf,sizeof(buf))) > 0 ){
        bufferevent_write(bev,buf,ret);
    }
    close(fd);
    return 0;
}
//发送目录,实际上组织一个html页面发给客户端,目录的内容作为列表显示
int send_dir(struct bufferevent *bev,const char *strPath)
{
    //需要拼出来一个html页面发送给客户端
    copy_file(bev,_DIR_PREFIX_FILE_);
    //send dir info 
    DIR *dir = opendir(strPath);
    if(dir == NULL){
        perror("opendir err");
        return -1;
    }
    char bufline[1024]={0};
    struct dirent *dent = NULL;
    while( (dent= readdir(dir) ) ){
        struct stat sb;
        stat(dent->d_name,&sb);
        if(dent->d_type == DT_DIR){
            //目录文件 特殊处理
            //格式 <a href="dirname/">dirname</a><p>size</p><p>time</p></br>
            memset(bufline,0x00,sizeof(bufline));
            sprintf(bufline,"<li><a href='%s/'>%32s</a>   %8ld</li>",dent->d_name,dent->d_name,sb.st_size);
            bufferevent_write(bev,bufline,strlen(bufline));
        }
        else if(dent->d_type == DT_REG){
            //普通文件 直接显示列表即可
            memset(bufline,0x00,sizeof(bufline));
            sprintf(bufline,"<li><a href='%s'>%32s</a>     %8ld</li>",dent->d_name,dent->d_name,sb.st_size);
            bufferevent_write(bev,bufline,strlen(bufline));
        }
    }
    closedir(dir);
    copy_file(bev,_DIR_TAIL_FILE_);
    //bufferevent_free(bev);
    return 0;
}
int http_request(struct bufferevent *bev,char *path)
{
    
    strdecode(path, path);//将中文问题转码成utf-8格式的字符串
    char *strPath = path;
    if(strcmp(strPath,"/") == 0 || strcmp(strPath,"/.") == 0){
        strPath = "./";
    }
    else{
        strPath = path+1;
    }
    struct stat sb;
    
    if(stat(strPath,&sb) < 0){
        //不存在 ,给404页面
        copy_header(bev,404,"NOT FOUND",get_mime_type("error.html"),-1);
        copy_file(bev,"error.html");
        return -1;
    }
    if(S_ISDIR(sb.st_mode)){
        //处理目录
        copy_header(bev,200,"OK",get_mime_type("ww.html"),sb.st_size);
        send_dir(bev,strPath);
        
    }
    if(S_ISREG(sb.st_mode)){
        //处理文件
        //写头
        copy_header(bev,200,"OK",get_mime_type(strPath),sb.st_size);
        //写文件内容
        copy_file(bev,strPath);
    }

    return 0;
}

void read_cb(struct bufferevent *bev, void *ctx)
{
    char buf[256]={0};
    char method[10],path[256],protocol[10];
    int ret = bufferevent_read(bev, buf, sizeof(buf));
    if(ret > 0){

        sscanf(buf,"%[^ ] %[^ ] %[^ \r\n]",method,path,protocol);
        if(strcasecmp(method,"get") == 0){
            //处理客户端的请求
            char bufline[256];
            write(STDOUT_FILENO,buf,ret);
            //确保数据读完
            while( (ret = bufferevent_read(bev, bufline, sizeof(bufline)) ) > 0){
                write(STDOUT_FILENO,bufline,ret);
            }
			http_request(bev,path);//处理请求

        }
    }
}
void bevent_cb(struct bufferevent *bev, short what, void *ctx)
{
    if(what & BEV_EVENT_EOF){//客户端关闭
        printf("client closed\n");
        bufferevent_free(bev);
    }
    else if(what & BEV_EVENT_ERROR){
        printf("err to client closed\n");
        bufferevent_free(bev);
    }
    else if(what & BEV_EVENT_CONNECTED){//连接成功
        printf("client connect ok\n");
    }
}
void listen_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int socklen, void *arg)
{
    //定义与客户端通信的bufferevent
    struct event_base *base = (struct event_base *)arg;
    struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    bufferevent_setcb(bev,read_cb,NULL,bevent_cb,base);//设置回调
    bufferevent_enable(bev,EV_READ|EV_WRITE);//启用读和写
}

int main(int argc,char *argv[])
{
	char workdir[256] = {0};
	sprintf(workdir,_WORK_DIR_,getenv("HOME"));//HOME=/home/itheima 
	chdir(workdir);
    struct event_base *base = event_base_new();//创建根节点
    struct sockaddr_in serv;
    serv.sin_family = AF_INET;
    serv.sin_port = htons(9999);
    serv.sin_addr.s_addr = htonl(INADDR_ANY);
    struct evconnlistener * listener =evconnlistener_new_bind(base,
                                     listen_cb, base, LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, -1,
                                                        (struct sockaddr *)&serv, sizeof(serv));//连接监听器
    

    event_base_dispatch(base);//循环

    event_base_free(base); //释放根节点
    evconnlistener_free(listener);//释放链接监听器
    return 0;
}

学习来自黑马

具体代码可在https://github.com/wodazhouyi/linux_netwrok查看

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值