Linux使用Libevent库实现一个网页服务器---C语言程序

server/2024/10/21 9:59:11/

Web服务器

这一个库的实现
其他的知识都是这一个专栏里面的文章

实际使用

编译的时候需要有一个libevent库

gcc httpserv.c -o httpserv -levent

实际使用的时候需要指定端口以及共享的目录

./httpserv 80 .

这一个函数会吧这一个文件夹下面的所有文件共享出去

在这里插入图片描述

实际的效果, 这里我是把我的笔记共享了一下

实现目标

  1. 使用libevent库进行处理客户端连接(listener_cb)
  2. 时候http协议和浏览器进行连接
  3. 获取连接以后把服务启的某个文件夹下面的文件目录返回
  4. 可以根据返回的目录获取文件信息(bufferevent的读回调函数)
  5. 日志会在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;
}

http://www.ppmy.cn/server/6142.html

相关文章

linux常用命令

查询此字符出现的次数 grep “开始带宽” 2024-04-17.log | wc -l 查询此字符出现的前1000行 grep “开始带宽” -C 1000 2024-04-17.log 查询日志前1000行 head -n 1000 2024-04-17.log 查询日志后1000行 tail -n 1000 2024-04-17.log 查看端口是否通 telnet 127.0.0.1 3306 查…

jmeter分布式压测

前提 调度机和执行机都要安装配置JDK和jmeter的运行环境 调度机和执行机上JDK和Jmeter的版本要保持一致 防火墙要关闭 整体思路 mac电脑当调度机&#xff0c;多个ubuntu虚拟机当执行机 调度机&#xff1a;配置执行机的ip等信息&#xff0c;后面会详细介绍&#xff0c;存放jme…

机器学习常用评价指标的公式和含义

在机器学习中&#xff0c;特别是在分类任务中&#xff0c;评价模型性能常用以下指标。这些指标主要基于混淆矩阵&#xff0c;该矩阵记录了实际类别与模型预测类别的对应情况。下面是这些指标的定义和计算公式&#xff1a; 1. TP&#xff08;True Positives&#xff09;: - …

uniapp之消除图片的空白占用空间

我们在使用uniapp开发的过程中一定会遇到一个情况就是我们加载的图片总有一点空白出现在不该出现的地方代码如下 <view style"background:#ff0000;"><image style"width:100%;"src"https://t7.baidu.com/it/u1819248061,230866778&fm19…

OpenHarmony 网络与连接—RPC连接

介绍 本示例使用ohos.rpc 相关接口&#xff0c;实现了一个前台选择商品和数目&#xff0c;后台计算总价的功能&#xff0c;使用rpc进行前台和后台的通信。 效果预览 使用说明&#xff1a; 点击商品种类的空白方框&#xff0c;弹出商品选择列表&#xff0c;选择点击对应的商品…

【行为型模式】观察者模式

一、观察者模式概述​ 软件系统其实有点类似观察者模式&#xff0c;目的&#xff1a;一个对象的状态或行为的变化将导致其他对象的状态或行为也发生改变&#xff0c;他们之间将产生联动。 观察者模式属于对象行为型&#xff1a; 1.定义了对象之间一种一对多的依赖关系&#xff…

Vue 指令、计算属性、侦听器

目录 指令 指令修饰符 按键修饰符 ​编辑 v-model修饰符 事件修饰符 v-bind对于样式操作的增强 操作class 对象 数组 操作style v-model应用于其他表单元素 computed计算属性 概念 基础语法 ​编辑 计算属性vs方法 computed计算属性 作用 语法 缓存特性 m…

【前端Vue】Vue从0基础完整教程第7篇:组件化开发,组件通信【附代码文档】

Vue从0基础到大神学习完整教程完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;vue基本概念&#xff0c;vue-cli的使用&#xff0c;vue的插值表达式&#xff0c;{{ gaga }}&#xff0c;{{ if (obj.age > 18 ) { } }}&#xff0c;vue指令&#xff0c;综合…