您的位置:首页 > 理论基础 > 计算机网络

tinyhttpd源码分析

2017-09-07 10:02 281 查看

http服务初始化:startup函数

startup函数用于初始化 httpd 服务,包括建立套接字,绑定端口,进行监听等**

通过socket函数建立socket,即资源分配。 socket()函数用于根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源。

地址族



数据类型

SOCK_STREAM 提供有序的、可靠的、双向的和基于连接的字节流,使用带外数据传送机制,为Internet地址族使用TCP。

SOCK_DGRAM 支持无连接的、不可靠的和使用固定大小(通常很小)缓冲区的数据报服务,为Internet地址族使用UDP.

资源与地址绑定:

sockaddr_in



AF_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/7090590

http://blog.csdn.net/suxinpingtao51/article/details/11809011

listen

http://blog.csdn.net/zhangzheng0413/article/details/8188967

int 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函数

接受客户端的连接,记录客户端信息,返回客户端的socket

http://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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: