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

Tinyhttpd阅读笔记

2016-05-07 21:46 549 查看

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协议报文格式有所了解

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: