找回密码
 注册
搜索
查看: 5768|回复: 0

VC++实现的WebSocket服务器端握手协议

[复制链接]
发表于 2012-7-22 18:23:54 | 显示全部楼层 |阅读模式
本程序是VC++实现windows上程序内嵌WebSocket的部分代码,因为想让浏览器和本地程序直接交互,最好的办法就是websocket,windows的exe程序内嵌一个websocket服务器端程序,浏览器访问localhost,建立交互,这种办法比做成插件更好,所以我采用这种办法来联通桌面程序和浏览器。VC++实现WebSocket的服务器代码,网上还是有示例的,不过基本上不能用,我找到的两个,一个是基于MinGW编译,还有一个是基于VC++2010编译的,还有一个libwebsocket,C语言实现的库,实在是大的惊人,最后,我决定自己实现,实现WebSocket其实不复杂,在普通的socket的服务器上添加一个握手协议,这个握手协议如果用脚本语言实现,非常简单,但用户C++实现就不容易了,我这里实现的基本上是C语言的版本,因为不想使用C++庞大的类库和模板。# Q- c( j6 v0 a) a
完整的windows版本socket握手实现:
( F, H7 t: p# H8 C9 t
5 D* E* P2 }6 h) h/ }1 v6 Nbool WebSocket::handshake(const char* src, struct handshake* hs){
1 D+ v7 k2 `: i+ [: i4 M        size_t src_len  = strlen(src), i = 0 ;
( t/ Y9 U  L4 {+ g# @7 y        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前/ C5 \- E' T3 H5 v' M" z! C7 n
        hs->host                = match_string(src, "Host: ", '\0');
9 `' S, C9 ^4 k* D        hs->origin              = match_string(src, "Origin: ", '\0');
5 v/ v. ?& v+ Z0 |0 I4 [, l        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');8 F6 M- h( }) o  t
        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');
. {$ @; t" R+ `        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0'); $ p2 |+ }9 Z* f' W
        char key3[8]="\0"; // 获取 key3,即最后的8位字符+ Z/ ?5 r* [& o1 i# \
        for (i = 0; i < 8; i++) key3[i] = src[src_len-8+i]; 3 C; S  ^  x9 e& g6 C' o2 G' W
        char digits1[64]="\0", digits2[64]="\0", c='\0';
5 y9 c0 i9 s) p, G, n0 y( K        size_t spaces1 = 0, spaces2 = 0;
( @! j% t: _) n" D6 z        size_t key1_len = strlen(hs->key1);
. O+ v4 w$ u# S* ?% C& N) p' W        size_t key2_len = strlen(hs->key2);
! s  A6 f9 L' ?; Q$ g) _  q( f        short d1 = 0, d2 = 0;/ ]6 Q4 o1 P( ]& _7 }# l+ c; M
        unsigned int result1, result2;* ~" G% w2 J/ K2 T
        for (i = 0; i < key1_len; i++){ % s2 @. ~( `/ B
                c = hs->key1[i];2 T4 N% i6 ~; |. d( e, Q
                if (c == 0x20) spaces1++;$ R5 h$ _/ s- Y& b4 T3 N- x4 r
                else if(c>='0' && c<='9') digits1[d1++]=c; - O9 C8 r( R6 h4 P
        }
/ Q/ G. `' ]4 s: m1 U        for (i = 0; i < key2_len; i++){
1 ?/ X5 @/ o: R/ s) l6 T                c = hs->key2[i];
$ P6 A+ C- H) _) a7 h. l' A                if (c == 0x20) spaces2++;
8 g0 A8 ?0 X: q! S                else if(c>='0' && c<='9') digits2[d2++]=c;
% y$ d: {; u+ f        }
7 Z1 E: X4 ~+ r& v* Z        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);6 C1 Y" h6 I# g0 {$ l
        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);1 a, r! U4 `; I- v- J2 j& [( y2 W3 T
        char chrkey1[4]="\0", chrkey2[4]="\0";2 d+ M) `1 d- S; o2 S( p
        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);- K9 l' f4 v' b+ s
        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);& f& q7 y/ D" [1 Y  P
        unsigned char raw[16]="\0", dig[16]="\0";
& C% h4 `( k* u3 Z9 O        // raw 表示未md5之前的字符串,规则就是前4位key1中的数字/空格数的整数值,# B9 A0 q; W8 n1 }  R8 t; x
        //  连接上key2的最后连接上头信息中的最后8位字符。, ~; U% L$ k( \9 a' P5 a. [
        memcpy(raw, chrkey1, 4);* @+ U  k2 r6 D# \4 S
        memcpy(&raw[4], chrkey2, 4);: z, ?/ ~) X' w# M+ Y
        memcpy(&raw[8], key3, 8);) |: r8 \" v* T5 e9 z6 E
        //计算的md5值
* R( i! X: L; m        md5_state_t state;0 |3 L9 x# a* k( J( `0 G7 l6 [& c
        md5_init(&state);* l9 A( _3 O0 T; g  O- ?9 o5 j
        md5_append(&state, raw, 16);
' i( y% ]; F6 q        md5_finish(&state, dig);
3 l6 ?( b8 X. p# Z  `# e        5 I! D, S$ `3 f! O& ^
        char handshake_str[BUFSIZ];3 x8 G* C3 w% }" ^" k+ Y  p# R" L* ^
        memset(handshake_str, 0x00, BUFSIZ);+ A0 N: C7 M/ K  o0 B
        char* handshakeFormat = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
1 y; ?& I5 p, n7 V1 \% J, o. X/ t                "Upgrade: WebSocket\r\n"
0 e9 E: A) R+ p$ {3 b% c$ ?. g                "Connection: Upgrade\r\n"
( l- Q. B) v' C$ r! F4 O                "Sec-WebSocket-Origin: %s\r\n" . {7 ]2 Y) b2 X( E0 x; D) C$ v
                "Sec-WebSocket-Location: ws://%s%s\r\n"% r1 k8 s; |6 k+ h, z9 D2 N& e4 @2 ?
                "Sec-WebSocket-Protocol: %s\r\n\r\n";+ n% x. T: D" \+ K$ J/ Y  R
        sprintf(handshake_str, handshakeFormat, hs->origin, hs->host, hs->resource, hs->protocol);7 [7 s7 Q) m9 i4 H# _
        free_handshake(hs); // 释放handshake指针,已经不用了!7 b& q$ {9 d3 _- H/ @' j
        char response[BUFSIZ];% V- M; l/ P1 b: g3 T$ b! b
        memset(response,0,BUFSIZ);
/ R6 M, m. u" \# k1 [        size_t j=0, handshake_len=strlen(handshake_str);) S  e, E& n( k
        for (i = 0; i < handshake_len; i++) response[i] = handshake_str[i];
, t; {* y& C' Z* }1 p( Q        for (j = 0; j < 16; i++, j++) response[i] = dig[j];- `% p: R; x% _
        // 这里的clientSocket就是连接好的socket对象了。
2 a% n, @& e! E& T! h9 W        int sent = send(clientSocket, response, strlen(response), 0);
! E& A( h' p7 R; G        return sent>0;: [1 z+ T' ~3 h$ g- p1 M: k9 \
}0 ]1 p! b" i6 H- l: ]2 {8 a
目录结构:6 H6 s- F3 C/ E5 _- D2 H& ]! ]
socket/) J, M7 d& W- G# \. ~1 M
      socket.c
7 `, b, \. K. _0 o3 e5 `4 T& B      md5/6 l1 S: w$ H5 r) p- M, X- e2 [3 [
        md5.h
/ r' B5 s! K7 h. T+ Y        md5.c
" w- Y- G% ]/ X9 b9 R: K& Q库文件下载位置: http://sourceforge.net/projects/libmd5-rfc/files/ 总共3个文件,: g7 D, X7 Y, z: f! z5 r  }
编译命令:  gcc socket.c md5/md5.c -o socket.o  
( M6 O& L3 l" Q% p0 _& `7 J GCC 4.3编译通过,执行 ./socket.o 可以看到打出的raw和md5后的字符串信息。 这里使用了一个网友用nodejs写的websocket实现版本,打出详细的头信息,然后复制到程序里,以测试生成的握手字符串是否一样。2 p: |: j8 o* w
// socket.c 代码。/ y1 p: z0 e; a9 B; v
// web-socket example
- p3 d$ _" m# u0 u  v5 r- y#include <stdio.h>
# W# ^! @* v% T" |+ F#include <stdlib.h>
& r0 U' V$ Q  [" _) P$ T  O#include <string.h>+ l, i" W; U: m" L4 {  a* {: L
#include <inttypes.h>
' s# Q4 o/ i) L) t3 F#include "md5/md5.h"
5 W0 O4 g& N: }+ g7 n4 A
$ W2 j+ ]+ g# M$ ?( T, k% O7 [8 ^# n// #define BUFSIZ 512
4 n1 W3 @! |" R0 q# x+ R
! g/ W" i+ f" S+ ~4 D1 B//定义handshake结构体变量6 Y7 ^. P9 _8 z* K+ U5 ^( ]( X
struct handshake {
" |. C% `: b" _: E, J, s( N        char *resource;
/ a( t+ M9 h+ C& M" Q: {% p5 `% F        char *host;1 `( d7 k% w$ R5 K
        char *origin;; h! ?4 C1 H! f3 c% X# h% M* Z/ d
        char *protocol;
  J6 z$ W( Y0 ]        char *key1;
/ ^& a5 X0 C) \# s4 V        char *key2;& E6 i6 y1 G  N5 ?
};: s- R0 x3 n/ o

5 {# h' h6 g  w4 w3 B5 i" O# w2 X: J/ w% x8 `0 H4 d8 v
//释放握手后不再使用的部分变量
, l/ s1 T. G* j2 Z5 @2 g' pvoid free_handshake(struct handshake* hs){! _, H, L1 l& W+ r9 h
        if( hs->resource != NULL )      free(hs->resource);5 E$ w' `8 K4 v8 _2 K0 U
        if( hs->host != NULL )          free(hs->host);
# T( M9 c- P+ Q. f7 ]. A0 {7 f6 ^        if( hs->origin != NULL )        free(hs->origin);; j: m3 g0 Y8 f  y  k4 b
        if( hs->protocol != NULL )      free(hs->protocol);
6 H9 X0 U. k! B& X% Z        if( hs->key1 != NULL )          free(hs->key1);
; B6 l& t" h! y4 q  _        if( hs->key2 != NULL )          free(hs->key2);3 Y- @- ?1 C' w5 J- h: O" g
}
4 S- f; a( z' W" C- \2 r7 y# N# p. S/ k) k! P
// 这里对上一篇的match_string做了点修改,3 C9 I; f# J# s$ t1 [$ W
// 增加了一个end参数,这样不必提取出字符串后,再做剔除处理* b$ v* V3 Y1 u7 t
char* match_string(const char* src, const char* pattern, char end){
1 t. [) ~0 `% v, _! Q  b+ U3 c$ e        char buf[BUFSIZ];8 U- F6 d  x" P6 m% U6 S
        memset(buf, 0, BUFSIZ);% a* b$ Z' l9 q6 C' B# q
        size_t src_len = strlen(src); ) ?- G& [& i! n# i  U; e/ Q8 y
        size_t ptn_len = strlen(pattern);; t0 [/ y$ Q, j6 C. ]7 n. X
        unsigned short b=0, p=0, i=0; 1 f8 C# ]6 X$ V5 }/ A
        char c='\0';
5 R8 o; }5 ?- Q8 P        for(i=0; i<src_len; i++){
" ^3 E7 O8 P" @                c = src[i];
2 i4 D3 ^1 d/ i- ^                if(p==ptn_len){ // p==ptn_len 表示正在匹配中
# a! G, r* p% Q" n4 J                        if(c=='\r' || c=='\n'  || (end !='\0' && c==end) ) p++; // 匹配结束0 g, c+ K; y, n. v- \6 A+ i
                        else buf[b++]=c; // 匹配到的字符
" `6 W+ C* g5 s7 L                }else if(p<ptn_len){ // 为达到匹配要求
' Z3 T3 d- l% |6 K' E2 f                        if(c==pattern[p]) p++;
0 l0 T  e( O# G- X                        else p=0;) G4 ]/ j  S" J/ l4 j! U: s
                }
4 E9 B2 O3 S& V; m        }) m$ l0 v" G8 k( `3 _
        size_t ret_len = strlen(buf);7 O# ^2 }. C5 c3 W" }8 @8 W/ K$ O
        char *ret_p;
) [* }0 m; v9 S1 N        if( ret_len>0 ){# s2 h1 z& i& H, g
                ret_p = (char*)calloc(ret_len+1,sizeof(char)); // 加 1 为了存储 '\0'
" n5 D0 {3 J# e3 k: w                memcpy(ret_p, buf, ret_len);7 o( g; M5 \2 }; ~8 a' I
        }else ret_p = NULL;) \+ j. |: I% a- l
        return ret_p;
. _4 w; p+ I( e0 w5 ?}
8 o! {7 N( ^' ?' ~: z- W$ R4 W4 |7 y: C' {9 ?% O/ J
// md5 加密函数,用的是网上一个实现的比较通用的版本。8 E0 c+ r; P& n" u
void md5(const char* src, size_t size, char* digest)
# {6 w* I$ f& N/ I{2 W3 _) E& f' r- C7 }; j; W* C
        md5_state_t state;
/ S: u2 Y' R) `  |4 \, _# B: Z        md5_init(&state);
0 @3 R* T6 H9 N, s( }, D3 @; J        md5_append(&state,  src, size);
8 Q4 N: ]  ~; o  z0 X: u$ N        md5_finish(&state, digest);& t3 X4 L1 T9 I$ W! V* R% l% w
}, p$ I, m( E" v! o

: k3 V4 S: ~; k: S! b; Nvoid p(char*s, int len){$ y7 W1 r0 {3 T' n) z! c( ^; C" b
        unsigned short i=0;
' r3 n0 N5 h2 g6 D/ c        for(i=0; i<len; i++) printf("%c",s[i]);
6 }) X, D8 e) F- {        printf("%c", '\n');
0 K) \6 q8 |$ z# `3 G; j}& f. A' k% w: G7 P& R: x, ]

) T: p; L- k5 c% G9 e/ Evoid handshake(const char* src, struct handshake* hs){
( Z  x* O1 o& b' L- e        size_t src_len  = strlen(src), i = 0 ;
3 S' v. }1 ~" O7 }        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前! k1 _+ _, \. G& }
        hs->host                = match_string(src, "Host: ", '\0');
; C/ o; o4 _6 R% a& `, `# c        hs->origin              = match_string(src, "Origin: ", '\0');& L$ r# V8 Y- y# h
        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');5 J. u' U4 i8 h
        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');! w/ p0 C$ }8 y
        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0');
1 N' x  t& I6 t$ e8 W0 j/ e        // 获取 key3,即最后的8位字符- L. J8 D7 b5 E7 J# G& D
        char key3[8]="\0";* W" R5 f$ _. Z5 b% A1 N9 T
        for (i = 0; i < 8; i++) key3[i] = src[src_len - 8 + i]; # E+ A$ l  e6 r
        char digits1[64]="\0", digits2[64]="\0", c='\0';
. _% K8 Y1 \+ d  v# ?, N        size_t spaces1 = 0, spaces2 = 0;
+ B3 A5 e: B2 {/ Q        size_t key1_len = strlen(hs->key1);
& E& [$ S9 Q3 @% O' B$ Z& k0 l% R        size_t key2_len = strlen(hs->key2);
  |3 q  }9 P7 t0 o5 n4 {1 h        short d1 = 0, d2 = 0;
' \" |. J& B( Z$ z: k/ ~( [        unsigned int result1, result2;
6 H; _2 m: J& t- }* G* ]* l, M        for (i = 0; i < key1_len; i++){ " K4 p% d# \9 W5 o
                c = hs->key1[i];
* H4 @& a4 u0 Y- c+ w                if (c == 0x20) spaces1++;
1 e- i  {1 Y/ ^9 k$ h& }. F; U* f                else if(c>='0' && c<='9') digits1[d1++]=c;
  k- t! }" S2 m* ?        }' d8 p. n- o2 Y6 [: Q8 E" G
        for (i = 0; i < key2_len; i++){
' f2 a1 o" K1 t) s0 f                c = hs->key2[i];
9 ?. ?; h4 Q0 Y3 L                if (c == 0x20) spaces2++;% _' n! o, q' Q
                else if(c>='0' && c<='9') digits2[d2++]=c;
/ r3 Y* @7 q) Q1 v; d        }
$ D' m( {" W5 s! B' q0 G! K& Y        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);
) \2 Z; K9 A, ~" V1 K$ d* `        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);/ o, ?$ E5 v: J. o6 H
        printf("ch1:%s\nch2:%s\n",digits1, digits2);
% u# }# F7 @# m- J+ l& D        printf("sp1:%d\nsp2:%d\n",spaces1, spaces2);
& g+ |; D$ h0 O3 \9 z        printf("d1:%d\nd2:%d\n"  ,result1, result2);
" @, ^# \1 G8 R# |( U        unsigned char chrkey1[4]="\0", chrkey2[4]="\0";
3 N  ~0 l0 S+ s        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);( V( Z0 H3 n  x% u* ~" @- s' }
        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);
" b; ~3 J4 J  p2 f" ~, S        printf("ch-key1:"); p(chrkey1,4);
7 \' t) f' I" A' |- w/ G( l        for(i=0; i<4;i++)printf("0x%02x ",chrkey1[i]);
2 M3 \& j; Y- E, S        printf("ch-key2:"); p(chrkey2,4);
) j. ]* \3 B! i, p        for(i=0; i<4;i++)printf("0x%02x ",chrkey2[i]);
: x3 N& \7 d: m4 z        unsigned char raw[16]="\0", dig[16]="\0";
2 l3 F! P2 |' c- g4 d% O( G        memcpy(raw, chrkey1, 4);
- E- O7 Z; H5 p+ Q: e        memcpy(&raw[4], chrkey2, 4);
2 ]% I" Z9 ^/ g- Z5 X        memcpy(&raw[8], key3, 8);
% Z. e/ i) `( u1 l& t' G8 V4 p        //计算的md5值
" A2 U, o; N9 u. l        printf("\nraw:");
4 ~+ s6 |5 G) \+ `+ l- _4 ?1 N        for(i=0; i<16; i++) printf("0x%02x ",raw[i]);9 g" N# Z+ h9 h/ m* f7 w  r
        md5(raw, 16, dig);9 t% w6 E2 k0 j7 v! W8 u: n
        printf("\nmd5:");3 K, A( V) w/ a& ^; U7 ]2 [- Z4 t" H, `
        for(i=0; i<16; i++) printf("0x%02x ",dig[i]);* \, V& T: U/ x' r4 f' L
}
$ j+ j: M. a  w" l4 E; ?
( c- Q- y* G- K# k8 n2 Y( |$ }3 _0 O1 p6 D/ @
int main()
; d# A+ Q  h6 F. E) K5 z{: O" c- W, i. [" Z  K3 |
        unsigned char msg[512] = "GET /pub/chat?q=me HTTP/1.1\r\n\. F. `" }# i* Y! Y; S
        Upgrade: WebSocket\r\n\
' l; F$ {/ M8 f1 V. Q# \5 E- s        Connection: Upgrade\r\n\" F+ F4 X2 j% ?( _" F
        Host: localhost:4400\r\n\
& E; O  w' E  e, a8 Q5 [        Origin: null\r\n\4 @+ o" J& L: P' _4 C
        Sec-WebSocket-Protocol: my-custom-chat-protocol\r\n\
  q. S% R3 W! Y" P. s0 i. @- n        Sec-WebSocket-Key1: x EO2 59186 4  28\\dY 0+\r\n\9 f& q2 q9 E& W* j
        Sec-WebSocket-Key2: 1  9  3  57695W    0\r\n\r\n";, k* d: [& f! k) R5 ^
        size_t len = strlen(msg);
3 T& p9 N( u& _7 z9 i- L: }        msg[len]  =0x1f;
( p6 M/ Q% B9 T2 @- M0 t        msg[len+1]=0xf6;* L, V) I; r$ B+ j& D' u2 _
        msg[len+2]=0xf3;
9 t8 Z! ^6 b, Z/ R! h- W        msg[len+3]=0x3f;/ [6 e% J! ]+ F$ S+ N( W! Z
        msg[len+4]=0xc7;0 ]$ ?- e! ^6 K6 [
        msg[len+5]=0x17;) t( J  _5 S  O
        msg[len+6]=0x20;
, D2 w8 d2 S# L- q5 ]3 h& V1 q        msg[len+7]=0x88;
* m1 S- [# _7 P0 L9 n" x! {) |        struct handshake hs = {NULL, NULL, NULL, NULL, NULL, NULL};) C  ~( ]0 _4 K+ \, F1 D, V
        handshake(msg, &hs);
6 v8 Q6 |4 Z' W& _$ e        free_handshake(&hs);2 `6 j* L) N2 n  a" C
        return 0;
% v+ j/ q) R1 j5 I}) C( T4 d% e2 f1 A' z7 y1 H
( }. O2 s) |# c5 p5 Z
! k. u. V  w9 q1 c% U2 T9 k  z* v
测试的结果:
" O1 Q, I  G1 V: vraw:0x19 0xbf 0x73 0xa4 0x01 0x27 0x5f 0xff 0x1f 0xf6 0xf3 0x3f 0xc7 0x17 0x20 0x88 - n1 K* S8 e2 o- g6 `5 G  D3 k
md5:0x61 0x30 0x1e 0xe8 0x8a 0x17 0xaf 0x39 0xd6 0xad 0xef 0xb9 0x6f 0x00 0x0f 0x68
) K" [( Q" _6 k- }6 D对比了nodejs的版本,握手部分生成没有错误。
/ s. m* u! O. ~  k6 O4 j
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|小黑屋|宁德市腾云网络科技有限公司 ( 闽ICP备2022007940号-5|闽公网安备 35092202000206号 )

GMT+8, 2026-8-15 08:02 , Processed in 0.023669 second(s), 19 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表