B/S模式的web通信(高并发服务器)

news2024/11/28 8:35:30

这里写目录标题

  • 目标
    • 实现的目标
  • 服务器代码(采用epoll实现服务器)
    • 整体框架
    • main函数
    • init_listen_fd函数(负责对lfd初始化的那一系列操作)
    • epoll_run函数
    • do_accept函数
    • do_read函数
      • 内容
      • 补充:http中的getline函数
    • 详解do_read函数(借助http协议进行通信的具体数据读写过程)
      • 首先实现单文件请求
        • 思路
        • 代码实现
        • 补充:正则表达式(简要介绍)
        • 补充:上述代码中用到的函数的原型
          • strncasecmp
          • stat

目标

实现的目标

在这里插入图片描述
我们要实现,在服务器上启动服务之后,通过浏览器访问Ip+port,可以访问到服务器某个目录的文件
当然也可以使用域名访问,域名就是对IP+port进行了包装,域名需要申请

服务器代码(采用epoll实现服务器)

整体框架

在这里插入图片描述
在这里插入图片描述

main函数

在这里插入图片描述
首先,为了更加灵活的进行服务端的启动,我们在main函数中,定义两个参数,用于进行启动参数的配置和读取

首先,判断argv是否读到了三个字符串,如果小于三个字符串,则启动时没有按照规定进行启动参数设置,提示启输入./server 端口 以及要发布的文件所在路径

之后,通过argv[2],拿到输入的第二个字符串,即端口,将其atoi,转为字面值一样的int型,就拿到了开放的端口

之后,因为http协议中,对于从浏览器发送而来的文件的位置,是以启动配置的第三个参数为参考根目录的相对路径,服务器要设法拿到服务器对应的路径,而拼接路径又过于繁琐,所以,使用chdir函数,该函数可以让服务端的工作目录跳转到某个目录下(实际上就是与cd的作用一样),所以,chdir(argv[2]),就是将服务器跳转到第三个参数所指明的目录内,这样,从http协议封装出来的数据包拿到的数据,可以直接拿到当前服务器使用,因为服务器的工作目录已经跳转到第三个参数的目录了

之后使用封装的函数,根据传来的端口,启动epoll监听

init_listen_fd函数(负责对lfd初始化的那一系列操作)

在这里插入图片描述
在这里插入图片描述

即,根据传入进来的port(端口)、epfd(epoll树根)等参数,进行lfd的创建以及上树

包括,创建socket、创建服务器地址结构、设置端口复用、给lfd绑定地址、设置监听上限、将lfd上树,预监听其读事件。
最后返回lfd

epoll_run函数

在这里插入图片描述
根据传入的port,首先,创建一个epoll_event数组,用于后续的epoll_wait监听时返回套接字数组。创建epoll监听树树根,之后传入init_listen_fd函数,创建监听套接字lfd,并将其上树

在这里插入图片描述
在lfd上树之后,我们就可以开始监听了
while循环内,使用epoll_wait开始监听,将监听结果返回到all_events数组。

之后拿到返回值,就是all_events数组的有效元素个数,也是其循环上限,
在循环内,我们默认只处理服务器的读事件(也就是只处理接收请求),其他事件不处理
将其元素挨个取出,转为指针,如果不是事件,直接continue,如果是,进行后续代码:
读事件又分为是lfd收到数据还是其他cfd受到数据:
如果是lfd,进行accept进行新的cfd创建与连接
如果是cfd,那么进行数据的读取

do_accept、do_read函数也是封装的函数

do_accept函数

在这里插入图片描述
根据传入进来的lfd,和epoll的树根,进行新的cfd的创建以及相关设置

首先,调用accept函数,进行新的cfd的创建,返回新的cfd,

之后,将其设置为非阻塞

之后设置ET模式,这是经典的ET+非阻塞,是高效模式

最后将其挂上树,进行监听

do_read函数

内容

补充:http中的getline函数

在这里插入图片描述
参数:第一个是表示从cfd文件描述符中读,第二个是传出参数,表示读到的数据,第三个是传出参数对应实参的开辟空间大小,使用时,直接传sizeof(第二个参)

其中,recv的第四个参数如果是0,那么就是真实的从缓冲区拿数据读进来
而如果其第四个参数是MSG_PEEK,那么就是拷贝读取,并不会动缓冲区中的数据

上图中的所有recv,每次都是只读取一个字节

返回值为所读取到的字节数,将数据读进传出参数buf中

详解do_read函数(借助http协议进行通信的具体数据读写过程)

首先实现单文件请求

思路

在这里插入图片描述
1、首先使用封住的getline,读收到的http协议的请求行。
2、然后对其进行拆分
3、之后判断文件是否存在,使用stat()函数,如下:
在这里插入图片描述
该函数传入文件路径,以及一个传出参数变量,即可通过返回值,判断文件是否存在,而这里传路径时,直接将拆分完的路径传入即可,会因为服务器已经在main函数时被chdir切换为了对应的目录,而浏览器也是以服务器启动时提供的路径为参考,所以,可以直接传入

4、判断传来的路径是文件还是目录(若是文件进行后续操作,若是目录,则将目录信息发送回去,此处不做研究)
5、使用open函数,打开文件,并将其内容读取到
6、首先封装http应答的数据包的协议头:
主要包含两部分,一个是第一行的版本号、状态码、状态描述
另一个是附加信息中的“文件类型”
7、将数据封装到协议头之后,最终生成http应答数据包,将其发送回浏览器

代码实现

在这里插入图片描述
更正:145行,sizeof内应该是line,而不是buf
因为我们会频繁用到将某个cfd,关闭,且下树的操作,所以,可以将该操作封装为一个函数,如上图中的disconnect函数

之后,对do_read进行实现,首先,创建并初始化一个字符数组
之后,get_line读取数据,传入“传出参数”line,拿到数据
之后,对返回值进行判断,如果返回值为0,表示是对端关闭,所以,我们也进行disconnect操作
而如果不是0,则要进行数据的拆分了,现在在line数组中的是“GET ./hello.c HTTP/1.1”,即是由三个字符串组成的字符数组,接下来是拆分这三个字符串:
使用sscanf函数:
在这里插入图片描述
首先定义三个字符数组,分别用于存储拆分后的结果
之后,调用sscanf函数,第一个参数传入要拆分的对象,第二个参数需要传入格式说明符(如%s,%d等),这里我们传入一个正则表达式,大概功能与“%s %s %s”差不多,注意要%s之间是空格,不可忽略
在这里插入图片描述
在这里插入图片描述
之后:
在这里插入图片描述
对于图中的第一部分,作用是:读取第一行之后的附加内容,不让其存留于缓冲区中,防止其影响下一次的读取。(因为我们这里心知肚明收到的肯定是GET请求,所以,并没有判断是否读到了附加内容结束,因为对于不是GET的请求,将附加内容读掉之后,接下来的“数据正文”是不可以直接丢掉的,应该存起来留用)
图中,如果len==‘\n’,本来是想表示读到了回车,也就是空行的下一行会自带换行回车,本意是想判断是否读完了整个附加数据(即读到了空行的下一行),但是这里的判断操作有点迷,到时候可视具体情况来定

对于第二部分,是将读到的第一个字符串与“GET”对比,查看是否相同,即查看第一个字符串是否是GET,
在linux中:
使用strncasecmp,该方法是将第一个参数与第二个参数相比看是否相同,且忽略大小写,且可以通过第三个参数指定比较前多少个字符
而strcasecmp,没有n,该方法也是比较第一个参数与第二个参数是否相同,忽略大小写,但是无法指定比较前多少个字符
(若此时用的是string,那么可以使用c_str()方法,将其转为char指针类型,再进行函数比较,或者将string每个字符统一转为大写,再与“GET”比较)
win中忽略大小写的字符指针之间的比较的函数是:stricmp()

上图中,比较读到的第一个字符串,与“GET”,且只比较前3个字符,这里是担心因为’\0’而误判,实际上这两个都被补了‘\0’,但是只比较前三个会无需考虑是否有’\0’。
而如果确实是GET的话,返回值为0,如果返回0,那么就要进行相关GET的操作了,字符指针=path+1,因为path第一个字符是‘/’,他并不是文件名的一部分,所以,跳过,直接+1,从第二个字符读取
然后封装一个函数,将文件名传入,进行相关的处理

之后对于http_request()函数:
在这里插入图片描述
我们已经确定了请求是GET,接下来就是对GET做相关的操作,对于GET请求,我们要判断文件是否存在,
判断文件是否存在,使用stat方法,使用方法如上图,先创建一个结构体,struct stat类型的结构体,之后,调用stat方法,第一个参数:文件名,第二个参数:结构体地址(该函数使用时要保证服务器当前工作目录切换到了要搜索文件所在的目录)

如果返回值不是0,那么代表文件不存在,这时要回发给浏览器404错误页面,而服务器的代码可以有一个perror,来进行异常显示,至于要不要exit退出服务器,并不一定,根据具体需求。

前面都没有问题的话,说明文件存在,这里,就要判断其是文件还是文件夹,使用stat自带的宏来判断:
S_ISREG(结构体变量.st_mode),如果为真,则是文件,如果S_ISDIR(结构体变量.st_mode)为真,则是文件夹(或者说是文件目录)
如果是文件,那么接下来的操作就是
1、回发http应答
2、回发请求的数据内容

对于我们封装的回发函数(send_respond):
在这里插入图片描述
这里我们应该清楚,所谓协议,实际上就是一堆字符串,他按照一定的格式,将有效数据糅合进一堆字符串中,所以,我们回发时,也是,主要进行字符串的拼接操作

首先,对于参数,我们需要:与浏览器通信的cfd(因为我们服务器还是使用socket的epoll服务器),状态码(用no表示),状态码描述(disp指针接收),文件类型(即附加内容中的第四行,这是必不可少的),文件长度
在调用时,将参数传入(这里注意,此前http_request并没有接收cfd,这里在此处更改,写代码的时候注意)

之后,在send_respond中:
首先定义一个字符串buf,初始化为0
之后使用sprintf,将字符串拼接到buf中,使用方法见上图。
而想要再次拼接,且在buf原有的内容后面追加的话,继续调用sprintf,但是第一个参数传buf+strlen(buf)
注意,在http中的换行是\r\n
将所有内容使用send发过去之后,还要将第9行的/r/n发回去

至此,GET的协议应答就发送完了

而对于,buf+srtlen(buf)的可行性:
在这里插入图片描述
strlen会计算字符串的字符个数,且不计算最后的\0,所以,不管最后有没有\0,buf+strlen(buf),都会来到有效数据的下一个位置,
如果有\0,那么就覆盖其\0,毕竟我们都想要追加内容,\0作为结束符,自然要被覆盖,且必须被覆盖,
如果没有,自然在后面接着写数据即可

发送完了协议应答,就要发送下面的数据正文了:
在这里插入图片描述
打开指定的本地文件,用fd接收(注意这里的fd是代表本地文件,而之前的cfd是通信文件描述符,即套接字,二者不要混淆),之后使用read从打开的本地文件中读数据给buf,然后再使用send将buf中的数据写给cfd

补充:正则表达式(简要介绍)

正则表达式,就是专门对字符串进行操作的,他可以表示出你对字符串的任何需求
在这里插入图片描述
其中,刚刚的[ ^空格],是匹配除空格之外的任意字符

更多请查看下列网站:
在这里插入图片描述

补充:上述代码中用到的函数的原型
strncasecmp

在这里插入图片描述

stat

在这里插入图片描述
下面是使用stat的宏来判断“使用stat搜索的文件”是文件还是文件夹:
在这里插入图片描述

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

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

相关文章

DE2-115开发板基于verilog和nioⅡ的流水灯实现

目录 一、 内容概要二、 实现2.1 基于Nios II软核的流水灯2.1.1 准备工作2.1.2 工程搭建2.1.3 硬件代码设计Ⅰ 连接IP核Ⅱ 编写代码Ⅲ 各种配置 2.1.4 软件代码设计Ⅰ 环境构建Ⅱ 编写代码 2.1.5 代码下载Ⅰ 硬件下载Ⅱ 软件下载 2.1.6 运行结果 2.2 Verilog流水灯 三、 心得体…

LORA学习笔记2——训练集处理

前言 对于ai训练来说,处理训练集是模型训练的重要环节。训练集的质量对最终模型的质量影响巨大。这里以二次元角色为例,记录下训练集处理的流程和一些心得。 素材准备 素材准备有以下几个需要注意的点: 通常训练二次元角色需要30张以上的…

栈和队列(栈的详解)

目录 栈栈的实现栈的结构栈的初始化栈的销毁入栈出栈获取栈顶元素栈的判空获取栈的数据个数test.c(测试)总结 栈 栈也是线性表(在逻辑上是顺序存储)的一种,栈只允许其在固定的一端进行插入和删除,栈中的元素遵循后进先出&#xf…

Linux-笔记 开发板Uboot命令使用

将之前自学的知识整理了一下笔记,以便回忆 信息查询命令 1、help/?:查看所支持命令 > ? md md - memory displayUsage: md [.b, .w, .l] address [# of objects]2、bdinfo:查询板子信息 > bdinfo arch_number 0x00000000 boot_p…

多商户Docker Supervisor进程管理器部署

Dockerfile 根目录下没有Dockerfile的可以复制下面的命令 # 使用基础镜像 FROM leekay0218/crmeb-mer## 复制代码 ## 在本地调试注释掉,使用映射把文件映射进去 #ADD ./ /var/www# 设置工作目录 WORKDIR /var/www# 设置时区为上海 ENV TZAsia/Shanghai RUN ln -sn…

对数据进行标准化和归一化

数据的形式:保存在CSV中,第一列为姓名,第二列之后为特征。 标准化 输入文件的路径,设置保存转化后的文件路径 import pandas as pd from sklearn.preprocessing import StandardScaler# 读取CSV文件 data pd.read_csv(rC:\User…

开发时如何快速分析代码和生成测试方法(Baidu Comate插件帮我一键分析)

目录 前言 Baidu Comate智能编码助手简介 安装教程 使用RabbitMQ一个绑定队列方法进行演示 进行测试现有功能 使用感觉 测试结果 前言 因为在开发代码的时候,发现有很多都是废话也不是很想写注释 的,毕竟程序员最讨厌的两件事情,一…

Hikyuu高性能量化研究框架助力探索

Hikyuu Quant Framework 是一款基于C/Python的开源量化交易分析与研究工具,主要用于A股市场的交易策略分析与回测,目前不支持期货等,需要自行改造。 Hikyuu的目标 Hikyuu的最初目的是为了快速对A股全市场股票进行策略回测和验证&#xff0c…

windows窗口消息队列与消息过程处理函数

在Windows窗口应用程序中,消息队列和窗口过程函数是实现消息驱动机制的核心组件。 消息队列(Message Queue): 消息队列是用于存储窗口消息的缓冲区。当用户与应用程序交互时,系统会将生成的消息插入到消息队列中&…

C++11:常用语法汇总

目录 🍁统一的列表初始化 { }initializer_list 🍁decltype 推导表达式类型🍁可变参数模板解析可变参数包方法一方法二 🍁lambda 表达式捕捉列表的使用运用场景举例lambda表达式 与 函数对象 🍁统一的列表初始化 { } 在…

电商购物系统首页的商品分类

如上图对商品的一个分类实际上和省市区的分类十分类似 , 都是通过自关联的方法来实现 , 但是这里不同的是 , 涉及到外键来获取数据 首先让我们来看一下最后通过后端返回数据的形式是什么样子的 """{1:{channels:[{id:1 , name:手机 , url:},{}{}],sub_cats:[{…

苍穹外卖面试总结

项目介绍 我完成了一个外卖项目,名叫苍穹外卖,是跟着黑马程序员的课程来自己动手写的。 项目基本实现了外卖客户端、商家端的后端完整业务。 商家端分为员工管理、文件上传、菜品管理、分类管理、套餐管理、店铺营业状态、订单下单派送等的管理、数据…

考研数学|强化阶段怎么刷《660》《880》《1000》?

强化阶段想要刷好题,首先要选一本适合自己的题集! 一般在强化阶段,大家用多个最多的题集就是660题,880题还有1000题 660题的特点是只训练客观题,虽然题目的质量很高,但是训练面还是比较窄 880题是综合训…

【SpringBoot】 什么是springboot(二)?springboot操作mybatisPlus、swagger、thymeleaf模板

文章目录 SpringBoot第三章1、整合mybatsPlus1-234-67-10问题2、整合pageHelper分页3、MP代码生成器1、编写yml文件2、导入依赖3、创建mp代码生成器4、生成代码5、编写配置类扫描mapper类6、编写控制器类4、swagger1、什么是swagger2、作用3、发展历程4、一个简单的swagger项目…

ADC模-数转换原理与实现

1. 今日摸鱼计划 今天来学习一下ADC的原理,然后把ADC给实现 ADC芯片:ADC128S102 视频: 18A_基于SPI接口的ADC芯片功能和接口时序介绍_哔哩哔哩_bilibili 18B_使用线性序列机思路分析SPI接口的ADC芯片接口时序_哔哩哔哩_bilibili 18C_基于线性序列机的S…

MFC的句柄概念以及句柄类型

在MFC(Microsoft Foundation Class)桌面应用程序中,窗口是通过句柄(Handle)来进行管理和操作的。 句柄是一个标识符,用于唯一标识和引用窗口、控件、设备上下文等对象。在MFC桌面应用程序中,常…

mac -- Navicat premium for mac 12的安装破解过程

下载准备好dmg文件,接下来开始安装破解。 镜像在官网下载,复制打开链接可以直接下载,可以直接下载中文版,不需要汉化:(下载包不能用,留言我发给你) 英文64位: http://dow…

【0003day】VOSviewer分析

这个软件也可以用知网,也可以用web of science。 首先,需要创建数据。这个数据如何创建,需要参考对应的教程。(本文以web of science为平台来做分析。) 首先,创建对应的数据库。 一直下一步 让后选择完…

Linux重定向及缓冲区理解

重定向: 在上一期虚拟文件系统中讲到了每个进程在打开后,都会默认打开3个文件,如下: stdin 标准输入(键盘) 文件描述符:0 stdout 标准输出(显示器)文件描述符&a…

Boss让你设计架构图,你懵逼了,解救你的参考图来啦。

架构图是指用于描述系统或软件的结构和组成部分之间关系的图形表示。 它是一种高层次的图示,用于展示系统的组件、模块、接口和数据流等,以及它们之间的相互作用和依赖关系。架构图通常被用于可视化系统的整体设计和组织结构,帮助人们理解系…