Linux高并发服务器开发(十三)Web服务器开发

文章目录

  • 1 使用的知识点
  • 2 http请求
    • get 和 post的区别
  • 3 整体功能介绍
  • 4 基于epoll的web服务器开发流程
  • 5 服务器代码
  • 6 libevent版本的本地web服务器


1 使用的知识点

在这里插入图片描述

2 http请求

在这里插入图片描述

get 和 post的区别

http协议请求报文格式:
1 请求行 GET /test.txt HTTP/1.1
2 请求行 健值对
3 空行 \r\n
4 数据

http协议响应消息格式:
1 状态行 200 表示成功, 404 表示请求的资源不存在
2 消息报头 健值对
3 空行 \r\n
4 响应正文

3 整体功能介绍

在这里插入图片描述

4 基于epoll的web服务器开发流程

  1. 创建socket,得到监听文件描述符lfd——socket

  2. 设置端口复用——setsockopt()

  3. 绑定——bind()

  4. 设置监听——listen()

  5. 创建epoll树,得到树根描述符epfd——epoll_create()

  6. 将监听文件描述符lfd上树——epoll_ctr(epfd,epoll_CTL_ADD…)\

  7. while(1)
    {
    // 等待事件发生
    nread = epoll_wait();
    if(nread < 0)
    {
    if(errno == EINTR)
    {
    continue;
    }
    break;
    }

    // 下面是有事件发生,循环处理没一个文件描述符
    for(i = 0; i<nready;i++)
    {
    sockfd = event[i].data.fd;
    // 有客户端连接请求到来
    if(sockfd = lfd)
    {
    cfd = accept();
    // 将新的cfd 上树
    epoll_ctl(epfd, EPOLL_CTL_ADD…);

     }
     // 有数据发来的情况
     else
     {
     	// 接受数据并进行处理
     	http_request();
     }
    

    }
    }
    在这里插入图片描述

int http_request(inf cfd)
{
// 读取请求行
Readline();
// 分析请求行,得到要请求的资源文件夹file
如 GET/hanzi.c /HTTP1.1
// 循环读完剩余的内核缓冲区的数据
while((n = Readline())>0);
// 判断文件是否存在
stat();
1. 文件不存在
返回错误页
组织应答消息: http响应格式消息 + 错误页正文内容
2. 文件存在
判断文件类型
2.1 普通文件
组织应答信息: http响应格式信息+消息正文
2.2 目录文件
组织应答消息: http响应格式消息 + html格式文件内容

}

5 服务器代码

//web服务端程序--使用epoll模型
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
#include <dirent.h>

#include "pub.h"
#include "wrap.h"

int http_request(int cfd, int epfd);

int main()
{
	//若web服务器给浏览器发送数据的时候, 浏览器已经关闭连接, 
	//则web服务器就会收到SIGPIPE信号
	struct sigaction act;
	act.sa_handler = SIG_IGN;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGPIPE, &act, NULL);
	
	//改变当前进程的工作目录
	char path[255] = {0};
	sprintf(path, "%s/%s", getenv("HOME"), "webpath");
	chdir(path);
	
	//创建socket--设置端口复用---bind
	int lfd = tcp4bind(9999, NULL);
	
	//设置监听
	Listen(lfd, 128);

	//创建epoll树
	int epfd = epoll_create(1024);
	if(epfd<0)
	{
		perror("epoll_create error");
		close(lfd);
		return -1;
	}
	
	//将监听文件描述符lfd上树
	struct epoll_event ev;
	ev.data.fd = lfd;
	ev.events = EPOLLIN;
	epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
	
	int i;
	int cfd;
	int nready;
	int sockfd;
	struct epoll_event events[1024];
	while(1)
	{
		//等待事件发生
		nready = epoll_wait(epfd, events, 1024, -1);
		if(nready<0)
		{
			if(errno==EINTR)
			{
				continue;
			}
			break;
		}
		
		for(i=0; i<nready; i++)
		{
			sockfd = events[i].data.fd;
			//有客户端连接请求
			if(sockfd==lfd)
			{
				//接受新的客户端连接
				cfd = Accept(lfd, NULL, NULL);
				
				//设置cfd为非阻塞
				int flag = fcntl(cfd, F_GETFL);
				flag |= O_NONBLOCK;
				fcntl(cfd, F_SETFL, flag);
				
				//将新的cfd上树
				ev.data.fd = cfd;
				ev.events = EPOLLIN;
				epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
			}
			else 
			{
				//有客户端数据发来
				http_request(sockfd, epfd);
			}			
		}		
	}
}

int send_header(int cfd, char *code, char *msg, char *fileType, int len)
{
	char buf[1024] = {0};
	sprintf(buf, "HTTP/1.1 %s %s\r\n", code, msg);
	sprintf(buf+strlen(buf), "Content-Type:%s\r\n", fileType);
	if(len>0)
	{
		sprintf(buf+strlen(buf), "Content-Length:%d\r\n", len);
	}
	strcat(buf, "\r\n");
	Write(cfd, buf, strlen(buf));
	return 0;
}

int send_file(int cfd, char *fileName)
{
	//打开文件
	int fd = open(fileName, O_RDONLY);
	if(fd<0)
	{
		perror("open error");
		return -1;
	}
	
	//循环读文件, 然后发送
	int n;
	char buf[1024];
	while(1)
	{
		memset(buf, 0x00, sizeof(buf));
		n = read(fd, buf, sizeof(buf));
		if(n<=0)
		{
			break;
		}
		else 
		{
			Write(cfd, buf, n);
		}
	}
}

int http_request(int cfd, int epfd)
{
	int n;
	char buf[1024];
	//读取请求行数据, 分析出要请求的资源文件名
	memset(buf, 0x00, sizeof(buf));
	n = Readline(cfd, buf, sizeof(buf));
	if(n<=0)
	{
		//printf("read error or client closed, n==[%d]\n", n);
		//关闭连接
		close(cfd);
		
		//将文件描述符从epoll树上删除
		epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
		return -1;	
	}
	printf("buf==[%s]\n", buf);
	//GET /hanzi.c HTTP/1.1
	char reqType[16] = {0};
	char fileName[255] = {0};
	char protocal[16] = {0};
	sscanf(buf, "%[^ ] %[^ ] %[^ \r\n]", reqType, fileName, protocal);
	//printf("[%s]\n", reqType);
	printf("--[%s]--\n", fileName);
	//printf("[%s]\n", protocal);
	
	char *pFile = fileName;
	if(strlen(fileName)<=1)
	{
		strcpy(pFile, "./");
	}
	else 
	{
		pFile = fileName+1;
	}
	
	//转换汉字编码
	strdecode(pFile, pFile);
	printf("[%s]\n", pFile);
	
	//循环读取完剩余的数据,避免产生粘包
	while((n=Readline(cfd, buf, sizeof(buf)))>0);
	
	//判断文件是否存在
	struct stat st;
	if(stat(pFile, &st)<0)
	{
		printf("file not exist\n");
		
		//发送头部信息
		send_header(cfd, "404", "NOT FOUND", get_mime_type(".html"), 0);
		
		//发送文件内容
		send_file(cfd, "error.html");	
	}
	else //若文件存在
	{
		//判断文件类型
		//普通文件
		if(S_ISREG(st.st_mode))
		{
			printf("file exist\n");
			//发送头部信息
			send_header(cfd, "200", "OK", get_mime_type(pFile), st.st_size);
			
			//发送文件内容
			send_file(cfd, pFile);
		}
		//目录文件
		else if(S_ISDIR(st.st_mode))
		{
			printf("目录文件\n");
			
			char buffer[1024];
			//发送头部信息
			send_header(cfd, "200", "OK", get_mime_type(".html"), 0);	
			
			//发送html文件头部
			send_file(cfd, "html/dir_header.html");	
			
			//文件列表信息
			struct dirent **namelist;
			int num;

			num = scandir(pFile, &namelist, NULL, alphasort);
			if (num < 0)
			{
			   perror("scandir");
			   close(cfd);
			   epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
			   return -1;
			   
			}
			else 
			{
			   while (num--) 
			   {
			       printf("%s\n", namelist[num]->d_name);
			       memset(buffer, 0x00, sizeof(buffer));
			       if(namelist[num]->d_type==DT_DIR)
			       {
			       		sprintf(buffer, "<li><a href=%s/>%s</a></li>", namelist[num]->d_name, namelist[num]->d_name);
			       }
			       else
			       {
			       		sprintf(buffer, "<li><a href=%s>%s</a></li>", namelist[num]->d_name, namelist[num]->d_name);
			       }
			       free(namelist[num]);
			       Write(cfd, buffer, strlen(buffer));
			   }
			   free(namelist);
			}
			//发送html尾部
			sleep(10);
			send_file(cfd, "html/dir_tail.html");		
		}
	}
	
	return 0;
}

6 libevent版本的本地web服务器

//通过libevent编写的web服务器
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "pub.h"
#include <event.h>
#include <event2/listener.h>
#include <dirent.h>

#define _WORK_DIR_ "%s/webpath"
#define _DIR_PREFIX_FILE_ "html/dir_header.html"
#define _DIR_TAIL_FILE_ "html/dir_tail.html"

int copy_header(struct bufferevent *bev,int op,char *msg,char *filetype,long filesize)
{
    char buf[4096]={0};
    sprintf(buf,"HTTP/1.1 %d %s\r\n",op,msg);
    sprintf(buf,"%sContent-Type: %s\r\n",buf,filetype);
    if(filesize >= 0){
        sprintf(buf,"%sContent-Length:%ld\r\n",buf,filesize);
    }
    strcat(buf,"\r\n");
    bufferevent_write(bev,buf,strlen(buf));
    return 0;
}
int copy_file(struct bufferevent *bev,const char *strFile)
{
    int fd = open(strFile,O_RDONLY);
    char buf[1024]={0};
    int ret;
    while( (ret = read(fd,buf,sizeof(buf))) > 0 ){
        bufferevent_write(bev,buf,ret);
    }
    close(fd);
    return 0;
}
//发送目录,实际上组织一个html页面发给客户端,目录的内容作为列表显示
int send_dir(struct bufferevent *bev,const char *strPath)
{
    //需要拼出来一个html页面发送给客户端
    copy_file(bev,_DIR_PREFIX_FILE_);
    //send dir info 
    DIR *dir = opendir(strPath);
    if(dir == NULL){
        perror("opendir err");
        return -1;
    }
    char bufline[1024]={0};
    struct dirent *dent = NULL;
    while( (dent= readdir(dir) ) ){
        struct stat sb;
        stat(dent->d_name,&sb);
        if(dent->d_type == DT_DIR){
            //目录文件 特殊处理
            //格式 <a href="dirname/">dirname</a><p>size</p><p>time</p></br>
            memset(bufline,0x00,sizeof(bufline));
            sprintf(bufline,"<li><a href='%s/'>%32s</a>   %8ld</li>",dent->d_name,dent->d_name,sb.st_size);
            bufferevent_write(bev,bufline,strlen(bufline));
        }
        else if(dent->d_type == DT_REG){
            //普通文件 直接显示列表即可
            memset(bufline,0x00,sizeof(bufline));
            sprintf(bufline,"<li><a href='%s'>%32s</a>     %8ld</li>",dent->d_name,dent->d_name,sb.st_size);
            bufferevent_write(bev,bufline,strlen(bufline));
        }
    }
    closedir(dir);
    copy_file(bev,_DIR_TAIL_FILE_);
    //bufferevent_free(bev);
    return 0;
}
int http_request(struct bufferevent *bev,char *path)
{
    
    strdecode(path, path);//将中文问题转码成utf-8格式的字符串
    char *strPath = path;
    if(strcmp(strPath,"/") == 0 || strcmp(strPath,"/.") == 0){
        strPath = "./";
    }
    else{
        strPath = path+1;
    }
    struct stat sb;
    
    if(stat(strPath,&sb) < 0){
        //不存在 ,给404页面
        copy_header(bev,404,"NOT FOUND",get_mime_type("error.html"),-1);
        copy_file(bev,"error.html");
        return -1;
    }
    if(S_ISDIR(sb.st_mode)){
        //处理目录
        copy_header(bev,200,"OK",get_mime_type("ww.html"),sb.st_size);
        send_dir(bev,strPath);
        
    }
    if(S_ISREG(sb.st_mode)){
        //处理文件
        //写头
        copy_header(bev,200,"OK",get_mime_type(strPath),sb.st_size);
        //写文件内容
        copy_file(bev,strPath);
    }

    return 0;
}

void read_cb(struct bufferevent *bev, void *ctx)
{
    char buf[256]={0};
    char method[10],path[256],protocol[10];
    int ret = bufferevent_read(bev, buf, sizeof(buf));
    if(ret > 0){

        sscanf(buf,"%[^ ] %[^ ] %[^ \r\n]",method,path,protocol);
        if(strcasecmp(method,"get") == 0){
            //处理客户端的请求
            char bufline[256];
            write(STDOUT_FILENO,buf,ret);
            //确保数据读完
            while( (ret = bufferevent_read(bev, bufline, sizeof(bufline)) ) > 0){
                write(STDOUT_FILENO,bufline,ret);
            }
			http_request(bev,path);//处理请求

        }
    }
}
void bevent_cb(struct bufferevent *bev, short what, void *ctx)
{
    if(what & BEV_EVENT_EOF){//客户端关闭
        printf("client closed\n");
        bufferevent_free(bev);
    }
    else if(what & BEV_EVENT_ERROR){
        printf("err to client closed\n");
        bufferevent_free(bev);
    }
    else if(what & BEV_EVENT_CONNECTED){//连接成功
        printf("client connect ok\n");
    }
}
void listen_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int socklen, void *arg)
{
    //定义与客户端通信的bufferevent
    struct event_base *base = (struct event_base *)arg;
    struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    bufferevent_setcb(bev,read_cb,NULL,bevent_cb,base);//设置回调
    bufferevent_enable(bev,EV_READ|EV_WRITE);//启用读和写
}

int main(int argc,char *argv[])
{
	char workdir[256] = {0};
	sprintf(workdir,_WORK_DIR_,getenv("HOME"));//HOME=/home/itheima 
	chdir(workdir);
    struct event_base *base = event_base_new();//创建根节点
    struct sockaddr_in serv;
    serv.sin_family = AF_INET;
    serv.sin_port = htons(9999);
    serv.sin_addr.s_addr = htonl(INADDR_ANY);
    struct evconnlistener * listener =evconnlistener_new_bind(base,
                                     listen_cb, base, LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, -1,
                                                        (struct sockaddr *)&serv, sizeof(serv));//连接监听器
    

    event_base_dispatch(base);//循环

    event_base_free(base); //释放根节点
    evconnlistener_free(listener);//释放链接监听器
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SQL索引事务

SQL索引事务 索引 创建主键约束(primary key),唯一约束(unique),外键约束(foreign key)时,会自动创建对应列的索引 1.1 查看索引 show index from 表名 现在这个表中没有索引,那么我们现在将这几个表删除之后创建新表 我们现在建立一个班级表一个学生表,并且学生表与班级表存…

EVM-MLIR:以MLIR编写的EVM

1. 引言 EVM_MLIR&#xff1a; 以MLIR编写的EVM。 开源代码实现见&#xff1a; https://github.com/lambdaclass/evm_mlir&#xff08;Rust&#xff09; 为使用MLIR和LLVM&#xff0c;将EVM-bytecode&#xff0c;转换为&#xff0c;machine-bytecode。LambdaClass团队在2周…

无人机水运应用场景

航行运输 通航管理&#xff08;海事通航管理处&#xff09; 配员核查流程 海事员通过VHF&#xff08;甚高频&#xff09;系统与船长沟通核查时间。 无人机根据AIS&#xff08;船舶自动识别系统&#xff09;报告的船舶位置&#xff0c;利用打点定位 功能飞抵船舶上方。 使用…

大型能源电力集团需要什么样的总部数据下发系统?

能源电力集团的组织结构是一个复杂的系统&#xff0c;包括多个职能部门和子分公司。这些子分公司负责具体的电力生产、销售、运维等业务。这些部门和公司协同工作&#xff0c;确保电力生产的顺利进行&#xff0c;同时关注公司的长期发展、市场拓展、人力资源管理、财务管理和公…

SCI一区级 | Matlab实现BO-Transformer-LSTM多特征分类预测/故障诊断

SCI一区级 | Matlab实现BO-Transformer-LSTM多特征分类预测/故障诊断 目录 SCI一区级 | Matlab实现BO-Transformer-LSTM多特征分类预测/故障诊断效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.【SCI一区级】Matlab实现BO-Transformer-LSTM特征分类预测/故障诊断&…

winform2

12.TabControl 导航控制条 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace zhiyou_…

发现CPU占用过高,该如何排查解决?

1.使用top命令 查看cpu占用最多的进程 2.使用 top -H -p pid 发现有两个线程占用比较大 3.将线程id转换为16进制 使用命令 printf 0x%x\n pid 4.使用 jstack pid | grep 线程id(16进制&#xff09; -A 20 &#xff08;显示20行&#xff09; 根据代码显示进行错误排查

2024年7月5日 (周五) 叶子游戏新闻

老板键工具来唤去: 它可以为常用程序自定义快捷键&#xff0c;实现一键唤起、一键隐藏的 Windows 工具&#xff0c;并且支持窗口动态绑定快捷键&#xff08;无需设置自动实现&#xff09;。 卸载工具 HiBitUninstaller: Windows上的软件卸载工具 《乐高地平线大冒险》为何不登陆…

娱乐圈惊爆已婚男星刘端端深夜幽会

【娱乐圈惊爆&#xff01;已婚男星刘端端深夜幽会&#xff0c;竟是《庆余年》二皇子“戏外风云”】在这个信息爆炸的时代&#xff0c;娱乐圈的每一次风吹草动都能瞬间点燃公众的热情。今日&#xff0c;知名娱乐博主刘大锤的一则预告如同投入湖中的巨石&#xff0c;激起了层层涟…

关于下载obsidian SimpRead Sync中报错的问题

参考Kenshin的配置方法&#xff0c;我却在输入简悦的配置文件目录时多次报错。 bug如下&#xff1a; 我发现导出来的配置文件格式如下&#xff1a; 然后根据报错的bug对此文件名进行修改&#xff0c;如下&#xff1a; 解决。

Java数据结构-树的面试题

目录 一.谈谈树的种类 二.红黑树如何实现 三.二叉树的题目 1.求一个二叉树的高度&#xff0c;有两种方法。 2.寻找二叉搜索树当中第K大的值 3、查找与根节点距离K的节点 4.二叉树两个结点的公共最近公共祖先 本专栏全是博主自己收集的面试题&#xff0c;仅可参考&#xf…

暑假前端知识速成【CSS】系列一

坚持就是希望&#xff01; 什么是CSS? CSS 指的是层叠样式表* (Cascading Style Sheets)CSS 描述了如何在屏幕、纸张或其他媒体上显示 HTML 元素CSS 节省了大量工作。它可以同时控制多张网页的布局外部样式表存储在 CSS 文件中 *&#xff1a;也称级联样式表。 CSS语法 在此例…

微信小程序的智慧物流平台-计算机毕业设计源码49796

目 录 摘要 1 绪论 1.1 研究背景 1.2 研究意义 1.3研究方法 1.4开发技术 1.4.1 微信开发者工具 1.4.2 Node.JS框架 1.4.3 MySQL数据库 1.5论文结构与章节安排 2系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1 用户登录流程 2.2.2 数据删除流程 2.3 系统功能分…

Windows 上帝模式是什么?开启之后有什么用处?

Windows 上帝模式是什么 什么是上帝模式&#xff1f;Windows 上帝模式&#xff08;God Mode&#xff09;是一个隐藏的文件夹&#xff0c;通过启用它&#xff0c;用户可以在一个界面中访问操作系统的所有管理工具和设置选项。这个功能最早出现在 Windows Vista 中&#xff0c;并…

【K8s】专题六(4):Kubernetes 稳定性之初始化容器

以下内容均来自个人笔记并重新梳理&#xff0c;如有错误欢迎指正&#xff01;如果对您有帮助&#xff0c;烦请点赞、关注、转发&#xff01;欢迎扫码关注个人公众号&#xff01; 目录 一、基本介绍 二、主要特点 三、资源清单&#xff08;示例&#xff09; 一、基本介绍 初…

小学英语语法

目录 a和an的用法名词的单复数be动词和人称代词&#xff08;主格&#xff09;指示代词形容词物主代词名词所有格双重所有格方位介词some&#xff0c;any和no的用法How many和How much的用法情态动词can的用法祈使句人称代词&#xff08;宾格&#xff09;常见实义动词的用法一般…

【MySQL备份】Percona XtraBackup总结篇

目录 1.前言 2.问题总结 2.1.为什么在恢复备份前需要准备备份 2.1.1. 保证数据一致性 2.1.2. 完成崩溃恢复过程 2.1.3. 解决非锁定备份的特殊需求 2.1.4. 支持增量和差异备份 2.1.5. 优化恢复性能 2.2.Percona XtraBackup的工作原理 3.注意事项 1.前言 在历经了详尽…

深入理解 Webhook 与 API 的区别

作为人类&#xff0c;我们希望技术能帮助我们更快捷、更便捷地与更多人交流。但要实现这一目标&#xff0c;我们首先需要找到一种方法让技术能够彼此对话。 这就是 API 和 Webhook 的用武之地。 API 和 Webhook 都能够促进两个应用之间的数据同步和传递。然而&#xff0c;它们…

MySQL视图教程(03):列出视图

文章目录 MySQL 列出视图语法使用场景示例结论 MySQL 列出视图 MySQL 是一种流行的关系型数据库管理系统&#xff0c;用于创建和管理数据库中的表、视图等对象。在 MySQL 中&#xff0c;视图是一种虚拟表&#xff0c;可以从一个或多个实际表中检索数据&#xff0c;并根据特定的…

springboot整合Camunda实现业务

1.bean实现 业务 1.画流程图 系统任务&#xff0c;实现方式 2.定义bean package com.jmj.camunda7test.process.config;import lombok.extern.slf4j.Slf4j; import org.camunda.bpm.engine.TaskService; import org.camunda.bpm.engine.delegate.JavaDelegate; import org.…