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

news2024/11/18 7:42:20

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_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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1610565.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Matlab之空间坐标系绘制平面图形

在空间直角坐标系中&#xff0c;绘制指定平面方程的图形 版本说明&#xff1a; 20240413_V1.01&#xff1a;更正代码错误&#xff0c;并修改输入参数类型&#xff08;测试用例得修改&#xff09; 20240413_V1.00&#xff1a;初始版本 一、平面方程 基本形式为&#xff1a;A…

[阅读笔记29][AgentStudio]A Toolkit for Building General Virtual Agents

这篇论文是24年3月提交的&#xff0c;提出了一个用于agent开发的全流程工具包。 作者提到目前agent开发主要有两个阻碍&#xff0c;一个是缺乏软件基础&#xff0c;另一个是缺乏在真实世界场景中进行评估。针对这两个阻碍&#xff0c;作者涉及了一个开发工具包&#xff0c;包括…

中颖51芯片学习8. ADC模数转换

中颖51芯片学习8. ADC模数转换 一、ADC工作原理简介1. 概念2. ADC实现方式3. 基准电压 二、中颖芯片ADC功能介绍1. 中颖芯片ADC特性2. ADC触发源&#xff08;1&#xff09;**软件触发**&#xff08;2&#xff09;**TIMER4定时器触发**&#xff08;3&#xff09;**外部中断2触发…

2024第八届图像、信号处理和通信国际会议 (ICISPC 2024)即将召开!

2024第八届图像、信号处理和通信国际会议 &#xff08;ICISPC 2024&#xff09;将于2024年7月19-21日在日本福冈举行。启迪思维&#xff0c;引领未来&#xff0c;ICISPC 2024的召开&#xff0c;旨在全球专家学者共襄盛举&#xff0c;聚焦图像信号&#xff0c;在图像中寻找美&am…

通用大模型研究重点之五:llama family

LLAMA Family decoder-only类型 LLaMA&#xff08;Large Language Model AI&#xff09;在4月18日公布旗下最大模型LLAMA3&#xff0c;参数高达4000亿。目前meta已经开源了80亿和700亿版本模型&#xff0c;主要升级是多模态、长文本方面工作。 模型特点&#xff1a;采用标准的…

企业监管工具:为何如此重要?

随着通信技术的发展&#xff0c;员工使用微信等即时通讯工具来进行工作沟通已经成为了常态。为了帮助企业有效地监管员工的工作微信使用情况&#xff0c;微信管理系统应运而生。 下面就一起来看看&#xff0c;它都有哪些功能吧&#xff01; 1、历史消息&#xff1a;洞察员工聊…

VMware设置Centos7静态ip

1、获取网段&#xff0c;子网掩码和网关 到此获取到的信息&#xff1a; 网段&#xff1a;192.168.204.128 ~ 192.168.204.254 子网掩码&#xff1a;255.255.255.0 网关IP&#xff1a;192.168.204.2 2、修改Centos系统的网络配置 使用命令vim /etc/sysconfig/network-scripts/…

一键搞定线性回归亚组森林图!快速生成顶级SCI论文的高清图!

现在亚组分析好像越来越流行&#xff0c;无论是观察性研究还是RCT研究&#xff0c;亚组分析一般配备森林图。 其实亚组分析的原理十分简单&#xff1a;它一般属于文章的附加内容&#xff0c;文章主体通过对全人群进行分析后&#xff0c;希望在亚组人群中进一步探索暴露与结局的…

DS:顺序表的实现

感谢各位友友的支持&#xff01;目前我的博客进行到了DS阶段&#xff0c;在此阶段首先会介绍一些数据结构相关的知识&#xff0c;然后再进行顺序表的学习。学习数据结构是为后面的通讯录项目打基础。 在学习数据结构之前&#xff0c;需要友友们掌握一些储备知识——结构体、指…

锦瑟香也MYLOVE:音质与颜值俱佳,入坑HiFi的热门好物!

当下尽管无线耳机大行其道&#xff0c;但有线耳机依旧保有其独特的魅力&#xff0c;特别是在音质表现上&#xff0c;它们拥有无线耳机难以企及的优势。如果对音质要求很高的话&#xff0c;口袋里还是少不了一副有线耳机。国产品牌中就有许多性价比高的有线耳机&#xff0c;它们…

Llama 3 开源!手把手带你进行大模型推理,部署,微调和评估

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学&#xff0c;针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 基于大模…

Linux网络编程--网络传输

Linux网络编程--网络传输 Linux网络编程TCP/IP网络模型网络通信的过程局域网通信跨网络通信&#xff1a;问题总结&#xff1a; Linux网络编程 TCP/IP网络模型 发送方&#xff08;包装&#xff09;&#xff1a; 应用层&#xff1a;HTTP HTTPS SSH等 —> 包含数据&#xff0…

如何在Windows安装Ollama大语言模型工具并实现无公网IP异地远程使用

文章目录 前言1. 运行Ollama2. 安装Open WebUI2.1 在Windows系统安装Docker2.2 使用Docker部署Open WebUI 3. 安装内网穿透工具4. 创建固定公网地址 前言 本文主要介绍如何在Windows系统快速部署Ollama开源大语言模型运行工具&#xff0c;并安装Open WebUI结合cpolar内网穿透软…

pycharm已有项目增加pipenv

pycharm已有项目增加pipenv 第一步 第一步 python base 需要安装pipenv pip install pipenv在设置&#xff0c;project 之后 会自动查找项目下的pipfile 和pipfile.lock 进行pip配置 如果网络较慢&#xff0c;可以修复pipfile下的url 为国内的pip源 [[source]] name "…

centos7安装openGauss数据库企业版

本文章是在CentOS7虚拟机上安装openGauss企业版数据库流程 1.下载安装包: https://opengauss.org/zh/download/ openGauss-5.0.1-CentOS-64bit-all.tar.gz 2.安装python3.6.9 见我的另一篇文章 CentOS7安装Python3-CSDN博客 3.检查工具依赖&#xff1a; 分别检查以下工具是…

符文协议的演变历程:从挑战到创新

在比特币网络长期面临的挑战中&#xff0c;与主流去中心化金融功能的兼容性一直是一大难题。相比之下&#xff0c;以太坊通过ERC-721和ERC-1155代币标准&#xff0c;为NFT和去中心化金融应用提供了支持&#xff0c;而比特币的应用范围却相对有限。然而&#xff0c;近年来&#…

2024燃动智火-业务视角的中国企业AI+学习发展报告

来源&#xff1a;新华三 学习型组织的数字化转型是众多企业关注的焦点&#xff0c;数字战略需要人才升级&#xff0c;数字 化学习加速人才培养。AI 技术在学习中的运用&#xff0c;为企业学习型组织的数字化转型插 上了飞翔的翅膀。这份报告解码了AI 时代企业的学习发展&#…

Docker(七):容器监控工具(Portainer、CAdvisor)

一&#xff1a;轻量级可视化监控工具Portainer 可视化监控工具, 可以通过docker安装&#xff0c;用于管理和监控docker&#xff0c;基本上的docker命令都有对应的按钮来操作。 # always 表示docker重启了该容器也跟着重启 docker run -d --name portainer -p 8000:8000 -p 90…

torch.gather用法详解

torch.gather是PyTorch中的一个函数&#xff0c;用于从源张量中按照指定的索引张量来收集数据。 基本语法如下&#xff0c; torch.gather(input, dim, index, *, sparse_gradFalse, outNone) → Tensor input&#xff1a;输入源张量dim&#xff1a;要收集数据的维度index&am…

多头蜗杆的轴截面和端截面的关系

最近有一个点,之前没有注意,就是多头蜗杆的导程与齿距的关系,它们会影响蜗杆断截面的形状,是不是听的有点别扭,往下看: 上图是一个蜗杆的轴剖面齿形,看到这个图形,如果看不到蜗杆实物或者有明显的标准,我们是没办法判断这个蜗杆的头数是多少。 从下面几张图可以看到,…