Uip的Webserver比较复杂,用c语言实现一个简单服务器的所有功能,路由功能,GET传参,动态页面生成等。 要运行Uip的WebServer 只需要: 1. 修改uip-con.h 里的#inlcude "webserver.h" 去除其注释 2. 在User/main.c 里加入 httpd_init(); //初始化服务器 浏览器访问 Uip WebServer 页面: 分析下 Uip Webserver 的运行过程,Uip WebServer使用到相关文件如下: httpd.c httpd.c中定义了一些字符阿斯克码,含义如下 1 | #define ISO_nl 0x0a // 换行 |
2 | #define ISO_space 0x20 // 空格 |
3 | #define ISO_bang 0x21 // ! |
4 | #define ISO_percent 0x25 // % |
5 | #define ISO_period 0x2e // . |
6 | #define ISO_slash 0x2f // / |
7 | #define ISO_colon 0x3a // : |
当Uip接收到Uip接收到底层传递的数据,将接收到的数据通过调用http_appcall(),传递给Webserver处理,再通过handle_connection()先后调用handle_input()函数和handle_output()函数 handle_input()主要作用是分析http数据流: 01 | static PT_THREAD(handle_input( struct httpd_state *s)) //分析http数据流, http数据流格式(eg. "GET /6?user=123 HTTP/ ...") |
08 | PSOCK_READTO(&s-> sin , ISO_space); //Uip使用这个函数从http数据流中剥离数据 |
11 | if ( strncmp (s->inputbuf, http_get, 4) != 0) { //判断数据流前4位字符是否为"GET ",判断是否为GET传参 |
12 | PSOCK_CLOSE_EXIT(&s-> sin ); |
15 | PSOCK_READTO(&s-> sin , ISO_space); |
17 | if (s->inputbuf[0] != ISO_slash) { |
18 | PSOCK_CLOSE_EXIT(&s-> sin ); |
21 | if (s->inputbuf[1] == ISO_space) { //请求路径为 "/ " (eg. 192.168.1.15/ ) 更新请求页面filename 为/index.html |
22 | strncpy (s->filename, http_index_html, sizeof (s->filename)); |
23 | } else { //根据请求路径,更新结构体中filename为相应页面名称 |
24 | s->inputbuf[PSOCK_DATALEN(&s-> sin ) - 1] = 0; |
25 | strncpy (s->filename, &s->inputbuf[0], sizeof (s->filename)); |
28 | /* httpd_log_file(uip_conn->ripaddr, s->filename);*/ |
30 | s->state = STATE_OUTPUT; |
33 | PSOCK_READTO(&s-> sin , ISO_nl); |
35 | if ( strncmp (s->inputbuf, http_referer, 8) == 0) { |
36 | s->inputbuf[PSOCK_DATALEN(&s-> sin ) - 2] = 0; |
37 | /* httpd_log(&s->inputbuf[9]);*/ |
分析数据得出访问页面的名字,存入一个全局的结构体中,handle_output()函数根据这个结构体获得要输出的页面名字,做相应处理: 01 | static PT_THREAD(handle_output( struct httpd_state *s)) |
05 | PT_BEGIN(&s->outputpt); |
07 | if (!httpd_fs_open(s->filename, &s->file)) { //没有此页面,打开404页面 |
08 | httpd_fs_open(http_404_html, &s->file); |
09 | strcpy (s->filename, http_404_html); |
10 | PT_WAIT_THREAD(&s->outputpt, |
13 | PT_WAIT_THREAD(&s->outputpt, |
16 | PT_WAIT_THREAD(&s->outputpt, |
19 | ptr = strchr (s->filename, ISO_period); //查找字符串s->filename中首次出现字符ISO_period的位置,返回首次出现c的位置的指针 |
20 | if (ptr != NULL && strncmp (ptr, http_shtml, 6) == 0) { //判断是否为 .shtml网页文件 |
21 | PT_INIT(&s->scriptpt); |
22 | PT_WAIT_THREAD(&s->outputpt, handle_script(s)); //若为 .shtml页面,则调用handle_script()生成动态网页 |
23 | } else { //不是 .shtml(eg. /index.html),输出该页面数据 |
24 | PT_WAIT_THREAD(&s->outputpt, |
28 | PSOCK_CLOSE(&s->sout); |
httpd_fs_open()定义于httpd-fs.c,用于读取相应页面的数据,将页面数据存入全局结构体中,是实现路由遍历的关键函数: 1 | int httpd_fs_open( const char *name, struct httpd_fs_file *file) |
5 | #endif /* HTTPD_FS_STATISTICS */ |
6 | struct httpd_fsdata_file_noconst *f; |
01 | //遍历所有的页面数据, 方便校验是否存在该页面 |
02 | for (f = ( struct httpd_fsdata_file_noconst *)HTTPD_FS_ROOT; //HTTPD_FS_ROOT 定义于httpd-fsdata.c, 定义了遍历入口 |
04 | f = ( struct httpd_fsdata_file_noconst *)f->next) { //加载下一个页面数据 ,遍历顺序由httpd_fsdata_file结构体中 next决定(见 httpd-fsdata.c) |
05 | if (httpd_fs_strcmp(name, f->name) == 0) { //校验请求的页面是否为此页面 |
08 | # if HTTPD_FS_STATISTICS |
10 | #endif /* HTTPD_FS_STATISTICS */ |
13 | # if HTTPD_FS_STATISTICS |
15 | #endif /* HTTPD_FS_STATISTICS */ |
http-fsdata.c 中包含了所有页面的数据。这里的页面数据都转换为ACAll存在数组中,还包括了层叠样式表 (.css文件) 和图片。其数组结构如下: 01 | static const unsigned char data_404_html[] = { |
03 | 0x2f, 0x34, 0x30, 0x34, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0, //文件名 /404.html |
05 | 0x3c, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0xa, 0x20, 0x20, 0x3c, //html文件转码为16进制数据(ASCLL) |
06 | 0x62, 0x6f, 0x64, 0x79, 0x20, 0x62, 0x67, 0x63, 0x6f, 0x6c, |
07 | 0x6f, 0x72, 0x3d, 0x22, 0x77, 0x68, 0x69, 0x74, 0x65, 0x22, |
08 | 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x63, 0x65, 0x6e, |
09 | 0x74, 0x65, 0x72, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, |
10 | 0x20, 0x3c, 0x68, 0x31, 0x3e, 0x34, 0x30, 0x34, 0x20, 0x2d, |
11 | 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, |
12 | 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x3c, 0x2f, 0x68, 0x31, 0x3e, |
13 | 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x68, 0x33, |
14 | 0x3e, 0x47, 0x6f, 0x20, 0x3c, 0x61, 0x20, 0x68, 0x72, 0x65, |
15 | 0x66, 0x3d, 0x22, 0x2f, 0x22, 0x3e, 0x68, 0x65, 0x72, 0x65, |
16 | 0x3c, 0x2f, 0x61, 0x3e, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x65, |
17 | 0x61, 0x64, 0x2e, 0x3c, 0x2f, 0x68, 0x33, 0x3e, 0xa, 0x20, |
18 | 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x63, 0x65, 0x6e, 0x74, 0x65, |
19 | 0x72, 0x3e, 0xa, 0x20, 0x20, 0x3c, 0x2f, 0x62, 0x6f, 0x64, |
20 | 0x79, 0x3e, 0xa, 0x3c, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3e, |
需要注意的是以下的一段程序: 01 | //结构体格式说明: 下一个页面地址(用于遍历网页) ,网页name地址 ,html数据起始地址 ,html数据长度 |
03 | const struct httpd_fsdata_file file_processes_shtml[] = {{NULL, data_processes_shtml, data_processes_shtml + 17, sizeof (data_processes_shtml) - 17}}; |
05 | const struct httpd_fsdata_file file_404_html[] = {{file_processes_shtml, data_404_html, data_404_html + 10, sizeof (data_404_html) - 10}}; |
07 | const struct httpd_fsdata_file file_files_shtml[] = {{file_404_html, data_files_shtml, data_files_shtml + 13, sizeof (data_files_shtml) - 13}}; |
09 | const struct httpd_fsdata_file file_footer_html[] = {{file_files_shtml, data_footer_html, data_footer_html + 13, sizeof (data_footer_html) - 13}}; |
11 | const struct httpd_fsdata_file file_header_html[] = {{file_footer_html, data_header_html, data_header_html + 13, sizeof (data_header_html) - 13}}; |
13 | const struct httpd_fsdata_file file_index_html[] = {{file_header_html, data_index_html, data_index_html + 12, sizeof (data_index_html) - 12}}; |
15 | const struct httpd_fsdata_file file_style_css[] = {{file_index_html, data_style_css, data_style_css + 11, sizeof (data_style_css) - 11}}; |
17 | const struct httpd_fsdata_file file_tcp_shtml[] = {{file_style_css, data_tcp_shtml, data_tcp_shtml + 11, sizeof (data_tcp_shtml) - 11}}; |
19 | const struct httpd_fsdata_file file_fade_png[] = {{file_tcp_shtml, data_fade_png, data_fade_png + 10, sizeof (data_fade_png) - 10}}; |
21 | const struct httpd_fsdata_file file_stats_shtml[] = {{file_fade_png, data_stats_shtml, data_stats_shtml + 13, sizeof (data_stats_shtml) - 13}}; |
23 | #define HTTPD_FS_ROOT file_stats_shtml //设定路由遍历入口页面,一定要保证所有页面都遍历过一次 |
25 | #define HTTPD_FS_NUMFILES 10 //设定页面数量 |
在 httpd_fs_open() 首先加载 file_stats_shtml页面数据 再加载file_stats_shtml结构体中下一个网页的数据 也就是file_fade_png的数据,同理 file_fade_png加载后一个页面数据 即 file_tcp_shtml数据 。。。 这样循环一次 就会加载所有的页面,实现里有遍历 Uip WebServer的动态网页生成 在uip/apps/Webserver/http-fs/下有Webserver 页面未转码的html文件,其中有很多 %! 和 %!: 字符 : 02 | < h1 >Network statistics</ h1 > |
04 | < table width = "300" border = "0" > |
09 | IP errors IP version/header length |
23 | Data packets without ACKs |
26 | No connection avaliable |
27 | Connection attempts to closed ports |
28 | </ pre ></ td >< td >< pre >%! net-stats |
这是实现动态页面的关键。 handle_output()函数中,找到相应页面数据后,若页面为.shtml,则会调用handle_script()函数: 01 | static PT_THREAD(handle_script( struct httpd_state *s)) |
05 | PT_BEGIN(&s->scriptpt); |
08 | while (s->file.len > 0) { |
10 | /* Check if we should start executing a script. */ //检测当前html数据(定义于httpd-fsdata.c)中是否存在字符 %! 和 %!: |
11 | if (*s->file.data == ISO_percent && |
12 | *(s->file.data + 1) == ISO_bang) { |
13 | s->scriptptr = s->file.data + 3; |
14 | s->scriptlen = s->file.len - 3; |
15 | if (*(s->scriptptr - 1) == ISO_colon) { //若为 %!: 根据其后变量名,打开并输出相应文件 |
16 | httpd_fs_open(s->scriptptr + 1, &s->file); //eg. %!: /header.html 打印/header.html |
17 | PT_WAIT_THREAD(&s->scriptpt, send_file(s)); |
18 | } else { //若为 %! 根据其后变量名,调用相应处理程序(定义于httpd-cgi.c) |
19 | PT_WAIT_THREAD(&s->scriptpt, //eg. %! file-stats 调用file-stats 绑定的file_stats()函数,打印出相关数据,实现动态网页 |
20 | httpd_cgi(s->scriptptr)(s, s->scriptptr)); |
24 | /* The script is over, so we reset the pointers and continue |
25 | sending the rest of the file. */ |
26 | s->file.data = s->scriptptr; |
27 | s->file.len = s->scriptlen; |
28 | } else { //当前html数据不存在 %! 和 %! |
29 | /* See if we find the start of script marker in the block of HTML |
uip 载入html数据的方法类似网页里的模板引擎的实现方法。当页面输出时,检测到有字符串 %! 和 %!: 时,则调用相应的cgi程序(httpd-cgi.c)处理,在httpd-cgi.c中做相应的数据处理,实现动态网页。
|