C语言学习之Tinyhttpd

前言

学完C语言语法之后,没有方向。网上搜索,搜索到tinyhttpd是超轻量型Http server, 使用C语言开发,全部代码只有502行(包括注释),附带一个简单的Client,可以通过阅读这段代码理解一个 Http Server 的本质。

但是发现根本看不懂,或者不知道从哪里入手。

还是要学下去的。

下载源码

github上都是fork的,没有找到官方地址。但是都大同小异,源码没有变化。找星多的吧。

https://github.com/EZLippi/Tinyhttpd

https://github.com/nengm/Tinyhttpd

看readme

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
This software is copyright 1999 by J. David Blackstone. Permission is granted to redistribute and modify this software under the terms of the GNU General Public License, available at http://www.gnu.org/ .

If you use this software or examine the code, I would appreciate knowing and would be overjoyed to hear about it at jdavidb@sourceforge.net .

This software is not production quality. It comes with no warranty of any kind, not even an implied warranty of fitness for a particular purpose. I am not responsible for the damage that will likely result if you use this software on your computer system.

I wrote this webserver for an assignment in my networking class in 1999. We were told that at a bare minimum the server had to serve pages, and told that we would get extra credit for doing "extras." Perl had introduced me to a whole lot of UNIX functionality (I learned sockets and fork from Perl!), and O'Reilly's lion book on UNIX system calls plus O'Reilly's books on CGI and writing web clients in Perl got me thinking and I realized I could make my webserver support CGI with little trouble.

Now, if you're a member of the Apache core group, you might not be impressed. But my professor was blown over. Try the color.cgi sample script and type in "chartreuse." Made me seem smarter than I am, at any rate. :)

Apache it's not. But I do hope that this program is a good educational tool for those interested in http/socket programming, as well as UNIX system calls. (There's some textbook uses of pipes, environment variables, forks, and so on.)

One last thing: if you look at my webserver or (are you out of mind?!?) use it, I would just be overjoyed to hear about it. Please email me. I probably won't really be releasing major updates, but if I help you learn something, I'd love to know!

Happy hacking!
J. David Blackstone

翻译后读到哪些信息:

1.非生产软件,教学软件,练习项目

2.Perl cgi

3.http、socket、系统调用、管道、环境变量、fork

跑起来

下载源码至linux系统。

直接make,会提示警告:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
gcc -g -W -Wall -lpthread  -o httpd httpd.c
httpd.c: 在函数‘execute_cgi’中:
httpd.c:282:9: 警告:实参为 NULL,需要非 NULL 值(实参 2) [-Wnonnull]
execl(path, NULL);
^
httpd.c:282:9: 警告:变量实参太少,不足以填满一个哨兵 [-Wformat=]
gcc -W -Wall -o client simpleclient.c
simpleclient.c: 在函数‘main’中:
simpleclient.c:26:9: 警告:隐式声明函数‘exit’ [-Wimplicit-function-declaration]
exit(1);
^
simpleclient.c:26:9: 警告:隐式声明与内建函数‘exit’不兼容 [默认启用]
simpleclient.c:32:5: 警告:隐式声明与内建函数‘exit’不兼容 [默认启用]
exit(0);
^
simpleclient.c:8:14: 警告:未使用的参数‘argc’ [-Wunused-parameter]
int main(int argc, char *argv[])
^
simpleclient.c:8:26: 警告:未使用的参数‘argv’ [-Wunused-parameter]
int main(int argc, char *argv[])
^

程序员不关心警告。

./httpd 出现:

1
httpd running on port 4000

访问:http://192.168.80.3:4000/

perl与 perl cgi <选修>

输入颜色跳转http://192.168.80.3:4000/color.cgi ,但是是空白页面。

cgi是脚本,语言是perl。所以要安装perl。一般linux上已经有了which perl一下。

安装perl-CGI。 yum install perl-CGI

测试:perl -MCGI -e ‘print “CGI.pm version $CGI::VERSION\n;”‘

修改cgi文件中第一行#!/usr/local/bin/perl -Tw 为 #!/usr/bin/perl -Tw。 就是你which出的路径

添加cgi文件的执行权限。

这时输入颜色,可以看到页面全部是那个颜色。

总结关注点

网上大部分关注点在http协议和源码添加注释上。

但是对于一个刚刚学完C语言语法的人来说,感觉不太实用。很主观。

我的关注点更多的是socket、系统调用、管道、环境变量、fork等。

源码解析<重点>

需要关注的文件有:httpd.c、simpleclient.c

httpd.c是服务器端的源码。

源码中如下函数声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*处理从套接字上监听到的一个 HTTP 请求,在这里可以很大一部分地体现服务器处理请求流程。*/
void accept_request(void *);
/*返回给客户端这是个错误请求,HTTP 状态吗 400 BAD REQUEST。*/
void bad_request(int);
/*读取服务器上某个文件写到 socket 套接字。*/
void cat(int, FILE *);
/*主要处理发生在执行 cgi 程序时出现的错误。*/
void cannot_execute(int);
/*把错误信息写到 perror 并退出。*/
void error_die(const char *);
/*运行 cgi 程序的处理,也是个主要函数。*/
void execute_cgi(int, const char *, const char *, const char *);
/*读取套接字的一行,把回车换行等情况都统一为换行符结束。*/
int get_line(int, char *, int);
/*把 HTTP 响应的头部写到套接字。*/
void headers(int, const char *);
/*主要处理找不到请求的文件时的情况。*/
void not_found(int);
/*调用 cat 把服务器文件返回给浏览器。*/
void serve_file(int, const char *);
/*初始化 httpd 服务,包括建立套接字,绑定端口,进行监听等。*/
int startup(u_short *);
/*返回给浏览器表明收到的 HTTP 请求所用的 method 不被支持。*/
void unimplemented(int);

通常: main -> startup -> accept_request -> execute_cgi。

如何找到某个类型或者某个函数

1.看名称,有的可以知道

2.搜索

1
grep -R "类型或者函数名" /usr/include

main

u_short

这个看字知义,是无符号short类型,在linux/coda.h。但是新版本已经不在使用了,新版本使用uint16_t。在stdint.h头文件中。

sockaddr_in

是用于表示IPv4套接字地址结构的数据类型,通常用于网络编程中。它定义在 netinet/in.h头文件中,并用于存储IP地址和端口号的信息,以便在套接字编程中使用。

sockaddr_in -> netinet/in.h -> __SOCKADDR_COMMON -> bits/sockaddr.h -> sa_family_t

sa_family_t 为别名,其实就是无符号整数(unsigned short int)

1
2
3
typedef unsigned short int sa_family_t;
#define __SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family\
1
2
3
4
5
6
7
8
9
10
11
12
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */

/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};

_SOCKADDR_COMMON (sin); 其实就是声明了一个无符号整型变量,变量名为sin_family。

1
2
3
4
5
6
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};

accept()

1
2
3
4
5
6
7
8
9
10
/* Await a connection on socket FD.
When a connection arrives, open a new socket to communicate with it,
set *ADDR (which is *ADDR_LEN bytes long) to the address of the connecting
peer and *ADDR_LEN to the address's actual length, and return the
new socket's descriptor, or -1 for errors.

This function is a cancellation point and therefore not marked with
__THROW. */
extern int accept (int __fd, __SOCKADDR_ARG __addr,
socklen_t *__restrict __addr_len);

第一个参数fd, 文件描述符

第二个参数addr, 是客户端地址结构体的长度

第三个参数addr_len,是客户端地址结构体的长度

阻塞等待客户端的连接

pthread_create()

1
2
3
4
5
6
7
8
/* Create a new thread, starting with execution of START-ROUTINE
getting passed ARG. Creation attributed come from ATTR. The new
handle is stored in *NEWTHREAD. */
extern int pthread_create (pthread_t *__restrict __newthread,
const pthread_attr_t *__restrict __attr,
void *(*__start_routine) (void *),
void *__restrict __arg) __THROWNL __nonnull ((1, 3));

每次收到请求,创建一个线程来处理接受到的请求

把client_sock转成地址作为参数传入pthread_create。


C语言学习之Tinyhttpd
http://hanqichuan.com/2023/10/07/c语言/C语言学习之Tinyhttpd/
作者
韩启川
发布于
2023年10月7日
许可协议