本帖最后由 咕噜 于 2015-4-19 18:25 编辑
原帖地址
http://www.ichanging.org/uip-webserver.html
这篇帖子写的不错,分享一下。对于理解uIP很有帮助。
Uip的Webserver比较复杂,用c语言实现一个简单服务器的所有功能,路由功能,GET传参,动态页面生成等。
要运行Uip的WebServer 只需要:
1. 修改uip-con.h 里的#inlcude "webserver.h" 去除其注释
2. 在User/main.c 里加入 httpd_init(); //初始化服务器
Uip+ stm32移植参见 Uip + Stm32移植问题总结
浏览器访问 Uip WebServer 页面:
分析下 Uip Webserver 的运行过程,Uip WebServer使用到相关文件如下:
httpd.c
httpd.c中定义了一些ASCII码,含义如下
- #define ISO_nl 0x0a // 换行
- #define ISO_space 0x20 // 空格
- #define ISO_bang 0x21 // !
- #define ISO_percent 0x25 // %
- #define ISO_period 0x2e // .
- #define ISO_slash 0x2f // /
- #define ISO_colon 0x3a // :
复制代码
当Uip接收到Uip接收到底层传递的数据,将接收到的数据通过调用http_appcall(),传递给Webserver处理,再通过handle_connection()先后调用handle_input()函数和handle_output()函数
handle_input()主要作用是分析http数据流:
- static PT_THREAD(handle_input(struct httpd_state *s)) //分析http数据流, http数据流格式(eg. "GET /6?user=123 HTTP/ ...")
- {
- char * ptr;
-
- PSOCK_BEGIN(&s->sin);
-
- //取出到下一个空格号之前的数据
- PSOCK_READTO(&s->sin, ISO_space); //Uip使用这个函数从http数据流中剥离数据
-
-
- if(strncmp(s->inputbuf, http_get, 4) != 0) { //判断数据流前4位字符是否为"GET ",判断是否为GET传参
- PSOCK_CLOSE_EXIT(&s->sin);
- }
-
- PSOCK_READTO(&s->sin, ISO_space);
-
- if(s->inputbuf[0] != ISO_slash) {
- PSOCK_CLOSE_EXIT(&s->sin);
- }
-
- if(s->inputbuf[1] == ISO_space) { //请求路径为 "/ " (eg. 192.168.1.15/ ) 更新请求页面filename 为/index.html
- strncpy(s->filename, http_index_html, sizeof(s->filename));
- } else { //根据请求路径,更新结构体中filename为相应页面名称
- s->inputbuf[PSOCK_DATALEN(&s->sin) - 1] = 0;
- strncpy(s->filename, &s->inputbuf[0], sizeof(s->filename));
- }
-
- /* httpd_log_file(uip_conn->ripaddr, s->filename);*/
-
- s->state = STATE_OUTPUT;
-
- while(1) {
- PSOCK_READTO(&s->sin, ISO_nl);
-
- if(strncmp(s->inputbuf, http_referer, 8) == 0) {
- s->inputbuf[PSOCK_DATALEN(&s->sin) - 2] = 0;
- /* httpd_log(&s->inputbuf[9]);*/
- }
- }
-
- PSOCK_END(&s->sin);
- }
复制代码 分析数据得出访问页面的名字,存入一个全局的结构体中,handle_output()函数根据这个结构体获得要输出的页面名字,做相应处理:
- static PT_THREAD(handle_output(struct httpd_state *s))
- {
- char *ptr;
-
- PT_BEGIN(&s->outputpt);
-
- if(!httpd_fs_open(s->filename, &s->file)) { //没有此页面,打开404页面
- httpd_fs_open(http_404_html, &s->file);
- strcpy(s->filename, http_404_html);
- PT_WAIT_THREAD(&s->outputpt,
- send_headers(s,
- http_header_404));
- PT_WAIT_THREAD(&s->outputpt,
- send_file(s));
- } else { //正常打印相应页面
- PT_WAIT_THREAD(&s->outputpt,
- send_headers(s,
- http_header_200));
- ptr = strchr(s->filename, ISO_period); //查找字符串s->filename中首次出现字符ISO_period的位置,返回首次出现c的位置的指针
- if(ptr != NULL && strncmp(ptr, http_shtml, 6) == 0) { //判断是否为 .shtml网页文件
- PT_INIT(&s->scriptpt);
- PT_WAIT_THREAD(&s->outputpt, handle_script(s)); //若为 .shtml页面,则调用handle_script()生成动态网页
- } else { //不是 .shtml(eg. /index.html),输出该页面数据
- PT_WAIT_THREAD(&s->outputpt,
- send_file(s));
- }
- }
- PSOCK_CLOSE(&s->sout);
- PT_END(&s->outputpt);
- }
复制代码
httpd_fs_open()定义于httpd-fs.c,用于读取相应页面的数据,将页面数据存入全局结构体中,是实现路由遍历的关键函数:
- int httpd_fs_open(const char *name, struct httpd_fs_file *file)
- {
- #if HTTPD_FS_STATISTICS
- u16_t i = 0;
- #endif /* HTTPD_FS_STATISTICS */
- struct httpd_fsdata_file_noconst *f;
复制代码
http-fsdata.c 中包含了所有页面的数据。这里的页面数据都转换为ACAll存在数组中,还包括了层叠样式表 (.css文件) 和图片。其数组结构如下:
- static const unsigned char data_404_html[] = {
- /* /404.html */
- 0x2f, 0x34, 0x30, 0x34, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0, //文件名 /404.html
-
- 0x3c, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0xa, 0x20, 0x20, 0x3c, //html文件转码为16进制数据(ASCLL)
- 0x62, 0x6f, 0x64, 0x79, 0x20, 0x62, 0x67, 0x63, 0x6f, 0x6c,
- 0x6f, 0x72, 0x3d, 0x22, 0x77, 0x68, 0x69, 0x74, 0x65, 0x22,
- 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x63, 0x65, 0x6e,
- 0x74, 0x65, 0x72, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20,
- 0x20, 0x3c, 0x68, 0x31, 0x3e, 0x34, 0x30, 0x34, 0x20, 0x2d,
- 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20,
- 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x3c, 0x2f, 0x68, 0x31, 0x3e,
- 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x68, 0x33,
- 0x3e, 0x47, 0x6f, 0x20, 0x3c, 0x61, 0x20, 0x68, 0x72, 0x65,
- 0x66, 0x3d, 0x22, 0x2f, 0x22, 0x3e, 0x68, 0x65, 0x72, 0x65,
- 0x3c, 0x2f, 0x61, 0x3e, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x65,
- 0x61, 0x64, 0x2e, 0x3c, 0x2f, 0x68, 0x33, 0x3e, 0xa, 0x20,
- 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x63, 0x65, 0x6e, 0x74, 0x65,
- 0x72, 0x3e, 0xa, 0x20, 0x20, 0x3c, 0x2f, 0x62, 0x6f, 0x64,
- 0x79, 0x3e, 0xa, 0x3c, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3e,
- 0};
复制代码
需要注意的是以下的一段程序:- //结构体格式说明: 下一个页面地址(用于遍历网页) ,网页name地址 ,html数据起始地址 ,html数据长度
- //其中的加减操作是为了去除文件名的长度
- const struct httpd_fsdata_file file_processes_shtml[] = {{NULL, data_processes_shtml, data_processes_shtml + 17, sizeof(data_processes_shtml) - 17}};
-
- const struct httpd_fsdata_file file_404_html[] = {{file_processes_shtml, data_404_html, data_404_html + 10, sizeof(data_404_html) - 10}};
-
- const struct httpd_fsdata_file file_files_shtml[] = {{file_404_html, data_files_shtml, data_files_shtml + 13, sizeof(data_files_shtml) - 13}};
-
- const struct httpd_fsdata_file file_footer_html[] = {{file_files_shtml, data_footer_html, data_footer_html + 13, sizeof(data_footer_html) - 13}};
-
- const struct httpd_fsdata_file file_header_html[] = {{file_footer_html, data_header_html, data_header_html + 13, sizeof(data_header_html) - 13}};
-
- const struct httpd_fsdata_file file_index_html[] = {{file_header_html, data_index_html, data_index_html + 12, sizeof(data_index_html) - 12}};
-
- const struct httpd_fsdata_file file_style_css[] = {{file_index_html, data_style_css, data_style_css + 11, sizeof(data_style_css) - 11}};
-
- const struct httpd_fsdata_file file_tcp_shtml[] = {{file_style_css, data_tcp_shtml, data_tcp_shtml + 11, sizeof(data_tcp_shtml) - 11}};
-
- const struct httpd_fsdata_file file_fade_png[] = {{file_tcp_shtml, data_fade_png, data_fade_png + 10, sizeof(data_fade_png) - 10}};
-
- const struct httpd_fsdata_file file_stats_shtml[] = {{file_fade_png, data_stats_shtml, data_stats_shtml + 13, sizeof(data_stats_shtml) - 13}};
-
- #define HTTPD_FS_ROOT file_stats_shtml //设定路由遍历入口页面,一定要保证所有页面都遍历过一次
-
- #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文件,其中有很多 %! 和 %!: 字符 :
- %!: /header.html
- <h1>Network statistics</h1>
- <center>
- <table width="300" border="0">
- <tr><td><pre>
- IP Packets received
- Packets sent
- Packets dropped
- IP errors IP version/header length
- IP length, high byte
- IP length, low byte
- IP fragments
- Header checksum
- Wrong protocol
- ICMP Packets received
- Packets sent
- Packets dropped
- Type errors
- TCP Packets received
- Packets sent
- Packets dropped
- Checksum errors
- Data packets without ACKs
- Resets
- Retransmissions
- No connection avaliable
- Connection attempts to closed ports
- </pre></td><td><pre>%! net-stats
- </pre></table>
- </center>
- %!: /footer.html
复制代码 这是实现动态页面的关键。
handle_output()函数中,找到相应页面数据后,若页面为.shtml,则会调用handle_script()函数:- static PT_THREAD(handle_script(struct httpd_state *s))
- {
- char *ptr;
-
- PT_BEGIN(&s->scriptpt);
-
-
- while(s->file.len > 0) {
-
- /* Check if we should start executing a script. */ //检测当前html数据(定义于httpd-fsdata.c)中是否存在字符 %! 和 %!:
- if(*s->file.data == ISO_percent &&
- *(s->file.data + 1) == ISO_bang) {
- s->scriptptr = s->file.data + 3;
- s->scriptlen = s->file.len - 3;
- if(*(s->scriptptr - 1) == ISO_colon) { //若为 %!: 根据其后变量名,打开并输出相应文件
- httpd_fs_open(s->scriptptr + 1, &s->file); //eg. %!: /header.html 打印/header.html
- PT_WAIT_THREAD(&s->scriptpt, send_file(s));
- } else { //若为 %! 根据其后变量名,调用相应处理程序(定义于httpd-cgi.c)
- PT_WAIT_THREAD(&s->scriptpt, //eg. %! file-stats 调用file-stats 绑定的file_stats()函数,打印出相关数据,实现动态网页
- httpd_cgi(s->scriptptr)(s, s->scriptptr));
- }
- next_scriptstate(s);
-
- /* The script is over, so we reset the pointers and continue
- sending the rest of the file. */
- s->file.data = s->scriptptr;
- s->file.len = s->scriptlen;
- } else { //当前html数据不存在 %! 和 %!
- /* See if we find the start of script marker in the block of HTML
- to be sent. */
-
- ...略去
复制代码
uip 载入html数据的方法类似网页里的模板引擎的实现方法。当页面输出时,检测到有字符串 %! 和 %!: 时,则调用相应的cgi程序(httpd-cgi.c)处理,在httpd-cgi.c中做相应的数据处理,实现动态网页。
看一下效果吧
|