公司做的网站打开慢,济阳网站建设哪家好,邯郸网络教研中心,建站模板与网站案例展示这里写目录标题 目标实现的目标 服务器代码#xff08;采用epoll实现服务器#xff09;整体框架main函数init_listen_fd函数#xff08;负责对lfd初始化的那一系列操作#xff09;epoll_run函数do_accept函数do_read函数内容补充#xff1a;http中的getline函数 详解do_re… 这里写目录标题 目标实现的目标 服务器代码采用epoll实现服务器整体框架main函数init_listen_fd函数负责对lfd初始化的那一系列操作epoll_run函数do_accept函数do_read函数内容补充http中的getline函数 详解do_read函数借助http协议进行通信的具体数据读写过程首先实现单文件请求思路代码实现补充正则表达式简要介绍补充上述代码中用到的函数的原型strncasecmpstat 目标
实现的目标 我们要实现在服务器上启动服务之后通过浏览器访问Ipport可以访问到服务器某个目录的文件 当然也可以使用域名访问域名就是对IPport进行了包装域名需要申请
服务器代码采用epoll实现服务器
整体框架 main函数 首先为了更加灵活的进行服务端的启动我们在main函数中定义两个参数用于进行启动参数的配置和读取
首先判断argv是否读到了三个字符串如果小于三个字符串则启动时没有按照规定进行启动参数设置提示启输入./server 端口 以及要发布的文件所在路径
之后通过argv[2]拿到输入的第二个字符串即端口将其atoi转为字面值一样的int型就拿到了开放的端口
之后因为http协议中对于从浏览器发送而来的文件的位置是以启动配置的第三个参数为参考根目录的相对路径服务器要设法拿到服务器对应的路径而拼接路径又过于繁琐所以使用chdir函数该函数可以让服务端的工作目录跳转到某个目录下实际上就是与cd的作用一样所以chdir(argv[2])就是将服务器跳转到第三个参数所指明的目录内这样从http协议封装出来的数据包拿到的数据可以直接拿到当前服务器使用因为服务器的工作目录已经跳转到第三个参数的目录了
之后使用封装的函数根据传来的端口启动epoll监听
init_listen_fd函数负责对lfd初始化的那一系列操作 即根据传入进来的port端口、epfdepoll树根等参数进行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的操作了字符指针path1因为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但是第一个参数传bufstrlen(buf) 注意在http中的换行是\r\n 将所有内容使用send发过去之后还要将第9行的/r/n发回去
至此GET的协议应答就发送完了
而对于bufsrtlen(buf)的可行性 strlen会计算字符串的字符个数且不计算最后的\0所以不管最后有没有\0bufstrlen(buf)都会来到有效数据的下一个位置 如果有\0那么就覆盖其\0毕竟我们都想要追加内容\0作为结束符自然要被覆盖且必须被覆盖 如果没有自然在后面接着写数据即可
发送完了协议应答就要发送下面的数据正文了 打开指定的本地文件用fd接收注意这里的fd是代表本地文件而之前的cfd是通信文件描述符即套接字二者不要混淆之后使用read从打开的本地文件中读数据给buf然后再使用send将buf中的数据写给cfd
补充正则表达式简要介绍
正则表达式就是专门对字符串进行操作的他可以表示出你对字符串的任何需求 其中刚刚的[ ^空格]是匹配除空格之外的任意字符
更多请查看下列网站
补充上述代码中用到的函数的原型
strncasecmp stat 下面是使用stat的宏来判断“使用stat搜索的文件”是文件还是文件夹