Programación de red Linux: servidor web

Directorio de artículos:

Uno: lenguaje de edición de hipertexto HTML

Dos: protocolo de transferencia de hipertexto HTTP

1. Flujo de mensajes de solicitud y respuesta 

 mihttpd.c

Función getline: utilizada para leer el encabezado del protocolo http

2. error 

2.1 Manejar un error y devolver la desconexión

2.2 La página de error muestra send_error

3. Escriba el encabezado del protocolo de respuesta http.

Escribir datos en el navegador send_file

4. El navegador solicita el directorio.

5. Codificación y decodificación de caracteres chinos.

6. Códigos de estado comunes HTTP

Tres: expresiones regulares

Cuatro: operación de archivos

1. Distinción de tipo de archivo get_file_type

2. Comunicación en un solo archivo

3. Determine si el archivo existe do_read

Cinco: implementación del servidor web

1. Implementar un servidor web basado en epoll

epoll_server.h

epoll_server.c

C Principal

2. Implementación de servidor web basado en libevent

libevent_http.h

libevent_http.c

C Principal


Uno: lenguaje de edición de hipertexto HTML

大多数标签成对儿出现, 不成对儿出现的被称为**短标签

Aquí está el conocimiento sobre la página web front-end.

Dos: protocolo de transferencia de hipertexto HTTP

1. Flujo de mensajes de solicitud y respuesta 

http请求消息(浏览器发给服务器):
    1. 请求行: 说明请求类型, 要访问的资源以及使用的http版本;
    2. 请求头: 说明服务器要使用的附加信息;
    3. 空行:必须!, 即使没有请求数据;
    4. 请求数据: 也叫主体, 可以添加任意的其他数据;

    以下是浏览器发送给服务器的http协议头内容举例:
        GET /hello.c HTTP/1.1
        Host:localhost:2222
        User-Agent:Mozilla/5.0(X11;Ubuntu;Linux i686;rv:24.0)Gecko/201001	01 Firefox/24.0
        Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
        Accept-Language:zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
        Accept-Encoding:gzip,deflate
        Connection:keep-alive
        If-Modified-Since:Fri,18 Jul 2014 08:36:36 GMT
        \r\n



http响应消息(服务器发给浏览器):
    1. 状态行: 包括http协议版本号, 状态码, 状态信息;
    2. 消息报头: 说明客户端要使用的一些附加信息;
    3. 空行: 必须!
    4. 响应正文: 服务器返回给客户端的文本信息(或数据流);

    以下是服务器发送给浏览器的http协议头内容举例:
        HTTP/1.1 200 OK
        Server:xhttpd
        Date:Fri,18 Jul 2014 14:34:26 GMT
        Content-Type:text/plain;charset=iso-8859-1
        Content-Length:32
        Content-Language:zh-CN
        Last-Modified:Fri,18,Jul 2014 08:36:36 GMT
        Connection:close
        \r\n


提交密码信息用get请求,就会明文显示,而post则不会显示出涉密信息
    Get          请求指定的页面信息,并返回实体主体
    Post         向指定资源提交数据进行处理请求(例如提交表单或者上传文件)
                 数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改

Head         类似于get请求,但是响应消息没有内容,只是获得报头
Put          从客户端向浏览器传送的数据取代指定的文档内容
Delete       请求服务器删除指定的页面
Connect      HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器
Options      允许客户端查看浏览器的性能
Trace        回显服务器收到的请求,主要用于测试和诊断
1. getline() 获取 http协议的第一行

2. 从首行中拆分  GET、文件名、协议版本。 获取用户请求的文件名

3. 判断文件是否存在。 stat()

4. 判断是文件还是目录

5. 是文件-- open -- read -- 写回给浏览器

6. 先写 http 应答协议头 : 	http/1.1 200 ok
			
				Content-Type:text/plain; charset=iso-8859-1 
7. 写文件数据

 mihttpd.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>

#define MAXSIZE 2048


// 获取一行 \r\n 结尾的数据 

int get_line(int cfd, char *buf, int size)
{
    int i = 0;
    char c = '\0';
    int n;
    while ((i < size-1) && (c != '\n')) {  
        n = recv(cfd, &c, 1, 0);
        if (n > 0) {     
            if (c == '\r') {            
                n = recv(cfd, &c, 1, MSG_PEEK);
                if ((n > 0) && (c == '\n')) {              
                    recv(cfd, &c, 1, 0);
                } else {                       
                    c = '\n';
                }
            }
            buf[i] = c;
            i++;
        } else {      
            c = '\n';
        }
    }
    buf[i] = '\0';
    
    if (-1 == n)
    	i = n;

    return i;
}

int init_listen_fd(int port, int epfd)
{
    // 创建监听的套接字 lfd
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if (lfd == -1) {    
        perror("socket error");
        exit(1);
    }
    // 创建服务器地址结构 IP+port
    struct sockaddr_in srv_addr;
    
    bzero(&srv_addr, sizeof(srv_addr));
    srv_addr.sin_family = AF_INET;
    srv_addr.sin_port = htons(port);
    srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // 端口复用
    int opt = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    
    // 给 lfd 绑定地址结构
    int ret = bind(lfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
    if (ret == -1) {   
        perror("bind error");
        exit(1);
    }
    // 设置监听上限
    ret = listen(lfd, 128);
    if (ret == -1) { 
        perror("listen error");
        exit(1);
    }
    
    // lfd 添加到 epoll 树上
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = lfd;
    
    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
    if (ret == -1) { 
        perror("epoll_ctl add lfd error");
        exit(1);
    }

    return lfd;
}

void do_accept(int lfd, int epfd)
{
	struct sockaddr_in clt_addr;
    socklen_t clt_addr_len = sizeof(clt_addr);
    
    int cfd = accept(lfd, (struct sockaddr*)&clt_addr, &clt_addr_len);
    if (cfd == -1) {   
        perror("accept error");
        exit(1);
    }

    // 打印客户端IP+port
    char client_ip[64] = {0};
    printf("New Client IP: %s, Port: %d, cfd = %d\n",
           inet_ntop(AF_INET, &clt_addr.sin_addr.s_addr, client_ip, sizeof(client_ip)),
           ntohs(clt_addr.sin_port), cfd);

    // 设置 cfd 非阻塞
    int flag = fcntl(cfd, F_GETFL);
    flag |= O_NONBLOCK;
    fcntl(cfd, F_SETFL, flag);

    // 将新节点cfd 挂到 epoll 监听树上
    struct epoll_event ev;
    ev.data.fd = cfd;
    
    // 边沿非阻塞模式
    ev.events = EPOLLIN | EPOLLET;
    
    int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
    if (ret == -1)  {
        perror("epoll_ctl add cfd error");
        exit(1);
    }
}

// 断开链接
void disconnect(int cfd, int epfd)
{
	int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
	if (ret != 0) {
		perror("epoll_ctl error");	
		exit(1);
	}
	close(cfd);
}

// 客户端端的fd, 错误号,错误描述,回发文件类型, 文件长度 
void send_respond(int cfd, int no, char *disp, char *type, int len)
{
	char buf[4096] = {0};
	
	sprintf(buf, "HTTP/1.1 %d %s\r\n", no, disp);
	send(cfd, buf, strlen(buf), 0);
	
	sprintf(buf, "Content-Type: %s\r\n", type);
	sprintf(buf+strlen(buf), "Content-Length:%d\r\n", len);
	send(cfd, buf, strlen(buf), 0);
	
	send(cfd, "\r\n", 2, 0);
}

// 发送服务器本地文件 给浏览器
void send_file(int cfd, const char *file)
{
	int n = 0, ret;
	char buf[4096] = {0};
	
	// 打开的服务器本地文件。  --- cfd 能访问客户端的 socket
	int fd = open(file, O_RDONLY);
	if (fd == -1) {
		// 404 错误页面
		perror("open error");
		exit(1);
	}
	
	while ((n = read(fd, buf, sizeof(buf))) > 0) {		
		ret = send(cfd, buf, n, 0);
		if (ret == -1) {
			perror("send error");	
			exit(1);
		}
		if (ret < 4096)		
			printf("-----send ret: %d\n", ret);
	}
	
	close(fd);		
}

// 处理http请求, 判断文件是否存在, 回发
void http_request(int cfd, const char *file)
{
	struct stat sbuf;
	
	// 判断文件是否存在
	int ret = stat(file, &sbuf);
	if (ret != 0) {
		// 回发浏览器 404 错误页面
		perror("stat");
		exit(1);	
	}
	
	if(S_ISREG(sbuf.st_mode)) {		// 是一个普通文件
		
		// 回发 http协议应答
		//send_respond(cfd, 200, "OK", " Content-Type: text/plain; charset=iso-8859-1", sbuf.st_size);	 
		send_respond(cfd, 200, "OK", "Content-Type:image/jpeg", -1);
		//send_respond(cfd, 200, "OK", "audio/mpeg", -1);
		
		// 回发 给客户端请求数据内容。
		send_file(cfd, file);
	}	
}

void do_read(int cfd, int epfd)
{
	// 读取一行http协议, 拆分, 获取 get 文件名 协议号	
	char line[1024] = {0};
	char method[16], path[256], protocol[16]; 
	
	int len = get_line(cfd, line, sizeof(line)); //读 http请求协议首行 GET /hello.c HTTP/1.1
	if (len == 0) {
		printf("服务器,检查到客户端关闭....\n");	
		disconnect(cfd, epfd);
	} else {
				
		sscanf(line, "%[^ ] %[^ ] %[^ ]", method, path, protocol);	
		printf("method=%s, path=%s, protocol=%s\n", method, path, protocol);
		
		while (1) {
			char buf[1024] = {0};
			len = get_line(cfd, buf, sizeof(buf));	
			if (buf[0] == '\n') {
				break;	
			} else if (len == -1)
				break;
		}	
		
	}
	
	if (strncasecmp(method, "GET", 3) == 0)
	{
		char *file = path+1;   // 取出 客户端要访问的文件名
		
		http_request(cfd, file);
		
		disconnect(cfd, epfd);
	}
}

void epoll_run(int port)
{
	int i = 0;
    struct epoll_event all_events[MAXSIZE];

    // 创建一个epoll监听树根
    int epfd = epoll_create(MAXSIZE);
    if (epfd == -1) { 
        perror("epoll_create error");
        exit(1);
    }
    
    // 创建lfd,并添加至监听树
    int lfd = init_listen_fd(port, epfd);
   
    while (1) {
    	// 监听节点对应事件
        int ret = epoll_wait(epfd, all_events, MAXSIZE, 0);
        if (ret == -1) {      
            perror("epoll_wait error");
            exit(1);
        }

        for (i=0; i<ret; ++i) {
        	    
            // 只处理读事件, 其他事件默认不处理
            struct epoll_event *pev = &all_events[i];
            
            // 不是读事件
            if (!(pev->events & EPOLLIN)) {                     
                continue;
            }
            if (pev->data.fd == lfd) {   	// 接受连接请求   
                
                do_accept(lfd, epfd);
                
            } else {						// 读数据
                
                do_read(pev->data.fd, epfd);
            }
        }
    }
}


int main(int argc, char *argv[])
{ 
    // 命令行参数获取 端口 和 server提供的目录
    if (argc < 3) 
    {
    	printf("./server port path\n");	
    }
    
    // 获取用户输入的端口 
    int port = atoi(argv[1]);
    
    // 改变进程工作目录
    int ret = chdir(argv[2]);
    if (ret != 0) {
    	perror("chdir error");	
    	exit(1);
    }

	// 启动 epoll监听
	epoll_run(port);

    return 0;
}

Función getline: utilizada para leer el encabezado del protocolo http

int get_line(int cfd,char* buf,int size){
	int i=0;
	char c='\0';
	int n=0;

	while((i<size-1)&&(c!='\n')){
		n=recv(cfd,&c,1,0);
		if(n>0){
			if(c=='\r'){
				/*拷贝读一次*/
				n=recv(cfd,&c,1,MSG_PEEK);
				if((n>0)&&(c=='\n')){
					recv(cfd,&c,1,0);
				}else{
					c='\n';
				}
			}
			buf[i]=c;
			i++;
		}else{
			c='\n';
		}
	}
	buf[i]='\0';

	if(n==-1){
		i=n;
	}
	return i;
}

2. error 

2.1 Manejar un error y devolver la desconexión

错误处理函数:
    void disconnect(int cfd,int epfd){
    	int ret=epoll_ctl(epfd,EPOLL_CTL_DEL,cfd,NULL);
	    if(ret!=0)
	    	perr_exit("epoll_ctl del error");
	    close(cfd);
	    return;
    }



读数据的思路:
    void do_read(int cfd,int epfd){
    	/*读取一行http协议,拆分,获取get文件名和协议号*/
    	char line[1024]={0};
    	int len=get_line(cfd,line,sizeof(line));
    	if(len==0){
    		printf("Client close\n");
    		disconnect(cfd,epfd);
    	}else{
	    	/*字符串拆分*/
	    }
	    return;
    }

2.2 La página de error muestra send_error

Se debe comprobar el valor de retorno, especialmente en las primeras etapas de desarrollo;

void send_error(int cfd,int status,char* title,char* text){
	char buf[BUFSIZ]={0};

	sprintf(buf,"%s %d %s\r\n","HTTP/1.1",status,title);
	sprintf(buf+strlen(buf),"Content-Type:%s\r\n","text/html");
	sprintf(buf+strlen(buf),"Content-Length:%d\r\n",-1);
	sprintf(buf+strlen(buf),"Contention:close\r\n");
	send(cfd,buf,strlen(buf),0);
	send(cfd,"\r\n",2,0);

	memset(buf,0,BUFSIZ);

	sprintf(buf,"<html><head><title>%d %s</title></head>\n",status,title);
	sprintf(buf+strlen(buf),"<body bgcolor=\"#cc99cc\"><h3 align=\"center\">%d %s</h4>\n",status,title);
	sprintf(buf+strlen(buf),"%s\n",text);
	sprintf(buf+strlen(buf),"<hr>\n</body>\n</html>\n");
	send(cfd,buf,strlen(buf),0);

	return;
}

3. Escriba el encabezado del protocolo de respuesta http.

void send_respond_head(int cfd,int no,const char* desp,const char* type,long len){
	char buf[1024]={0};

	sprintf(buf,"HTTP/1.1 %d %s\r\n",no,desp);
	send(cfd,buf,strlen(buf),0);

	sprintf(buf,"Content-Type:%s\r\n",type);
	sprintf(buf+strlen(buf),"Content-Length:%d\r\n",len);
	send(cfd,buf,strlen(buf),0);

	send(cfd,"\r\n",2,0);
	return;
}

Escribir datos en el navegador send_file

void send_file(int cfd,const char* filename){
	int n=0;
	int ret=0;
	int fd=0;
	char buf[BUFSIZ]={0};

	fd=open(filename,O_RDONLY);
	if(fd==-1){
		send_error(cfd,404,"Not Found","No such file or direntry");
		exit(1);
	}

	while((n=read(fd,buf,sizeof(buf)))>0){
		ret=send(cfd,buf,n,0);
		if(ret==-1){
			if(errno==EAGAIN){
				perror("send error:");
				continue;
			}else if(errno==EINTR){
				perror("send error:");
				continue;
			}else{
				perror("send error:");
				exit(1);
			}
		}
	}
	if(n==-1)
		perr_exit("read file error");
	close(fd);
	return;
}

4. El navegador solicita el directorio.

/*发送目录数据-OK*/
void send_dir(int cfd,const char* dirname){
	int i=0;
	int ret=0;
	int num=0;

	char buf[4096]={0};
	sprintf(buf,"<html><head><title>目录名:%s</title></head>",dirname);
	sprintf(buf+strlen(buf),"<body><h1>当前目录:%s</h1><table>",dirname);

		char enstr[1024]={0};
		char path[1024]={0};

		struct dirent** ptr;
		num=scandir(dirname,&ptr,NULL,alphasort);

		for(i=0;i<num;++i){
			char* name=ptr[i]->d_name;

			sprintf(path,"%s/%s",dirname,name);
			printf("path=%s\n",path);
			struct stat st;
			stat(path,&st);
			
			/*编码生成Unicode编码:诸如%E5%A7...等*/
			encode_str(enstr,sizeof(enstr),name);

			if(S_ISREG(st.st_mode)){
				sprintf(buf+strlen(buf),"<tr><td><a href=\"%s\">%s</a></td>%ld</td></tr>",
						enstr,name,(long)st.st_size);
			}else if(S_ISDIR(st.st_mode)){
				sprintf(buf+strlen(buf),"<tr><td><a href=\"%s/\">%s</a></td><td>%ld</td></tr>",
						enstr,name,(long)st.st_size);
			}

			ret=send(cfd,buf,strlen(buf),0);
			if(ret==-1){
				if(errno==EAGAIN){
					perror("send error:");
					continue;
				}else if(errno==EINTR){
					perror("send error:");
					continue;
				}else{
					perror("send error:");
					exit(1);
				}
			}
			memset(buf,0,sizeof(buf));
		}

	sprintf(buf+strlen(buf),"</table></body></html>");
	send(cfd,buf,strlen(buf),0);

	printf("dir message send OK\n");
	return;
}

5. Codificación y decodificación de caracteres chinos.

Cada carácter chino se transcodificará a Unicode en la parte frontal del navegador para su visualización.

Por lo tanto, al acceder a un archivo con caracteres chinos, la operación de codificación debe realizarse cuando el servidor devuelve datos al navegador y la operación de decodificación debe realizarse cuando el navegador solicita el archivo de caracteres chinos en el directorio de recursos ;

/*16进制字符转化为10进制-OK*/
int hexit(char c){
	if(c>='0'&&c<='9')
		return c-'0';
	if(c>='a'&&c<='f')
		return c-'a'+10;
	if(c>='A'&&c<='F')
		return c-'A'+10;

	return 0;
}


/*解码函数-OK*/
void decode_str(char* to,char* from){
	for(;*from!='\0';++to,++from){
		if(from[0]=='%'&&isxdigit(from[1])&&isxdigit(from[2])){
			*to=hexit(from[1])*16+hexit(from[2]);
			from+=2;
		}else{
			*to=*from;
		}
	}
	*to='\0';
	return;
}


/*编码函数-OK*/
void encode_str(char* to,int tosize,const char* from){
	int tolen=0;
	for(tolen=0;(*from!='\0')&&(tolen+4<tosize);++from){
		if(isalnum(*from)||strchr("/_.-~",*from)!=(char*)0){
			*to=*from;
			++to;
			++tolen;
		}else{
			sprintf(to,"%%%02x",(int)*from&0xff);
			to+=3;
			tolen+=3;
		}
	}
	*to='\0';
	return;
}

6. Códigos de estado comunes HTTP

http状态码由三位数字组成,第一个数字代表响应的类别,有五种分类:
    1xx  指示信息--表示请求已接收,继续处理
    2xx  成功--表示请求已被成功接收、理解、接受
    3xx  重定向--要完成请求必须进行更进一步的操作
    4xx  客户端错误--请求有语法错误或请求无法实现
    5xx  服务器端错误--服务器未能实现合法的请求


常见的状态码如下:
    200 OK  客户端请求成功
    301  Moved Permanently         重定向
    400 Bad Request                客户端请求有语法错误,不能被服务器所理解
    401 Unauthorized               请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用 
    403 Forbidden                  服务器收到请求,但是拒绝提供服务
    404 Not Found                  请求资源不存在,eg:输入了错误的URL
    500 Internal Server Error      服务器发生不可预期的错误
    503 Server Unavailable         服务器当前不能处理客户端的请求,一段时间后可能恢复正常

Tres: expresiones regulares

expresión regular

Formato de código en línea

Cuatro: operación de archivos

1. Distinción de tipo de archivo get_file_type

http与浏览器交互时,为使浏览器能够识别文件信息,所以需要传递文件类型,这也是响应消息必填项,常见的类型如下:
    普通文件:  text/plain; charset=utf-8
    *.html:    text/html; charset=utf-8
    *.jpg:     image/jpeg
    *.gif:     image/gif
    *.png:     image/png
    *.wav:     audio/wav
    *.avi:     video/x-msvideo
    *.mov:     video/quicktime
    *.mp3:     audio/mpeg
/*判断文件类型*/
const char* get_file_type(const char* name){
	char* dot;
	dot=strrchr(name,'.');

	if(dot==NULL)
		return "text/plain; charset=utf-8";
	if(strcmp(dot,".html")==0||strcmp(dot,"htm")==0)
		return "text/html; charset=utf-8";
	if(strcmp(dot,".jpg")==0||strcmp(dot,"jpeg")==0)
		return "image/jpeg";
	if(strcmp(dot,".gif")==0)
		return "image/gif";
	if(strcmp(dot,".png")==0)
		return "image/png";
	if(strcmp(dot,".css")==0)
		return "text/css";
	if(strcmp(dot,".wav")==0)
		return "audio/wav";
	if(strcmp(dot,".mp3")==0)
		return "audio/mpeg";
	if(strcmp(dot,".avi")==0)
		return "video/x-msvideo";
	/*其他的文件一律当作文本文件处理*/
	return "text/plain; charset=utf-8";
}

2. Comunicación en un solo archivo

单文件通信流程分析:

    - getline()获取http协议的第一行;

    - 从首行中拆分GET, 文件名, 协议版本. 获取用户请求的文件名;

    - 判断文件是否存在, 用stat函数;

    - 是文件, open打开, read内容, 写回给浏览器;

    - 先写http应答协议头:
        HTTP/1.1 200 OK
        Server:xhttpd
        Date:Fri,18 Jul 2014 14:34:26 GMT
        Content-Type:text/plain;charset=iso-8859-1
        Content-Length:32
        Content-Language:zh-CN
        Last-Modified:Fri,18,Jul 2014 08:36:36 GMT
        Connection:close
        \r\n

    - 再写文件数据;

3. Determine si el archivo existe do_read

void do_read(int cfd,int epfd){
	/*读取一行http协议,拆分,获取get文件名和协议号*/
	char line[1024]={0};
	int len=get_line(cfd,line,sizeof(line));
	if(len==0){
		printf("Client close\n");
		disconnect(cfd,epfd);
	}else{
		printf("----请求头----\n");
		printf("请求行数据:%s\n",line);
		/*清除多余数据,不让他们拥塞缓冲区*/
		while(1){
			char buf[1024]={0};
			len=get_line(cfd,buf,sizeof(buf));
			if(buf[0]=='\n'){
				break;
			}else if(len==-1){
				break;
			}
		}
		printf("----请求尾----\n");
	}
	/*确定是GET方法(忽略大小写比较字符串前n个字符)*/
	if(strncasecmp("get",line,3)==0){
		http_request(line,cfd);
	}
	disconnect(cfd,epfd);
	return;
}

Cinco: implementación del servidor web

可使用telnet命令, 借助IP和port, 模拟浏览器行为, 在终端中对访问的服务器进行调试, 方便查看服务器会发给浏览器的http协议数据:
    `telnet 127.0.0.1 9527`

    `GET /hello.c http/1.1`


此时在终端中可查看到服务器回发给浏览器的http应答协议及数据内容, 可根据该信息进行调试;

1. Implementar un servidor web basado en epoll

epoll_server.h

#ifndef _EPOLL_SERVER_H
#define _EPOLL_SERVER_H

	//初始化监听
		int init_listen_fd(int port, int epfd);
	//epoll模型
		void epoll_run(int port);
	//接受新连接处理
		void do_accept(int lfd, int epfd);
	//读数据
		void do_read(int cfd, int epfd);
	//解析http请求消息的每一行内容
		int get_line(int sock, char *buf, int size);
	//断开连接的函数
		void disconnect(int cfd, int epfd);
	//http请求处理
		void http_request(const char* request, int cfd);
	//发送响应头
		void send_respond_head(int cfd, int no, const char* desp, const char* type, long len);
	//发送文件
		void send_file(int cfd, const char* filename);
	//发送目录内容
		void send_dir(int cfd, const char* dirname);
	//编码函数
		void encode_str(char* to, int tosize, const char* from);
	//解码函数
		void decode_str(char *to, char *from);
	//通过文件名获取文件的类型
		const char *get_file_type(const char *name);
 
#endif

epoll_server.c

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/stat.h>
#include <ctype.h>
#include "epoll_server.h"

#define MAXSIZE 2000

void send_error(int cfd, int status, char *title, char *text)
{
	char buf[4096] = {0};

	sprintf(buf, "%s %d %s\r\n", "HTTP/1.1", status, title);
	sprintf(buf+strlen(buf), "Content-Type:%s\r\n", "text/html");
	sprintf(buf+strlen(buf), "Content-Length:%d\r\n", -1);
	sprintf(buf+strlen(buf), "Connection: close\r\n");
	send(cfd, buf, strlen(buf), 0);
	send(cfd, "\r\n", 2, 0);

	memset(buf, 0, sizeof(buf));

	sprintf(buf, "<html><head><title>%d %s</title></head>\n", status, title);
	sprintf(buf+strlen(buf), "<body bgcolor=\"#cc99cc\"><h2 align=\"center\">%d %s</h4>\n", status, title);
	sprintf(buf+strlen(buf), "%s\n", text);
	sprintf(buf+strlen(buf), "<hr>\n</body>\n</html>\n");
	send(cfd, buf, strlen(buf), 0);
	
	return ;
}

void epoll_run(int port)
{
    int i = 0;

    // 创建一个epoll树的根节点
    int epfd = epoll_create(MAXSIZE);
    if(epfd == -1) {   
        perror("epoll_create error");
        exit(1);
    }

    // 添加要监听的节点
    // 先添加监听lfd
    int lfd = init_listen_fd(port, epfd);

    // 委托内核检测添加到树上的节点
    struct epoll_event all[MAXSIZE];
    while(1) {
    
        int ret = epoll_wait(epfd, all, MAXSIZE, 0);
        if(ret == -1) {
        
            perror("epoll_wait error");
            exit(1);
        }

        // 遍历发生变化的节点
        for(i=0; i<ret; ++i)
        {
            // 只处理读事件, 其他事件默认不处理
            struct epoll_event *pev = &all[i];
            if(!(pev->events & EPOLLIN)) {
             
                // 不是读事件
                continue;
            }
            if(pev->data.fd == lfd){
             
                // 接受连接请求
                do_accept(lfd, epfd);
            } else {
            
                // 读数据
                printf("======================before do read, ret = %d\n", ret);
                do_read(pev->data.fd, epfd);
                printf("=========================================after do read\n");
            }
        }
    }
}

// 读数据
void do_read(int cfd, int epfd)
{
    // 将浏览器发过来的数据, 读到buf中 
    char line[1024] = {0};
    // 读请求行
    int len = get_line(cfd, line, sizeof(line));
    if(len == 0) {   
        printf("客户端断开了连接...\n");
        // 关闭套接字, cfd从epoll上del
        disconnect(cfd, epfd);         
    } else { 
    	printf("============= 请求头 ============\n");   
        printf("请求行数据: %s", line);
        // 还有数据没读完,继续读走
		while (1) {
			char buf[1024] = {0};
			len = get_line(cfd, buf, sizeof(buf));	
			if (buf[0] == '\n') {
				break;	
			} else if (len == -1)
				break;
		}
        printf("============= The End ============\n");
    }
    
    // 判断get请求
    if(strncasecmp("get", line, 3) == 0) { // 请求行: get /hello.c http/1.1   
        // 处理http请求
        http_request(line, cfd);
        
        // 关闭套接字, cfd从epoll上del
        disconnect(cfd, epfd);         
    }
}

// 断开连接的函数
void disconnect(int cfd, int epfd)
{
    int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
    if(ret == -1) {   
        perror("epoll_ctl del cfd error");
        exit(1);
    }
    close(cfd);
}

// http请求处理
void http_request(const char* request, int cfd)
{
    // 拆分http请求行
    char method[12], path[1024], protocol[12];
    sscanf(request, "%[^ ] %[^ ] %[^ ]", method, path, protocol);
    printf("method = %s, path = %s, protocol = %s\n", method, path, protocol);

    // 转码 将不能识别的中文乱码 -> 中文
    // 解码 %23 %34 %5f
    decode_str(path, path);
        
    char* file = path+1; // 去掉path中的/ 获取访问文件名
    
    // 如果没有指定访问的资源, 默认显示资源目录中的内容
    if(strcmp(path, "/") == 0) {    
        // file的值, 资源目录的当前位置
        file = "./";
    }

    // 获取文件属性
    struct stat st;
    int ret = stat(file, &st);
    if(ret == -1) { 
        send_error(cfd, 404, "Not Found", "NO such file or direntry");     
        return;
    }

    // 判断是目录还是文件
    if(S_ISDIR(st.st_mode)) {  		// 目录 
        // 发送头信息
        send_respond_head(cfd, 200, "OK", get_file_type(".html"), -1);
        // 发送目录信息
        send_dir(cfd, file);
    } else if(S_ISREG(st.st_mode)) { // 文件        
        // 发送消息报头
        send_respond_head(cfd, 200, "OK", get_file_type(file), st.st_size);
        // 发送文件内容
        send_file(cfd, file);
    }
}

// 发送目录内容
void send_dir(int cfd, const char* dirname)
{
    int i, ret;

    // 拼一个html页面<table></table>
    char buf[4094] = {0};

    sprintf(buf, "<html><head><title>目录名: %s</title></head>", dirname);
    sprintf(buf+strlen(buf), "<body><h1>当前目录: %s</h1><table>", dirname);

    char enstr[1024] = {0};
    char path[1024] = {0};
    
    // 目录项二级指针
    struct dirent** ptr;
    int num = scandir(dirname, &ptr, NULL, alphasort);
    
    // 遍历
    for(i = 0; i < num; ++i) {
    
        char* name = ptr[i]->d_name;

        // 拼接文件的完整路径
        sprintf(path, "%s/%s", dirname, name);
        printf("path = %s ===================\n", path);
        struct stat st;
        stat(path, &st);

		// 编码生成 %E5 %A7 之类的东西
        encode_str(enstr, sizeof(enstr), name);
        
        // 如果是文件
        if(S_ISREG(st.st_mode)) {       
            sprintf(buf+strlen(buf), 
                    "<tr><td><a href=\"%s\">%s</a></td><td>%ld</td></tr>",
                    enstr, name, (long)st.st_size);
        } else if(S_ISDIR(st.st_mode)) {		// 如果是目录       
            sprintf(buf+strlen(buf), 
                    "<tr><td><a href=\"%s/\">%s/</a></td><td>%ld</td></tr>",
                    enstr, name, (long)st.st_size);
        }
        ret = send(cfd, buf, strlen(buf), 0);
        if (ret == -1) {
            if (errno == EAGAIN) {
                perror("send error:");
                continue;
            } else if (errno == EINTR) {
                perror("send error:");
                continue;
            } else {
                perror("send error:");
                exit(1);
            }
        }
        memset(buf, 0, sizeof(buf));
        // 字符串拼接
    }

    sprintf(buf+strlen(buf), "</table></body></html>");
    send(cfd, buf, strlen(buf), 0);

    printf("dir message send OK!!!!\n");
#if 0
    // 打开目录
    DIR* dir = opendir(dirname);
    if(dir == NULL)
    {
        perror("opendir error");
        exit(1);
    }

    // 读目录
    struct dirent* ptr = NULL;
    while( (ptr = readdir(dir)) != NULL )
    {
        char* name = ptr->d_name;
    }
    closedir(dir);
#endif
}

// 发送响应头
void send_respond_head(int cfd, int no, const char* desp, const char* type, long len)
{
    char buf[1024] = {0};
    // 状态行
    sprintf(buf, "http/1.1 %d %s\r\n", no, desp);
    send(cfd, buf, strlen(buf), 0);
    // 消息报头
    sprintf(buf, "Content-Type:%s\r\n", type);
    sprintf(buf+strlen(buf), "Content-Length:%ld\r\n", len);
    send(cfd, buf, strlen(buf), 0);
    // 空行
    send(cfd, "\r\n", 2, 0);
}

// 发送文件
void send_file(int cfd, const char* filename)
{
    // 打开文件
    int fd = open(filename, O_RDONLY);
    if(fd == -1) {   
        send_error(cfd, 404, "Not Found", "NO such file or direntry");
        exit(1);
    }

    // 循环读文件
    char buf[4096] = {0};
    int len = 0, ret = 0;
    while( (len = read(fd, buf, sizeof(buf))) > 0 ) {   
        // 发送读出的数据
        ret = send(cfd, buf, len, 0);
        if (ret == -1) {
            if (errno == EAGAIN) {
                perror("send error:");
                continue;
            } else if (errno == EINTR) {
                perror("send error:");
                continue;
            } else {
                perror("send error:");
                exit(1);
            }
        }
    }
    if(len == -1)  {  
        perror("read file error");
        exit(1);
    }

    close(fd);
}

// 解析http请求消息的每一行内容
int get_line(int sock, char *buf, int size)
{
    int i = 0;
    char c = '\0';
    int n;
    while ((i < size - 1) && (c != '\n')) {    
        n = recv(sock, &c, 1, 0);
        if (n > 0) {        
            if (c == '\r') {            
                n = recv(sock, &c, 1, MSG_PEEK);
                if ((n > 0) && (c == '\n')) {               
                    recv(sock, &c, 1, 0);
                } else {                            
                    c = '\n';
                }
            }
            buf[i] = c;
            i++;
        } else {       
            c = '\n';
        }
    }
    buf[i] = '\0';

    return i;
}

// 接受新连接处理
void do_accept(int lfd, int epfd)
{
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    int cfd = accept(lfd, (struct sockaddr*)&client, &len);
    if(cfd == -1) {  
        perror("accept error");
        exit(1);
    }

    // 打印客户端信息
    char ip[64] = {0};
    printf("New Client IP: %s, Port: %d, cfd = %d\n",
           inet_ntop(AF_INET, &client.sin_addr.s_addr, ip, sizeof(ip)),
           ntohs(client.sin_port), cfd);

    // 设置cfd为非阻塞
    int flag = fcntl(cfd, F_GETFL);
    flag |= O_NONBLOCK;
    fcntl(cfd, F_SETFL, flag);

    // 得到的新节点挂到epoll树上
    struct epoll_event ev;
    ev.data.fd = cfd;
    // 边沿非阻塞模式
    ev.events = EPOLLIN | EPOLLET;
    int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
    if(ret == -1) { 
        perror("epoll_ctl add cfd error");
        exit(1);
    }
}

int init_listen_fd(int port, int epfd)
{
    // 创建监听的套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd == -1) {  
        perror("socket error");
        exit(1);
    }

    // lfd绑定本地IP和port
    struct sockaddr_in serv;
    memset(&serv, 0, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(port);
    serv.sin_addr.s_addr = htonl(INADDR_ANY);

    // 端口复用
    int flag = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
    
    int ret = bind(lfd, (struct sockaddr*)&serv, sizeof(serv));
    if(ret == -1) {    
        perror("bind error");
        exit(1);
    }

    // 设置监听
    ret = listen(lfd, 64);
    if(ret == -1) {    
        perror("listen error");
        exit(1);
    }

    // lfd添加到epoll树上
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = lfd;
    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
    if(ret == -1) {  
        perror("epoll_ctl add lfd error");
        exit(1);
    }
    return lfd;
}

// 16进制数转化为10进制
int hexit(char c)
{
    if (c >= '0' && c <= '9')
        return c - '0';
    if (c >= 'a' && c <= 'f')
        return c - 'a' + 10;
    if (c >= 'A' && c <= 'F')
        return c - 'A' + 10;

    return 0;
}

/*
 *  这里的内容是处理%20之类的东西!是"解码"过程。
 *  %20 URL编码中的‘ ’(space)
 *  %21 '!' %22 '"' %23 '#' %24 '$'
 *  %25 '%' %26 '&' %27 ''' %28 '('......
 *  相关知识html中的‘ ’(space)是&nbsp
 */
void encode_str(char* to, int tosize, const char* from)
{
    int tolen;

    for (tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from) {    
        if (isalnum(*from) || strchr("/_.-~", *from) != (char*)0) {      
            *to = *from;
            ++to;
            ++tolen;
        } else {
            sprintf(to, "%%%02x", (int) *from & 0xff);
            to += 3;
            tolen += 3;
        }
    }
    *to = '\0';
}

void decode_str(char *to, char *from)
{
    for ( ; *from != '\0'; ++to, ++from  ) {     
        if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) {       
            *to = hexit(from[1])*16 + hexit(from[2]);
            from += 2;                      
        } else {
            *to = *from;
        }
    }
    *to = '\0';
}

// 通过文件名获取文件的类型
const char *get_file_type(const char *name)
{
    char* dot;

    // 自右向左查找‘.’字符, 如不存在返回NULL
    dot = strrchr(name, '.');   
    if (dot == NULL)
        return "text/plain; charset=utf-8";
    if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
        return "text/html; charset=utf-8";
    if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
        return "image/jpeg";
    if (strcmp(dot, ".gif") == 0)
        return "image/gif";
    if (strcmp(dot, ".png") == 0)
        return "image/png";
    if (strcmp(dot, ".css") == 0)
        return "text/css";
    if (strcmp(dot, ".au") == 0)
        return "audio/basic";
    if (strcmp( dot, ".wav" ) == 0)
        return "audio/wav";
    if (strcmp(dot, ".avi") == 0)
        return "video/x-msvideo";
    if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)
        return "video/quicktime";
    if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
        return "video/mpeg";
    if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
        return "model/vrml";
    if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
        return "audio/midi";
    if (strcmp(dot, ".mp3") == 0)
        return "audio/mpeg";
    if (strcmp(dot, ".ogg") == 0)
        return "application/ogg";
    if (strcmp(dot, ".pac") == 0)
        return "application/x-ns-proxy-autoconfig";

    return "text/plain; charset=utf-8";
}

C Principal

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include "epoll_server.h"

int main(int argc, const char* argv[])
{
    if(argc < 3)
    {
        printf("eg: ./a.out port path\n");
        exit(1);
    }

    // 采用指定的端口
    int port = atoi(argv[1]);

    // 修改进程工作目录, 方便后续操作
    int ret = chdir(argv[2]);
    if(ret == -1)
    {
        perror("chdir error");
        exit(1);
    }
    
    // 启动epoll模型 
    epoll_run(port);

    return 0;
}

2. Implementación de servidor web basado en libevent

libevent_http.h

#ifndef _LIBEVENT_HTTP_H
#define _LIBEVENT_HTTP_H
// 包含 event.h 头文件,这是 libevent 库的核心部分
#include <event2/event.h>
 
	//处理连接的事件回调
		void conn_eventcb(struct bufferevent *bev, short events, void *user_data);
	//处理连接的读回调
		void conn_readcb(struct bufferevent *bev, void *user_data);	
	//获取文件类型的字符串表示	 
		const char *get_file_type(char *name);	
	//用来将字符 c 转换为十六进制表示的函数	 
		int hexit(char c);	
	//处理新连接的监听器回调函数	 
		void listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
						 struct sockaddr *sa, int socklen, void *user_data);					 
	//生成 HTTP 响应的函数	 
		int response_http(struct bufferevent *bev, const char *method, char *path);	
	//向客户端发送目录内容的函数	 
		int send_dir(struct bufferevent *bev,const char *dirname);	
	//向客户端发送错误页面的函数 
		int send_error(struct bufferevent *bev);	
	//将文件发送给 HTTP 客户端的函数	 
		int send_file_to_http(const char *filename, struct bufferevent *bev);	
	//向 HTTP 客户端发送 HTTP头的函数	 
		int send_header(struct bufferevent *bev, int no, const char* desp, const char *type, long len);	
	//信号回调	 
		void signal_cb(evutil_socket_t sig, short events, void *user_data);	
	//解码
		void strdecode(char *to, char *from);
	//编码
		void strencode(char* to, size_t tosize, const char* from);
 
#endif

libevent_http.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <string.h>
#include <dirent.h>
#include <time.h>
#include <signal.h>
#include <ctype.h>
#include <errno.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include "libevent_http.h"

#define _HTTP_CLOSE_ "Connection: close\r\n"

int response_http(struct bufferevent *bev, const char *method, char *path)
{
    if(strcasecmp("GET", method) == 0){
        //get method ...
        strdecode(path, path);
        char *pf = &path[1];

        if(strcmp(path, "/") == 0 || strcmp(path, "/.") == 0)
        {
            pf="./";
        }

        printf("***** http Request Resource Path =  %s, pf = %s\n", path, pf);

        struct stat sb;
        if(stat(pf,&sb) < 0)
        {
            perror("open file err:");
            send_error(bev);
            return -1;
        }

        if(S_ISDIR(sb.st_mode))//处理目录
        {
            //应该显示目录列表
            send_header(bev, 200, "OK", get_file_type(".html"), -1);
            send_dir(bev, pf);
        }
        else //处理文件
        {
            send_header(bev, 200, "OK", get_file_type(pf), sb.st_size);
            send_file_to_http(pf, bev);
        }
    }

    return 0;
}

/*
     *charset=iso-8859-1	西欧的编码,说明网站采用的编码是英文;
     *charset=gb2312		说明网站采用的编码是简体中文;
     *charset=utf-8			代表世界通用的语言编码;
     *						可以用到中文、韩文、日文等世界上所有语言编码上
     *charset=euc-kr		说明网站采用的编码是韩文;
     *charset=big5			说明网站采用的编码是繁体中文;
     *
     *以下是依据传递进来的文件名,使用后缀判断是何种文件类型
     *将对应的文件类型按照http定义的关键字发送回去
*/
const char *get_file_type(char *name)
{
    char* dot;

    dot = strrchr(name, '.');	//自右向左查找‘.’字符;如不存在返回NULL

    if (dot == (char*)0)
        return "text/plain; charset=utf-8";
    if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
        return "text/html; charset=utf-8";
    if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
        return "image/jpeg";
    if (strcmp(dot, ".gif") == 0)
        return "image/gif";
    if (strcmp(dot, ".png") == 0)
        return "image/png";
    if (strcmp(dot, ".css") == 0)
        return "text/css";
    if (strcmp(dot, ".au") == 0)
        return "audio/basic";
    if (strcmp( dot, ".wav") == 0)
        return "audio/wav";
    if (strcmp(dot, ".avi") == 0)
        return "video/x-msvideo";
    if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)
        return "video/quicktime";
    if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
        return "video/mpeg";
    if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
        return "model/vrml";
    if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
        return "audio/midi";
    if (strcmp(dot, ".mp3") == 0)
        return "audio/mpeg";
    if (strcmp(dot, ".ogg") == 0)
        return "application/ogg";
    if (strcmp(dot, ".pac") == 0)
        return "application/x-ns-proxy-autoconfig";

    return "text/plain; charset=utf-8";
}

int send_file_to_http(const char *filename, struct bufferevent *bev)
{
    int fd = open(filename, O_RDONLY);
    int ret = 0;
    char buf[4096] = {0};

    while((ret = read(fd, buf, sizeof(buf)) ) )
    {
        bufferevent_write(bev, buf, ret);
        memset(buf, 0, ret);
    }
    close(fd);
    return 0;
}

int send_header(struct bufferevent *bev, int no, const char* desp, const char *type, long len)
{
    char buf[256]={0};

    sprintf(buf, "HTTP/1.1 %d %s\r\n", no, desp);
    //HTTP/1.1 200 OK\r\n
    bufferevent_write(bev, buf, strlen(buf));
    // 文件类型
    sprintf(buf, "Content-Type:%s\r\n", type);
    bufferevent_write(bev, buf, strlen(buf));
    // 文件大小
    sprintf(buf, "Content-Length:%ld\r\n", len);
    bufferevent_write(bev, buf, strlen(buf));
    // Connection: close
    bufferevent_write(bev, _HTTP_CLOSE_, strlen(_HTTP_CLOSE_));
    //send \r\n
    bufferevent_write(bev, "\r\n", 2);

    return 0;
}

int send_error(struct bufferevent *bev)
{
    send_header(bev,404, "File Not Found", "text/html", -1);
    send_file_to_http("404.html", bev);
    return 0;
}

int send_dir(struct bufferevent *bev,const char *dirname)
{
    char encoded_name[1024];
    char path[1024];
    char timestr[64];
    struct stat sb;
    struct dirent **dirinfo;
    int i;

    char buf[4096] = {0};
    sprintf(buf, "<html><head><meta charset=\"utf-8\"><title>%s</title></head>", dirname);
    sprintf(buf+strlen(buf), "<body><h1>当前目录:%s</h1><table>", dirname);
    //添加目录内容
    int num = scandir(dirname, &dirinfo, NULL, alphasort);
    for(i=0; i<num; ++i)
    {
        // 编码
        strencode(encoded_name, sizeof(encoded_name), dirinfo[i]->d_name);

        sprintf(path, "%s%s", dirname, dirinfo[i]->d_name);
        printf("############# path = %s\n", path);
        if (lstat(path, &sb) < 0)
        {
            sprintf(buf+strlen(buf), 
                    "<tr><td><a href=\"%s\">%s</a></td></tr>\n", 
                    encoded_name, dirinfo[i]->d_name);
        }
        else
        {
            strftime(timestr, sizeof(timestr), 
                     "  %d  %b   %Y  %H:%M", localtime(&sb.st_mtime));
            if(S_ISDIR(sb.st_mode))
            {
                sprintf(buf+strlen(buf), 
                        "<tr><td><a href=\"%s/\">%s/</a></td><td>%s</td><td>%ld</td></tr>\n",
                        encoded_name, dirinfo[i]->d_name, timestr, sb.st_size);
            }
            else
            {
                sprintf(buf+strlen(buf), 
                        "<tr><td><a href=\"%s\">%s</a></td><td>%s</td><td>%ld</td></tr>\n", 
                        encoded_name, dirinfo[i]->d_name, timestr, sb.st_size);
            }
        }
        bufferevent_write(bev, buf, strlen(buf));
        memset(buf, 0, sizeof(buf));
    }
    sprintf(buf+strlen(buf), "</table></body></html>");
    bufferevent_write(bev, buf, strlen(buf));
    printf("################# Dir Read OK !!!!!!!!!!!!!!\n");

    return 0;
}

void conn_readcb(struct bufferevent *bev, void *user_data)
{
    printf("******************** begin call %s.........\n",__FUNCTION__);
    char buf[4096]={0};
    char method[50], path[4096], protocol[32];
    bufferevent_read(bev, buf, sizeof(buf));
    printf("buf[%s]\n", buf);
    sscanf(buf, "%[^ ] %[^ ] %[^ \r\n]", method, path, protocol);
    printf("method[%s], path[%s], protocol[%s]\n", method, path, protocol);
    if(strcasecmp(method, "GET") == 0)
    {
        response_http(bev, method, path);
    }
    printf("******************** end call %s.........\n", __FUNCTION__);
}

void conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{
    printf("******************** begin call %s.........\n", __FUNCTION__);
    if (events & BEV_EVENT_EOF)
    {
        printf("Connection closed.\n");
    }
    else if (events & BEV_EVENT_ERROR)
    {
        printf("Got an error on the connection: %s\n",
               strerror(errno));
    }

    bufferevent_free(bev);
    printf("******************** end call %s.........\n", __FUNCTION__);
}

void signal_cb(evutil_socket_t sig, short events, void *user_data)
{
    struct event_base *base = user_data;
    struct timeval delay = { 1, 0 };

    printf("Caught an interrupt signal; exiting cleanly in one seconds.\n");
    event_base_loopexit(base, &delay);
}

void listener_cb(struct evconnlistener *listener, 
                 evutil_socket_t fd, struct sockaddr *sa, int socklen, void *user_data)
{
    printf("******************** begin call-------%s\n",__FUNCTION__);
    struct event_base *base = user_data;
    struct bufferevent *bev;
    printf("fd is %d\n",fd);
    bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    if (!bev)
    {
        fprintf(stderr, "Error constructing bufferevent!");
        event_base_loopbreak(base);
        return;
    }
    bufferevent_flush(bev, EV_READ | EV_WRITE, BEV_NORMAL);
    bufferevent_setcb(bev, conn_readcb, NULL, conn_eventcb, NULL);
    bufferevent_enable(bev, EV_READ | EV_WRITE);

    printf("******************** end call-------%s\n",__FUNCTION__);
}

/*
 * 这里的内容是处理%20之类的东西!是"解码"过程。
 * %20 URL编码中的‘ ’(space)
 * %21 '!' %22 '"' %23 '#' %24 '$'
 * %25 '%' %26 '&' %27 ''' %28 '('......
 * 相关知识html中的‘ ’(space)是&nbsp
 */
void strdecode(char *to, char *from)
{
    for ( ; *from != '\0'; ++to, ++from)
    {
        if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2]))
        {
            // 依次判断from中 %20 三个字符
            *to = hexit(from[1])*16 + hexit(from[2]);
            // 移过已经处理的两个字符(%21指针指向1),表达式3的++from还会再向后移一个字符
            from += 2;
        }
        else
        {
            *to = *from;
        }
    }
    *to = '\0';
}

//16进制数转化为10进制, return 0不会出现
int hexit(char c)
{
    if (c >= '0' && c <= '9')
        return c - '0';
    if (c >= 'a' && c <= 'f')
        return c - 'a' + 10;
    if (c >= 'A' && c <= 'F')
        return c - 'A' + 10;

    return 0;
}

// "编码",用作回写浏览器的时候,将除字母数字及/_.-~以外的字符转义后回写。
// strencode(encoded_name, sizeof(encoded_name), name);
void strencode(char* to, size_t tosize, const char* from)
{
    int tolen;

    for (tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from)
    {
        if (isalnum(*from) || strchr("/_.-~", *from) != (char*)0)
        {
            *to = *from;
            ++to;
            ++tolen;
        }
        else
        {
            sprintf(to, "%%%02x", (int) *from & 0xff);
            to += 3;
            tolen += 3;
        }
    }
    *to = '\0';
}

C Principal

#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <event2/bufferevent.h>
#include <event2/listener.h>
#include <event2/event.h>
#include "libevent_http.h"

int main(int argc, char **argv)
{
    if(argc < 3)
    {
        printf("./event_http port path\n");
        return -1;
    }
    if(chdir(argv[2]) < 0) {
        printf("dir is not exists: %s\n", argv[2]);
        perror("chdir err:");
        return -1;
    }

    struct event_base *base;
    struct evconnlistener *listener;
    struct event *signal_event;

    struct sockaddr_in sin;
    base = event_base_new();
    if (!base)
    {
        fprintf(stderr, "Could not initialize libevent!\n");
        return 1;
    }

    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(atoi(argv[1]));

    // 创建监听的套接字,绑定,监听,接受连接请求
    listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
                    LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1,
                    (struct sockaddr*)&sin, sizeof(sin));
    if (!listener)
    {
        fprintf(stderr, "Could not create a listener!\n");
        return 1;
    }

    // 创建信号事件, 捕捉并处理
    signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
    if (!signal_event || event_add(signal_event, NULL)<0) 
    {
        fprintf(stderr, "Could not create/add a signal event!\n");
        return 1;
    }

    // 事件循环
    event_base_dispatch(base);

    evconnlistener_free(listener);
    event_free(signal_event);
    event_base_free(base);

    printf("done\n");

    return 0;
}

Supongo que te gusta

Origin blog.csdn.net/liu17234050/article/details/132507511
Recomendado
Clasificación