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

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.hqwc.cn/news/636278.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于电商独立站搭建中电商API数据采集接口的应用

搭建供应链系统时&#xff0c;您可能需要与电商平台进行集成&#xff0c;以实现订单管理、库存同步、物流跟踪等功能。以下是一些常见的电商接口&#xff0c;可以帮助您构建供应链系统&#xff1a; 1. **淘宝开放平台接口**&#xff1a;淘宝开放平台提供了丰富的接口&#xff…

【VSLAM】VINO-Mono安装部署与运行

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍VINO-Mono安装部署与运行。 学其所用&#xff0c;用其所学。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;下次更新不迷…

【超级简单】vscode进入服务器的docker容器

前提 1、已经运行docker容器 2、已经用vscode链接服务器 在vscode中安装的插件 Dev Containers docker 在容器中安装的依赖 yum install openssh-server yum install openssh-clientsvscode进入服务器的docker容器 找到自己的容器&#xff0c;右键点击&#xff0c;找到…

【状态压缩 并集查找 图论】2157. 字符串分组

本文涉及知识点 状态压缩 并集查找 图论 LeetCode2157. 字符串分组 给你一个下标从 0 开始的字符串数组 words 。每个字符串都只包含 小写英文字母 。words 中任意一个子串中&#xff0c;每个字母都至多只出现一次。 如果通过以下操作之一&#xff0c;我们可以从 s1 的字母集…

强固型工业电脑在称重系统+叉车电脑,称重量体扫码一体机,物流分拣线工作站行业应用

称重系统叉车电脑行业应用 背景介绍 在叉车上安装称重传感器&#xff0c;通过对举升压力的自动检测&#xff0c;将压力信号转换为电流或电压信号&#xff0c;经过A/D转换&#xff0c;使模拟信号变为数字信号&#xff0c;经微处理器进行数据处理后通过蓝牙、串口或者USB接口将称…

【AI自媒体制作】【AI工具】Midjourney中文站

Midjourney Midjourney中文站, MJ中文站 - 专业AI绘图网站 广场 绘画广场&#xff1a; 包含大量其他用户生成好的图片&#xff0c;可以自由保存。 视频广场&#xff1a; 普通用户目前只支持查看&#xff0c;无法下载 画夹广场&#xff1a; 有很多免费的画夹&#xff0c;比…

(1)认识人工智能

第一章 认识人工智能 引言 本人目前大三&#xff0c;双非一本的人工智能专业&#xff0c;代码能力不算太差&#xff0c;做过项目&#xff0c;也打了比赛&#xff0c;获了奖&#xff0c;但是走技术路线总会有否定自己的感觉&#xff0c;可能是感觉自己的才能没有在搞技术方面实…

Nacos注册中心实战

注册中心实战 1.快速使用1.1 版本选择1.2 父pom1.3 nacos-client pom1.4 nacos-client bootstrap.yaml配置 2.常用配置3.Nacos Server环境搭建3.1 单机模式启动3.1.1 解压&#xff0c;进入nacos目录 进入bin目录下&#xff0c;编辑startup.cmd脚本&#xff0c; 设置启动模式为单…

数据仓库作业五:第8章 关联规则挖掘

目录 第8章 关联规则挖掘作业题 第8章 关联规则挖掘 作业题 1、设4-项集 X { a , b , c , d } X\{a,b,c,d\} X{a,b,c,d}&#xff0c;试求出由 X X X 导出的所有关联规则。 解&#xff1a; 首先生成项集的所有非空真子集。这包括&#xff1a; { a } , { b } , { c } , {…

vivado 在波形查看器中查看 ILA 探针数据

在波形查看器中查看 ILA 探针数据 Vivado 集成设计环境 (IDE) 中的“ ILA 波形查看器 (ILA waveform viewer) ”提供了一种强大的方法 &#xff0c; 可分析从 ILA 调 试核采集的数据。成功触发 ILA 核并采集数据后 &#xff0c; Vivado 会以从 ILA 核收集的数据自动填…

solidity入门

Solidity 是以太坊智能合约开发的主要编程语言&#xff0c;支持多种数据类型&#xff0c;其中数组是一种非常常用和灵活的数据结构。在本教程中&#xff0c;我们将深入探讨 Solidity 中数组的各种类型、创建规则以及常见操作。 ### 固定长度数组 固定长度数组在声明时指定了数…

前端css中table表格的属性使用

前端css中table表格的属性使用 一、前言二、常见的表格属性1.边框的样式2.布局和对齐3.间距和填充4.背景和颜色5.字体的样式6.边框的圆角 三、简单的表格&#xff0c;例子11.源码12.源码1效果截图 四、给表格添加动画效果&#xff0c;例子21.源码22.源码2的运行效果 五、结语六…