|
|
本程序是VC++实现windows上程序内嵌WebSocket的部分代码,因为想让浏览器和本地程序直接交互,最好的办法就是websocket,windows的exe程序内嵌一个websocket服务器端程序,浏览器访问localhost,建立交互,这种办法比做成插件更好,所以我采用这种办法来联通桌面程序和浏览器。VC++实现WebSocket的服务器代码,网上还是有示例的,不过基本上不能用,我找到的两个,一个是基于MinGW编译,还有一个是基于VC++2010编译的,还有一个libwebsocket,C语言实现的库,实在是大的惊人,最后,我决定自己实现,实现WebSocket其实不复杂,在普通的socket的服务器上添加一个握手协议,这个握手协议如果用脚本语言实现,非常简单,但用户C++实现就不容易了,我这里实现的基本上是C语言的版本,因为不想使用C++庞大的类库和模板。
3 j T9 s* } N" d2 w完整的windows版本socket握手实现:
% x* m% h& Q& O8 d! ^; w6 g- I I0 J6 V" R, @% ^3 z5 Q2 k1 v# ~
bool WebSocket::handshake(const char* src, struct handshake* hs){
/ g$ T' Q$ o3 \' }: `8 |4 m size_t src_len = strlen(src), i = 0 ;+ o& H, W* N- c. I' |
hs->resource = match_string(src, "GET ", 0x20); // 提取空格之前2 I1 M: j7 `1 l+ L! J
hs->host = match_string(src, "Host: ", '\0');
0 R9 p* k x$ z0 r, S hs->origin = match_string(src, "Origin: ", '\0');$ \, n7 U! b, m% u" X( F% W8 e% v
hs->protocol = match_string(src, "Sec-WebSocket-Protocol: ", '\0');
, x2 s* c+ k' T5 f9 B( p* L hs->key1 = match_string(src, "Sec-WebSocket-Key1: ", '\0');
4 c; k$ s9 g: T, i7 E+ F hs->key2 = match_string(src, "Sec-WebSocket-Key2: ", '\0'); 9 D7 q" F9 s8 \; }' e+ A; }& c
char key3[8]="\0"; // 获取 key3,即最后的8位字符
( j7 X' ~* ~" Q& [: I, W for (i = 0; i < 8; i++) key3[i] = src[src_len-8+i];
5 Y6 J6 `, c0 V( Z4 {( D6 o char digits1[64]="\0", digits2[64]="\0", c='\0';
' U! O# K- b U/ z% ] size_t spaces1 = 0, spaces2 = 0;7 |& n' G& ]6 Q
size_t key1_len = strlen(hs->key1);8 A9 q- L' E* D& C! L
size_t key2_len = strlen(hs->key2);
6 N% p. f8 Y1 l+ r7 E$ V short d1 = 0, d2 = 0;
3 w5 y) ? ^. g O8 W) b; D& d unsigned int result1, result2;2 \3 Y0 R( M$ R1 A: G$ ]- f
for (i = 0; i < key1_len; i++){
) t$ K0 R8 `) \9 k% x# S c = hs->key1[i];3 R' q1 \7 Y- K: _' h
if (c == 0x20) spaces1++;4 f) w7 d9 D- O$ d( ^
else if(c>='0' && c<='9') digits1[d1++]=c;
3 }2 l B" q% A8 @* i+ @! V) ` }
5 z. ^- E; ]# d$ C for (i = 0; i < key2_len; i++){ $ \9 T* U' h( ^1 P
c = hs->key2[i];1 t( K1 b6 Y/ t# s, A5 R$ d! k
if (c == 0x20) spaces2++;" c; w" r# O) }6 o; C$ n
else if(c>='0' && c<='9') digits2[d2++]=c;
* B8 C+ T+ _* O ^1 ? }
6 u; k4 a2 `+ H$ P& Z$ [1 U result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);
! P9 x1 }! E1 y% S result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);
3 v1 n* C a* T* H. h( } char chrkey1[4]="\0", chrkey2[4]="\0";
4 _8 ~: B% J9 V7 A" u5 M! e for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);
2 r/ b. m" q: ^# o( x' w6 | for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);/ V" _: Q w T. [3 x6 o
unsigned char raw[16]="\0", dig[16]="\0";
, N' M6 `) D2 e4 c" i/ H1 r; I // raw 表示未md5之前的字符串,规则就是前4位key1中的数字/空格数的整数值,, ~# A K4 Z& X& O) [; l
// 连接上key2的最后连接上头信息中的最后8位字符。
1 U! ?* {5 e/ x- X0 R memcpy(raw, chrkey1, 4); K% l/ u; B. ?7 J/ T/ {
memcpy(&raw[4], chrkey2, 4);9 A1 P$ L: r! E( J
memcpy(&raw[8], key3, 8);& W0 j. o4 [) i& r0 J( F. M0 |0 Z
//计算的md5值' v; R$ `" e {- b
md5_state_t state;* V0 ]& P! {8 D: _% W0 \9 m
md5_init(&state);
+ \( Z& c( l! x/ P" ~ md5_append(&state, raw, 16);% l0 }, r0 N; X/ s8 x+ ^$ y/ ^
md5_finish(&state, dig);+ P$ m* s* a/ r2 e: a/ R8 ^" k
: J9 D/ S% n0 l# W0 h$ s char handshake_str[BUFSIZ];
- ]. |* n( [$ M" W* b memset(handshake_str, 0x00, BUFSIZ);
9 `3 i* G$ {- E# ]! b char* handshakeFormat = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"/ d1 ^: D, g) M/ F$ B- x
"Upgrade: WebSocket\r\n"
; D8 n( O; |# h( g "Connection: Upgrade\r\n"
: y0 y8 }8 m0 I$ g3 \) c "Sec-WebSocket-Origin: %s\r\n" ) u5 I& \4 M# R
"Sec-WebSocket-Location: ws://%s%s\r\n". h, V( O: l( L% ` D
"Sec-WebSocket-Protocol: %s\r\n\r\n";
' k6 T! ^9 d" | sprintf(handshake_str, handshakeFormat, hs->origin, hs->host, hs->resource, hs->protocol);9 }6 n. U4 o0 ~! Z0 i7 P" |; @7 A
free_handshake(hs); // 释放handshake指针,已经不用了!
0 s& h* t5 G2 S* M char response[BUFSIZ];
# S9 ]1 p$ p2 [# C' H memset(response,0,BUFSIZ);+ a) o; Z8 H& X: e6 ~1 @
size_t j=0, handshake_len=strlen(handshake_str);
$ `- R, b6 g! u: U. g for (i = 0; i < handshake_len; i++) response[i] = handshake_str[i];. y0 o3 @! W' T/ N
for (j = 0; j < 16; i++, j++) response[i] = dig[j];2 x `3 i7 p4 |$ j( q1 K, `
// 这里的clientSocket就是连接好的socket对象了。
) N4 m4 Q7 U3 q1 { int sent = send(clientSocket, response, strlen(response), 0);
6 P) t% d0 A0 g3 r) W T+ T return sent>0;% o% ]5 \; O% ]# r
}
. a% M8 \% ?5 N- }/ U0 @目录结构:
" f6 B$ P+ F$ v0 I3 S! E% M5 gsocket/
8 X+ e p+ x9 s0 X socket.c6 e- J. n/ t: D5 h$ M' q
md5/% V- `$ O0 b$ K) i; ^# `' Z2 C( g
md5.h
+ {3 j$ l z, \( ]" d' N3 A1 l md5.c
. v5 k% W" ^1 o1 a- g2 Z库文件下载位置: http://sourceforge.net/projects/libmd5-rfc/files/ 总共3个文件,
( G9 G" ^ W) V7 y编译命令: gcc socket.c md5/md5.c -o socket.o
N) C3 @+ c& n2 {8 g GCC 4.3编译通过,执行 ./socket.o 可以看到打出的raw和md5后的字符串信息。 这里使用了一个网友用nodejs写的websocket实现版本,打出详细的头信息,然后复制到程序里,以测试生成的握手字符串是否一样。' Z I9 n0 X! }; L
// socket.c 代码。
1 d# \3 W; I0 C2 v* s0 U// web-socket example
3 o* h! U) }0 c#include <stdio.h>- B' w$ p, e' q5 u
#include <stdlib.h>
' b6 q1 u) A5 K9 \* T# W) T, C#include <string.h>
: v/ [; { }6 b8 q$ `#include <inttypes.h>
% A& Q' y# i/ |#include "md5/md5.h"
/ I* i# e& {! L% f/ v+ E
0 j4 Q' e- a+ O, V& r) p// #define BUFSIZ 512
9 H* O( V6 F2 Q2 d4 p( w! @' ] O
//定义handshake结构体变量
# M* {$ T6 \3 {struct handshake {) n' B4 m4 t2 N% X
char *resource;1 Q9 d/ p2 A# e) C
char *host; g& X+ L, F6 z, t
char *origin;5 ^8 h7 c( ?; Z% g
char *protocol;
8 @$ h& g1 U& O# k0 Z" z1 ~8 k char *key1;
) z" W+ S7 K5 X2 J% I: E2 C+ z char *key2;
, o, c. R8 z% E m+ T5 Y1 c, [};% f; \% U( t4 |# L7 |. q5 _) b1 l
& h. q- X* q+ o/ x% T; ]: I! r* w- b& X+ d' _+ f: w
//释放握手后不再使用的部分变量
4 _0 X! I, B* y+ i9 w/ vvoid free_handshake(struct handshake* hs){! h" ~; x- `+ h0 d2 H' {
if( hs->resource != NULL ) free(hs->resource);" {* e" W8 B4 U+ }( [
if( hs->host != NULL ) free(hs->host);
+ ?0 Q4 e$ g4 T% N. r if( hs->origin != NULL ) free(hs->origin);& h5 W8 U: T i# r
if( hs->protocol != NULL ) free(hs->protocol);
: g+ v# `2 f" R if( hs->key1 != NULL ) free(hs->key1);
" `, U4 z. K% M6 p4 Y+ Y: l if( hs->key2 != NULL ) free(hs->key2);
) N3 T* O; E% i7 x# U. n}
& `: a: l1 x, ~6 ~4 Z& F) I7 L: u( y4 j" H( Q
// 这里对上一篇的match_string做了点修改,* U7 u" J' \+ H6 T5 R
// 增加了一个end参数,这样不必提取出字符串后,再做剔除处理
% i1 ?5 g+ }$ K6 ichar* match_string(const char* src, const char* pattern, char end){
% a! Q. w0 d# b. v9 L$ G7 i char buf[BUFSIZ];* z2 W1 W1 e* d3 V$ f, q
memset(buf, 0, BUFSIZ);
& w! D3 Z9 |0 u7 y size_t src_len = strlen(src); * n \7 q; ~- N7 L
size_t ptn_len = strlen(pattern);
0 s2 h+ Q- K. N+ i$ x unsigned short b=0, p=0, i=0; 0 t! M& S$ p* ?5 \
char c='\0';2 Z9 }6 f) g7 M6 k' i/ g
for(i=0; i<src_len; i++){
/ R! d3 t+ w% M7 K c = src[i];6 i4 ?" ]# j% k* [) w" V) i
if(p==ptn_len){ // p==ptn_len 表示正在匹配中
4 U9 H2 n7 ]$ B7 l if(c=='\r' || c=='\n' || (end !='\0' && c==end) ) p++; // 匹配结束
3 k, C, h, |8 ]3 K% d8 [1 z( R else buf[b++]=c; // 匹配到的字符
5 B7 C8 r7 E0 t }else if(p<ptn_len){ // 为达到匹配要求. n5 f5 G. w5 u0 a& d
if(c==pattern[p]) p++; ^: @8 C% J+ s
else p=0;3 e1 {) u3 q' z7 E
}
5 R* \0 `# D N# |( a# Y$ f }2 R$ B5 A! o' i# e5 R
size_t ret_len = strlen(buf);* t0 ]8 O+ h) z7 \
char *ret_p; % ~2 i7 B1 r; V9 C+ A
if( ret_len>0 ){1 ?7 r | h8 C4 o: F
ret_p = (char*)calloc(ret_len+1,sizeof(char)); // 加 1 为了存储 '\0'/ T/ i/ j7 `, _; }5 Z
memcpy(ret_p, buf, ret_len);6 k4 v& Q/ r B8 n
}else ret_p = NULL;: N2 K" s+ N6 z! l* h7 E0 e& g* A8 x
return ret_p; 7 `8 s1 F3 W- M" A
}
) ]; o, [8 \ M# u( A
8 b" ]: k( D& i9 O// md5 加密函数,用的是网上一个实现的比较通用的版本。1 q6 y/ s; \. j! z
void md5(const char* src, size_t size, char* digest)
; M0 e, N) Z: ~! ]* }{
" ]9 s+ y- h0 S3 a. j% n9 @ md5_state_t state;
0 x/ B/ g1 U; n md5_init(&state);2 f0 D, a6 Y3 L w/ V
md5_append(&state, src, size);
4 I: h6 x% z+ ?5 D$ r1 I9 b7 s md5_finish(&state, digest);5 D) H* A( l( C/ Q) R, e
}9 s R6 _. d9 }! @6 O5 Y
3 X# D: N* B6 g A
void p(char*s, int len){5 S7 q! E7 d, M- n% r
unsigned short i=0;
" X1 l# ~, D$ U# a for(i=0; i<len; i++) printf("%c",s[i]);6 @8 O# A6 u/ `8 V
printf("%c", '\n');
) P6 n. Y) @) s) ~# C}
6 @- |5 t- f' q* D( k& ^
/ R5 \8 B- G+ l0 x* {, Q y6 ?: K Yvoid handshake(const char* src, struct handshake* hs){, ]. G1 U$ q# E$ m m9 s% I
size_t src_len = strlen(src), i = 0 ;
5 H* e2 d( P$ _+ t$ } hs->resource = match_string(src, "GET ", 0x20); // 提取空格之前* b7 F9 y& G; K7 r# c! P
hs->host = match_string(src, "Host: ", '\0');8 w4 M" _. x: T4 O' q% z
hs->origin = match_string(src, "Origin: ", '\0');' P# e+ U& u# L. D! o0 I" o: }$ J
hs->protocol = match_string(src, "Sec-WebSocket-Protocol: ", '\0');
" F5 z" G- N7 l/ n8 Z. P3 Q; y7 | hs->key1 = match_string(src, "Sec-WebSocket-Key1: ", '\0');, l! y$ k& \4 m
hs->key2 = match_string(src, "Sec-WebSocket-Key2: ", '\0'); 7 n2 l8 A0 z" c9 w3 _
// 获取 key3,即最后的8位字符
6 c4 ~ J% c; z r9 q char key3[8]="\0";! u k" Z- c8 @2 I$ U
for (i = 0; i < 8; i++) key3[i] = src[src_len - 8 + i];
8 w0 j# k! T. N; h char digits1[64]="\0", digits2[64]="\0", c='\0';/ V- T* ?. ]3 P. q: O
size_t spaces1 = 0, spaces2 = 0;6 k2 F8 b: p9 A* P) s
size_t key1_len = strlen(hs->key1);
% e( ]) B- S5 Y4 Z size_t key2_len = strlen(hs->key2);
9 m) b* v/ @' f/ E; h" C short d1 = 0, d2 = 0;
5 i3 j. u. S" S) w. q$ \9 f" S3 [" u unsigned int result1, result2;' d0 W- q6 E! ~+ f6 Z
for (i = 0; i < key1_len; i++){ ) s* e! x" o( h
c = hs->key1[i];. I4 t+ [" I+ V/ z+ g; t& W
if (c == 0x20) spaces1++;
7 V/ \0 S" Z+ l, ^ else if(c>='0' && c<='9') digits1[d1++]=c;
5 M- b, Q8 l% d2 W, y* t. } }
" d7 J0 \8 q: z2 S% r5 { for (i = 0; i < key2_len; i++){
3 o G- E; M7 N$ M0 n( e$ g c = hs->key2[i];- V: Q! q3 H+ G. V+ y5 `! P- J
if (c == 0x20) spaces2++;
8 d7 B0 u3 ?; O3 S* A2 V L' V$ N else if(c>='0' && c<='9') digits2[d2++]=c;
1 z' a9 M9 G. B/ T }6 d3 p d% F, [! g; ~! w2 Q
result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);1 G5 ~, K1 J/ d) f& v
result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);* }* \! G. z+ o( _2 o4 }9 M
printf("ch1:%s\nch2:%s\n",digits1, digits2);8 ]0 f; o& _+ O& V# d; P" e3 O0 }
printf("sp1:%d\nsp2:%d\n",spaces1, spaces2);, T; v9 p& }2 {, p! n
printf("d1:%d\nd2:%d\n" ,result1, result2);
# t; b( e+ W) @9 E unsigned char chrkey1[4]="\0", chrkey2[4]="\0";
% _, n( y& y: g/ t for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);/ E) I" G& g! p" |, }% E3 V
for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);; R1 S# W; w% D' V/ t6 r
printf("ch-key1:"); p(chrkey1,4);
, f% d( d9 ^2 u3 U* I/ w" O for(i=0; i<4;i++)printf("0x%02x ",chrkey1[i]); ?; ?. Z, [% K: B7 ^
printf("ch-key2:"); p(chrkey2,4);
3 D! c- T5 w+ e6 u for(i=0; i<4;i++)printf("0x%02x ",chrkey2[i]);
1 G; ?2 R4 O8 }; P2 l unsigned char raw[16]="\0", dig[16]="\0";) U& ?2 {; @3 w; J, v9 k
memcpy(raw, chrkey1, 4);, s* v; p% J& _% D# r8 e0 Q, A
memcpy(&raw[4], chrkey2, 4); |6 H( k( v* A
memcpy(&raw[8], key3, 8);& ?- m( ]/ x* p3 @
//计算的md5值
( |/ X7 b7 h$ f, P" O6 O+ |4 l8 w printf("\nraw:");
% O4 [; E7 o5 |1 u' r! I for(i=0; i<16; i++) printf("0x%02x ",raw[i]);; A7 D& p* X4 g' H
md5(raw, 16, dig);
: y& \; g$ |7 _& y" J printf("\nmd5:");& ^. }1 j; M7 J w0 i8 I
for(i=0; i<16; i++) printf("0x%02x ",dig[i]);
. ?! Y7 s6 N. z7 }$ O} F1 q1 C# A8 U1 c
& i( |/ C9 f- \ h J6 l, N- O) \
int main()* U: z w# i% E5 P
{
4 n( C( t8 u4 d+ T6 y, r# Q unsigned char msg[512] = "GET /pub/chat?q=me HTTP/1.1\r\n\ Q: o% }3 J! b! w5 q: z
Upgrade: WebSocket\r\n\, ]4 W$ ^& p [, O* a0 L
Connection: Upgrade\r\n\
A! W. i$ U+ _+ }' n3 d* Z Host: localhost:4400\r\n\: U, V- ~$ S0 v. d4 f
Origin: null\r\n\3 `' l2 r) P4 _; D3 D9 j
Sec-WebSocket-Protocol: my-custom-chat-protocol\r\n\5 g+ Y" ?1 l6 g/ d' R6 n" {
Sec-WebSocket-Key1: x EO2 59186 4 28\\dY 0+\r\n\
N) |+ ~ p" s- z Sec-WebSocket-Key2: 1 9 3 57695W 0\r\n\r\n";+ e/ Q3 L E. g0 t
size_t len = strlen(msg);2 A6 E" o; F5 l2 y
msg[len] =0x1f;
8 p! B8 Q8 [$ v. n" A1 G0 { msg[len+1]=0xf6;" }9 x4 J$ c8 N% j8 ^, `. }: H
msg[len+2]=0xf3;* S- l/ C- B& V% Q5 ~6 y$ b" ]
msg[len+3]=0x3f;6 h6 q1 X- w$ r) S/ \
msg[len+4]=0xc7;
, a+ L' k' Z% Y7 g# y9 u msg[len+5]=0x17;
0 X+ a' |- \3 R+ Y$ z3 z msg[len+6]=0x20;
( Q7 `; `5 {: `+ _" D0 S' x msg[len+7]=0x88;5 G' `$ m5 j% o
struct handshake hs = {NULL, NULL, NULL, NULL, NULL, NULL};
3 [& g3 W* O& j+ D1 Z handshake(msg, &hs);+ @, @( D. f! Q2 x: d8 y+ M
free_handshake(&hs);, n+ c* O* k$ v0 h. |' T
return 0;
0 G& ~( z7 q, ~; Q}" g- d: v9 }. e+ i( d
5 Z; M1 E6 F: `* c
+ m" J9 p5 }& Y/ U5 ?
测试的结果:
( n7 O6 e$ o$ x/ m* p" l7 ^raw:0x19 0xbf 0x73 0xa4 0x01 0x27 0x5f 0xff 0x1f 0xf6 0xf3 0x3f 0xc7 0x17 0x20 0x88 ) |( u3 p' f, G6 c& [% A; @& G$ n$ }5 c
md5:0x61 0x30 0x1e 0xe8 0x8a 0x17 0xaf 0x39 0xd6 0xad 0xef 0xb9 0x6f 0x00 0x0f 0x68 * y+ n5 l O6 ]2 b D( [2 Z
对比了nodejs的版本,握手部分生成没有错误。
: g- d1 | h l* k3 H; C7 W1 G8 B0 F |
|