Tinyhttpd阅读笔记
1、简介
tinyhttpd是一个开源的超轻量型Http Server,阅读其源码,可以对http协议,微型服务器有进一步的了解。
源码链接;
参考博客:tinyhttpd源码分析
2、笔记
------------------------------------------------------2016年5月7日21:31:35----------------------------------------------------
函数原型目录,列举出整个项目所要用到的所有函数原型
void accept_request(void *); void bad_request(int); void cat(int, FILE *); void cannot_execute(int); void error_die(const char *); void execute_cgi(int, const char *, const char *, const char *); int get_line(int, char *, int); void headers(int, const char *); void not_found(int); void serve_file(int, const char *); int startup(u_short *); void unimplemented(int);
每个函数的作用在项目的readme文件中有简单的描述:
函数 | 描述 |
accept_request | 处理从套接字上监听到的一个 HTTP 请求,在这里可以很大一部分地体现服务器处理请求流程 |
bad_request | 返回给客户端这是个错误请求,HTTP 状态吗 400 BAD REQUEST |
cat | 读取服务器上某个文件写到 socket 套接字 |
cannot_execute | 主要处理发生在执行 cgi 程序时出现的错误 |
error_die | 把错误信息写到 perror 并退出 |
execute_cgi | 运行 cgi 程序的处理,也是个主要函数 |
get_line | 读取套接字的一行,把回车换行等情况都统一为换行符结束 |
headers | 把 HTTP 响应的头部写到套接字 |
not_found | 主要处理找不到请求的文件时的情况 |
sever_file | 调用 cat 把服务器文件返回给浏览器 |
startup | 初始化 httpd 服务,包括建立套接字,绑定端口,进行监听等 |
unimplemented | 返回给浏览器表明收到的 HTTP 请求所用的 method 不被支持 |
建议的源码阅读顺序:
main--->startup--->accept_request--->execute_cgi
按照这个顺序理清整个服务器工作流程。
main函数
int main(void) { int server_sock = -1; u_short port = 4000; int client_sock = -1; struct sockaddr_in client_name; socklen_t client_name_len = sizeof(client_name); pthread_t newthread; server_sock = startup(&port); printf("httpd running on port %d\n", port); while (1) { client_sock = accept(server_sock, (struct sockaddr *)&client_name, &client_name_len); if (client_sock == -1) error_die("accept"); /* accept_request(client_sock); */ if (pthread_create(&newthread , NULL, (void *)accept_request, (void *)&client_sock) != 0) perror("pthread_create"); } close(server_sock); return(0); }
第3-7行是socket相关变量定义,有关socket的详细介绍可以上网查阅相关资料或者查看书籍《Unix网络编程》;
第10行调用startup函数,有关该函数的内容后续简要解释;
第13-23行,监听客户端向服务端发来的请求连接,当接收一个连接之后,创建一个线程执行相应的请求,线程入口函数是accept_request,另外有关Linux中pthread_create的使用需要注意一些细节问题。
第25行关闭服务端的套接字。
startup函数
函数参数是一个端口号,用于创建对应端口号的tcp套接字
int startup(u_short *port) { int httpd = 0; struct sockaddr_in name; httpd = socket(PF_INET, SOCK_STREAM, 0); if (httpd == -1) error_die("socket"); memset(&name, 0, sizeof(name)); name.sin_family = AF_INET; name.sin_port = htons(*port); name.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0) error_die("bind"); if (*port == 0) /* if dynamically allocating a port */ { socklen_t namelen = sizeof(name); if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1) error_die("getsockname"); *port = ntohs(name.sin_port); } if (listen(httpd, 5) < 0) error_die("listen"); return(httpd); }
第6行创建一个socket套接字,由于其参数是SOCK_STREAM,因此创建的是tcp套接字;
第7-8行,如果套接字创建不成功,则调用封装好的错误处理函数error_die,该函数比较简单,就是打印错误信息并结束整个程序。
第13-23则是创建socket服务端的基本流程,先是bind操作,接下来listen操作;
注意第15行,如果port为0,表示端口号是通过系统自动分配的,需要通过getsockname获取,有关这方面的知识查看《Unix网络编程》书籍
Startup函数执行完成后,服务端的准备工作完成,将进入监听状态,等待客户端的连接,通过accept函数判断是否有客户端请求连接,如果有,服务端创建一个新的线程执行客户请求,线程的入口函数是accept_request;
accept_request函数
该函数的参数是个void*,实际传递给函数的参数是一个套接字描述符。
void accept_request(void *arg) { int client = *(int*)arg; char buf[1024]; size_t numchars; char method[255]; char url[255]; char path[512]; size_t i, j; struct stat st; int cgi = 0; /* becomes true if server decides this is a CGI * program */ char *query_string = NULL; numchars = get_line(client, buf, sizeof(buf)); i = 0; j = 0; while (!ISspace(buf[i]) && (i < sizeof(method) - 1)) { method[i] = buf[i]; i++; } j=i; method[i] = '\0'; if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) { unimplemented(client); return; } if (strcasecmp(method, "POST") == 0) cgi = 1; i = 0; while (ISspace(buf[j]) && (j < numchars)) j++; while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < numchars)) { url[i] = buf[j]; i++; j++; } url[i] = '\0'; if (strcasecmp(method, "GET") == 0) { query_string = url; while ((*query_string != '?') && (*query_string != '\0')) query_string++; if (*query_string == '?') { cgi = 1; *query_string = '\0'; query_string++; } } sprintf(path, "htdocs%s", url); if (path[strlen(path) - 1] == '/') strcat(path, "index.html"); if (stat(path, &st) == -1) { while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ numchars = get_line(client, buf, sizeof(buf)); not_found(client); } else { if ((st.st_mode & S_IFMT) == S_IFDIR) strcat(path, "/index.html"); if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH) ) cgi = 1; if (!cgi) serve_file(client, path); else execute_cgi(client, path, method, query_string); } close(client); }
第3-13行,变量说明部分;
第15行,通过get_line函数,从指定的套接字描述符的缓存中读取一行数据,数据长度为size,但是如果遇到'\n',函数直接返回,也就是这一行内容最多有size字节,函数返回实际读取到的字节数。
第17-23行,获取http请求头部;理解这一部分需要对http协议报文格式有所了解
- 无线传感器网络原理
- HTTP各类状态码汇总
- python网络应用入门:网络爬虫的使用
- http响应状态
- VMware + Ubuntu16.04 网络无法使用问题
- Web前端性能优化(二)使用内容分发网络
- 07 网络通讯(Net)
- Web前端性能优化(一)减少Http请求
- android 6.0 SDK中删除HttpClient的相关类的解决方法
- Servlet的HttpServletResponse输出
- HTML中<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">理解
- 初识HTTP协议
- HTTP 请求:GET vs. POST
- Android网络编程要学的东西与Http协议学习
- 判断网络类型
- [Java]一个TCP文本上传相关的异常处理和偶然引出的中文编码问题
- TCPdump抓包命令详解
- iOS 9.0以后网络请求异常
- Android Volley完全解析(二),使用Volley加载网络图片
- No mapping found for HTTP request with URI [/DZCX_Manage/host/toHostMsg.action