Web服务器
这一个库的实现
其他的知识都是这一个专栏里面的文章
实际使用
编译的时候需要有一个libevent库
gcc httpserv.c -o httpserv -levent
实际使用的时候需要指定端口以及共享的目录
./httpserv 80 .
这一个函数会吧这一个文件夹下面的所有文件共享出去
实际的效果, 这里我是把我的笔记共享了一下
实现目标
- 使用libevent库进行处理客户端连接(listener_cb)
- 时候http协议和浏览器进行连接
- 获取连接以后把服务启的某个文件夹下面的文件目录返回
- 可以根据返回的目录获取文件信息(bufferevent的读回调函数)
- 日志会在http.log文件里面
/*************************************************************************> File Name: http0.c> Author: XvSenfeng> Mail: 1458612070@qq.com > Created Time: Thu 18 Apr 2024 11:11:53 AM CST************************************************************************/#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <signal.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>//读取一行
int get_line(struct bufferevent *bev, char *buf, int size){int i = 0;char c = '\0';int n;static char temp, todeal = 0;if(todeal == 1){buf[0] = temp;i++;todeal = 0;}while((i < size - 1) && (c != '\n')){n = bufferevent_read(bev, &c, 1);if(n > 0){if(c == '\r'){n = bufferevent_read(bev, &temp, 1);if((n > 0) && (temp == '\n')){buf[i++] = temp;break;}else{buf[i++] = '\n';todeal = 1;break;}}buf[i] = c;i++;}else{c = '\n';}}buf[i] = '\0';if(n == -1){i = n;}return i;
}
//根据文件的后缀, 获取文件的类型(用于HTTP协议通讯)
//name:文件名
//type:传出参数
void get_file_type(const char *name, char *type){if(strstr(name, ".html")){strcpy(type, "text/html; charset=utf-8");}else if(strstr(name, ".jpg")){strcpy(type, "image/jpeg");}else if(strstr(name, ".png")){strcpy(type, "image/png");}else if(strstr(name, ".gif")){strcpy(type, "image/gif");}else if(strstr(name, ".wav")){strcpy(type, "audio/wav");}else if(strstr(name, ".mp3")){strcpy(type, "audio/mp3");}else if(strstr(name, ".mp4")){strcpy(type, "video/mp4");}else{strcpy(type, "text/plain; charset=utf-8");}
}
//发送一个文件
//filename:文件名
//bev:使用的事件缓冲区
void send_file(const char *filename, struct bufferevent *bev){char buf[1024];//打开文件int fd = open(filename, O_RDONLY);if(fd == -1){perror("open error");exit(1);}//发送文件内容int len = 0;while((len = read(fd, buf, sizeof(buf))) > 0){bufferevent_write(bev, buf, len);}close(fd);
}
//发送HTTP协议的头部
//错误号,错误描述,文件类型,文件长度,bufferevent,文件名
void send_respond(int no, char *disp, char *type, long size, struct bufferevent *bev){//发送http响应char buf[1024] = {0};sprintf(buf, "HTTP/1.1 %d %s\r\n", no, disp);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", size);bufferevent_write(bev, buf, strlen(buf));sprintf(buf, "Connection: close\r\n");bufferevent_write(bev, buf, strlen(buf));sprintf(buf, "\r\n");bufferevent_write(bev, buf, strlen(buf));
}
//把文本转化为URL格式, 可用于网址
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';
}
//发送一个文件夹目录
//dirname:文件夹名字
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 = malloc(10240);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);}}if(strlen(buf)>10000){break; }//bufferevent_write(bev, buf, strlen(buf));// memset(buf, 0, sizeof(buf));}sprintf(buf+strlen(buf), "</table></body></html>");send_respond(200, "OK", "text/html", strlen(buf), bev);bufferevent_write(bev, buf, strlen(buf));printf("################# Dir Read OK !!!!!!!!!!!!!!\n");return 0;
}
//发送一个错误页面
void send_404(struct bufferevent *bev){//发送一个404页面struct stat sbuf;stat("404.html", &sbuf);send_respond(404, "Not Found", "text/html", sbuf.st_size, bev);send_file("404.html", bev);
}
//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;
}void strdecode(char *to, char *from);
void http_request(const char *filename1, struct bufferevent *bev){struct stat sbuf;char filename[1024];strdecode(filename,(char *) filename1);int ret = stat(filename, &sbuf);if(ret != 0){perror("stat error");send_404(bev);return;}char buf[128];get_file_type(filename, buf);//判断是不是目录if(S_ISDIR(sbuf.st_mode)){send_dir(bev, filename);}else{//打开文件//send_respond(200, "OK", "text/html", sbuf.st_size, bev);send_respond(200, "OK", buf, sbuf.st_size, bev);send_file(filename, bev);} printf("read cb over");
}void read_cb(struct bufferevent *bev, void *arg){char line[1024];int len = get_line(bev, line, sizeof(line));if(len <= 0){printf("get line error\n");bufferevent_free(bev);return;}printf("http header: %s", line);//判断是不是空行if(strcmp(line, "\n") == 0 || strcmp(line, "\r\n") == 0){printf("空行\n");//断开连接bufferevent_free(bev);return;}//判断是不是请求行char path[1024] = {0}, protocol[20] = {0};sscanf(line, "%*s %s %s", path, protocol);//读取剩余数据char buf[1024] = {0};while(1){len = get_line(bev, buf, sizeof(buf));if(len <= 0){break;}if(strcmp(buf, "\n") == 0 || strcmp(buf, "\r\n") == 0){break;}}if(strncasecmp(line, "GET", 3) == 0){char *file = path + 1;if(strcmp(path, "/")==0){file = "./";}http_request(file, bev);signal_over = 1;}else{printf("POST\n");}
}
//写回调, 这一没啥用
void write_cb(struct bufferevent *bev, void *arg){printf("write_cb\n");
}
//事件callback函数, 某一次连接被打断的时候会调用这一个函数
void event_cb(struct bufferevent *bev, short events, void *arg){if(events & BEV_EVENT_EOF){printf("connection closed\n");}else if(events & BEV_EVENT_ERROR){printf("some other error\n");}bufferevent_free(bev);
}//监听的回调函数
void listener_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int socklen, void *arg){struct event_base *base = (struct event_base *)arg;struct sockaddr_in *sin = (struct sockaddr_in *)addr;//获取客户端ip和端口char ip[16];inet_ntop(AF_INET, &sin->sin_addr.s_addr, ip, sizeof(ip));printf("accept a client %s:%d\n", ip, ntohs(sin->sin_port));//创建bufferevent, 之后使用bufferevent的回调函数处理连接事件struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);if(bev == NULL){printf("bufferevent error");return;}//设置读写回调bufferevent_flush(bev, EV_READ | EV_WRITE, BEV_NORMAL);bufferevent_setcb(bev, read_cb, write_cb, event_cb, arg);bufferevent_enable(bev, EV_READ | EV_WRITE);
}
//处理信号的回调函数
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);
}
//把一个url
void strdecode(char *to, char *from)
{for ( ; *from != '\0'; ++to, ++from){//检测一下下面的三个字符是不是%xx格式的if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])){//依次判断from中 %20 三个字符, 把这三个字符转换为10进制的数字*to = hexit(from[1])*16 + hexit(from[2]);// 移过已经处理的两个字符(%21指针指向1),表达式3的++from还会再向后移一个字符from += 2;}else{*to = *from;}}*to = '\0';
}int main(int argc, char *argv[]){int pid;//看一下参数的个数对不对if(argc < 3){printf("./serv port path");return 0;}//建立一个守护进程pid = fork();if(pid > 0){exit(1);}//切换工作目录, 使用第三个参数const char *path = argv[2];int ret = chdir(path);if(ret == -1){perror("chdir error");return -1;}pid = setsid();umask(0022);close(STDIN_FILENO);int fd;//一个日志文件fd = open("http.log", O_RDWR|O_CREAT);if(fd==-1){perror("open error");exit(1);}dup2(fd, STDOUT_FILENO);dup2(fd, STDERR_FILENO);//获取连接的端口int port = atoi(argv[1]);//创建服务器地址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);//创建event_basestruct event_base *base = event_base_new();if(base == NULL){printf("event base error");return -1;}//创建监听器struct evconnlistener *listener = evconnlistener_new_bind(base, listener_cb, base, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1, (struct sockaddr *)&serv, sizeof(serv));if(listener == NULL){printf("listener error");return -1;}struct event *signal_event;//绑定信号回调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_base_free(base);return 0;
}