本帖最后由 咕噜 于 2015-4-19 18:25 编辑 6 ]% V% u' Y6 j, u" G
8 v$ ~2 y4 {; B1 t4 j5 q8 s. {原帖地址) H+ B$ D' G6 \9 a, K, k
http://www.ichanging.org/uip-webserver.html2 y7 U- ^1 m" O" d" ~
这篇帖子写的不错,分享一下。对于理解uIP很有帮助。
; W. f$ u, C# h; V! f9 x0 u% E- }Uip的Webserver比较复杂,用c语言实现一个简单服务器的所有功能,路由功能,GET传参,动态页面生成等。
0 w% n, U$ c+ {) \: l; P要运行Uip的WebServer 只需要:
( X0 p# {% G, F. j% }5 M3 R1. 修改uip-con.h 里的#inlcude "webserver.h" 去除其注释3 _) S* h/ }0 v& A
2. 在User/main.c 里加入 httpd_init(); //初始化服务器- w. w' i/ L, C9 V- N
Uip+ stm32移植参见 Uip + Stm32移植问题总结 , T. T+ o; k& c2 B, i
浏览器访问 Uip WebServer 页面:. n1 J8 e) X. E9 t8 k7 |) e
$ y: d3 q% j5 C6 E6 Z1 |
分析下 Uip Webserver 的运行过程,Uip WebServer使用到相关文件如下:
/ G8 J) n% m1 g; Q5 ?
' ~9 [- B$ P; N8 a rhttpd.c
5 ]1 L7 a& o. L9 x7 ^httpd.c中定义了一些ASCII码,含义如下+ n" W( `' E V3 a
- #define ISO_nl 0x0a // 换行# P' Q* x! O# K! w! n
- #define ISO_space 0x20 // 空格2 Z, O" [* i7 a8 m
- #define ISO_bang 0x21 // !. f# r0 a1 T. o
- #define ISO_percent 0x25 // %
) [/ u2 z2 @ @ - #define ISO_period 0x2e // .
7 ]3 X0 ~. [* y" \ - #define ISO_slash 0x2f // /7 H6 `& u! U$ n4 ?
- #define ISO_colon 0x3a // :
复制代码
/ }; o& F9 Y7 i3 U# S当Uip接收到Uip接收到底层传递的数据,将接收到的数据通过调用http_appcall(),传递给Webserver处理,再通过handle_connection()先后调用handle_input()函数和handle_output()函数- ~2 Z Y0 z v; P
handle_input()主要作用是分析http数据流:' F6 k8 I0 }: j4 j
) m" ^0 n3 X( S1 K& L- p* l- static PT_THREAD(handle_input(struct httpd_state *s)) //分析http数据流, http数据流格式(eg. "GET /6?user=123 HTTP/ ...")# `2 B2 M" |$ A# a) T2 t; F- G, q
- {
& G8 e' \5 h! ]& X - char * ptr;0 U3 M4 T5 B J. F6 W2 K
- $ I0 [+ q0 v' B" N6 m
- PSOCK_BEGIN(&s->sin);
0 f! K8 \5 @6 N - ) |' o) |/ C" A) @: o
- //取出到下一个空格号之前的数据
& o( L) V! M% b- W - PSOCK_READTO(&s->sin, ISO_space); //Uip使用这个函数从http数据流中剥离数据
0 ^4 f0 D6 A3 q0 Y3 o, j -
5 Y8 T# P+ J, a. P& l/ U+ t. S -
% I1 G% Z% Z% [2 w4 \: q4 B9 D - if(strncmp(s->inputbuf, http_get, 4) != 0) { //判断数据流前4位字符是否为"GET ",判断是否为GET传参
7 {. J1 F7 o) m4 ?" S - PSOCK_CLOSE_EXIT(&s->sin);
2 `, n! R. B l' x4 J: A/ [* z: p - }
1 q$ W' m0 `: k5 S2 c7 L -
! C5 I* _6 H) Q' @/ u+ O - PSOCK_READTO(&s->sin, ISO_space);
7 p& V Y5 v$ T% l$ w -
5 |9 o: v" R3 @% B0 f9 t* E z. E4 _* g - if(s->inputbuf[0] != ISO_slash) { ; f% i% t3 L. ~6 C0 G5 e8 P0 D. f& F
- PSOCK_CLOSE_EXIT(&s->sin);
3 W" a; u* R2 G0 r6 \# B5 \ - }
& r+ w" L& j5 j# v - 7 c/ A' ~+ j7 i% @
- if(s->inputbuf[1] == ISO_space) { //请求路径为 "/ " (eg. 192.168.1.15/ ) 更新请求页面filename 为/index.html5 `8 \/ `2 i- Q4 |
- strncpy(s->filename, http_index_html, sizeof(s->filename));
/ L$ C2 I8 W3 Z6 |$ q* D7 T - } else { //根据请求路径,更新结构体中filename为相应页面名称- i2 Y0 c9 e/ N6 e7 R5 u
- s->inputbuf[PSOCK_DATALEN(&s->sin) - 1] = 0;$ o% h! f) o8 ~" h: d
- strncpy(s->filename, &s->inputbuf[0], sizeof(s->filename)); Z* w6 g1 [1 |
- } z8 m0 c v' \0 b \' {
- * O" i( r C# _) Z* m
- /* httpd_log_file(uip_conn->ripaddr, s->filename);*/; i- G& l. k y3 Z
-
. L6 m) L4 j4 A8 Q, L/ n8 J+ P - s->state = STATE_OUTPUT;
: }1 n( [: Z6 X -
+ f) o+ E6 z' z/ H3 m - while(1) {1 r% F7 d* l0 F$ n/ u4 P
- PSOCK_READTO(&s->sin, ISO_nl);
* h8 q$ ?+ [+ W) f! B -
% D' V; G; W* Z4 H8 k8 w - if(strncmp(s->inputbuf, http_referer, 8) == 0) {# [. ]% x2 X2 M! c, Z
- s->inputbuf[PSOCK_DATALEN(&s->sin) - 2] = 0;3 |) J( u( b @" \; k
- /* httpd_log(&s->inputbuf[9]);*/3 h( Z3 J4 Z' {
- }, e d; t9 I" L
- }. ~5 n7 L, \, j; W8 q3 \
-
- X9 [* }9 a& C4 { - PSOCK_END(&s->sin);
$ ]+ ^9 `# {) _, | - }
复制代码 分析数据得出访问页面的名字,存入一个全局的结构体中,handle_output()函数根据这个结构体获得要输出的页面名字,做相应处理:) Y- n& s( U- j8 L) i$ _
- static PT_THREAD(handle_output(struct httpd_state *s))
' Z% P, R2 K% D4 j+ Y+ ] - {0 B7 p# j6 \9 `/ D! ]
- char *ptr; x3 g5 l. @( C0 m3 k# I6 ?
- ) @3 `) m( ]% g2 K2 r
- PT_BEGIN(&s->outputpt);* v' \) Y3 N8 c) `" {/ L- N# A
-
: [. @- a1 \( G. d/ _! d' Q - if(!httpd_fs_open(s->filename, &s->file)) { //没有此页面,打开404页面
" R7 N5 I1 y! k/ W9 c; W - httpd_fs_open(http_404_html, &s->file);
8 r% R% g4 ?& S" B# E. [. j - strcpy(s->filename, http_404_html);. ^7 b0 B1 `& t
- PT_WAIT_THREAD(&s->outputpt,
3 X1 U$ K0 n- g. ?" q - send_headers(s, f; @: Q( q) ?
- http_header_404));5 B6 H% s: z4 Z8 N; n
- PT_WAIT_THREAD(&s->outputpt,+ {, ^- g) w9 f! `
- send_file(s));. q, y1 M1 q8 a
- } else { //正常打印相应页面
2 E" u! n( K a) P - PT_WAIT_THREAD(&s->outputpt, 2 i3 e7 m6 Q2 L8 W
- send_headers(s,
O9 J: i7 K" ^* u X - http_header_200));
6 b: d1 _6 M+ W' t - ptr = strchr(s->filename, ISO_period); //查找字符串s->filename中首次出现字符ISO_period的位置,返回首次出现c的位置的指针8 w8 K! \' j9 I @- s3 u3 K4 p% Y- F
- if(ptr != NULL && strncmp(ptr, http_shtml, 6) == 0) { //判断是否为 .shtml网页文件
8 ^9 i$ P" l2 E G$ m- \ - PT_INIT(&s->scriptpt);
* S' s# D* ?8 `5 F - PT_WAIT_THREAD(&s->outputpt, handle_script(s)); //若为 .shtml页面,则调用handle_script()生成动态网页
2 w8 @2 d& P, E - } else { //不是 .shtml(eg. /index.html),输出该页面数据$ W& u$ E" |9 C( M0 d8 j' X# {
- PT_WAIT_THREAD(&s->outputpt,
! z9 |" d: Y, ]4 E - send_file(s));. f- b, O/ u2 X, J4 f. c3 s# {
- }0 R7 h Y! X) [2 W' E
- }7 Y7 p6 h' k2 Z
- PSOCK_CLOSE(&s->sout);/ Z2 m6 @( g6 n- G$ Z& V! D- A {) b& F' }
- PT_END(&s->outputpt);
1 q- ~1 R( ^! ~# [' S - }
复制代码
- G( h# `5 f2 ~3 ^9 F1 nhttpd_fs_open()定义于httpd-fs.c,用于读取相应页面的数据,将页面数据存入全局结构体中,是实现路由遍历的关键函数:" D" v- a) M% y+ _% ]# X) f9 l* m( K
- int httpd_fs_open(const char *name, struct httpd_fs_file *file)
; h$ ^0 U, L0 u- x+ s' @( `9 J9 w9 m - {
1 u: D: I& E" y5 S8 L; c. Q, b - #if HTTPD_FS_STATISTICS
8 g) n9 P4 {( L0 y4 M6 z/ q - u16_t i = 0;/ V, U1 i& X8 u7 T9 {
- #endif /* HTTPD_FS_STATISTICS */
) T* B K; }! J8 N. j6 d/ w - struct httpd_fsdata_file_noconst *f;
复制代码
( F8 n' h/ D6 x( s1 O1 |
M/ @3 D" h. _& I* Y- Uhttp-fsdata.c 中包含了所有页面的数据。这里的页面数据都转换为ACAll存在数组中,还包括了层叠样式表 (.css文件) 和图片。其数组结构如下:. l: T% {0 \: i [7 x
- R0 C: `7 w* [
- static const unsigned char data_404_html[] = {
. k/ ^2 M! j7 r, V3 i' a - /* /404.html */+ j; h7 o- {* P) L6 e
- 0x2f, 0x34, 0x30, 0x34, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0, //文件名 /404.html
" ^5 V! S+ h/ A& i) N* k+ r -
1 ^- Q9 v. O+ A - 0x3c, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0xa, 0x20, 0x20, 0x3c, //html文件转码为16进制数据(ASCLL)
0 {+ N7 B T# C( m! I' _+ b7 I5 d - 0x62, 0x6f, 0x64, 0x79, 0x20, 0x62, 0x67, 0x63, 0x6f, 0x6c, 2 y, r3 ]; \0 U, m6 K- x3 j
- 0x6f, 0x72, 0x3d, 0x22, 0x77, 0x68, 0x69, 0x74, 0x65, 0x22,
* [; F; X) }5 \0 G4 d - 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x63, 0x65, 0x6e,
- i4 @$ b: S) U; B2 d) d0 ? H/ Y - 0x74, 0x65, 0x72, 0x3e, 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 1 F2 `3 x% c* `/ [5 H5 b* R/ V* V
- 0x20, 0x3c, 0x68, 0x31, 0x3e, 0x34, 0x30, 0x34, 0x20, 0x2d, ; r$ p- P: M2 C- b% Y/ I; w Y. h/ C) J
- 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, # z5 D$ y$ f. g g6 b
- 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x3c, 0x2f, 0x68, 0x31, 0x3e, % g5 {. s, k/ ^" Z! b
- 0xa, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x68, 0x33, ) Y' {2 r M; h# h' E5 T& I
- 0x3e, 0x47, 0x6f, 0x20, 0x3c, 0x61, 0x20, 0x68, 0x72, 0x65,
* C0 a& t& ~3 [4 P0 z* |/ S - 0x66, 0x3d, 0x22, 0x2f, 0x22, 0x3e, 0x68, 0x65, 0x72, 0x65,
+ L$ ~" Z' s, ] - 0x3c, 0x2f, 0x61, 0x3e, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x65,
" N+ c# p0 z6 N$ K( W3 ?$ K" U - 0x61, 0x64, 0x2e, 0x3c, 0x2f, 0x68, 0x33, 0x3e, 0xa, 0x20,
; k, k! \8 S0 ?4 S) k - 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x63, 0x65, 0x6e, 0x74, 0x65,
$ O: O! X; V$ H5 z8 g - 0x72, 0x3e, 0xa, 0x20, 0x20, 0x3c, 0x2f, 0x62, 0x6f, 0x64, % `" m3 P7 W" S. v8 T
- 0x79, 0x3e, 0xa, 0x3c, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3e,
- k+ M9 x% R$ |6 i& U1 q3 K# S - 0};
复制代码 ' |% t+ o# I7 N8 T4 N$ N W; y) n& B
需要注意的是以下的一段程序:- //结构体格式说明: 下一个页面地址(用于遍历网页) ,网页name地址 ,html数据起始地址 ,html数据长度
7 R8 W! v( F, S' ]3 H2 P - //其中的加减操作是为了去除文件名的长度" n- k" v" H. R `+ p. p- f- w2 K
- const struct httpd_fsdata_file file_processes_shtml[] = {{NULL, data_processes_shtml, data_processes_shtml + 17, sizeof(data_processes_shtml) - 17}};
' M! \( i ~* o/ B - 8 p1 x, F! [, M+ j" W3 Z; i
- const struct httpd_fsdata_file file_404_html[] = {{file_processes_shtml, data_404_html, data_404_html + 10, sizeof(data_404_html) - 10}};
0 I4 ]3 r! Z$ y6 C( b -
* ]/ u! @" ?- b% h: w - const struct httpd_fsdata_file file_files_shtml[] = {{file_404_html, data_files_shtml, data_files_shtml + 13, sizeof(data_files_shtml) - 13}};
; X. ^7 _/ ~8 b6 w0 ` - * `) I& v- m' l& J1 z
- const struct httpd_fsdata_file file_footer_html[] = {{file_files_shtml, data_footer_html, data_footer_html + 13, sizeof(data_footer_html) - 13}};' X1 s, Z8 i( W" Y
-
7 Q8 j/ E" w% p; I - const struct httpd_fsdata_file file_header_html[] = {{file_footer_html, data_header_html, data_header_html + 13, sizeof(data_header_html) - 13}};& q \- w I) K8 L: c5 A9 A
- 4 O$ o+ }1 b4 ~) D( Q; u, F ? C% Q
- const struct httpd_fsdata_file file_index_html[] = {{file_header_html, data_index_html, data_index_html + 12, sizeof(data_index_html) - 12}};6 C2 Y4 ^, M/ O
-
; {# l, B- Y4 w3 _: x" C4 M9 O - const struct httpd_fsdata_file file_style_css[] = {{file_index_html, data_style_css, data_style_css + 11, sizeof(data_style_css) - 11}};; J v- a0 [* p, L5 o; T& i
- 1 U, Y. ]" [' V# F
- const struct httpd_fsdata_file file_tcp_shtml[] = {{file_style_css, data_tcp_shtml, data_tcp_shtml + 11, sizeof(data_tcp_shtml) - 11}};
; T* W r B& c6 t4 X$ S* Z1 B -
5 U$ q+ _7 m. K9 v$ ?; [ - const struct httpd_fsdata_file file_fade_png[] = {{file_tcp_shtml, data_fade_png, data_fade_png + 10, sizeof(data_fade_png) - 10}};
8 _2 z* h0 i; f7 s5 }( I: U: G - B3 \: s4 k; Z, y. F+ p8 e; N
- const struct httpd_fsdata_file file_stats_shtml[] = {{file_fade_png, data_stats_shtml, data_stats_shtml + 13, sizeof(data_stats_shtml) - 13}};/ b- F# [: J" }; u$ O# M7 h
-
" v; Q" u4 ]. B/ E8 D; S - #define HTTPD_FS_ROOT file_stats_shtml //设定路由遍历入口页面,一定要保证所有页面都遍历过一次
. ?$ d% e* l% n; q0 m - % |' Z8 E, c* [
- #define HTTPD_FS_NUMFILES 10 //设定页面数量
复制代码 4 O0 `6 I9 f, M4 r! P4 r& {0 g
在 httpd_fs_open() 首先加载 file_stats_shtml页面数据 再加载file_stats_shtml结构体中下一个网页的数据 也就是file_fade_png的数据,同理 file_fade_png加载后一个页面数据 即 file_tcp_shtml数据 。。。 这样循环一次 就会加载所有的页面,实现里有遍历# Z2 e n0 ~+ U5 e2 |( H) a
Uip WebServer的动态网页生成; u/ ~ ]5 i7 D/ e/ Q- G
在uip/apps/Webserver/http-fs/下有Webserver 页面未转码的html文件,其中有很多 %! 和 %!: 字符 :
3 n, D6 j& L5 W) w2 o6 x- S5 K- %!: /header.html( [1 b0 j3 H: n& l
- <h1>Network statistics</h1>6 h/ u- O6 H6 |/ P3 D/ O5 @
- <center>7 ]+ F2 \" B# W7 v7 V
- <table width="300" border="0">2 V p' E% t6 H
- <tr><td><pre>4 t/ d! Z) W9 ~( o1 ?
- IP Packets received: l# g2 W% q: ?1 z) A
- Packets sent, |! @9 z, S) `+ X% [
- Packets dropped1 p9 C7 n" G" g! g; n0 ]0 G
- IP errors IP version/header length0 ^$ |- Q7 \) M% c: e
- IP length, high byte- M4 N; c; i+ ~ ]6 Z( P3 A
- IP length, low byte" h' C) Z) F. p/ o
- IP fragments0 e N+ m7 V' H5 Q) S/ K A; V& b' m
- Header checksum" i& w' b* u7 z$ g9 [, c k
- Wrong protocol# Y% V- T8 S0 n$ X5 G6 F- `
- ICMP Packets received
5 j. o8 A9 D" X3 k% f& B; F/ B - Packets sent
& M) [8 |7 q9 J: ]' W2 u: i - Packets dropped2 ] v: Z b8 v6 U! O0 a* R
- Type errors! {) s' R) G L, K2 F
- TCP Packets received( L2 u# p" U/ _, t; Q
- Packets sent# P7 I# t4 C2 d4 |6 K
- Packets dropped4 _+ K# z% S/ q3 j2 n# O
- Checksum errors+ q* Z9 o: l: H! `4 j1 f
- Data packets without ACKs. b' v; V( N, O" W; `+ Q9 X
- Resets
7 O }) a) W, X5 g/ n. z - Retransmissions
- l8 e& g. G3 Y6 l& d( ? - No connection avaliable q% ?- W. b* m6 s
- Connection attempts to closed ports
: Q: l1 u. u2 q Y7 k. y% ]1 @ - </pre></td><td><pre>%! net-stats
' M, j0 x( b0 V- { - </pre></table>% \( w$ [4 T# x+ m9 F8 [8 J% A# }
- </center>
$ ]7 S& t6 W3 C" E5 P - %!: /footer.html
复制代码 这是实现动态页面的关键。+ W6 ~; r' ]0 v: u( k% f/ q) v
1 Q" _$ `! n( c. X3 C- Qhandle_output()函数中,找到相应页面数据后,若页面为.shtml,则会调用handle_script()函数:- static PT_THREAD(handle_script(struct httpd_state *s)) # \ e: M g% f; G& F( h' C
- {' }0 `8 m9 n) Z! K# z
- char *ptr;
" ^2 N* e1 P. t3 [( A( e4 C/ L - , H& Z# H6 v [4 ]
- PT_BEGIN(&s->scriptpt);: D6 R6 W A+ H6 Y) ~2 N
- 8 ]& d9 q; d% S8 c8 B1 E0 [
- 1 S: u+ u) e- @4 T# S5 N
- while(s->file.len > 0) {
. p( f) n4 o) K0 v3 r7 w( n -
2 l5 N \8 n5 {6 I! D8 f( Q I - /* Check if we should start executing a script. */ //检测当前html数据(定义于httpd-fsdata.c)中是否存在字符 %! 和 %!:
& ]; i% y5 ~. F - if(*s->file.data == ISO_percent &&
1 V! t; ~/ q3 _# Q) L- P; ?5 V: E - *(s->file.data + 1) == ISO_bang) { , [8 S, A$ h4 I& `
- s->scriptptr = s->file.data + 3;
' n1 H5 ^: V, k - s->scriptlen = s->file.len - 3;
4 _# r- J, \* H( G' B8 k' A - if(*(s->scriptptr - 1) == ISO_colon) { //若为 %!: 根据其后变量名,打开并输出相应文件
& k, E4 S. z+ _) j: f; C: B - httpd_fs_open(s->scriptptr + 1, &s->file); //eg. %!: /header.html 打印/header.html* ^. F2 x' v: v3 J6 m2 k2 \# V
- PT_WAIT_THREAD(&s->scriptpt, send_file(s));
) _$ O0 I& k0 K& M. P/ x" | - } else { //若为 %! 根据其后变量名,调用相应处理程序(定义于httpd-cgi.c)
: ]" q) X6 `1 r - PT_WAIT_THREAD(&s->scriptpt, //eg. %! file-stats 调用file-stats 绑定的file_stats()函数,打印出相关数据,实现动态网页
+ _$ H" `) e' A+ L - httpd_cgi(s->scriptptr)(s, s->scriptptr));" |1 l2 L, i& q4 \* x2 |0 b
- }* X9 E$ V0 {4 s1 m( b
- next_scriptstate(s);
' p0 b8 Z+ N( `* e) d" h- k -
9 ~0 _ r9 v! R6 R [% y - /* The script is over, so we reset the pointers and continue$ F, ~" Q& V# K# J0 s
- sending the rest of the file. */" e$ k3 k9 k7 n( R) b
- s->file.data = s->scriptptr;
* q. r7 Q' n7 f% Z6 G0 | - s->file.len = s->scriptlen;
& M( c# S& y5 }2 c$ H - } else { //当前html数据不存在 %! 和 %!
2 f* i2 v4 `4 R5 k - /* See if we find the start of script marker in the block of HTML5 R& i6 ^9 e/ ?: a
- to be sent. */
% m1 v3 `5 M' r, h: P -
. V. ?& ~0 P8 a3 E% W - ...略去
复制代码 5 O- p, U' }3 y7 v
uip 载入html数据的方法类似网页里的模板引擎的实现方法。当页面输出时,检测到有字符串 %! 和 %!: 时,则调用相应的cgi程序(httpd-cgi.c)处理,在httpd-cgi.c中做相应的数据处理,实现动态网页。
$ |8 k4 ~: L w" O9 V
( A" `9 n, P5 v& e* W1 C, o- Y0 J& d% m
看一下效果吧
7 R! v5 p2 W2 X
9 t H2 \; B' Y) D4 c
" m, L; ~8 x. } |