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