找回密码
 注册
搜索
查看: 5678|回复: 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++庞大的类库和模板。
/ J6 ~- K3 d8 H& [  P8 z完整的windows版本socket握手实现:
/ x$ h: L0 R, x6 u, g0 O! {* P
8 P1 A0 F. q2 ]8 G; C! @  hbool WebSocket::handshake(const char* src, struct handshake* hs){0 |3 t4 n7 e5 P
        size_t src_len  = strlen(src), i = 0 ;& s; G  M; G2 H
        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前
- K5 a. _5 C. w/ i" a' y* l6 A' x        hs->host                = match_string(src, "Host: ", '\0');, Z1 w( h9 r* ^& V2 A! b
        hs->origin              = match_string(src, "Origin: ", '\0');4 L! X- G0 H3 W* A7 V( f) F
        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');# i8 V' M4 `4 u. x7 u
        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');7 p! f$ p( L+ q5 b. ^4 d! t- d5 w
        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0'); ; d9 I2 e: n: s  S* ~
        char key3[8]="\0"; // 获取 key3,即最后的8位字符
, L4 R- m" c; i5 e) d  E" n        for (i = 0; i < 8; i++) key3[i] = src[src_len-8+i];
5 ]1 ^; B! [9 V        char digits1[64]="\0", digits2[64]="\0", c='\0';
2 ?5 _7 z" g: m$ {* T$ k+ K5 U% F/ r0 W        size_t spaces1 = 0, spaces2 = 0;
$ P" k  a9 c+ S3 O' L3 k+ ]" r, O        size_t key1_len = strlen(hs->key1);
- k( I7 T9 }- Z: w# i8 H# N        size_t key2_len = strlen(hs->key2);
. ~# q: _: D. x' W  v        short d1 = 0, d2 = 0;2 J9 `, c: q0 A
        unsigned int result1, result2;& F0 l/ D! I: n5 N- v+ r
        for (i = 0; i < key1_len; i++){
0 Q! C3 U2 v0 c) L( f. F                c = hs->key1[i];
: s# }/ Q7 d- F5 `                if (c == 0x20) spaces1++;7 h* E! ~9 ^+ J& o, D5 Q, e5 r. L
                else if(c>='0' && c<='9') digits1[d1++]=c;
/ I2 O" F7 {" s+ P! N        }
* k# A& g& k* H' V5 Z* v        for (i = 0; i < key2_len; i++){ 6 A* }7 X% r2 S; V% H- _
                c = hs->key2[i];, x/ X, _9 y! Y' ^
                if (c == 0x20) spaces2++;
) N7 G& L: p- w& }* L                else if(c>='0' && c<='9') digits2[d2++]=c; ) N9 E' |8 N4 \( g# j/ A) S" P: o
        }% x) Y3 }  @% w
        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);
( i9 \5 I3 g2 E9 Y$ L5 }        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);
" `3 P$ g- Y8 y. P. Z2 F, p4 G        char chrkey1[4]="\0", chrkey2[4]="\0";
; W4 Y2 a" r- n, o* R& N! {( H6 u        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);
$ w3 P% T) ]+ [& `* _* o        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);
% v+ }9 {( u3 e3 y  M3 l+ m% t        unsigned char raw[16]="\0", dig[16]="\0";
2 \# ?" k% i, k  N8 i/ p- v' \        // raw 表示未md5之前的字符串,规则就是前4位key1中的数字/空格数的整数值,
: O8 O9 v! B& Y' f+ ~6 z; Z        //  连接上key2的最后连接上头信息中的最后8位字符。
8 q# G8 K; d' P/ o& }6 j4 G6 h        memcpy(raw, chrkey1, 4);
+ f  Q8 V9 D* b4 ^% V        memcpy(&raw[4], chrkey2, 4);# C% l+ e/ A. I) S* i9 K$ u
        memcpy(&raw[8], key3, 8);1 c( l" [) O. A8 J! G, U
        //计算的md5值
, h# q% K: |' B" u9 L( K) h- m        md5_state_t state;% U# u' `$ ^$ |9 C8 v9 I
        md5_init(&state);
" F4 I, N# ^& H! `# }; q        md5_append(&state, raw, 16);+ R, x) ~- H2 P  g3 E0 D
        md5_finish(&state, dig);
4 g) ^4 ]6 i9 K9 Y. k        
" a" P  s. D: o+ t# @! V' e7 N        char handshake_str[BUFSIZ];
5 K* I" S: q/ A        memset(handshake_str, 0x00, BUFSIZ);7 O) O3 r$ [) m: N+ X% D
        char* handshakeFormat = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
' n, n. g2 \( b6 j& d6 n; d                "Upgrade: WebSocket\r\n"8 l7 K' H8 k. i5 x5 L* M& ?# e2 W
                "Connection: Upgrade\r\n"
( z1 u' W5 }. o) c( q4 F3 Z& m1 a                "Sec-WebSocket-Origin: %s\r\n"
5 v8 m: U& i  `                "Sec-WebSocket-Location: ws://%s%s\r\n"
' W; h% n4 A9 _, e                "Sec-WebSocket-Protocol: %s\r\n\r\n";
9 P4 S: m- I) b3 R( w) Y        sprintf(handshake_str, handshakeFormat, hs->origin, hs->host, hs->resource, hs->protocol);
! O5 E' V5 K* {1 z  C- `        free_handshake(hs); // 释放handshake指针,已经不用了!8 R3 {* ^1 G) @5 m* k4 A' w
        char response[BUFSIZ];
" [( W" M, Q3 _( [% X" Q* S        memset(response,0,BUFSIZ);5 C$ d8 f4 d% u* o9 L
        size_t j=0, handshake_len=strlen(handshake_str);  ~- k' r4 b" t, t/ M: b
        for (i = 0; i < handshake_len; i++) response[i] = handshake_str[i];
$ t) W# @, N) R        for (j = 0; j < 16; i++, j++) response[i] = dig[j];+ T! w- h) B) X
        // 这里的clientSocket就是连接好的socket对象了。7 h, @% q4 _: m
        int sent = send(clientSocket, response, strlen(response), 0);
1 S8 i" ]- \  P# ^/ O0 k' B        return sent>0;
& ~4 L: F* J* F3 I/ B/ Z}' |7 f3 _  ^9 U2 q8 ^
目录结构:# P0 f7 G: ^. i/ a
socket/
! P" z& {+ i, `* {: Q# {* F; [: K      socket.c4 X& y  P7 j9 r0 o& u2 c
      md5/
* L+ ]9 q+ M  x1 N9 T# M& S7 ]        md5.h* Y, }" g  V" l6 K/ ]7 v1 d
        md5.c
; p8 m5 b3 J- j7 K4 N+ V) Z) A库文件下载位置: http://sourceforge.net/projects/libmd5-rfc/files/ 总共3个文件,+ O2 Y2 Y  @6 ^# f( y$ O5 i- A
编译命令:  gcc socket.c md5/md5.c -o socket.o  
. k1 L3 f. d5 H# H2 j GCC 4.3编译通过,执行 ./socket.o 可以看到打出的raw和md5后的字符串信息。 这里使用了一个网友用nodejs写的websocket实现版本,打出详细的头信息,然后复制到程序里,以测试生成的握手字符串是否一样。( o5 h5 Z& j8 k8 _6 o8 I' l
// socket.c 代码。6 j& f5 g9 u6 E+ ~+ u5 _2 l
// web-socket example
: K3 ?% Q& ?' U5 x! }#include <stdio.h>9 {" e1 \% l6 }' L+ |6 }+ i- i# q
#include <stdlib.h>) L4 F9 a' W+ ~$ ~$ m$ w
#include <string.h>: _6 D6 E& N7 M+ B& N
#include <inttypes.h>) x6 a8 `6 o% t6 u8 w8 j
#include "md5/md5.h"
) m& s6 q9 X0 p3 \4 U: w7 {& f' \: C/ W
// #define BUFSIZ 512
& a8 N, e, u- R! E  p3 K. G! }  I" @
( m. n( Z  V6 b3 W5 ^//定义handshake结构体变量8 a' o0 e1 v7 x0 R
struct handshake {
5 v, x2 B# S) |! d        char *resource;
. [# @; ~6 }6 [* u$ \1 i        char *host;
9 t; b5 }0 x, m/ {3 v        char *origin;
4 D  O% e/ H: N/ T$ J) d        char *protocol;  M) Z3 ]/ H: z4 j+ M( n% c3 o
        char *key1;! U) s! H6 m2 s! i1 o
        char *key2;
! \# v% y5 F- r2 I& H};
  F* z+ z, c, n. A: S. K# L/ O/ p0 A' M! m
, O0 @) s) N% ]; V2 y  [7 e
//释放握手后不再使用的部分变量% O* G% i8 `( _5 G
void free_handshake(struct handshake* hs){
7 |$ M) H& _4 H. H- q        if( hs->resource != NULL )      free(hs->resource);
- `# x2 r' @2 b' I" [( m        if( hs->host != NULL )          free(hs->host);
2 G# r- L$ g4 M8 ^6 ]        if( hs->origin != NULL )        free(hs->origin);1 `5 `/ |/ E) a1 k3 H( v
        if( hs->protocol != NULL )      free(hs->protocol);
5 q( k) A7 k/ P% q; @1 b: V( r        if( hs->key1 != NULL )          free(hs->key1);
% g( V: U& K5 k+ a$ Y        if( hs->key2 != NULL )          free(hs->key2);
" l3 {$ ?7 ?6 [2 h7 H7 e( _}
1 n' @& j/ f" \) ^
6 d$ b# o. j8 D0 |8 P// 这里对上一篇的match_string做了点修改,2 G& H% u( E5 S8 i
// 增加了一个end参数,这样不必提取出字符串后,再做剔除处理
% w) z3 W- C$ `/ X+ f# p& n9 jchar* match_string(const char* src, const char* pattern, char end){
  J: Y9 b: Z* _' l$ W  @2 k9 v9 T        char buf[BUFSIZ];# d* Y' |' v6 p+ j; G* P
        memset(buf, 0, BUFSIZ);
7 I1 D9 V! l0 f+ g. d        size_t src_len = strlen(src); ) v% x* g$ }( T
        size_t ptn_len = strlen(pattern);
5 J# f8 b  `! k8 i        unsigned short b=0, p=0, i=0; 1 Y0 C) R+ I2 @
        char c='\0';
0 }9 J3 P: ?) j+ J3 t& o& h) H        for(i=0; i<src_len; i++){
0 |2 r% m/ [1 k+ y6 H                c = src[i];
& s& O. ]( r8 D( k1 v                if(p==ptn_len){ // p==ptn_len 表示正在匹配中
1 f" X! C9 \9 Z                        if(c=='\r' || c=='\n'  || (end !='\0' && c==end) ) p++; // 匹配结束5 B4 y" p* z/ A+ J/ V% F
                        else buf[b++]=c; // 匹配到的字符
; f9 K! m" z+ Y" \                }else if(p<ptn_len){ // 为达到匹配要求# I0 C/ L- \% h7 F6 F* x6 i0 K: G* Z9 F
                        if(c==pattern[p]) p++;
: [% C" Z8 R1 _9 j                        else p=0;+ u% i2 O: h9 w6 Z' _$ x* K
                }6 @8 L6 q+ u7 [# L2 U$ U
        }, o. S. v. b! W8 t7 M/ |
        size_t ret_len = strlen(buf);3 z' Q7 r* ^2 x' }* X, H
        char *ret_p;
7 S2 q& f8 I& H        if( ret_len>0 ){
  {4 R9 d3 Z' `) W                ret_p = (char*)calloc(ret_len+1,sizeof(char)); // 加 1 为了存储 '\0'
% D! A% I) d; z2 P                memcpy(ret_p, buf, ret_len);) _" y2 p9 j* V8 y  Z: M) F, A; j
        }else ret_p = NULL;2 L8 p: n- W2 C% N( Y  _
        return ret_p; 3 z1 Q) U: X6 p2 f
}4 s8 u9 _$ u6 @
5 p2 T8 J5 s' {% d
// md5 加密函数,用的是网上一个实现的比较通用的版本。* ^2 ~) C/ Y, j3 Q; N
void md5(const char* src, size_t size, char* digest)
# G7 F+ _. \5 g1 c{
( Z5 h6 n3 ^. ^1 l, E# {' L        md5_state_t state;# M. c3 B5 h# @. ?2 h8 w. U
        md5_init(&state);: P! P; v4 U# {; A( i+ w2 V9 N" t0 K
        md5_append(&state,  src, size);& P2 K) z9 Q; Q$ u: u/ o
        md5_finish(&state, digest);
* `; C: F* e% Q}7 m$ W1 L  g- r1 a1 a: q( ?

, m7 ]9 L( }$ e8 p7 I% e3 w4 q3 i- hvoid p(char*s, int len){2 g5 V+ L  N; X9 A8 p% l! E; }
        unsigned short i=0;
$ F" {2 `% Y; }6 b        for(i=0; i<len; i++) printf("%c",s[i]);
+ r' i1 U. l6 w: Z/ E" u, ?; ~0 E- Q        printf("%c", '\n');8 f1 J1 W- c, ?" ]1 _9 N
}
' K8 a1 _% C& T( h
4 K, {% W" u: H- [. x) Q2 Cvoid handshake(const char* src, struct handshake* hs){) m6 B- ]" S4 j: S% m( s
        size_t src_len  = strlen(src), i = 0 ;
  {  A3 T5 P" f4 c* I        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前- F' P2 k/ P2 I- r2 X: z
        hs->host                = match_string(src, "Host: ", '\0');/ J2 N+ ?5 o  K
        hs->origin              = match_string(src, "Origin: ", '\0');
; x7 A8 G# s# f3 V& h        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');
2 D; k0 [7 d3 i0 W- \0 J        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');) k0 y) |* G- q5 @: J
        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0');
' C+ l$ P5 R' J! E* m: {        // 获取 key3,即最后的8位字符
+ S: R4 |+ O% B+ r        char key3[8]="\0";
2 d/ I/ E3 |  L* P3 T) C8 Q6 r; C        for (i = 0; i < 8; i++) key3[i] = src[src_len - 8 + i];
: x2 [2 M4 W9 W        char digits1[64]="\0", digits2[64]="\0", c='\0';9 k. x* t9 T3 ]2 v4 x6 u+ n7 i
        size_t spaces1 = 0, spaces2 = 0;' R5 c0 b0 _: C0 n5 j* s, Z* \. e0 O4 o6 ^
        size_t key1_len = strlen(hs->key1);6 o. f3 ]6 F; T
        size_t key2_len = strlen(hs->key2);
. _( |+ u, i( j! D" f4 s        short d1 = 0, d2 = 0;; O3 K( a8 M; T7 Z4 v! [
        unsigned int result1, result2;1 Z4 H) x3 M+ V6 I( ?' O
        for (i = 0; i < key1_len; i++){
1 V2 v0 o' c) T$ @! }7 U* x                c = hs->key1[i];  o) D, n8 q1 E/ V* ~. E
                if (c == 0x20) spaces1++;
% T% t. e5 O7 m  \                else if(c>='0' && c<='9') digits1[d1++]=c;
" N% g' P* ?0 {9 m' @        }1 f. }$ ~9 g* C: H  r8 i
        for (i = 0; i < key2_len; i++){
& W3 Q# ]% V: D4 k2 G9 Q                c = hs->key2[i];8 f: U' q) g( @0 _
                if (c == 0x20) spaces2++;) G) X3 X9 H" J3 F, l
                else if(c>='0' && c<='9') digits2[d2++]=c;
+ L. v, A" @$ ]) E" F        }
' y8 W6 t0 U& L9 N4 |* P        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);
2 w8 g' b7 A6 ]: X4 p6 a" I1 e# y        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);' L" G0 `6 L! w1 [/ \* n
        printf("ch1:%s\nch2:%s\n",digits1, digits2);- |9 _0 U: V6 r
        printf("sp1:%d\nsp2:%d\n",spaces1, spaces2);
3 O: e; r; ]% J' w5 z2 E9 k; ]' P        printf("d1:%d\nd2:%d\n"  ,result1, result2);
* }  B: b, {' h0 s8 D8 ]5 `        unsigned char chrkey1[4]="\0", chrkey2[4]="\0";4 B- E2 A3 \# H  a+ L
        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);, u$ J  Y" H+ V: \- b6 S
        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);6 R7 U0 Q: S, [4 Z0 b: i
        printf("ch-key1:"); p(chrkey1,4);
( G( a) ]  U# m        for(i=0; i<4;i++)printf("0x%02x ",chrkey1[i]);  s& O) r% [6 V1 H5 j3 \0 M
        printf("ch-key2:"); p(chrkey2,4);
' L) s; c, Y9 q" n        for(i=0; i<4;i++)printf("0x%02x ",chrkey2[i]);
: c+ I4 g# h! L) K% h# i+ E' u/ o        unsigned char raw[16]="\0", dig[16]="\0";' ~3 j/ {, t5 I& V, K0 m4 N2 {
        memcpy(raw, chrkey1, 4);2 y7 L; c7 Z: S6 |  U: e2 b
        memcpy(&raw[4], chrkey2, 4);/ v: Z4 [6 t' h" V1 q( B
        memcpy(&raw[8], key3, 8);
) X; J7 W# ?5 W$ j  b        //计算的md5值% M' x9 i, l7 P" t2 J# |
        printf("\nraw:");+ I/ X0 b0 }7 P: @, z3 {# Y
        for(i=0; i<16; i++) printf("0x%02x ",raw[i]);
6 L2 d" y7 w8 T- U/ I4 p; ?5 i        md5(raw, 16, dig);
& Z; Y7 l9 L6 }+ @2 o) V' l! `- j: ?        printf("\nmd5:");
8 E- j; X( q4 L  R  |        for(i=0; i<16; i++) printf("0x%02x ",dig[i]);. m, q8 L& k( M, E' c
}: A/ e2 E4 u% J& {6 d+ Y1 H. Q
3 _$ H2 Q) D/ m7 |; v& \( w

0 h* g: H( }5 `% ^( r: S4 eint main()$ e: [: O; S/ i  G, r1 [' T: R
{
5 W/ L+ f( T/ I. e; u& t        unsigned char msg[512] = "GET /pub/chat?q=me HTTP/1.1\r\n\
( ^) m1 ?: A7 d: [        Upgrade: WebSocket\r\n\( T' `* {' q) P) M( g7 b
        Connection: Upgrade\r\n\
" S  J- }$ ]1 ]6 V8 M8 @) A- G        Host: localhost:4400\r\n\; _% z8 Z+ F# w9 O3 n
        Origin: null\r\n\+ ^( ~6 y- V8 \, G
        Sec-WebSocket-Protocol: my-custom-chat-protocol\r\n\* {7 |" u9 I& n4 l" `/ p
        Sec-WebSocket-Key1: x EO2 59186 4  28\\dY 0+\r\n\! Y2 L/ I, [! [( G3 l: i
        Sec-WebSocket-Key2: 1  9  3  57695W    0\r\n\r\n";. ^4 V" Z6 |4 S2 P, K! }$ C4 u9 U. E
        size_t len = strlen(msg);- @" }" b) |# R
        msg[len]  =0x1f;* e# {' Q/ e0 k) k
        msg[len+1]=0xf6;
+ M4 g1 y( i0 l) f3 Q        msg[len+2]=0xf3;
+ [) n* I5 K- E5 G6 j4 r" `        msg[len+3]=0x3f;
2 G, X: i( ^+ }  R  R        msg[len+4]=0xc7;
4 N7 \1 b' x9 L        msg[len+5]=0x17;6 q) q/ J7 |6 v; p7 i  S
        msg[len+6]=0x20;
3 X" l6 k9 t7 y0 T, S7 n        msg[len+7]=0x88;
3 H* O; J+ l" ~- y        struct handshake hs = {NULL, NULL, NULL, NULL, NULL, NULL};, i5 K  S6 E# c& |5 H7 X1 m6 z
        handshake(msg, &hs);
+ d" x6 u: t8 A$ D# h" n        free_handshake(&hs);) [6 V0 T+ N; v5 v
        return 0; # F. @, g3 F; j: X6 b8 ^& G$ O
}
0 P9 \" U: I& Y9 s7 \
$ n& p( n2 r8 H% K# H+ b1 w' z( r& H- i
测试的结果:/ I' @) ^3 C+ J9 K, @) f  d1 X
raw:0x19 0xbf 0x73 0xa4 0x01 0x27 0x5f 0xff 0x1f 0xf6 0xf3 0x3f 0xc7 0x17 0x20 0x88 " j4 ?$ Z0 D! A9 W
md5:0x61 0x30 0x1e 0xe8 0x8a 0x17 0xaf 0x39 0xd6 0xad 0xef 0xb9 0x6f 0x00 0x0f 0x68 & ~, D6 j! u1 n( n. l& W
对比了nodejs的版本,握手部分生成没有错误。) ]/ c5 Z0 N& h  h. a: R8 S
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-6-18 06:48 , Processed in 0.019798 second(s), 17 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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