├── htdocs ├── introduction.html ├── readme.md ├── index2.html ├── index.html ├── date.cgi ├── color.cgi ├── check.cgi ├── register.cgi └── register.html ├── Makefile ├── README.md ├── ThreadPool.h └── httpd.cpp /htdocs/introduction.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fltflt/Tinyhttpd_with_threadpool_epoll/HEAD/htdocs/introduction.html -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: httpd 2 | 3 | httpd: httpd.cpp 4 | g++ -W -Wall -o httpd httpd.cpp -lpthread 5 | 6 | clean: 7 | rm httpd 8 | -------------------------------------------------------------------------------- /htdocs/readme.md: -------------------------------------------------------------------------------- 1 | # 文件含义 2 | 3 | ## 动态页面请求 4 | (1) date.cgi与index2.html对应,用于显示当前时间 5 | 6 | (2) color.cgi与index.html对应,用于提交颜色,并显示这个颜色页面 7 | 8 | (3) register.cgi与register.html对应,用于显示当前时间 9 | 10 | ## 静态页面请求 11 | 12 | (1) introduction.html对应,用于显示个人资料 13 | -------------------------------------------------------------------------------- /htdocs/index2.html: -------------------------------------------------------------------------------- 1 | 2 | Index 3 | 4 |

TODAY is 5 |

CGI demo get current date 6 |
7 | Enter a color: 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /htdocs/index.html: -------------------------------------------------------------------------------- 1 | 2 | Index 3 | 4 |

Welcome to J. David's webserver. 5 |

CGI demo 6 |
7 | Enter a color: 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /htdocs/date.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Content-Type: text/html" 3 | echo 4 | echo "" 5 | echo "
Today is:
" 6 | echo "
" 7 | date 8 | echo "
" 9 | echo "" 10 | -------------------------------------------------------------------------------- /htdocs/color.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -Tw 2 | 3 | use strict; 4 | use CGI; 5 | 6 | my($cgi) = new CGI; 7 | 8 | print $cgi->header; 9 | my($color) = "blue"; 10 | $color = $cgi->param('color') if defined $cgi->param('color'); 11 | 12 | print $cgi->start_html(-title => uc($color), 13 | -BGCOLOR => $color); 14 | print $cgi->h1("This is $color"); 15 | print $cgi->end_html; 16 | -------------------------------------------------------------------------------- /htdocs/check.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -Tw 2 | 3 | use strict; 4 | use CGI; 5 | 6 | my($cgi) = new CGI; 7 | 8 | print $cgi->header('text/html'); 9 | print $cgi->start_html(-title => "Example CGI script", 10 | -BGCOLOR => 'red'); 11 | print $cgi->h1("CGI Example"); 12 | print $cgi->p, "This is an example of CGI\n"; 13 | print $cgi->p, "Parameters given to this script:\n"; 14 | print ""; 20 | print $cgi->end_html, "\n"; 21 | -------------------------------------------------------------------------------- /htdocs/register.cgi: -------------------------------------------------------------------------------- 1 | #!/share/home/flt/flt_data/python3/bin/python3.6 2 | #coding:utf-8 3 | import sys,os 4 | import urllib 5 | length = os.getenv('CONTENT_LENGTH') 6 | 7 | if length: 8 | postdata = sys.stdin.read(int(length)) 9 | print ("Content-type:text/html\n") 10 | print ('') 11 | print ('') 12 | print ('POST') 13 | print ('') 14 | print ('') 15 | print ('

POST data

') 16 | print ('') 20 | print ('') 21 | print ('') 22 | 23 | else: 24 | print ("Content-type:text/html\n") 25 | print ('no found') 26 | 27 | 28 | -------------------------------------------------------------------------------- /htdocs/register.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 注册信息 4 | 5 | 6 | 7 |
8 | 账号: 9 |
10 |
11 | 密码: 12 |
13 |
14 | 15 | 16 | 爱好:体育唱歌 17 |
18 |
19 | 性别:女 20 |
21 |
22 | 自我介绍:
23 | 26 |
27 |
28 | 地址: 29 | 34 |
35 |
36 | 37 | 38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 项目简介-Tinyhttpd_with_threadpool_epoll 2 | 3 | 本项目是基于Tinyhttpd扩展实现的web服务器,基于epoll事件驱动I/O,采用高效的Reactor模型+线程池进行客户端连接任务管理,支持高并发的Get与Post的http请求。 4 | 5 | # 项目构建方法 6 | 7 | 1. git clone https://github.com/fltflt/Tinyhttpd_with_threadpool_epoll.git 8 | 2. cd Tinyhttpd_with_threadpool_epoll 9 | 3. make 10 | 4. ./httpd 11 | 12 | # 主要技术(详见[我的csdn博客](https://blog.csdn.net/qq_39751437/article/details/105265301)) 13 | 14 | - 动态请求解析技术Cgi 15 | 16 | + Reactor模式 17 | 18 | * Epoll I/O多路复用技术 19 | 20 | - 线程池 21 | 22 | - Socket网络编程相关知识 23 | 24 | - http报文格式 25 | 26 | - http请求命令get/post 27 | 28 | + 进程通信(管道pipe) 29 | 30 | # 注意事项 31 | 32 | - 端口配置在httpd.cpp 默认为3000 33 | + HTTP请求页面为htdocs目录下的.html与.cgi后缀文件(可以手动添加) 34 | 35 | # 改进方向 36 | - 添加命令行解析 37 | + 增加定时器删除不常用连接 38 | - 内存池 39 | # 参考文章 40 | - [Tinyhttpd-for-linux 学习](https://github.com/fltflt/Tinyhttpd-for-linux) 41 | - [网络编程模式Reactor详解](https://blog.csdn.net/qq_39751437/article/details/105446909) 42 | - [项目--搭建web服务器与客户端](https://blog.csdn.net/qq_39751437/article/details/105265301) 43 | - [计算机网络与网络编程主要面试内容](https://blog.csdn.net/qq_39751437/article/details/104969909) 44 | - [C++实现的高并发web服务器](https://github.com/Fizell/webServer) 45 | - [如何实现一个服务器](https://zyearn.github.io/blog/2015/05/16/how-to-write-a-server/) 46 | - [高性能HTTP服务器:设计和思路](https://blog.csdn.net/qq_41111491/article/details/104288554) 47 | -------------------------------------------------------------------------------- /ThreadPool.h: -------------------------------------------------------------------------------- 1 | #ifndef THREAD_POOL_H 2 | #define THREAD_POOL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | class ThreadPool { 15 | public: 16 | ThreadPool(size_t); 17 | template 18 | auto enqueue(F&& f, Args&&... args) 19 | -> std::future::type>; 20 | ~ThreadPool(); 21 | private: 22 | // need to keep track of threads so we can join them 23 | std::vector< std::thread > workers; 24 | // the task queue 25 | std::queue< std::function > tasks; 26 | 27 | // synchronization 28 | std::mutex queue_mutex; 29 | std::condition_variable condition; 30 | bool stop; 31 | }; 32 | 33 | // the constructor just launches some amount of workers 34 | inline ThreadPool::ThreadPool(size_t threads) 35 | : stop(false) 36 | { 37 | for(size_t i = 0;i task; 44 | 45 | { 46 | std::unique_lock lock(this->queue_mutex); 47 | this->condition.wait(lock, 48 | [this]{ return this->stop || !this->tasks.empty(); }); 49 | if(this->stop && this->tasks.empty()) 50 | return; 51 | task = std::move(this->tasks.front()); 52 | this->tasks.pop(); 53 | } 54 | 55 | task(); 56 | } 57 | } 58 | ); 59 | } 60 | 61 | // add new work item to the pool 62 | template 63 | auto ThreadPool::enqueue(F&& f, Args&&... args) 64 | -> std::future::type> 65 | { 66 | using return_type = typename std::result_of::type; 67 | 68 | auto task = std::make_shared< std::packaged_task >( 69 | std::bind(std::forward(f), std::forward(args)...) 70 | ); 71 | 72 | std::future res = task->get_future(); 73 | { 74 | std::unique_lock lock(queue_mutex); 75 | 76 | // don't allow enqueueing after stopping the pool 77 | if(stop) 78 | throw std::runtime_error("enqueue on stopped ThreadPool"); 79 | 80 | tasks.emplace([task](){ (*task)(); }); 81 | } 82 | condition.notify_one(); 83 | return res; 84 | } 85 | 86 | // the destructor joins all threads 87 | inline ThreadPool::~ThreadPool() 88 | { 89 | { 90 | std::unique_lock lock(queue_mutex); 91 | stop = true; 92 | } 93 | condition.notify_all(); 94 | for(std::thread &worker: workers) 95 | worker.join(); 96 | } 97 | 98 | #endif 99 | -------------------------------------------------------------------------------- /httpd.cpp: -------------------------------------------------------------------------------- 1 | /* J. David's webserver */ 2 | /* This is a simple webserver. 3 | * Created November 1999 by J. David Blackstone. 4 | * CSE 4344 (Network concepts), Prof. Zeigler 5 | * University of Texas at Arlington 6 | */ 7 | /* This program compiles for Sparc Solaris 2.6. 8 | * To compile for Linux: 9 | * 1) Comment out the #include line. 10 | * 2) Comment out the line that defines the variable newthread. 11 | * 3) Comment out the two lines that run pthread_create(). 12 | * 4) Uncomment the line that runs accept_request(). 13 | * 5) Remove -lsocket from the Makefile. 14 | */ 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "ThreadPool.h" 30 | #include 31 | using namespace std; 32 | 33 | #define ISspace(x) isspace((int)(x)) 34 | //函数说明:检查参数c是否为空格字符, 35 | //也就是判断是否为空格(' ')、定位字符(' \t ')、CR(' \r ')、换行(' \n ')、垂直定位字符(' \v ')或翻页(' \f ')的情况。 36 | //返回值:若参数c 为空白字符,则返回非 0,否则返回 0。 37 | 38 | 39 | #define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"//定义server名称 40 | 41 | //void accept_request(int);//接收请求 42 | void *accept_request(void* client); 43 | void bad_request(int);//无效请求 44 | void cat(int, FILE *); 45 | void cannot_execute(int); 46 | void error_die(const char *); 47 | void execute_cgi(int, const char *, const char *, const char *); 48 | int get_line(int, char *, int); 49 | void headers(int, const char *); 50 | void not_found(int); 51 | void serve_file(int, const char *); 52 | int startup(u_short *); 53 | void unimplemented(int); 54 | 55 | /**********************************************************************/ 56 | /* A request has caused a call to accept() on the server port to 57 | * return. Process the request appropriately. 58 | * Parameters: the socket connected to the client */ 59 | /**********************************************************************/ 60 | //接收客户端的连接,并读取请求数据 61 | void *accept_request(void* from_client) 62 | { 63 | int client = *(int *)from_client; 64 | char buf[1024]; 65 | int numchars; 66 | char method[255]; 67 | char url[255]; 68 | char path[512]; 69 | size_t i, j; 70 | struct stat st; 71 | int cgi = 0; /* becomes true if server decides this is a CGI 72 | * program */ 73 | char *query_string = NULL; 74 | //获取一行HTTP报文数据 75 | numchars = get_line(client, buf, sizeof(buf)); 76 | // 77 | i = 0; j = 0; 78 | //对于HTTP报文来说,第一行的内容即为报文的起始行,格式为 , 79 | //每个字段用空白字符相连 80 | while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) 81 | { 82 | //提取其中的请求方式是GET还是POST 83 | method[i] = buf[j]; 84 | i++; j++; 85 | } 86 | method[i] = '\0'; 87 | //函数说明:strcasecmp()用来比较参数s1 和s2 字符串,比较时会自动忽略大小写的差异。 88 | //返回值:若参数s1 和s2 字符串相同则返回0。s1 长度大于s2 长度则返回大于0 的值,s1 长度若小于s2 长度则返回小于0 的值。 89 | if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) 90 | { 91 | //tinyhttp仅仅实现了GET和POST 92 | unimplemented(client); 93 | return NULL; 94 | } 95 | //cgi为标志位,置1说明开启cgi解析 96 | if (strcasecmp(method, "POST") == 0) 97 | //如果请求方法为POST,需要cgi解析 98 | cgi = 1; 99 | 100 | i = 0; 101 | //将method后面的后边的空白字符略过 102 | while (ISspace(buf[j]) && (j < sizeof(buf))) 103 | j++; 104 | //继续读取request-URL 105 | while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) 106 | { 107 | url[i] = buf[j]; 108 | i++; j++; 109 | } 110 | url[i] = '\0'; 111 | //如果是GET请求,url可能会带有?,有查询参数 112 | if (strcasecmp(method, "GET") == 0) 113 | { 114 | query_string = url; 115 | while ((*query_string != '?') && (*query_string != '\0')) 116 | query_string++; 117 | if (*query_string == '?') 118 | { 119 | //如果带有查询参数,需要执行cgi,解析参数,设置标志位为1 120 | cgi = 1; 121 | //将解析参数截取下来 122 | *query_string = '\0'; 123 | query_string++; 124 | } 125 | } 126 | //以上已经将起始行解析完毕 127 | //url中的路径格式化到path 128 | sprintf(path, "htdocs%s", url); 129 | //学习到这里明天继续TODO 130 | //如果path只是一个目录,默认设置为首页index.html 131 | if (path[strlen(path) - 1] == '/') 132 | strcat(path, "index.html"); 133 | 134 | //函数定义: int stat(const char *file_name, struct stat *buf); 135 | //函数说明: 通过文件名filename获取文件信息,并保存在buf所指的结构体stat中 136 | //返回值: 执行成功则返回0,失败返回-1,错误代码存于errno(需要include ) 137 | if (stat(path, &st) == -1) { 138 | //假如访问的网页不存在,则不断的读取剩下的请求头信息,并丢弃即可 139 | while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ 140 | numchars = get_line(client, buf, sizeof(buf)); 141 | //最后声明网页不存在 142 | not_found(client); 143 | } 144 | else 145 | { 146 | //如果访问的网页存在则进行处理 147 | if ((st.st_mode & S_IFMT) == S_IFDIR)//S_IFDIR代表目录 148 | //如果路径是个目录,那就将主页进行显示 149 | strcat(path, "/index.html"); 150 | if ((st.st_mode & S_IXUSR) || 151 | (st.st_mode & S_IXGRP) || 152 | (st.st_mode & S_IXOTH) ) 153 | //S_IXUSR:文件所有者具可执行权限 154 | //S_IXGRP:用户组具可执行权限 155 | //S_IXOTH:其他用户具可读取权限 156 | cgi = 1; 157 | if (!cgi) 158 | //将静态文件返回 159 | serve_file(client, path); 160 | else 161 | //执行cgi动态解析 162 | execute_cgi(client, path, method, query_string); 163 | 164 | } 165 | 166 | close(client);//因为http是面向无连接的,所以要关闭 167 | 168 | return NULL; 169 | } 170 | 171 | /**********************************************************************/ 172 | /* Inform the client that a request it has made has a problem. 173 | * Parameters: client socket */ 174 | /**********************************************************************/ 175 | void bad_request(int client) 176 | { 177 | char buf[1024]; 178 | //发送400 179 | sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n"); 180 | send(client, buf, sizeof(buf), 0); 181 | sprintf(buf, "Content-type: text/html\r\n"); 182 | send(client, buf, sizeof(buf), 0); 183 | sprintf(buf, "\r\n"); 184 | send(client, buf, sizeof(buf), 0); 185 | sprintf(buf, "

Your browser sent a bad request, "); 186 | send(client, buf, sizeof(buf), 0); 187 | sprintf(buf, "such as a POST without a Content-Length.\r\n"); 188 | send(client, buf, sizeof(buf), 0); 189 | } 190 | 191 | /**********************************************************************/ 192 | /* Put the entire contents of a file out on a socket. This function 193 | * is named after the UNIX "cat" command, because it might have been 194 | * easier just to do something like pipe, fork, and exec("cat"). 195 | * Parameters: the client socket descriptor 196 | * FILE pointer for the file to cat */ 197 | /**********************************************************************/ 198 | void cat(int client, FILE *resource) 199 | { 200 | //发送文件的内容 201 | char buf[1024]; 202 | //读取文件到buf中 203 | fgets(buf, sizeof(buf), resource); 204 | while (!feof(resource))//判断文件是否读取到末尾 205 | { 206 | //读取并发送文件内容 207 | send(client, buf, strlen(buf), 0); 208 | fgets(buf, sizeof(buf), resource); 209 | } 210 | } 211 | 212 | /**********************************************************************/ 213 | /* Inform the client that a CGI script could not be executed. 214 | * Parameter: the client socket descriptor. */ 215 | /**********************************************************************/ 216 | void cannot_execute(int client) 217 | { 218 | char buf[1024]; 219 | //发送500 220 | sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n"); 221 | send(client, buf, strlen(buf), 0); 222 | sprintf(buf, "Content-type: text/html\r\n"); 223 | send(client, buf, strlen(buf), 0); 224 | sprintf(buf, "\r\n"); 225 | send(client, buf, strlen(buf), 0); 226 | sprintf(buf, "

Error prohibited CGI execution.\r\n"); 227 | send(client, buf, strlen(buf), 0); 228 | } 229 | 230 | /**********************************************************************/ 231 | /* Print out an error message with perror() (for system errors; based 232 | * on value of errno, which indicates system call errors) and exit the 233 | * program indicating an error. */ 234 | /**********************************************************************/ 235 | void error_die(const char *sc) 236 | { 237 | perror(sc); 238 | exit(1); 239 | } 240 | 241 | /**********************************************************************/ 242 | /* Execute a CGI script. Will need to set environment variables as 243 | * appropriate. 244 | * Parameters: client socket descriptor 245 | * path to the CGI script */ 246 | /**********************************************************************/ 247 | //执行cgi动态解析 248 | void execute_cgi(int client, const char *path, 249 | const char *method, const char *query_string) 250 | { 251 | char buf[1024]; 252 | int cgi_output[2];//声明的读写管道,切莫被名称给忽悠,会给出图进行说明 253 | int cgi_input[2];// 254 | pid_t pid; 255 | int status; 256 | int i; 257 | char c; 258 | int numchars = 1; 259 | int content_length = -1; 260 | 261 | buf[0] = 'A'; buf[1] = '\0'; 262 | if (strcasecmp(method, "GET") == 0) 263 | //如果是GET请求 264 | //读取并且丢弃头信息 265 | while ((numchars > 0) && strcmp("\n", buf)) 266 | numchars = get_line(client, buf, sizeof(buf)); 267 | else 268 | { 269 | //处理的请求为POST 270 | numchars = get_line(client, buf, sizeof(buf)); 271 | while ((numchars > 0) && strcmp("\n", buf)) 272 | {//循环读取头信息找到Content-Length字段的值 273 | buf[15] = '\0';//目的是为了截取Content-Length: 274 | 275 | if (strcasecmp(buf, "Content-Length:") == 0) 276 | //"Content-Length: 15" 277 | content_length = atoi(&(buf[16]));//获取Content-Length的值 278 | numchars = get_line(client, buf, sizeof(buf)); 279 | } 280 | if (content_length == -1) { 281 | //错误请求 282 | bad_request(client); 283 | return; 284 | } 285 | } 286 | //返回正确响应码200 287 | sprintf(buf, "HTTP/1.0 200 OK\r\n"); 288 | send(client, buf, strlen(buf), 0); 289 | //#include 290 | //int pipe(int filedes[2]); 291 | //返回值:成功,返回0,否则返回-1。参数数组包含pipe使用的两个文件的描述符。fd[0]:读管道,fd[1]:写管道。 292 | //必须在fork()中调用pipe(),否则子进程不会继承文件描述符。 293 | //两个进程不共享祖先进程,就不能使用pipe。但是可以使用命名管道。 294 | //pipe(cgi_output)执行成功后,cgi_output[0]:读通道 cgi_output[1]:写通道,这就是为什么说不要被名称所迷惑 295 | if (pipe(cgi_output) < 0) { 296 | cannot_execute(client); 297 | return; 298 | } 299 | if (pipe(cgi_input) < 0) { 300 | cannot_execute(client); 301 | return; 302 | } 303 | 304 | if ( (pid = fork()) < 0 ) { 305 | cannot_execute(client); 306 | return; 307 | } 308 | //fork出一个子进程运行cgi脚本 309 | if (pid == 0) /* 子进程: 运行CGI 脚本 */ 310 | { 311 | char meth_env[255]; 312 | char query_env[255]; 313 | char length_env[255]; 314 | 315 | dup2(cgi_output[1], 1);//1代表着stdout,0代表着stdin,将系统标准输出重定向为cgi_output[1] 316 | dup2(cgi_input[0], 0);//将系统标准输入重定向为cgi_input[0],这一点非常关键, 317 | //cgi程序中用的是标准输入输出进行交互 318 | close(cgi_output[0]);//关闭了cgi_output中的读通道 319 | close(cgi_input[1]);//关闭了cgi_input中的写通道 320 | //CGI标准需要将请求的方法存储环境变量中,然后和cgi脚本进行交互 321 | //存储REQUEST_METHOD 322 | sprintf(meth_env, "REQUEST_METHOD=%s", method); 323 | putenv(meth_env); 324 | if (strcasecmp(method, "GET") == 0) { 325 | //存储QUERY_STRING 326 | sprintf(query_env, "QUERY_STRING=%s", query_string); 327 | putenv(query_env); 328 | } 329 | else { /* POST */ 330 | //存储CONTENT_LENGTH 331 | sprintf(length_env, "CONTENT_LENGTH=%d", content_length); 332 | putenv(length_env); 333 | } 334 | // 表头文件#include 335 | // 定义函数 336 | // int execl(const char * path,const char * arg,....); 337 | // 函数说明 338 | // execl()用来执行参数path字符串所代表的文件路径,接下来的参数代表执行该文件时传递过去的argv(0)、argv[1]……,最后一个参数必须用空指针(NULL)作结束。 339 | // 返回值 340 | // 如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。 341 | execl(path, path, NULL);//执行CGI脚本 342 | exit(0); 343 | } else { /* 父进程 */ 344 | close(cgi_output[1]);//关闭了cgi_output中的写通道,注意这是父进程中cgi_output变量和子进程要区分开 345 | close(cgi_input[0]);//关闭了cgi_input中的读通道 346 | if (strcasecmp(method, "POST") == 0) 347 | for (i = 0; i < content_length; i++) { 348 | //开始读取POST中的内容 349 | recv(client, &c, 1, 0); 350 | //将数据发送给cgi脚本 351 | write(cgi_input[1], &c, 1); 352 | } 353 | //读取cgi脚本返回数据 354 | while (read(cgi_output[0], &c, 1) > 0) 355 | //发送给浏览器 356 | send(client, &c, 1, 0); 357 | //运行结束关闭 358 | close(cgi_output[0]); 359 | close(cgi_input[1]); 360 | //定义函数:pid_t waitpid(pid_t pid, int * status, int options); 361 | //函数说明:waitpid()会暂时停止目前进程的执行, 直到有信号来到或子进程结束. 362 | //如果在调用wait()时子进程已经结束, 则wait()会立即返回子进程结束状态值. 子进程的结束状态值会由参数status 返回, 363 | //而子进程的进程识别码也会一快返回. 364 | //如果不在意结束状态值, 则参数status 可以设成NULL. 参数pid 为欲等待的子进程识别码, 其他数值意义如下: 365 | //1、pid<-1 等待进程组识别码为pid 绝对值的任何子进程. 366 | //2、pid=-1 等待任何子进程, 相当于wait(). 367 | //3、pid=0 等待进程组识别码与目前进程相同的任何子进程. 368 | //4、pid>0 等待任何子进程识别码为pid 的子进程. 369 | waitpid(pid, &status, 0); 370 | } 371 | } 372 | 373 | /**********************************************************************/ 374 | /* Get a line from a socket, whether the line ends in a newline, 375 | * carriage return, or a CRLF combination. Terminates the string read 376 | * with a null character. If no newline indicator is found before the 377 | * end of the buffer, the string is terminated with a null. If any of 378 | * the above three line terminators is read, the last character of the 379 | * string will be a linefeed and the string will be terminated with a 380 | * null character. 381 | * Parameters: the socket descriptor 382 | * the buffer to save the data in 383 | * the size of the buffer 384 | * Returns: the number of bytes stored (excluding null) */ 385 | /**********************************************************************/ 386 | //解析一行http报文 387 | int get_line(int sock, char *buf, int size) 388 | { 389 | int i = 0; 390 | char c = '\0'; 391 | int n; 392 | 393 | while ((i < size - 1) && (c != '\n')) 394 | { 395 | n = recv(sock, &c, 1, 0); 396 | /* DEBUG printf("%02X\n", c); */ 397 | if (n > 0) 398 | { 399 | if (c == '\r') 400 | { 401 | n = recv(sock, &c, 1, MSG_PEEK); 402 | /* DEBUG printf("%02X\n", c); */ 403 | if ((n > 0) && (c == '\n')) 404 | recv(sock, &c, 1, 0); 405 | else 406 | c = '\n'; 407 | } 408 | buf[i] = c; 409 | i++; 410 | } 411 | else 412 | c = '\n'; 413 | } 414 | buf[i] = '\0'; 415 | 416 | return(i); 417 | } 418 | 419 | /**********************************************************************/ 420 | /* Return the informational HTTP headers about a file. */ 421 | /* Parameters: the socket to print the headers on 422 | * the name of the file */ 423 | /**********************************************************************/ 424 | void headers(int client, const char *filename) 425 | { 426 | char buf[1024]; 427 | (void)filename; /* could use filename to determine file type */ 428 | //发送HTTP头 429 | strcpy(buf, "HTTP/1.0 200 OK\r\n"); 430 | send(client, buf, strlen(buf), 0); 431 | strcpy(buf, SERVER_STRING); 432 | send(client, buf, strlen(buf), 0); 433 | sprintf(buf, "Content-Type: text/html\r\n"); 434 | send(client, buf, strlen(buf), 0); 435 | strcpy(buf, "\r\n"); 436 | send(client, buf, strlen(buf), 0); 437 | } 438 | 439 | /**********************************************************************/ 440 | /* Give a client a 404 not found status message. */ 441 | /**********************************************************************/ 442 | void not_found(int client) 443 | { 444 | char buf[1024]; 445 | //返回404 446 | sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n"); 447 | send(client, buf, strlen(buf), 0); 448 | sprintf(buf, SERVER_STRING); 449 | send(client, buf, strlen(buf), 0); 450 | sprintf(buf, "Content-Type: text/html\r\n"); 451 | send(client, buf, strlen(buf), 0); 452 | sprintf(buf, "\r\n"); 453 | send(client, buf, strlen(buf), 0); 454 | sprintf(buf, "Not Found\r\n"); 455 | send(client, buf, strlen(buf), 0); 456 | sprintf(buf, "

The server could not fulfill\r\n"); 457 | send(client, buf, strlen(buf), 0); 458 | sprintf(buf, "your request because the resource specified\r\n"); 459 | send(client, buf, strlen(buf), 0); 460 | sprintf(buf, "is unavailable or nonexistent.\r\n"); 461 | send(client, buf, strlen(buf), 0); 462 | sprintf(buf, "\r\n"); 463 | send(client, buf, strlen(buf), 0); 464 | } 465 | 466 | /**********************************************************************/ 467 | /* Send a regular file to the client. Use headers, and report 468 | * errors to client if they occur. 469 | * Parameters: a pointer to a file structure produced from the socket 470 | * file descriptor 471 | * the name of the file to serve */ 472 | /**********************************************************************/ 473 | //将请求的文件发送回浏览器客户端 474 | void serve_file(int client, const char *filename) 475 | { 476 | FILE *resource = NULL; 477 | int numchars = 1; 478 | char buf[1024]; 479 | buf[0] = 'A'; buf[1] = '\0';//这个赋值不清楚是干什么的 480 | while ((numchars > 0) && strcmp("\n", buf)) //将HTTP请求头读取并丢弃 481 | numchars = get_line(client, buf, sizeof(buf)); 482 | //打开文件 483 | resource = fopen(filename, "r"); 484 | if (resource == NULL) 485 | //如果文件不存在,则返回not_found 486 | not_found(client); 487 | else 488 | { 489 | //添加HTTP头 490 | headers(client, filename); 491 | //并发送文件内容 492 | cat(client, resource); 493 | } 494 | fclose(resource);//关闭文件句柄 495 | } 496 | 497 | /**********************************************************************/ 498 | /* This function starts the process of listening for web connections 499 | * on a specified port. If the port is 0, then dynamically allocate a 500 | * port and modify the original port variable to reflect the actual 501 | * port. 502 | * Parameters: pointer to variable containing the port to connect on 503 | * Returns: the socket */ 504 | /**********************************************************************/ 505 | //启动服务端 506 | int startup(u_short *port) 507 | { 508 | 509 | //服务器处理步骤 510 | /* 511 | 第一步:创建端口号,使用函数int socketid = socket(family, type, protocol);; 512 | 第二步:将地址结构体绑定在端口号上,使用函数int bind(int fd, const struct sockaddr *, socklen_t); 513 | 第三步:循环监听客户端请求,使用函数listen(sockfd, 5);(第一个参数是 socket 描述符,第二个参数是最大连接数) 514 | 515 | 第四步:接受客户端的请求,new_socket = accept(socket_desc, (struct sockaddr *)&client, (socklen_t*)&c); 516 | 第五步:使用read()/write()进行数据交换 517 | 第六步:关闭连接,使用close(socketid); 518 | */ 519 | 520 | 521 | //客户端处理步骤 522 | /* 523 | 第一步:创建端口号,使用函数int socketid = socket(family, type, protocol); 524 | 第二步:将地址结构体绑定在端口号上,使用函数int bind(int fd, const struct sockaddr *, socklen_t); 525 | 第三步:请求连接,使用函数int connect(int socket, const struct sockaddr* address, size_t address_len); 526 | 527 | 第四步:使用read()/write()进行数据交换; 528 | 第五步:关闭连接,使用close(socketid); 529 | */ 530 | int httpd = 0; 531 | struct sockaddr_in name; 532 | //设置http socket 533 | httpd = socket(PF_INET, SOCK_STREAM, 0); 534 | if (httpd == -1) 535 | error_die("socket"); 536 | memset(&name, 0, sizeof(name)); 537 | 538 | //创建地址结构体struct sockaddr_in,主要包含地址和端口号 539 | name.sin_family = AF_INET; 540 | name.sin_port = htons(*port); 541 | name.sin_addr.s_addr = htonl(INADDR_ANY); 542 | 543 | 544 | //绑定端口 545 | if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0) 546 | error_die("bind"); 547 | if (*port == 0) /*动态分配一个端口 */ 548 | { 549 | socklen_t namelen = sizeof(name); 550 | if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1) 551 | error_die("getsockname"); 552 | *port = ntohs(name.sin_port); 553 | } 554 | //监听连接 555 | if (listen(httpd, 5) < 0) 556 | error_die("listen"); 557 | return(httpd); 558 | } 559 | 560 | /**********************************************************************/ 561 | /* Inform the client that the requested web method has not been 562 | * implemented. 563 | * Parameter: the client socket */ 564 | /**********************************************************************/ 565 | void unimplemented(int client) 566 | { 567 | char buf[1024]; 568 | //发送501说明相应方法没有实现 569 | sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n"); 570 | send(client, buf, strlen(buf), 0); 571 | sprintf(buf, SERVER_STRING); 572 | send(client, buf, strlen(buf), 0); 573 | sprintf(buf, "Content-Type: text/html\r\n"); 574 | send(client, buf, strlen(buf), 0); 575 | sprintf(buf, "\r\n"); 576 | send(client, buf, strlen(buf), 0); 577 | sprintf(buf, "Method Not Implemented\r\n"); 578 | send(client, buf, strlen(buf), 0); 579 | sprintf(buf, "\r\n"); 580 | send(client, buf, strlen(buf), 0); 581 | sprintf(buf, "

HTTP request method not supported.\r\n"); 582 | send(client, buf, strlen(buf), 0); 583 | sprintf(buf, "\r\n"); 584 | send(client, buf, strlen(buf), 0); 585 | } 586 | 587 | /**********************************************************************/ 588 | 589 | int main(void) 590 | { 591 | int server_sock = -1; 592 | u_short port = 3000; 593 | int connfd_fd = -1; 594 | struct sockaddr_in client_name; 595 | socklen_t client_name_len = sizeof(client_name); 596 | 597 | 598 | //创建线程池 599 | ThreadPool pool(4); 600 | printf("线程池创建成功!\n"); 601 | //pthread_t newthread; 602 | //启动server socket 603 | server_sock = startup(&port); 604 | 605 | 606 | //创建epoll事件 607 | int epfd, nfds; 608 | //生成用于处理accept的epoll专用的文件描述符 609 | epfd=epoll_create(5); 610 | struct epoll_event ev,events[20]; 611 | ev.data.fd = server_sock; 612 | //设置要处理的事件类型 613 | ev.events=EPOLLIN|EPOLLET; 614 | //注册epoll事件 615 | epoll_ctl(epfd,EPOLL_CTL_ADD,server_sock,&ev); 616 | 617 | printf("httpd running on port:%d\n", port); 618 | 619 | 620 | 621 | 622 | for ( ; ; ) 623 | { 624 | //等待epoll事件的发生 625 | nfds = epoll_wait(epfd,events,20,500); 626 | //处理所发生的所有事件 627 | for (int i = 0; i < nfds; ++i) 628 | { 629 | if (events[i].data.fd == server_sock)//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。 630 | { 631 | connfd_fd = accept(server_sock,(sockaddr *)&client_name, &client_name_len); 632 | if (connfd_fd < 0) 633 | { 634 | perror("connfd_fd < 0"); 635 | exit(1); 636 | } 637 | char *str = inet_ntoa(client_name.sin_addr); 638 | cout << "accapt a connection from " << str << endl; 639 | 640 | 641 | ev.data.fd = connfd_fd; 642 | //设置用于注测的读操作事件 643 | ev.events = EPOLLIN|EPOLLET; 644 | //注册ev 645 | epoll_ctl(epfd,EPOLL_CTL_ADD,connfd_fd,&ev); 646 | } 647 | else if (events[i].events&EPOLLIN)//如果是已经连接的用户,并且收到数据,那么进行读入。 648 | { 649 | std::cout<<"start worker thread ID:"<