tinyhttpd源码分析
2017-09-07 10:02
281 查看
http服务初始化:startup函数
startup函数用于初始化 httpd 服务,包括建立套接字,绑定端口,进行监听等**
通过socket函数建立socket,即资源分配。 socket()函数用于根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源。地址族
数据类型
SOCK_STREAM 提供有序的、可靠的、双向的和基于连接的字节流,使用带外数据传送机制,为Internet地址族使用TCP。
SOCK_DGRAM 支持无连接的、不可靠的和使用固定大小(通常很小)缓冲区的数据报服务,为Internet地址族使用UDP.
资源与地址绑定:
sockaddr_inAF_INET(又称 PF_INET)是 IPv4 网络协议的套接字类型,AF_INET6 则是 IPv6 的;而 AF_UNIX 则是 Unix 系统本地通信。
选择 AF_INET 的目的就是使用 IPv4 进行通信。因为 IPv4 使用 32 位地址,相比 IPv6 的 128 位来说,计算更快,便于用于局域网通信。
而且 AF_INET 相比 AF_UNIX 更具通用性,因为 Windows 上有 AF_INET 而没有 AF_UNIX。
bind函数
http://blog.csdn.net/david_xtd/article/details/7090590http://blog.csdn.net/suxinpingtao51/article/details/11809011
listen
http://blog.csdn.net/zhangzheng0413/article/details/8188967int startup(u_short *port) { int httpd = 0; //sockaddr_in 是 IPV4的套接字地址结构。定义在<netinet/in.h>,参读《TLPI》P1202 struct sockaddr_in name; //socket()用于创建一个用于 socket 的描述符,函数包含于<sys/socket.h>。参读《TLPI》P1153 //这里的PF_INET其实是与 AF_INET同义,具体可以参读《TLPI》P946 httpd = socket(PF_INET, SOCK_STREAM, 0); if (httpd == -1) error_die("socket"); memset(&name, 0, sizeof(name)); name.sin_family = AF_INET; //htons(),ntohs() 和 htonl()包含于<arpa/inet.h>, 参读《TLPI》P1199 //将*port 转换成 4000 以网络字节序表示的16位整数 name.sin_port = htons(*port); //INADDR_ANY是一个 IPV4通配地址的常量,包含于<netinet/in.h> //大多实现都将其定义成了0.0.0.0 参读《TLPI》P1187 name.sin_addr.s_addr = htonl(INADDR_ANY); //bind()用于绑定地址与 socket。参读《TLPI》P1153 //如果传进去的sockaddr结构中的 sin_port 指定为0,这时系统会选择一个临时的端口号 if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0) error_die("bind"); //如果调用 bind 后端口号仍然是0,则手动调用getsockname()获取端口号 if (*port == 0) /* if dynamically allocating a port */ { int namelen = sizeof(name); //getsockname()包含于<sys/socker.h>中,参读《TLPI》P1263 //调用getsockname()获取系统给 httpd 这个 socket 随机分配的端口号 if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1) error_die("getsockname"); *port = ntohs(name.sin_port); } //最初的 BSD socket 实现中,backlog 的上限是5.参读《TLPI》P1156 if (listen(httpd, 5) < 0) error_die("listen"); return(httpd); }
等待客户端的连接:accept函数
接受客户端的连接,记录客户端信息,返回客户端的sockethttp://www.360doc.com/content/13/0908/17/13253385_313070996.shtml
while (1) { //阻塞等待客户端的连接,参读《TLPI》P1157 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, accept_request, client_sock) != 0) perror("pthread_create");*/ }
处理客户端请求:
分析字符串,分析http请求方式,调用cgi处理程序。cgi:https://www.zhihu.com/question/19582041
管道通信:http://blog.csdn.net/jcjc918/article/details/42129311
void accept_request(int client) { char buf[1024]; int 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; //读http 请求的第一行数据(request line),把请求方法存进 method 中 numchars = get_line(client, buf, sizeof(buf)); i = 0; j = 0; while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) { method[i] = buf[j]; i++; j++; } method[i] = '\0'; //如果请求的方法不是 GET 或 POST 任意一个的话就直接发送 response 告诉客户端没实现该方法 if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) { unimplemented(client); return; } //如果是 POST 方法就将 cgi 标志变量置一(true) if (strcasecmp(method, "POST") == 0) cgi = 1; i = 0; //跳过所有的空白字符(空格) while (ISspace(buf[j]) && (j < sizeof(buf))) j++; //然后把 URL 读出来放到 url 数组中 while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) { url[i] = buf[j]; i++; j++; } url[i] = '\0'; //如果这个请求是一个 GET 方法的话 if (strcasecmp(method, "GET") == 0) { //用一个指针指向 url query_string = url; //去遍历这个 url,跳过字符 ?前面的所有字符,如果遍历完毕也没找到字符 ?则退出循环 while ((*query_string != '?') && (*query_string != '\0')) query_string++; //退出循环后检查当前的字符是 ?还是字符串(url)的结尾 if (*query_string == '?') { //如果是 ? 的话,证明这个请求需要调用 cgi,将 cgi 标志变量置一(true) cgi = 1; //从字符 ? 处把字符串 url 给分隔会两份 *query_string = '\0'; //使指针指向字符 ?后面的那个字符 query_string++; } } //将前面分隔两份的前面那份字符串,拼接在字符串htdocs的后面之后就输出存储到数组 path 中。相当于现在 path 中存储着一个字符串 sprintf(path, "htdocs%s", url); //如果 path 数组中的这个字符串的最后一个字符是以字符 / 结尾的话,就拼接上一个"index.html"的字符串。首页的意思 if (path[strlen(path) - 1] == '/') strcat(path, "index.html"); //在系统上去查询该文件是否存在 if (stat(path, &st) == -1) { //如果不存在,那把这次 http 的请求后续的内容(head 和 body)全部读完并忽略 while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ numchars = get_line(client, buf, sizeof(buf)); //然后返回一个找不到文件的 response 给客户端 not_found(client); } else { //文件存在,那去跟常量S_IFMT相与,相与之后的值可以用来判断该文件是什么类型的 //S_IFMT参读《TLPI》P281,与下面的三个常量一样是包含在<sys/stat.h> if ((st.st_mode & S_IFMT) == S_IFDIR) //如果这个文件是个目录,那就需要再在 path 后面拼接一个"/index.html"的字符串 strcat(path, "/index.html"); //S_IXUSR, S_IXGRP, S_IXOTH三者可以参读《TLPI》P295 if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH) ) //如果这个文件是一个可执行文件,不论是属于用户/组/其他这三者类型的,就将 cgi 标志变量置一 cgi = 1; if (!cgi) //如果不需要 cgi 机制的话, serve_file(client, path); else //如果需要则调用 execute_cgi(client, path, method, query_string); } close(client); }
相关文章推荐
- TinyHTTPd源码分析
- 源码分析之tinyhttpd(一)
- tinyhttpd源码分析
- 源码分析之tinyhttpd-0.1
- HTTP服务器的本质:tinyhttpd源码分析及拓展
- TinyHTTPd--超轻量型Http Server源码分析
- 兄弟连区块链教程btcpool矿池源码分析statshttpd模块解析
- tinyhttpd源码学习2
- Tinyhttpd源码学习(httpd.c)<四>
- 阅读源码:tinyhttpd
- http服务器雏形tinyhttpd源码
- Android作为HTTP服务器--NanoHTTPD源码分析
- mini-httpd源码分析-mini-httpd.c之外总结
- mini-httpd源码分析-tdate_parse.h
- Tiny210 U-BOOT(三)----配置时钟频率源码分析
- tinyhttp源码分析
- Tinyhttpd源码解析
- apache(httpd-2.2.14) mod_ssl源码分析之二(mod_ssl处理流程分析)
- NanoHttpd源码分析
- 【源码剖析】tinyhttpd —— C 语言实现最简单的 HTTP 服务器