找回密码
 注册
查看: 5835|回复: 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++庞大的类库和模板。
! q9 p1 N# b2 y, f. a, r完整的windows版本socket握手实现:
# I' [1 }! s! e. E' w
$ c- m2 ~! j5 y/ E0 X. b8 ~bool WebSocket::handshake(const char* src, struct handshake* hs){
1 e. l7 q( e+ t) K6 E2 S. r* G        size_t src_len  = strlen(src), i = 0 ;* ?2 i/ O% M% T5 b5 d4 ~
        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前
) B6 z1 h) x+ T7 n9 s3 C% ]        hs->host                = match_string(src, "Host: ", '\0');
$ l& Y  N3 ]8 n4 [# X        hs->origin              = match_string(src, "Origin: ", '\0');7 d  B2 z( o3 N1 ~5 b
        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');. j5 Y& H9 V4 C8 M! X2 I
        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');; m9 s1 N% A2 o$ \" s1 \8 M5 ]7 @
        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0');
0 J9 {7 d9 m% c/ W6 S        char key3[8]="\0"; // 获取 key3,即最后的8位字符
; l& X  R' s# b4 j" f- E. s" p        for (i = 0; i < 8; i++) key3[i] = src[src_len-8+i];
% G2 I0 k3 ^0 v6 w- J% @        char digits1[64]="\0", digits2[64]="\0", c='\0';% q5 o5 {5 S  n, t  J
        size_t spaces1 = 0, spaces2 = 0;
5 y, H' i7 ]" M+ @) M        size_t key1_len = strlen(hs->key1);) a5 L! F/ s) \' g! M- i# Q
        size_t key2_len = strlen(hs->key2);- ?9 A, J& a+ `' J. U6 O) Y- O! }; y
        short d1 = 0, d2 = 0;
% O& v, h5 K. J, Z" w0 n& s* D        unsigned int result1, result2;2 N5 ^$ Q  A$ O& s# z" F5 |) {! u& ?3 L
        for (i = 0; i < key1_len; i++){ 5 @4 l$ e, K3 y/ L, |% r
                c = hs->key1[i];2 a3 \; d7 O3 i8 {, U
                if (c == 0x20) spaces1++;5 A' R) F6 ]! t4 o5 C( @7 `
                else if(c>='0' && c<='9') digits1[d1++]=c; 6 x' O5 p0 H7 g8 @: E$ E
        }
6 ?: ^0 n: ?& c        for (i = 0; i < key2_len; i++){ ' L: f: p4 A: G* P
                c = hs->key2[i];
& R5 U) `* K" g+ r+ ?- R2 j                if (c == 0x20) spaces2++;4 [( H' T$ a3 }& ]6 u6 y" H, i
                else if(c>='0' && c<='9') digits2[d2++]=c; & v8 H" p) F% }$ o% @
        }4 H+ c) O( `: @6 }
        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);: D' }* t  j( \
        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);
" y& c" x6 ]6 E4 Y3 h        char chrkey1[4]="\0", chrkey2[4]="\0";
) S! O7 o. I- C2 p; {) ?8 X5 z        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);
/ n' a, m5 E% v, r        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);. X; f0 B' j" L& \# h) ]: p, V0 u
        unsigned char raw[16]="\0", dig[16]="\0";; s6 g3 ~  C& p: d4 @9 K
        // raw 表示未md5之前的字符串,规则就是前4位key1中的数字/空格数的整数值,* @  o) N6 X7 ]7 T; f/ l0 {/ I2 y
        //  连接上key2的最后连接上头信息中的最后8位字符。
5 ?1 k0 t+ Z- `5 ?* v        memcpy(raw, chrkey1, 4);' H& F. d3 E8 y. F+ L& M. A& C& I
        memcpy(&raw[4], chrkey2, 4);
- M( I9 B6 v6 X" A: |# F9 O( ^        memcpy(&raw[8], key3, 8);
7 ?/ g' y, ~- L' ]4 ~: q( c; J        //计算的md5值5 |. p! D9 V$ ]6 y* H
        md5_state_t state;
' ^7 |& K* \4 y7 {! W        md5_init(&state);
% O  a3 {1 M# q        md5_append(&state, raw, 16);
, N1 c  o) a% Z9 u) v/ n: Q* d        md5_finish(&state, dig);
& r, Y3 K/ f4 o5 R        6 N2 f3 T- C' N1 I! P
        char handshake_str[BUFSIZ];$ \+ }; C( O. s$ M: a
        memset(handshake_str, 0x00, BUFSIZ);
' J: c5 {% h, ?0 \% i        char* handshakeFormat = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"/ y& P: \) l) G  H
                "Upgrade: WebSocket\r\n"
, k6 D2 u4 \' G) E. G: c+ l* T                "Connection: Upgrade\r\n"( W( ?) T' B) _! y. Y
                "Sec-WebSocket-Origin: %s\r\n"
8 ~3 K. J9 e: K" H                "Sec-WebSocket-Location: ws://%s%s\r\n"  |/ V! [  O+ W# A$ x9 B9 ~
                "Sec-WebSocket-Protocol: %s\r\n\r\n";
( d  n' ?) X6 q+ b        sprintf(handshake_str, handshakeFormat, hs->origin, hs->host, hs->resource, hs->protocol);; Z) R6 @2 Z8 X( ~! d4 W
        free_handshake(hs); // 释放handshake指针,已经不用了!+ [1 F& p2 l( \9 i- o+ ~5 B
        char response[BUFSIZ];  e0 D1 R9 [! V( n8 F
        memset(response,0,BUFSIZ);! N( f6 ]& c, M- `
        size_t j=0, handshake_len=strlen(handshake_str);
- B9 \2 k/ Y( p' o$ _$ n        for (i = 0; i < handshake_len; i++) response[i] = handshake_str[i];
' _0 B! z4 d) h& ~/ i. m        for (j = 0; j < 16; i++, j++) response[i] = dig[j];
& r0 b& e# B, C7 }        // 这里的clientSocket就是连接好的socket对象了。
' {) ]$ K3 B9 E        int sent = send(clientSocket, response, strlen(response), 0);
! i9 P7 G& j) m+ g% C/ u# B        return sent>0;
6 E* Q3 g2 v% f. \}
  x8 @" [7 I7 a目录结构:
, f  b  e( _$ s# z; t; W6 w: Rsocket/
! }. i- U9 ]. E3 y5 `7 f      socket.c; i3 L  N4 ?8 c, K7 r
      md5/
3 e1 W) l  G2 ?# A1 C  Z        md5.h5 D! {7 M5 r( G. y1 ^
        md5.c
; s+ s, {! P0 Y% k库文件下载位置: http://sourceforge.net/projects/libmd5-rfc/files/ 总共3个文件,: f- H! ~( K: }; y6 @4 c0 F& X; T
编译命令:  gcc socket.c md5/md5.c -o socket.o  % l: J' b$ h! |1 J0 P( k/ ]
GCC 4.3编译通过,执行 ./socket.o 可以看到打出的raw和md5后的字符串信息。 这里使用了一个网友用nodejs写的websocket实现版本,打出详细的头信息,然后复制到程序里,以测试生成的握手字符串是否一样。/ ?$ [5 \  I2 ]
// socket.c 代码。
6 J7 R3 Q' B' S! ]/ P// web-socket example   i& a! u  X) Z
#include <stdio.h>
' A& \, m# P1 |5 b2 {- c! r#include <stdlib.h>
$ S4 T' K' b& [# i0 J, {#include <string.h>% q5 {3 s. c) Q* |2 N( p. H
#include <inttypes.h>
  p: N8 x$ X; H2 W; h2 y, T#include "md5/md5.h"
3 y6 B. Y- S, j. R% A- h; p) Z- b& x; g3 L) P0 D7 H' F5 z. P
// #define BUFSIZ 512, g$ ^. |! R# r0 A0 D
' b# k# U0 k, c" c4 v0 d% U
//定义handshake结构体变量8 F+ U0 ~' }1 c/ O5 C+ Q/ p
struct handshake {
/ W1 h- {, m  p6 Z        char *resource;
  a; y% b! \# ?$ T: V        char *host;8 g. |0 Y4 E* E$ V% ?
        char *origin;
$ W/ s1 B1 A& |- C( n0 H& a        char *protocol;
8 B  b3 I  C) e/ a, m        char *key1;
, |' y8 E; V8 w        char *key2;
; g0 y" Y8 i; v, m: T  \1 g% A};9 I# U% }2 N& n" }# I

% ^2 I& Z( F3 S" {' A3 m+ r+ l  p" h4 E5 C
//释放握手后不再使用的部分变量
$ O& ~: k3 C- _+ s7 Svoid free_handshake(struct handshake* hs){
7 u0 |* N5 p# f2 Y        if( hs->resource != NULL )      free(hs->resource);& n% j2 i( _+ }) B3 F$ h
        if( hs->host != NULL )          free(hs->host);
! [: y+ x. B# y2 a5 B        if( hs->origin != NULL )        free(hs->origin);
$ L1 |, [0 e3 \' `* _! [        if( hs->protocol != NULL )      free(hs->protocol);
) Z: [  a& ?& s) c        if( hs->key1 != NULL )          free(hs->key1);$ A/ C- k6 ~- \
        if( hs->key2 != NULL )          free(hs->key2);
) C2 z" @) V1 @2 d4 m% W. u}; w+ N1 v/ [  v" c) x, e0 E

8 h' h) ^* h, d, ^! P// 这里对上一篇的match_string做了点修改,2 o& Z' {+ }+ R/ x
// 增加了一个end参数,这样不必提取出字符串后,再做剔除处理: L% Q5 \% |  a5 E4 `" e: }
char* match_string(const char* src, const char* pattern, char end){) `2 c' g1 ?$ n
        char buf[BUFSIZ];
# ^- J& `$ f; v# P        memset(buf, 0, BUFSIZ);$ b( f& v/ X  Q
        size_t src_len = strlen(src);
1 p8 U! ?! t8 l: R' G        size_t ptn_len = strlen(pattern);
( I, R  `# J1 [8 s7 e! X        unsigned short b=0, p=0, i=0; ( ~  a( Y% Y" n7 q
        char c='\0';0 u1 k/ |$ b6 y) G+ }
        for(i=0; i<src_len; i++){# E3 @3 c, @, `* `8 q
                c = src[i];* E: ~) t; a3 g- s" Z) k
                if(p==ptn_len){ // p==ptn_len 表示正在匹配中9 K! ~, c6 v6 W
                        if(c=='\r' || c=='\n'  || (end !='\0' && c==end) ) p++; // 匹配结束
( T4 e7 S$ B6 p% G" r2 |                        else buf[b++]=c; // 匹配到的字符 5 R; n- q6 I1 _' i
                }else if(p<ptn_len){ // 为达到匹配要求
  o8 U' e  g' G/ t                        if(c==pattern[p]) p++;
" Q; _+ Y8 S$ V$ H, ^8 U$ ]                        else p=0;
+ A; C5 b0 |& D                }
- q- n( M- P4 o. d9 G' f6 F        }
! r; M/ ?8 A9 n8 c        size_t ret_len = strlen(buf);
" |' @' Z5 V' m5 q( ^        char *ret_p;
, F$ V! I2 u3 _; @1 E        if( ret_len>0 ){! x  ?# e* M( _8 {. t; W9 c
                ret_p = (char*)calloc(ret_len+1,sizeof(char)); // 加 1 为了存储 '\0'
6 u: `, t2 b- \9 F* T/ e! W                memcpy(ret_p, buf, ret_len);& A- b, @0 V+ U
        }else ret_p = NULL;
* M$ E, E. y) U2 L$ i0 h. @% K        return ret_p;
* \- o) q/ q( O2 O$ m% M- q}: z# U" r- X2 `
6 v& h, w" q2 X" g, F1 C
// md5 加密函数,用的是网上一个实现的比较通用的版本。
# ^6 E" Q3 h" N0 _3 ~void md5(const char* src, size_t size, char* digest)
! T' x( z3 S; G, J; }{$ j3 s* o2 n  F& @! a
        md5_state_t state;) a" A8 p. }0 }, Q5 Y
        md5_init(&state);) J: I8 d6 F' D* {* M, U
        md5_append(&state,  src, size);
$ I3 P# [* W& `! b. |. a8 r1 t        md5_finish(&state, digest);
/ o8 N1 q% t5 G2 U5 q}
7 @2 y- Q8 P1 F/ m& ~) l
7 _: h; W0 u) qvoid p(char*s, int len){* _0 e# V7 I. Z2 u
        unsigned short i=0;* ~! X. c  M% H- L: B
        for(i=0; i<len; i++) printf("%c",s[i]);$ Y9 y! Z1 \3 `2 O
        printf("%c", '\n');
* l1 R, c1 X+ T1 U; f}
2 W# G  j, `7 i. [/ w. ~6 Z
8 K( u* j8 f( c9 F7 xvoid handshake(const char* src, struct handshake* hs){
. h; X5 ]+ V3 L3 e        size_t src_len  = strlen(src), i = 0 ;0 m, M, z( Y4 ]& p
        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前* i; q" \" q* `4 i
        hs->host                = match_string(src, "Host: ", '\0');7 _0 p0 L# W& @( b( `, ]
        hs->origin              = match_string(src, "Origin: ", '\0');
7 Z6 W$ k6 I7 Y) y# T        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');1 P/ A, |& c7 O7 A2 P
        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');
5 g. h; h% c* t6 N1 e        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0'); & G5 Z  F" b5 J5 S; O
        // 获取 key3,即最后的8位字符! k2 k8 e9 Y- A* \; D
        char key3[8]="\0";! k  o: c8 D# l
        for (i = 0; i < 8; i++) key3[i] = src[src_len - 8 + i]; + z: ]/ K2 w: v/ X7 ^8 R9 ?
        char digits1[64]="\0", digits2[64]="\0", c='\0';
$ @/ ]; C1 @# s% r% Q3 h) S* |# J        size_t spaces1 = 0, spaces2 = 0;
. Y% a, x8 _! J0 q# p$ P1 [0 S        size_t key1_len = strlen(hs->key1);: Y% L% c- V# _1 [% U2 G# ~
        size_t key2_len = strlen(hs->key2);
  A7 A. p. L8 c9 ?7 F* u# g        short d1 = 0, d2 = 0;
! A9 K7 A6 X. ]- X+ A! X        unsigned int result1, result2;$ a# y6 ]  @0 L) A2 h! q0 C
        for (i = 0; i < key1_len; i++){
% y4 j, S% O" C1 f% x* Z0 h                c = hs->key1[i];
# J5 k6 u3 |. O7 T0 [+ G% h% @3 [- K                if (c == 0x20) spaces1++;8 q  z  g+ C4 m" s2 \
                else if(c>='0' && c<='9') digits1[d1++]=c; ! l+ C: d, J% ~- e- \2 M
        }
, \/ p4 W0 K- E) g5 T        for (i = 0; i < key2_len; i++){ 7 S: r+ Q- r5 b
                c = hs->key2[i];
' D6 e0 T( H0 `& B4 j% D2 q- t$ n( \                if (c == 0x20) spaces2++;, w0 g& d: Y) x2 o# }8 g9 ~
                else if(c>='0' && c<='9') digits2[d2++]=c; ( R( X$ T. J( E( w5 g
        }
8 V2 o( ^% }% |; p: z( n& ?4 Z, M        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);
/ P# a1 g2 |$ p) J$ `        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);
0 J8 R/ i$ k6 s- ~( i        printf("ch1:%s\nch2:%s\n",digits1, digits2);3 ?* i) M: O: R& c- ^  p
        printf("sp1:%d\nsp2:%d\n",spaces1, spaces2);
. F4 r+ D+ s! x/ f1 X        printf("d1:%d\nd2:%d\n"  ,result1, result2); : r5 ]7 @4 u) G' E' m
        unsigned char chrkey1[4]="\0", chrkey2[4]="\0";& B" C& p3 f* R1 C! R* R
        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);4 B2 W! s1 b- E0 o( |: A
        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);# j5 L6 E6 g% G) \4 s* i
        printf("ch-key1:"); p(chrkey1,4);; i- k( ], |: Y& w
        for(i=0; i<4;i++)printf("0x%02x ",chrkey1[i]);  F' W. j$ |" V; K: S% U. q/ [
        printf("ch-key2:"); p(chrkey2,4);. h# I% v9 T' f! ]
        for(i=0; i<4;i++)printf("0x%02x ",chrkey2[i]);! g8 E% p8 y2 v. V' d' G8 {
        unsigned char raw[16]="\0", dig[16]="\0";5 b6 F1 m7 R$ P! [2 B4 ~& r  V  z5 g/ S
        memcpy(raw, chrkey1, 4);
. i7 D5 E8 c& T8 f( O2 g        memcpy(&raw[4], chrkey2, 4);" z$ I& s: \) A
        memcpy(&raw[8], key3, 8);
! D# R2 R' B- n9 `/ f        //计算的md5值
9 L8 [! g* r; Z- z        printf("\nraw:");) d' y2 _6 n5 l
        for(i=0; i<16; i++) printf("0x%02x ",raw[i]);6 \8 P3 }9 X% X
        md5(raw, 16, dig);) O" ?* ]- ]; L9 _: [9 c2 n
        printf("\nmd5:");
+ e$ J7 w7 k( I2 T# S1 `- w* D        for(i=0; i<16; i++) printf("0x%02x ",dig[i]);3 ~7 q8 `( [4 T- \  H' I
}% p5 n1 k% t- m/ G4 r# t  ?. D: W

! |3 Q. Q  g, u4 t  P
8 T% I+ ?/ v! Y1 `8 @int main()4 [3 @3 t+ }7 }5 g5 L
{; i) ~$ Y) X& G% n* z3 f) n
        unsigned char msg[512] = "GET /pub/chat?q=me HTTP/1.1\r\n\3 _; d! E. J6 T; X1 j1 L
        Upgrade: WebSocket\r\n\
) ]% n" x. b# ?; b: U1 A        Connection: Upgrade\r\n\
: A) f  a0 M  O$ ?( W' J3 H8 O        Host: localhost:4400\r\n\( m0 a9 q  P/ _6 S; e. M
        Origin: null\r\n\
( h3 G' ^, p: I. D( q; p1 f        Sec-WebSocket-Protocol: my-custom-chat-protocol\r\n\
: C& s$ t; h7 w        Sec-WebSocket-Key1: x EO2 59186 4  28\\dY 0+\r\n\
1 @/ y& x" z: M! Y        Sec-WebSocket-Key2: 1  9  3  57695W    0\r\n\r\n";0 d( G2 {0 F' a( e5 a# z
        size_t len = strlen(msg);
3 w# F# N1 a' Y        msg[len]  =0x1f;
3 E1 S; K' f0 _        msg[len+1]=0xf6;
* X, s3 q9 y$ `" g  K+ A3 ]+ F        msg[len+2]=0xf3;0 f" D$ L; x: c" k# C# b
        msg[len+3]=0x3f;
9 `! X" D3 C1 |* |        msg[len+4]=0xc7;
$ b5 v& f' ]/ ~6 N/ `' Y4 e) }! Y        msg[len+5]=0x17;6 h+ H/ v8 ]& R. ~4 m3 {3 d* D3 ^9 l
        msg[len+6]=0x20;
' v) l) x8 W* Y' H+ @        msg[len+7]=0x88;
. h3 y6 e/ \% X8 v1 [0 y- L/ V: I* t        struct handshake hs = {NULL, NULL, NULL, NULL, NULL, NULL};
0 C" M3 t7 F" r1 w6 t  n1 U: i- Z        handshake(msg, &hs);: Y1 P; v2 ?" h1 H4 S8 B; S
        free_handshake(&hs);
. }! T: a7 x4 e/ p        return 0;
9 T5 ^9 y) D, j$ c/ v4 o}1 K  U7 b5 ~5 D7 n- f) m! X3 l
8 G  a8 W" C3 V5 ~6 h

& i- w( e) Z/ l1 l# X, `测试的结果:
( k4 c) K8 I% b3 q* ?% lraw:0x19 0xbf 0x73 0xa4 0x01 0x27 0x5f 0xff 0x1f 0xf6 0xf3 0x3f 0xc7 0x17 0x20 0x88 7 t2 }% v% P' r! `
md5:0x61 0x30 0x1e 0xe8 0x8a 0x17 0xaf 0x39 0xd6 0xad 0xef 0xb9 0x6f 0x00 0x0f 0x68
7 Z6 \' T: I& V/ h对比了nodejs的版本,握手部分生成没有错误。  I2 i5 J* ~( B, z) M
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-10-1 19:25 , Processed in 0.022737 second(s), 19 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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