找回密码
 注册
搜索
查看: 5680|回复: 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++庞大的类库和模板。* k$ g, U9 \# n- q# U: Y
完整的windows版本socket握手实现:1 T+ k3 a- d9 V( c; N  d
: M1 }6 J# u+ m0 i) u
bool WebSocket::handshake(const char* src, struct handshake* hs){: K# U( J% l9 F4 Y$ n3 l
        size_t src_len  = strlen(src), i = 0 ;
- i, P7 U: F% v0 z3 E& S" V5 @        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前( i. R4 d  _8 M4 F% P- Z
        hs->host                = match_string(src, "Host: ", '\0');' u( z. _" V5 I8 p0 j
        hs->origin              = match_string(src, "Origin: ", '\0');
. U2 F  n; N. e9 @2 v$ k        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');
3 B/ y/ \; ?! W+ i+ B        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');" a0 {, c: S2 c1 `6 L; J
        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0');
8 r) l6 D; S! a/ y        char key3[8]="\0"; // 获取 key3,即最后的8位字符
. G( p5 a! ]3 h* `0 ^. P1 {/ _        for (i = 0; i < 8; i++) key3[i] = src[src_len-8+i]; 7 d7 V/ E- ?# X  c( s" H) A
        char digits1[64]="\0", digits2[64]="\0", c='\0';
  h) T' F1 e3 k        size_t spaces1 = 0, spaces2 = 0;
- g$ y" }; F5 }) A/ b        size_t key1_len = strlen(hs->key1);
0 M, l2 K4 l2 ]/ }5 g        size_t key2_len = strlen(hs->key2);  p) f. L' g9 |7 z3 C
        short d1 = 0, d2 = 0;+ T: i0 [' D" D/ h, |
        unsigned int result1, result2;
: o& G+ }/ I& R. V/ l  |2 C        for (i = 0; i < key1_len; i++){
  F$ }* V& y. T6 @8 I4 q) p4 ]                c = hs->key1[i];0 [9 i+ S: Q$ Q! D% a" B! a# w; z
                if (c == 0x20) spaces1++;2 g- s$ A  G& R  L
                else if(c>='0' && c<='9') digits1[d1++]=c; ( m" j0 ?7 @, {4 ~3 z( e- @
        }- |4 p, D, g" z" k6 l
        for (i = 0; i < key2_len; i++){
: x' [# Z4 ?& `                c = hs->key2[i];
* G2 s3 R7 T, Z- h                if (c == 0x20) spaces2++;
, w1 F4 @/ F; N. c( n2 }: f' C                else if(c>='0' && c<='9') digits2[d2++]=c; & ~5 i8 P$ g+ J: t; `+ z
        }
! F; P  x* f1 O  M4 s2 F3 n        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);# [# ]8 S( A' m  h: @
        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);
7 E, S1 F# i( m: @2 Z/ T2 R        char chrkey1[4]="\0", chrkey2[4]="\0";
: X6 a4 G/ x; H        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);
$ J: f4 Z6 _9 w5 v" l& W, K2 ~        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);! {* k8 K2 E) z. g
        unsigned char raw[16]="\0", dig[16]="\0";" n# q3 N7 `1 c8 {6 J3 K
        // raw 表示未md5之前的字符串,规则就是前4位key1中的数字/空格数的整数值,. s. W; t! j. G# c7 {9 W
        //  连接上key2的最后连接上头信息中的最后8位字符。% [7 T1 m7 X9 X/ B' K0 V( A) ^# l
        memcpy(raw, chrkey1, 4);7 i, O9 q9 a+ \8 V1 @
        memcpy(&raw[4], chrkey2, 4);& v* b4 h' w5 X
        memcpy(&raw[8], key3, 8);
, U) e% X0 W0 G        //计算的md5值
1 ^0 n4 D- c/ n, Q        md5_state_t state;* K4 G- _) G+ k) W* d  y( s
        md5_init(&state);
1 T% |, K8 N! G" c8 n# A! j0 e: U        md5_append(&state, raw, 16);# _# P, n; a9 m& ?0 k" x5 O
        md5_finish(&state, dig);
3 p! a* [' z, Y6 n        & L) z: m( o- b! a
        char handshake_str[BUFSIZ];  m. p" r1 s, T8 m" T% W# d
        memset(handshake_str, 0x00, BUFSIZ);
2 Q) M1 z, o* i: H8 {        char* handshakeFormat = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"$ U$ b  c( U, C3 q( i. |$ l
                "Upgrade: WebSocket\r\n"- T/ U% t7 Y7 _1 c' h
                "Connection: Upgrade\r\n"
& ^! e: `1 ^7 ^  L0 |3 [4 I                "Sec-WebSocket-Origin: %s\r\n" 3 T4 Q- W, @" Y* l9 A, a1 a
                "Sec-WebSocket-Location: ws://%s%s\r\n"4 s  {& d9 ]" |+ U3 |
                "Sec-WebSocket-Protocol: %s\r\n\r\n";+ @7 v( f) r. R7 q+ J: _
        sprintf(handshake_str, handshakeFormat, hs->origin, hs->host, hs->resource, hs->protocol);
! M. w$ Y4 E9 [+ @6 x  P( t        free_handshake(hs); // 释放handshake指针,已经不用了!
% x5 L% G, p2 h+ R" K- n! l        char response[BUFSIZ];  P, h, c+ x% @8 H0 |
        memset(response,0,BUFSIZ);
! s5 `* f/ z; J; d        size_t j=0, handshake_len=strlen(handshake_str);0 g: C5 Z1 a! Y6 q- x# Y2 n% a
        for (i = 0; i < handshake_len; i++) response[i] = handshake_str[i];
* p- j4 n1 L1 e/ C3 N+ @        for (j = 0; j < 16; i++, j++) response[i] = dig[j];( k+ \6 ]5 ^8 [0 o
        // 这里的clientSocket就是连接好的socket对象了。
) O& L* q: H: R: x8 f        int sent = send(clientSocket, response, strlen(response), 0);9 X" L4 n2 p9 D
        return sent>0;
, u5 i* R# U- @/ K8 @}  m, l1 w% S( ]( Y* X; o
目录结构:7 L+ s9 Y. y4 q$ X4 b. ^* v& |
socket/' g; G& W) y9 Y; W. ^
      socket.c
3 e; {* y$ N4 q: c1 ~2 c0 d      md5/4 Y8 K3 I+ ]0 E: `4 C
        md5.h
8 J5 c1 s9 B6 K. Z: C$ ~3 G3 u1 b: O        md5.c/ n1 P4 P, T# v& V6 F& f2 l9 z
库文件下载位置: http://sourceforge.net/projects/libmd5-rfc/files/ 总共3个文件,  f% Y% {6 v9 \/ A3 ~
编译命令:  gcc socket.c md5/md5.c -o socket.o  
- {' {0 w: F% w* }+ z' h, z9 S3 ~ GCC 4.3编译通过,执行 ./socket.o 可以看到打出的raw和md5后的字符串信息。 这里使用了一个网友用nodejs写的websocket实现版本,打出详细的头信息,然后复制到程序里,以测试生成的握手字符串是否一样。
. i6 a, j7 _) J, H, i$ U9 A// socket.c 代码。
3 q( u3 _; H0 \6 P: J// web-socket example / y) M% f) m% g4 Y" A9 E+ u' g- R
#include <stdio.h>: a5 l% d; A% \3 v
#include <stdlib.h>
' D4 g+ s9 k! [" ?8 R! g#include <string.h>" \7 `" ]& i0 L- {# n/ q
#include <inttypes.h>6 U# ?8 ?/ w8 x$ F+ @
#include "md5/md5.h". U8 j! Z1 d& P; J

; l1 H2 f9 J; ^6 g// #define BUFSIZ 5123 X8 h8 H7 c/ i- R

2 _; u$ E. P0 i9 ]3 y. @- \//定义handshake结构体变量
9 v2 g2 ?$ Q8 X) B( ystruct handshake {
2 E  j$ Y8 M7 d9 A/ x, E; D        char *resource;
8 J8 |2 i7 k6 k        char *host;
; A% ~7 t' \3 j" i6 l        char *origin;8 v% k+ |( G  k6 _; \4 F! {
        char *protocol;
  R# M( {1 z3 C  D5 S# @( }        char *key1;
! O% W* ~! \+ I3 Z) q        char *key2;
' _9 y+ s; ]3 N! j2 g0 |- D8 r$ M( v};
/ w& ]5 j4 K" y9 p8 _, B5 `  c" g+ Z6 H. |
3 z# e! ?2 a" w) K
//释放握手后不再使用的部分变量
% z9 A+ C5 C5 N0 j1 B. Vvoid free_handshake(struct handshake* hs){$ A! {1 f4 M1 P( g1 b
        if( hs->resource != NULL )      free(hs->resource);; \; {/ c) A6 z# t
        if( hs->host != NULL )          free(hs->host);
9 I4 ~- W0 R* H$ ~6 ~' f% ]        if( hs->origin != NULL )        free(hs->origin);& O4 ]( M" j+ q9 m
        if( hs->protocol != NULL )      free(hs->protocol);: K$ m% h9 B0 l" m* l+ }' r8 o
        if( hs->key1 != NULL )          free(hs->key1);% {1 r6 j4 L8 S% }! X. l
        if( hs->key2 != NULL )          free(hs->key2);
$ n/ V4 y' N" K9 r, {/ o- f}" i2 u3 {9 N, A6 M5 U1 \
( u* V2 ~- M. J% \) c4 @' t
// 这里对上一篇的match_string做了点修改,
# `/ Y) F8 d3 Z; G2 ?, u$ |9 _5 J// 增加了一个end参数,这样不必提取出字符串后,再做剔除处理  k0 W8 ?; {+ S+ C' t
char* match_string(const char* src, const char* pattern, char end){" o) x* @3 |5 G1 P3 b  x
        char buf[BUFSIZ];9 K% M: Z  O* N  u
        memset(buf, 0, BUFSIZ);$ e) t/ d- `4 G$ z$ |2 g' e) F
        size_t src_len = strlen(src);
1 F3 F0 i3 _  C# S# T0 i        size_t ptn_len = strlen(pattern);9 l& J" b3 @' Q  y5 k( Y3 y8 F6 K7 v
        unsigned short b=0, p=0, i=0;
1 E0 B6 K9 E* v* ~        char c='\0';
4 r' ~' Z8 N7 n1 C        for(i=0; i<src_len; i++){. z' d6 E* E8 A2 r' |3 v
                c = src[i];
  r6 D% Z& e- m$ w0 J7 |# [9 k& v. [3 y, K                if(p==ptn_len){ // p==ptn_len 表示正在匹配中
; C; c1 X( r) H                        if(c=='\r' || c=='\n'  || (end !='\0' && c==end) ) p++; // 匹配结束9 r: m0 U5 c* C4 v; h# F
                        else buf[b++]=c; // 匹配到的字符
% d5 k  `3 Y. k7 ?5 ]6 Y% ~                }else if(p<ptn_len){ // 为达到匹配要求$ c0 c: v  U7 h9 a1 A- ]
                        if(c==pattern[p]) p++;! G0 ^. q8 _+ q8 R" t1 L" A
                        else p=0;8 N- _) Y4 z( I
                }
8 @& g' l. D& m1 F' o& X: {        }$ K  k% v/ ~; `( ?# z7 J
        size_t ret_len = strlen(buf);3 Z/ ]: V$ Q/ t2 q( s7 S' M/ u
        char *ret_p;
4 J+ F+ K* t- x" ~/ I, q- h4 e9 c        if( ret_len>0 ){6 P% t, c; o# T
                ret_p = (char*)calloc(ret_len+1,sizeof(char)); // 加 1 为了存储 '\0'! N, r$ f9 x3 j
                memcpy(ret_p, buf, ret_len);2 F0 d3 T* m1 U4 B
        }else ret_p = NULL;
! s, r0 y  [/ T        return ret_p;
( a5 m$ x2 C. e}
7 Y4 o% t8 G% Y
0 D! l% E. c  H2 k* ]8 r. ~& T% P// md5 加密函数,用的是网上一个实现的比较通用的版本。
5 k" C- k- T$ g, }void md5(const char* src, size_t size, char* digest)
& \8 }# i% m( q: `% v5 y2 Q: s{3 v# H% Z9 p2 o' ~
        md5_state_t state;
: k/ A' p  a/ a- @. w% Z  N" j6 K- Y        md5_init(&state);
) e  B3 {1 O' T! b! k6 D$ ]        md5_append(&state,  src, size);+ ^% ^' ^  U, o$ V( K, k9 A
        md5_finish(&state, digest);( Z+ v: |% h, D
}& x' M% q3 C$ D

4 T5 I  E: Y1 H6 G; d: Fvoid p(char*s, int len){
: ~; K* r! J/ Z$ e* k        unsigned short i=0;* X- ^% h* H9 b+ ?  |* w
        for(i=0; i<len; i++) printf("%c",s[i]);
( d! ~; z6 X3 z1 U( K: @3 D7 ]        printf("%c", '\n');7 \( o2 q; p, X+ |" b- L! w% F
}  ~! k1 w* h; [; R% m; M3 k' P

/ [( a8 S7 |2 i$ W; m3 f( Ivoid handshake(const char* src, struct handshake* hs){6 w! q, _' Q5 a& K
        size_t src_len  = strlen(src), i = 0 ;5 j/ V5 g+ \1 `8 u2 m7 Q2 E
        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前
1 ^8 n# ]# O/ C" m0 ?( S6 m" L        hs->host                = match_string(src, "Host: ", '\0');
' x# k+ G# K# _+ \* N/ a$ g* |        hs->origin              = match_string(src, "Origin: ", '\0');
. x% E0 I* R: X4 r+ J4 ]        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');  v! U2 p! t2 q
        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');* y8 k. ?& l3 [3 t( f+ b
        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0'); % _* C) v; ~# ]7 T7 E; \
        // 获取 key3,即最后的8位字符+ s& ^8 O# f( j( H6 o
        char key3[8]="\0";
+ x1 }  b+ P( o! ?3 ^        for (i = 0; i < 8; i++) key3[i] = src[src_len - 8 + i];
* R( Y: F/ T& [+ P1 O        char digits1[64]="\0", digits2[64]="\0", c='\0';4 Y$ e; I' U* i9 `6 k9 l
        size_t spaces1 = 0, spaces2 = 0;
0 Y0 L3 M2 _9 ]# ~        size_t key1_len = strlen(hs->key1);7 }/ \/ V" a$ y. x0 S' d# U8 Y
        size_t key2_len = strlen(hs->key2);: H& h# F8 X8 g2 {& [5 F
        short d1 = 0, d2 = 0;* b0 H* \; ?9 F- O  x( @+ v% c
        unsigned int result1, result2;/ ^0 f2 A: B/ s6 X; V# ^6 v
        for (i = 0; i < key1_len; i++){
) A2 t& V7 s2 g, S; ]) W6 U                c = hs->key1[i];
% q5 |$ E, C' r( c, G' T4 N7 m                if (c == 0x20) spaces1++;" ]* d. b! N+ Z. A3 T: ?( N, B
                else if(c>='0' && c<='9') digits1[d1++]=c; ! O# r" [  D7 f: z
        }
; V. ]) A, e/ e  E/ F7 @8 |9 y        for (i = 0; i < key2_len; i++){
$ Q) I/ p2 `: W                c = hs->key2[i];
& H+ R0 Z6 M! w. C0 o7 t3 @                if (c == 0x20) spaces2++;+ c3 O5 h7 F" ^4 M
                else if(c>='0' && c<='9') digits2[d2++]=c;
3 @- @& Y& O6 g, T2 X, ]( u5 D( D        }! j- W4 B8 D) U% k: H
        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);* u: V7 F9 v! c* [0 {! G
        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);
+ b* L) m1 y2 Y; T" t- n# A$ U        printf("ch1:%s\nch2:%s\n",digits1, digits2);
9 ?; j3 e/ ~' S: s7 g, A( F        printf("sp1:%d\nsp2:%d\n",spaces1, spaces2);+ k0 F* \& P/ H# E: K- y+ \; ?" g
        printf("d1:%d\nd2:%d\n"  ,result1, result2);
- z  H5 `, `% @( G1 C1 ^3 |$ w/ i        unsigned char chrkey1[4]="\0", chrkey2[4]="\0";2 A1 Q# s. J. f" {+ ]
        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);4 r) N6 O: M# S
        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);
2 N2 ?' l6 c9 P1 s4 e/ k/ N' q        printf("ch-key1:"); p(chrkey1,4);
4 L! `& i+ ?4 l% j        for(i=0; i<4;i++)printf("0x%02x ",chrkey1[i]);
9 _( p1 [$ T' O6 h+ C        printf("ch-key2:"); p(chrkey2,4);/ i" o4 x+ w* p& Y
        for(i=0; i<4;i++)printf("0x%02x ",chrkey2[i]);
4 U$ h" G* m4 q! L2 v% T$ B        unsigned char raw[16]="\0", dig[16]="\0";
; O; d; J+ x3 M6 `2 B- q# M5 K        memcpy(raw, chrkey1, 4);& u- a0 A' X+ }4 }0 p. @
        memcpy(&raw[4], chrkey2, 4);
) ^8 [3 U  ?* t3 w2 v        memcpy(&raw[8], key3, 8);9 X3 W1 o# S6 F
        //计算的md5值
3 i2 h1 J8 K+ y5 t4 J5 B1 u! @        printf("\nraw:");1 t& t2 C: ]+ \( H! I( @' `
        for(i=0; i<16; i++) printf("0x%02x ",raw[i]);
$ i' j! V" ?* Z; b        md5(raw, 16, dig);+ @1 J$ Y7 J: d0 [! X; h% O* w+ e
        printf("\nmd5:");
8 a/ v8 O1 d) C        for(i=0; i<16; i++) printf("0x%02x ",dig[i]);$ e  c* s* J6 X- [, X& a
}  m% i& g0 p( y3 b, W7 t

" {3 ]$ m8 A7 T. L9 O3 @
( T+ N( p, e" Q" `# b2 k3 E5 Kint main()9 p8 V9 r% L: Q1 c0 s% W
{/ F' R4 H. D, F6 ], H" T; C# R4 _; h/ z) d
        unsigned char msg[512] = "GET /pub/chat?q=me HTTP/1.1\r\n\* e% _. X5 b6 d1 t! S
        Upgrade: WebSocket\r\n\. P  J7 @  a5 s+ f2 V; a; E
        Connection: Upgrade\r\n\
1 r+ H* Z! l6 Y9 p# c/ Z        Host: localhost:4400\r\n\4 N2 M) d, W. ?$ _0 @& e7 @
        Origin: null\r\n\" y) A: ], y, \" S3 _& {  A+ l( o6 C
        Sec-WebSocket-Protocol: my-custom-chat-protocol\r\n\
2 t+ ^4 m. a9 v# N& _        Sec-WebSocket-Key1: x EO2 59186 4  28\\dY 0+\r\n\
7 {  A/ N' z( x. v& \        Sec-WebSocket-Key2: 1  9  3  57695W    0\r\n\r\n";
% [& i: i* R5 o! v$ }! n- g        size_t len = strlen(msg);
1 ?# ]4 j" G; v- a        msg[len]  =0x1f;% j; F  V& m: U" Z/ I
        msg[len+1]=0xf6;' J; k+ Y3 j1 |1 Z
        msg[len+2]=0xf3;6 X. j% i5 E' f# S$ T2 `( Y
        msg[len+3]=0x3f;
# R  w* Y9 R+ R1 i8 D  _        msg[len+4]=0xc7;4 i+ ~! D8 j, v) m- z$ i# Z
        msg[len+5]=0x17;4 R, A( s/ j6 ^# L
        msg[len+6]=0x20;
7 R0 t% t0 t, s. [6 ]7 R        msg[len+7]=0x88;
) n* m; A* V0 W8 W. w        struct handshake hs = {NULL, NULL, NULL, NULL, NULL, NULL};! ~( h" w" {: n' _* n4 H5 L
        handshake(msg, &hs);
: i! [# n/ q' h' v. g/ s# x# I6 `        free_handshake(&hs);
" G5 x0 M& L* v9 h' m  w7 P        return 0; " S3 x+ `* d* J& v2 @' e9 J* k
}
5 |8 P9 q) ?5 j
( W, T( _/ y4 U2 B: o0 J7 u* X
, ^  e: f$ h! r/ j5 i4 Z测试的结果:! j7 D+ K& X' _  I
raw:0x19 0xbf 0x73 0xa4 0x01 0x27 0x5f 0xff 0x1f 0xf6 0xf3 0x3f 0xc7 0x17 0x20 0x88
6 M8 O4 D3 z! \, a) |9 K; Y7 dmd5:0x61 0x30 0x1e 0xe8 0x8a 0x17 0xaf 0x39 0xd6 0xad 0xef 0xb9 0x6f 0x00 0x0f 0x68
" J$ A* L) l$ Q  v" S1 k* y4 z对比了nodejs的版本,握手部分生成没有错误。
; F  v: g% \. r
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-6-18 08:01 , Processed in 0.019383 second(s), 17 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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