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--设置端口复用---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;
}

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

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

相关文章

第一次的pentest show总结

第一次的pentest show总结 前言 开始之前&#xff0c;我特别感谢TryHackMe(英)、HackTheBox(美)、zero-point security(英)、offsec(美)等平台&#xff0c;使我们能够通过网络以线上的方式学习与练习&#xff0c;打破传统线下各地区教育资源差异大的限制&#xff0c;对网络教…

03:EDA的进阶使用

使用EDA设计一个38译码器电路和245放大电路 1、38译码器1.1、查看74HC138芯片数据1.2、电路设计 2、245放大电路2.1、查看数据手册2.2、设计电路 3、绘制PCB3.1、导入3.2、放置3.3、飞线3.4、特殊方式连接GND3.5、泪滴3.6、配置丝印和划分区域3.7、添加typc接口供电 1、38译码器…

20.5.【C语言】求长度的两种方式

1.sizeof 用于测数据类型的长度的函数&#xff08;详细见第3篇&#xff09; 2.strlen 其计算长度时只有遇到\0才会停止&#xff0c;并且\0不会计算在内 如char arr[]{a,1,b}; printf("%d\n",strlen(arr)); 结果是个随机数&#xff01;strlen读内存中的数据&…

快递物流运输中的RFID智能锁控应用方案

一、物流货运管理的痛点分析 1.1 货物安全与监控难题 物流货运过程中&#xff0c;货物安全是首要关注的问题。传统的锁控方式存在诸多不足&#xff0c;例如易被撬锁、监控盲点以及难以实时追踪货物状态。据统计&#xff0c;每年因货物丢失或损坏导致的经济损失高达数十亿美元…

小白也可以部署私有化大模型知识库_私有化知识库

透过产品了解RAG技术原理&#xff0c;对未来大模型应用开发也将起到事半功倍的效果。 虽然网络上有很多此类技术文章&#xff0c;这里自己也进行一次总结&#xff0c;加深印象的同时给小伙伴做一个参考&#xff0c;多多交流。 准备 Linux服务器、windows也可以 Docker环境&…

Excel多表格合并

我这里一共有25张表格: 所有表的表头和格式都一样,但是内容不一样: 现在我要做的是把所有表格的内容合并到一起,研究了一下发现WPS的这项功能要开会员的,本来想用代码撸出来的,但是后来想想还是找其他办法,后来找到"易用宝"这个插件,这个插件可以从如下地址下载:ht…

C++基础21 二维数组及相关问题详解

这是《C算法宝典》C基础篇的第21节文章啦~ 如果你之前没有太多C基础&#xff0c;请点击&#x1f449;C基础&#xff0c;如果你C语法基础已经炉火纯青&#xff0c;则可以进阶算法&#x1f449;专栏&#xff1a;算法知识和数据结构&#x1f449;专栏&#xff1a;数据结构啦 ​ 目…

谷歌地图Google JS API 实现

demo实现 实现源码&#x1f447; // 谷歌地图Google JS API 实现 <template><div class"myMap"><gmp-map :center"center" zoom"15" map-id"ab6b6643adfa1a70"><gmp-advanced-markerv-for"(res, index) in…

MySQL | JDBC连接数据库详细教程【全程干货】

文章目录 一、什么是JDBC&#xff1f;二、JDBC工作原理三、使用JDBC连接MySQL数据库【✔】1、安装对应数据驱动包2、将jar包导入项目中3、编写代码连接数据库【⭐】1️⃣ 创建数据源2️⃣ 和数据库建立网络连接3️⃣ 构造SQL语句4️⃣ 执行SQL语句5️⃣ 断开连接&#xff0c;释…

nginx安装演示(离线安装,直接安装在Linux中)

文章目录 1、创建文件夹 tool / nginx2、把安装文件放到 /opt/tool/nginx 目录下面3、yum install gcc4、yum install gcc-c5、tar -zxvf pcre-8.37.tar.gz6、./configure7、make8、make install9、tar -zxvf openssl-1.0.1t.tar.gz10、./config11、/config 1、创建文件夹 tool…

人脉社群平台微信小程序系统源码

&#x1f31f;【解锁人脉新纪元&#xff1a;探索人脉社群平台小程序】&#x1f31f; &#x1f680;【开篇&#xff1a;为什么我们需要人脉社群平台小程序&#xff1f;】&#x1f680; 在这个快节奏的时代&#xff0c;人脉不再是简单的名片交换&#xff0c;而是通往成功与机遇…

昇思25天学习打卡营第17天|ChatGLM-6B聊天demo

一、简介&#xff1a; 本次实验&#xff0c;基于MindNLP和ChatGLM6B模型搭建一个小的聊天应用&#xff0c;ChatGLM6B 是基于 GLM-4 模型开发的开源对话机器人&#xff0c;拥有 62 亿个参数&#xff0c;能够进行自然流畅的语言交流。在对话中&#xff0c;ChatGLM6B 可以胜任文案…

我关于Excel使用点滴的笔记

本篇笔记是我关于Excel使用点滴的学习笔记&#xff0c;摘要和地址链接列表。临时暂挂&#xff0c;后面可能在不需要时删除。 (笔记模板由python脚本于2024年06月28日 12:23:32创建&#xff0c;本篇笔记适合初通Python&#xff0c;熟悉六大基本数据(str字符串、int整型、float浮…

(2024)KAN: Kolmogorov–Arnold Networks:评论

KAN: Kolmogorov–Arnold Networks: A review 公和众与号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 1. MLP 也有可学习的激活函数 2. 标题的意义 3. KAN 是具有样条基激活函数的 M…

python通过COM Interface控制CANoe

python通过COM Interface控制CANoe 介绍打开示例工程python代码示例介绍 通过python可以控制CANoe的启动和停止等其他操作,在做高阶的自动化项目时可以用到。 打开示例工程 python代码示例 # --------------------------------------------------------------------------…

卫星网络——Walker星座简单介绍

一、星座构型介绍 近年来&#xff0c;随着卫星应用领的不断拓展&#xff0c;许多任务已经无法单纯依靠单颗卫星来完成。与单个卫星相比&#xff0c;卫星星座的覆盖范围显著增加&#xff0c;合理的星座构型可以使其达到全球连续覆盖或全球多重连续覆盖&#xff0c;这样的特性使得…

Redis三种模式——主从复制、哨兵模式、集群

一、Redis模式 Redis有三种模式&#xff1a;分别是主从同步/复制、哨兵模式、Cluster 主从复制&#xff1a;主从复制是高可用Redis的基础&#xff0c;哨兵和群集都是在主从复制基础上实现高可用的。主从复制主要实现了数据的多机备份&#xff0c;以及对于读操作的负载均衡和简…

关于ORACLE单例数据库中的logfile的切换、删除以及添加

一、有关logfile的状态解释 UNUSED&#xff1a; 尚未记录change的空白group&#xff08;一般会出现在loggroup刚刚被添加&#xff0c;或者刚刚使用了reset logs打开数据库&#xff0c;或者使用clear logfile后&#xff09; CURRENT: 当前正在被LGWR使用的gro…

深入理解【 String类】

目录 1、String类的重要性 2、常用方法 2、1 字符串构造 2、2 String对象的比较 2、3 字符串查找 2、4字符转换 数值和字符串转换&#xff1a; 大小写转化&#xff1a; 字符串转数组&#xff1a; 格式转化&#xff1a; 2、5 字符串替换 2、6字符串拆分 2、7 字符串…

Mysql中间件和高可用

文章目录 一、MySQL中间件代理服务器MycatMycat应用场景Mycat部署 实现读写分离 二、MySQL高可用高可用解决方案MHA高可用实现MHA 一、MySQL中间件代理服务器 数据库主要分为两大类&#xff1a;关系型数据库与 NoSQL 数据库&#xff08;非关系型数据库&#xff09;。 数据库主…