当前位置:   article > 正文

C++初级项目-webserver(1)_c++ webserver

c++ webserver

1.引言

Web服务器是一个基于Linux的简单的服务器程序,其主要功能是接收HTTP请求并发送HTTP响应,从而使客户端能够访问网站上的内容。本项目旨在使用C++语言,基于epoll模型实现一个简单的Web服务器。选择epoll模型是为了高效地处理大量并发连接。

2.项目概览

这个项目的目标是实现一个简单的Web服务器,可以处理基本的HTTP请求并发送相应的HTTP响应。项目结构包括服务器初始化、Epoll模型的使用、事件处理循环、HTTP请求处理、文件发送、错误处理等关键模块。技术和工具方面使用了C++语言、epoll模型以及socket编程。

根据这个服务器可以实现下面的功能,打开Linux环境下的文件。

在浏览器上面的搜索栏输入http://192.168.44.3:9999/hanzi.c

192.168.44.3是Linux环境的本机IP地址,9999是端口号,hanzi.c是打开的文件名

3.Epoll模型

1. 基本概念和优势

  • Epoll简介:Epoll(Event Poll)是Linux内核为处理大量文件描述符而设计的一种高效的I/O事件通知机制。它允许程序监视多个文件描述符上的事件状态,而无需轮询这些文件描述符。

  • 优势:

    • 高效的事件通知机制:Epoll使用基于事件的机制,只有当事件发生时才会通知应用程序,避免了轮询的开销。
    • 支持大量并发连接: 适用于处理大量并发连接的场景,能够有效管理数以千计的文件描述符。
    • 适用于非阻塞I/O: 与非阻塞模型结合使用,使得应用程序能够同时处理多个连接而不被阻塞。

2. 创建Epoll树和添加文件描述符

  1. // 创建epoll树
  2. int epfd = epoll_create(1024);
  3. if (epfd < 0) {
  4. perror("epoll_create error");
  5. close(lfd);
  6. return -1;
  7. }
  8. // 将监听文件描述符lfd添加到epoll树上
  9. struct epoll_event ev;
  10. ev.data.fd = lfd;
  11. ev.events = EPOLLIN;
  12. epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);

  • epoll_create: 创建一个epoll实例,返回一个用于标识该实例的文件描述符。
  • epoll_ctl: 控制epoll实例的行为,可以用于注册、修改或删除文件描述符。

3. Epoll事件处理循环

  1. int nready;
  2. struct epoll_event events[1024];
  3. while (1) {
  4. // 等待事件发生
  5. nready = epoll_wait(epfd, events, 1024, -1);
  6. if (nready < 0) {
  7. if (errno == EINTR) {
  8. continue;
  9. }
  10. break;
  11. }
  12. for (int i = 0; i < nready; i++) {
  13. int sockfd = events[i].data.fd;
  14. // 处理监听文件描述符lfd上的事件
  15. if (sockfd == lfd) {
  16. // 接受新的客户端连接
  17. int cfd = Accept(lfd, NULL, NULL);
  18. // 设置cfd为非阻塞
  19. int flag = fcntl(cfd, F_GETFL);
  20. flag |= O_NONBLOCK;
  21. fcntl(cfd, F_SETFL, flag);
  22. // 将新的cfd添加到epoll树上
  23. ev.data.fd = cfd;
  24. ev.events = EPOLLIN;
  25. epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
  26. } else {
  27. // 处理客户端数据
  28. http_request(sockfd);
  29. }
  30. }
  31. }

  • epoll_wait: 等待事件发生,返回就绪事件的数量。
  • events数组: 存储发生事件的文件描述符和事件类型。
  • EPOLLIN: 表示文件描述符上有可读数据。
  • Accept函数: 用于接受新的客户端连接。
  • fcntl函数: 用于设置文件描述符的属性,将其设置为非阻塞。

通过这样的Epoll模型,服务器能够高效地处理并发连接,只在有事件发生时才进行相应的处理,避免了不必要的轮询。

4. 事件处理循环

1. 服务器主循环

服务器的主循环是一个持续运行的事件处理循环,通过调用等待事件的发生。一旦有事件发生,主循环将负责处理这些事件。epoll_wait

  • epoll_wait: 等待事件发生,返回就绪事件的数量。
  • events数组: 存储发生事件的文件描述符和事件类型。
  • EPOLLIN: 表示文件描述符上有可读数据。
  • Accept函数: 用于接受新的客户端连接。
  • fcntl函数: 用于设置文件描述符的属性,将其设置为非阻塞。

2. 处理连接请求和客户端数据

在主循环中,通过判断就绪事件的文件描述符,可以区分是监听文件描述符lfd上的连接请求还是客户端文件描述符上的数据到达事件。

  1. // 处理监听文件描述符lfd上的事件
  2. if (sockfd == lfd) {
  3. // 接受新的客户端连接
  4. int cfd = Accept(lfd, NULL, NULL);
  5. // 设置cfd为非阻塞
  6. int flag = fcntl(cfd, F_GETFL);
  7. flag |= O_NONBLOCK;
  8. fcntl(cfd, F_SETFL, flag);
  9. // 将新的cfd添加到epoll树上
  10. ev.data.fd = cfd;
  11. ev.events = EPOLLIN;
  12. epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
  13. } else {
  14. // 处理客户端数据
  15. http_request(sockfd);
  16. }

如果是监听文件描述符lfd上的事件,表示有新的客户端连接请求,通过函数接受连接,并将新的客户端文件描述符设置为非阻塞,然后将其添加到epoll树上,监听其读事件。Accept

如果是客户端文件描述符上的事件,表示有数据到达,调用函数处理客户端的HTTP请求。http_request

通过这样的事件处理循环,服务器能够实时响应连接请求### 事件处理循环.

5.HTTP请求处理


1. 解析HTTP请求行


在处理客户端数据时,首先需要解析HTTP请求行,提取请求类型、文件名和协议版本。这是通过读取客户端发送的数据并解析其中的信息来实现的。


此代码从客户端文件描述符sockfd中读取HTTP请求行数据,然后使用函数解析出请求类型(GET、POST等)、文件名和协议版本。这样,服务器就能了解客户端请求的基本信息。sscanf

2. 区分请求类型,处理GET请求
在得到请求类型后,服务器通常需要根据不同的请求类型采取不同的处理方式。以下是处理GET请求的简化示例:

  1. //判断文件是否存在
  2. struct stat st;
  3. if(stat(pFile, &st)<0)
  4. {
  5. printf("file not exist\n");
  6. //发送头部信息
  7. send_header(cfd, "404", "NOT FOUND", get_mime_type(".html"), 0);
  8. //发送文件内容
  9. send_file(cfd, "error.html");
  10. }
  11. else //若文件存在
  12. {
  13. //判断文件类型
  14. //普通文件
  15. if(S_ISREG(st.st_mode))
  16. {
  17. printf("file exist\n");
  18. //发送头部信息
  19. send_header(cfd, "200", "OK", get_mime_type(pFile), st.st_size);
  20. //发送文件内容
  21. send_file(cfd, pFile);
  22. }
  23. //目录文件
  24. else if(S_ISDIR(st.st_mode))
  25. {
  26. }
  27. }


在这个例子中,如果是GET请求,服务器首先检查请求的文件是否存在。如果文件存在,就发送HTTP响应头,然后发送文件内容;如果文件不存在,就发送404错误页面。对于其他类型的请求(非GET请求),服务器返回501 Not Implemented的错误响应。

6.完整代码和项目包

webserver.c

  1. //web服务端程序--使用epoll模型
  2. #include <unistd.h>
  3. #include <sys/epoll.h>
  4. #include <fcntl.h>
  5. #include <sys/stat.h>
  6. #include <string.h>
  7. #include <signal.h>
  8. #include <dirent.h>
  9. #include "pub.h"
  10. #include "wrap.h"
  11. int http_request(int cfd);
  12. int main()
  13. {
  14. //改变当前进程的工作目录
  15. char path[255] = {0};
  16. sprintf(path, "%s/%s", getenv("HOME"), "webpath");
  17. chdir(path);
  18. //创建socket--设置端口复用---bind
  19. int lfd = tcp4bind(9999, NULL);
  20. //设置监听
  21. Listen(lfd, 128);
  22. //创建epoll树
  23. int epfd = epoll_create(1024);
  24. if(epfd<0)
  25. {
  26. perror("epoll_create error");
  27. close(lfd);
  28. return -1;
  29. }
  30. //将监听文件描述符lfd上树
  31. struct epoll_event ev;
  32. ev.data.fd = lfd;
  33. ev.events = EPOLLIN;
  34. epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
  35. int i;
  36. int cfd;
  37. int nready;
  38. int sockfd;
  39. struct epoll_event events[1024];
  40. while(1)
  41. {
  42. //等待事件发生
  43. nready = epoll_wait(epfd, events, 1024, -1);
  44. if(nready<0)
  45. {
  46. if(errno==EINTR)
  47. {
  48. continue;
  49. }
  50. break;
  51. }
  52. for(i=0; i<nready; i++)
  53. {
  54. sockfd = events[i].data.fd;
  55. //有客户端连接请求
  56. if(sockfd==lfd)
  57. {
  58. //接受新的客户端连接
  59. cfd = Accept(lfd, NULL, NULL);
  60. //设置cfd为非阻塞
  61. int flag = fcntl(cfd, F_GETFL);
  62. flag |= O_NONBLOCK;
  63. fcntl(cfd, F_SETFL, flag);
  64. //将新的cfd上树
  65. ev.data.fd = cfd;
  66. ev.events = EPOLLIN;
  67. epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
  68. }
  69. else
  70. {
  71. //有客户端数据发来
  72. http_request(cfd);
  73. }
  74. }
  75. }
  76. }
  77. int send_header(int cfd, char *code, char *msg, char *fileType, int len)
  78. {
  79. char buf[1024] = {0};
  80. sprintf(buf, "HTTP/1.1 %s %s\r\n", code, msg);
  81. sprintf(buf+strlen(buf), "Content-Type:%s\r\n", fileType);
  82. if(len>0)
  83. {
  84. sprintf(buf+strlen(buf), "Content-Length:%d\r\n", len);
  85. }
  86. strcat(buf, "\r\n");
  87. Write(cfd, buf, strlen(buf));
  88. return 0;
  89. }
  90. int send_file(int cfd, char *fileName)
  91. {
  92. //打开文件
  93. int fd = open(fileName, O_RDONLY);
  94. if(fd<0)
  95. {
  96. perror("open error");
  97. return -1;
  98. }
  99. //循环读文件, 然后发送
  100. int n;
  101. char buf[1024];
  102. while(1)
  103. {
  104. memset(buf, 0x00, sizeof(buf));
  105. n = read(fd, buf, sizeof(buf));
  106. if(n<=0)
  107. {
  108. break;
  109. }
  110. else
  111. {
  112. Write(cfd, buf, n);
  113. }
  114. }
  115. }
  116. int http_request(int cfd)
  117. {
  118. int n;
  119. char buf[1024];
  120. //读取请求行数据, 分析出要请求的资源文件名
  121. memset(buf, 0x00, sizeof(buf));
  122. Readline(cfd, buf, sizeof(buf));
  123. printf("buf==[%s]\n", buf);
  124. //GET /hanzi.c HTTP/1.1
  125. char reqType[16] = {0};
  126. char fileName[255] = {0};
  127. char protocal[16] = {0};
  128. sscanf(buf, "%[^ ] %[^ ] %[^ \r\n]", reqType, fileName, protocal);
  129. printf("[%s]\n", reqType);
  130. printf("[%s]\n", fileName);
  131. printf("[%s]\n", protocal);
  132. char *pFile = fileName+1;
  133. printf("[%s]\n", pFile);
  134. //循环读取完剩余的数据
  135. while((n=Readline(cfd, buf, sizeof(buf)))>0);
  136. //判断文件是否存在
  137. struct stat st;
  138. if(stat(pFile, &st)<0)
  139. {
  140. printf("file not exist\n");
  141. //发送头部信息
  142. send_header(cfd, "404", "NOT FOUND", get_mime_type(".html"), 0);
  143. //发送文件内容
  144. send_file(cfd, "error.html");
  145. }
  146. else //若文件存在
  147. {
  148. //判断文件类型
  149. //普通文件
  150. if(S_ISREG(st.st_mode))
  151. {
  152. printf("file exist\n");
  153. //发送头部信息
  154. send_header(cfd, "200", "OK", get_mime_type(pFile), st.st_size);
  155. //发送文件内容
  156. send_file(cfd, pFile);
  157. }
  158. //目录文件
  159. else if(S_ISDIR(st.st_mode))
  160. {
  161. }
  162. }
  163. }

本文用到了俩个库pub.h 和wrap.h 这俩个头文件

本文在提供了完整的代码包:https://download.csdn.net/download/qq_64691289/88547649

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/889282
推荐阅读
相关标签
  

闽ICP备14008679号