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服务器开发流程
-
创建socket,得到监听文件描述符lfd——socket
-
设置端口复用——setsockopt()
-
绑定——bind()
-
设置监听——listen()
-
创建epoll树,得到树根描述符epfd——epoll_create()
-
将监听文件描述符lfd上树——epoll_ctr(epfd,epoll_CTL_ADD…)\
-
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--设置端口复用---bindint 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.1char 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)
{//定义与客户端通信的buffereventstruct 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;
}
相关文章:

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 空…...

人工智能系列-NumPy(二)
🌈个人主页:羽晨同学 💫个人格言:“成为自己未来的主人~” 链接数组 anp.array([[1,2],[3,4]]) print(第一个数组:) print(a) print(\n) bnp.array([[5,6],[7,8]]) print(第二个数组:) print(b) print(\n) print…...

[单master节点k8s部署]19.监控系统构建(四)kube-state-metrics
kube-state-metrics 是一个Kubernetes的附加组件,它通过监听 Kubernetes API 服务器来收集和生成关于 Kubernetes 对象(如部署、节点和Pod等)的状态的指标。这些指标可供 Prometheus 进行抓取和存储,从而使你能够监控和分析Kubern…...

字符串函数5-9题(30 天 Pandas 挑战)
字符串函数 1. 相关知识点1.5 字符串的长度条件判断1.6 apply映射操作1.7 python大小写转换1.8 正则表达式匹配2.9 包含字符串查询 2. 题目2.5 无效的推文2.6 计算特殊奖金2.7 修复表中的名字2.8 查找拥有有效邮箱的用户2.9 患某种疾病的患者 1. 相关知识点 1.5 字符串的长度条…...

【C语言题目】34.猜凶手
文章目录 作业标题作业内容2.解题思路3.具体代码 作业标题 猜凶手 作业内容 日本某地发生了一件谋杀案,警察通过排查确定杀人凶手必为4个嫌疑犯的一个。 以下为4个嫌疑犯的供词: A说:不是我。 B说:是C。 C说:是D。 D说ÿ…...
C++ 多进程多线程间通信
目录 一、进程间通信 1、管道(Pipe) 2、消息队列(Message Queue) 3、共享内存(Shared Memory) 4、信号量(Semaphore) 5、套接字(Socket) 6、信号&…...
怎么做防御系统IPS
入侵防御系统(IPS)是入侵检测系统(IDS)的增强版本,它不仅检测网络流量中的恶意活动,还能自动采取措施阻止这些活动。实现IPS的主要工具包括Snort和Suricata。以下是使用Snort和Suricata来实现IPS的详细步骤…...

达梦数据库的系统视图v$auditrecords
达梦数据库的系统视图v$auditrecords 在达梦数据库(DM Database)中,V$AUDITRECORDS 是专门用来存储和查询数据库审计记录的重要系统视图。这个视图提供了对所有审计事件的访问权限,包括操作类型、操作用户、时间戳、目标对象等信…...
Spring Boot与MyBatis-Plus:代码逆向生成指南
在Spring Boot项目中使用MyBatis-Plus进行代码逆向生成,可以通过MyBatis-Plus提供的代码生成器来快速生成实体类、Mapper接口、Service接口及其实现类等。以下是一个简单的示例步骤: 代码逆向生成 1.添加依赖: 在pom.xml文件中添加MyBati…...

【MySQL】mysql访问
mysql访问 1.引入MySQL 客户端库2.C/C 进行增删改3.查询的处理细节4.图形化界面访问数据库4.1下载MYSQL Workbench4.2MYSQL Workbench远程连接数据库 点赞👍👍收藏🌟🌟关注💖💖 你的支持是对我最大的鼓励&a…...

(1)Jupyter Notebook 下载及安装
目录 1. Jupyter Notebook是什么?2. Jupyter Notebook特征3. 组成部分3.1 网页应用3.2 文档 4. 适用场景5. 利用Google Colab安装Jupyter Notebook3.1 什么是 Colab?3.2 访问 Google Colab3.3 新建笔记本 1. Jupyter Notebook是什么? 百度百科…...

监控平台zabbix对接grafana
本次博客基于监控平台zabbix介绍与部署-CSDN博客的环境下进行的 1、安装grafana并启动 添加一台虚拟机20.0.0.30 (1)系统初始化 [rootzx3 ~]# systemctl stop firewalld [rootzx3 ~]# setenforce 0 [rootzx3 ~]#(2)安装并启动…...

14-11 2024 年的 13 个 AI 趋势
2024 年的 13 个 AI 趋势 人工智能对环境的影响和平人工智能人工智能支持的问题解决和决策针对人工智能公司的诉讼2024 年美国总统大选与人工智能威胁人工智能、网络犯罪和社会工程威胁人工智能治疗孤独与对人工智能的情感依赖人工智能影响者中国争夺人工智能霸主地位人工智能…...
计算机大方向的选择
选专业要了解自己的兴趣所在。 即想要学习什么样的专业,如果有明确的专业意向,就可以有针对性地选择那些专业实力较强的院校。 2.如果没有明确的专业意向,可以优先考虑一下院校。 确定一下自己想要选择综合性院校还是理工类院校或是像财经或者…...
使用Qt Installer Framework在centos7中打包
文章目录 步骤 1: 安装Qt和Qt Installer Framework安装Qt安装Qt Installer Framework步骤 2: 创建项目目录结构步骤 3: 编写安装脚本配置文件(config/config.xml)Package 信息meta/package.xmldata 目录步骤 4: 编写安装脚本步骤 5: 生成安装程序总结在CentOS 7中使用Qt Inst…...

您的私人办公室!-----ONLYOFFICE8.1版本的桌面编辑器测评
随时随地创建并编辑文档,还可就其进行协作 ONLYOFFICE 文档是一款强大的在线编辑器,为您使用的平台提供文本文档、电子表格、演示文稿、表单和 PDF 编辑工具。 网页地址链接: https://www.onlyoffice.com/zh/office-suite.aspxhttps://www…...
点估计和参数分布的对比
点估计(Point Estimation)和 参数分布(Parameter Distribution)是统计学中两种不同的参数估计方法。 文章目录 点估计(Point Estimation)参数分布(Parameter Distribution)对比总结 …...

桌面保存的Word文件删除怎么找回?超实用的三个方法?
在日常工作和学习中,我们经常会使用Word文档进行文字编辑和文件保存。但是,有时由于操作失误或系统故障,我们会不小心将存放在电脑桌面重要的Word文件删除了。导致无法挽回的损失,但幸运的是,有一些方法可以帮助我们找…...

【leetcode】双指针算法题
文章目录 1.算法思想2.移动零3.复写零方法一方法二 4.快乐数5.盛水最多的容器方法一(暴力求解)方法二(左右指针) 6.有效三角形的个数方法一(暴力求解)方法二(左右指针) 7.两数之和8.…...
vue-router 源码分析——8.重定向
这是对vue-router 3 版本的源码分析。 本次分析会按以下方法进行: 按官网的使用文档顺序,围绕着某一功能点进行分析。这样不仅能学习优秀的项目源码,更能加深对项目的某个功能是如何实现的理解。这个对自己的技能提升,甚至面试时…...

idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...

Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...

搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...

论文阅读:LLM4Drive: A Survey of Large Language Models for Autonomous Driving
地址:LLM4Drive: A Survey of Large Language Models for Autonomous Driving 摘要翻译 自动驾驶技术作为推动交通和城市出行变革的催化剂,正从基于规则的系统向数据驱动策略转变。传统的模块化系统受限于级联模块间的累积误差和缺乏灵活性的预设规则。…...
云原生周刊:k0s 成为 CNCF 沙箱项目
开源项目推荐 HAMi HAMi(原名 k8s‑vGPU‑scheduler)是一款 CNCF Sandbox 级别的开源 K8s 中间件,通过虚拟化 GPU/NPU 等异构设备并支持内存、计算核心时间片隔离及共享调度,为容器提供统一接口,实现细粒度资源配额…...
2025年低延迟业务DDoS防护全攻略:高可用架构与实战方案
一、延迟敏感行业面临的DDoS攻击新挑战 2025年,金融交易、实时竞技游戏、工业物联网等低延迟业务成为DDoS攻击的首要目标。攻击呈现三大特征: AI驱动的自适应攻击:攻击流量模拟真实用户行为,差异率低至0.5%,传统规则引…...

Python环境安装与虚拟环境配置详解
本文档旨在为Python开发者提供一站式的环境安装与虚拟环境配置指南,适用于Windows、macOS和Linux系统。无论你是初学者还是有经验的开发者,都能在此找到适合自己的环境搭建方法和常见问题的解决方案。 快速开始 一分钟快速安装与虚拟环境配置 # macOS/…...