这里写目录标题
- 目标
- 实现的目标
- 服务器代码(采用epoll实现服务器)
- 整体框架
- main函数
- init_listen_fd函数(负责对lfd初始化的那一系列操作)
- epoll_run函数
- do_accept函数
- do_read函数
- 内容
- 补充:http中的getline函数
- 详解do_read函数(借助http协议进行通信的具体数据读写过程)
- 首先实现单文件请求
- 思路
- 代码实现
- 补充:正则表达式(简要介绍)
目标
实现的目标
我们要实现,在服务器上启动服务之后,通过浏览器访问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之间是空格,不可忽略
补充:正则表达式(简要介绍)
正则表达式,就是专门对字符串进行操作的,他可以表示出你对字符串的任何需求
首先来看字符类:
其中,刚刚的[ ^空格],是匹配除空格之外的任意字符
更多请查看下列网站: