基于epoll的web服务器

前言

我们在大量并发的时候epoll有高的性能,所以我们选择用他去做一个BS模型的web服务器

web服务器

网络上是epoll并发,在解析http命令是下面的思路
基于epoll的web服务器

具体的主要看代码的思路
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <strings.h>
#include <dirent.h>
#include "wrap.h"
#include "pub.h"

int send_head(int connfd,char *cord,char *msg,char *file,int len)
{
	char buf[1024]={0};
	
	sprintf(buf,"HTTP/1.1 %s %s\r\n",cord,msg);
	sprintf(buf+strlen(buf), "Content-Type:%s\r\n",file);
	if (len>0)
	{
		sprintf(buf+strlen(buf), "Content-Length:%d\r\n", len);
	}
	strcat(buf,"\r\n");
	printf ("%s",buf);
	Write(connfd,buf,strlen(buf));
	return 0;
}
int send_file (int connfd,char *file)
{
	//使用文件I/O读写文件
	int num=0;
	int fd=open(file,O_RDONLY);
	if (fd<0)
	{
		perror("open error");
		return -1;
	}
	int n=0;
	char buf[1024];
	while (1)
	{
		//bzero (buf,1024);
		memset(buf,0x00,sizeof(buf));
		n = read(fd,buf,sizeof(buf));
		//printf("---- %s----\n",buf);
		if (n<=0)
			break;
		else
			Write(connfd,buf,n);
	}
	return 0;
}

void http_reques(int connfd,int epfd)
{
	//获取头部信息
	int n=0;
	char buf[1000]={0};
	memset(buf, 0x00, sizeof(buf));
	n=Readline (connfd,buf,sizeof(buf));
	if (n<=0)
	{
		printf("readline error\n");
		close (connfd);
		epoll_ctl(epfd,EPOLL_CTL_DEL,connfd,NULL);
		return;
	}
	printf ("%s\n",buf);
	char type[16]={0};
	char name[255]={0};
	char port[16]={0};
	sscanf(buf,"%[^ ] %[^ ] %[^ \r\n]",type,name,port);
	
	//去除前面的“/”
	int n1=0;
	char *pname=name;
	if (strlen(pname)<=1)
	{
		strcpy(pname,"./");
	}
	else 
	{
		pname=name+1;
	}
	//通过对字符的转换来处理中文编码问题
	strdecode(pname, pname);
	printf("[%s]\n", pname);
	//将缓存区其他的请求行 空行 正文清空
	while ((n1=Readline(connfd,buf,sizeof(buf)))>0);

	//判断文件是否存在
	struct stat st;
	if (stat(pname,&st)<0)
	{
		printf ("file not exist\n");
		//响应头
		send_head(connfd, "404", "NOT FOUND", get_mime_type(".html"), 0);
		
		//响应文件内容
		send_file(connfd, "error.html");	

	}
	else
	{
		//若为目录文件
		if (S_ISREG(st.st_mode))
		{
			printf ("file exist\n");
			send_head (connfd,"200","OK",get_mime_type(pname),st.st_size);
			send_file (connfd,pname);
		}
		else if (S_ISDIR(st.st_mode))
		{
			printf ("目录文件\n");
			char buffer[1024];
			//响应头
			send_head (connfd,"200","OK",get_mime_type(".html"),0);
			//响应前面的头
			send_file (connfd,"html/dir_header.html");

			struct dirent **namelist;
			int num,wn;
			//解析目录
			num = scandir(pname,&namelist,NULL,alphasort);
			if (num < 0)
			{
				perror ("scandir");
				close (connfd);
				epoll_ctl(epfd,EPOLL_CTL_DEL,connfd,NULL);
				return;
			}
			else 
			{
				//循环读
				while (num--)
				{
					//bzero (buffer,1024);
					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(connfd,buffer,strlen(buffer)+1);
				}
				free(namelist);
			}
			//发送尾
			send_file(connfd,"html/dir_tail.html");
		}
	}
}

int main(int argc, char *argv[])
{
	//切换目录到家目录
	char path[100]={0};
	sprintf (path,"%s/%s",getenv("HOME"),"xixi/w_网络/http");
	chdir (path);
	
	//创建网络描述符
	int sfd=socket(AF_INET,SOCK_STREAM,0);
	if (sfd<0)
	{
		perror ("socker");
		exit(-1);
	}

	//设置端口复用
	int opt=1;
	setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

	//初始化包含应用层和网络层的ip结构体
	struct sockaddr_in ip,ipp;
	ip.sin_family =AF_INET;
	ip.sin_port =htons(10034);
	ip.sin_addr.s_addr =inet_addr("0.0.0.0");
	//ip.sin_addr.s_addr =inet_addr("172.26.46.72");
	socklen_t len = sizeof(ip);

	//绑定
	if (bind(sfd,(struct sockaddr*)&ip,len))
	{
		perror ("bind");
		exit(-1);
	}

	//监听
	if (listen(sfd,155))
	{
		perror ("listen");
		exit (-1);
	}

	//epoll跟结点的创建
	int epfd=epoll_create (1000);
	if (epfd <0)
	{
		perror ("epoll create");
		exit (-1);
	}

	struct epoll_event tep,ep[1000];
	int client[1000],ret,n,socked;
	char buf[100];
	int max=-1,connfd,j,cfd;
	char str[100];

	//将接收到的客户端初始化为-1
	for (int i=0;i<1000;i++)
	{
		client[i]=-1;
	}
	
	//将监听的文件描述符上树
	tep.events=EPOLLIN;
	tep.data.fd=sfd;
	int res=epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&tep);
	if (res==-1)
	{
		perror ("epoll_ctl");
		exit (-1);
	}
	
	//循环监听
	while (1)
	{
		res=epoll_wait(epfd,ep,1000,-1);
		if (res==-1)
		{
			perror ("wait");
			exit(-1);
		}

		for (int i=0;i<res;i++)
		{
			cfd=ep[i].data.fd;
			if (!(ep[i].events & EPOLLIN))
				continue;
			if (ep[i].data.fd==sfd)
			{
				//接收文件描述符
				len =sizeof(ipp);
				connfd=accept(sfd,(void*)&ipp,&len);
				//设置非阻塞I/O,保证后面可以读完缓存区的信息流
				int flag=fcntl(connfd,F_GETFL);
				flag |=O_NONBLOCK;
				fcntl(connfd,F_SETFL,flag);
				printf("received from %s at PORT %d\n", 
						inet_ntop(AF_INET, &ipp.sin_addr, str, sizeof(str)), 
						ntohs(ipp.sin_port));
			
				for (j=0;j<1000;j++)
				{
					if (client[j]<0)
					{
						client[j]=connfd;
						break;
					}
				}
				if (connfd ==1000)
				{
					puts ("too many client");
					exit(-1);
				}
				if (j >max)
					max=j;
				tep.events=EPOLLIN;
				tep.data.fd= connfd;
				ret=epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&tep);
				if (ret==-1)
				{
					perror ("ctl add");
					exit(-1);
				}
			}
			else
			{
				//处理http信息
				http_reques(cfd,epfd);
			}
		}

	}
	close (sfd);
	close (epfd);
	return 0;
}

上一篇:I/O多路复用中的水平触发和边缘触发


下一篇:从IO模型到协程(三) 多路复用之select、poll和epoll