找回密码
 注册
查看: 5837|回复: 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++庞大的类库和模板。
* a) F1 Z2 M9 ~& F+ ?" [# [) e完整的windows版本socket握手实现:
0 Y( I5 |& p' o5 h4 w
* X1 n/ k8 q& v3 Lbool WebSocket::handshake(const char* src, struct handshake* hs){2 T0 J% {9 V- O1 @- m
        size_t src_len  = strlen(src), i = 0 ;
: h1 m3 X5 d$ I0 |$ H        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前) W- R: R$ ?) @+ q& [/ U" e0 ^3 x( G
        hs->host                = match_string(src, "Host: ", '\0');  @8 n- Q' x( j  p' J
        hs->origin              = match_string(src, "Origin: ", '\0');  U& X/ S% n5 \1 W
        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');4 e4 s9 \; `  k" @9 M3 o
        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');, U; e+ K& \. n9 |+ N, A, L$ X) |! @
        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0');
" w  H/ A" e% r) V- d8 h        char key3[8]="\0"; // 获取 key3,即最后的8位字符
+ t' Z' W8 {# D0 K3 H' m* r        for (i = 0; i < 8; i++) key3[i] = src[src_len-8+i]; 3 y1 I9 e# {" ~
        char digits1[64]="\0", digits2[64]="\0", c='\0';
6 w3 V" ^- m7 f5 h7 y; H& X        size_t spaces1 = 0, spaces2 = 0;
3 y5 v: P! u& r6 c4 B* ^        size_t key1_len = strlen(hs->key1);/ L& e* U0 g- x5 M- Q
        size_t key2_len = strlen(hs->key2);0 l/ |6 [% _" @: y$ y
        short d1 = 0, d2 = 0;
# Y' x) `$ q, s6 r1 z4 r        unsigned int result1, result2;
; N& S( Y4 y5 T/ [        for (i = 0; i < key1_len; i++){
2 S) F! k+ }- Y+ j                c = hs->key1[i];
8 Y$ t! m7 {8 D& o3 t                if (c == 0x20) spaces1++;, W3 ^/ E- }6 ?/ b5 o1 e4 W6 l) G
                else if(c>='0' && c<='9') digits1[d1++]=c; , q: X0 _1 L' V8 B! p% ?
        }4 v$ H+ i. g0 I3 W& F
        for (i = 0; i < key2_len; i++){ : W. l( O" Q* N
                c = hs->key2[i];
7 G: U' o7 f  j# p( e9 }                if (c == 0x20) spaces2++;
+ K" \! y+ V" q$ Q                else if(c>='0' && c<='9') digits2[d2++]=c;
2 T& Y! B* T4 b) j0 q        }0 J- ]% f8 V" N5 f3 `
        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);
2 m8 |* a* [& q, [) I6 E% ~& v        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);
# o, m& F, ~( A) \        char chrkey1[4]="\0", chrkey2[4]="\0";
! }* b5 ?+ g# Z8 o. w        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);& t  X1 v7 j: H, @$ d5 W
        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);5 j+ C# z) D# G7 c7 `& B+ N$ d& I% _
        unsigned char raw[16]="\0", dig[16]="\0";+ I' q; o, h1 w
        // raw 表示未md5之前的字符串,规则就是前4位key1中的数字/空格数的整数值,
4 h/ t& F, `$ w& [! T" H        //  连接上key2的最后连接上头信息中的最后8位字符。1 H2 F' E! I# }& x
        memcpy(raw, chrkey1, 4);) z1 M% U2 N) b; g7 u) M; x0 C; E
        memcpy(&raw[4], chrkey2, 4);' i" x6 b9 s0 J; Q
        memcpy(&raw[8], key3, 8);& b/ g# y6 D) j
        //计算的md5值
  d2 c& ?: Q: V( o* T        md5_state_t state;
5 Y% l7 E7 Z# h2 v9 k6 l. Z) }        md5_init(&state);
* X; T3 ^/ ^5 |6 e7 l% P        md5_append(&state, raw, 16);
/ i$ b( O2 ]5 F/ X& i- `1 z        md5_finish(&state, dig);0 s' U# m1 |7 B5 r6 E4 u' G
        
) h/ J2 |6 L' ^5 \        char handshake_str[BUFSIZ];3 r' M5 j3 C2 W
        memset(handshake_str, 0x00, BUFSIZ);( g: O* B8 N4 P2 _% w$ f
        char* handshakeFormat = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"0 ]* {, y1 f# z! H
                "Upgrade: WebSocket\r\n"1 M. `. T# ?* E% ^7 S9 U
                "Connection: Upgrade\r\n"
! [/ K4 a1 G7 t. \                "Sec-WebSocket-Origin: %s\r\n" 0 b; `- a- _: K4 v
                "Sec-WebSocket-Location: ws://%s%s\r\n"
* y7 W/ S# c2 H& P, E/ l                "Sec-WebSocket-Protocol: %s\r\n\r\n";# n# ~0 M2 L% K1 X- t6 m: `
        sprintf(handshake_str, handshakeFormat, hs->origin, hs->host, hs->resource, hs->protocol);
8 E5 Q" _/ p. M9 @) Q+ c/ B        free_handshake(hs); // 释放handshake指针,已经不用了!
# L* q; y4 U- v9 j3 P        char response[BUFSIZ];
  D4 t) r) ]; B" X- d1 `4 D        memset(response,0,BUFSIZ);4 r3 w" s! }# k1 I" }" N! R
        size_t j=0, handshake_len=strlen(handshake_str);
' F! t2 ]" C; X% B+ G- ?' v, l( H        for (i = 0; i < handshake_len; i++) response[i] = handshake_str[i];
+ }8 J5 _3 q4 X( k1 `( E! W1 J        for (j = 0; j < 16; i++, j++) response[i] = dig[j];
6 I1 P4 }# C' ~        // 这里的clientSocket就是连接好的socket对象了。
! M0 v% [# t9 }) p        int sent = send(clientSocket, response, strlen(response), 0);8 j. Y3 g0 E# |3 w8 ]
        return sent>0;
# D7 D( w. j. m+ J0 l) K}
3 `9 T: E8 Z# b  p) y5 K; c目录结构:
( m" i1 B& T- E/ G5 Y$ U! Ssocket/* E  U: K. d. g% }% ~5 E! }9 W
      socket.c4 N: v( ]! e5 Y7 a4 y9 y. g
      md5/
) ^" U2 t/ d" M8 r, j# v        md5.h
/ M( u. X- e/ A1 f4 J! j6 a# k        md5.c
' a- ~4 J9 Y+ Y0 k6 B; n9 @  _库文件下载位置: http://sourceforge.net/projects/libmd5-rfc/files/ 总共3个文件,% B- i/ @; ~' g- @
编译命令:  gcc socket.c md5/md5.c -o socket.o  
: w' a3 U/ P- V6 A0 N5 b& T GCC 4.3编译通过,执行 ./socket.o 可以看到打出的raw和md5后的字符串信息。 这里使用了一个网友用nodejs写的websocket实现版本,打出详细的头信息,然后复制到程序里,以测试生成的握手字符串是否一样。4 q' }( ~* w2 q- `# \
// socket.c 代码。
+ \& Z+ d4 r6 P! Y8 n' L// web-socket example 6 ^/ I8 y7 l1 s# }2 N
#include <stdio.h>
# n+ Q5 ~( q/ B#include <stdlib.h>8 f8 Y9 v4 k) Y
#include <string.h>( S( O7 Z1 {2 @, y% k( D/ @4 l
#include <inttypes.h>
) _( D+ e4 _4 o3 O) Y7 y# f) g#include "md5/md5.h"% V( r' b. m% m) O
% x1 `! n/ T* g. V; h: v9 h5 o2 k
// #define BUFSIZ 512- w# @$ g" ~/ f

( s1 e9 f: S+ p0 Q//定义handshake结构体变量
; l1 o" r/ U8 l! Q( Ostruct handshake {0 `; t  s9 b( @) r( Y% e/ ]
        char *resource;* h8 ^$ V6 c, o5 {
        char *host;
, E% c5 @  T5 O4 I        char *origin;8 o3 M2 R4 a" v( }% ?
        char *protocol;
4 _* e& N  e# C$ g4 f, u. f. i# {4 X        char *key1;
& u: |! B) c7 j9 [        char *key2;& j) m3 ~6 o( Y
};* H& _4 [6 D6 l/ F; g. |* v9 I  V

) R4 e5 V( H& a; O; ]# t# H
, Q% b6 j, P. x$ D3 ?0 \//释放握手后不再使用的部分变量2 Z8 H* Q' l1 f$ n. s! N$ ^  U
void free_handshake(struct handshake* hs){8 r( [$ y4 j6 x5 c
        if( hs->resource != NULL )      free(hs->resource);& K9 m" ^$ ?. H* E) D" z
        if( hs->host != NULL )          free(hs->host);0 q. D; M5 Y6 ^1 u" i# e
        if( hs->origin != NULL )        free(hs->origin);
1 Y: E: f6 p% e- p! q: _2 }; m        if( hs->protocol != NULL )      free(hs->protocol);- i( [1 V/ L7 T! e$ T
        if( hs->key1 != NULL )          free(hs->key1);& C4 ^; J) [% o- d
        if( hs->key2 != NULL )          free(hs->key2);5 h5 `, V# S" j$ v
}
$ q1 h) E; x2 ^* z! J, x/ d8 ^  y  R
// 这里对上一篇的match_string做了点修改,# t; n7 b1 y+ f! {
// 增加了一个end参数,这样不必提取出字符串后,再做剔除处理
1 d( P7 D; o* d' gchar* match_string(const char* src, const char* pattern, char end){
; b6 W; T. w7 O% T( O/ P% I; S- k5 T0 q        char buf[BUFSIZ];8 q" n: o) y9 M  h
        memset(buf, 0, BUFSIZ);8 r! e6 ^$ h# A( C0 W# B, y% l
        size_t src_len = strlen(src); " r- U* K5 P; u; S6 m
        size_t ptn_len = strlen(pattern);
% W: _* k3 y2 b: w        unsigned short b=0, p=0, i=0;
; O6 D2 V+ y2 U8 n4 N! y( g        char c='\0';: C1 @7 r9 {, x4 Q
        for(i=0; i<src_len; i++){
& H% D1 C* w2 O# h0 s                c = src[i];
, A3 c: Q0 r* V2 C- X9 f- r                if(p==ptn_len){ // p==ptn_len 表示正在匹配中
2 A4 ^! f! Z# Z* K" U                        if(c=='\r' || c=='\n'  || (end !='\0' && c==end) ) p++; // 匹配结束
2 ?+ i( w* Q% N# F) Q                        else buf[b++]=c; // 匹配到的字符
* u$ d. E$ d. d% K* R& O. r8 T2 t5 b                }else if(p<ptn_len){ // 为达到匹配要求
, Q& C; K5 ^1 ~4 t                        if(c==pattern[p]) p++;. B- @, F5 g) [8 n- N
                        else p=0;
, \4 u7 E1 o' ^; B                }
' v* x+ \' Q4 @- |0 o+ {; P        }
; Y  e9 B% P# d) T( x: q' m3 k1 k! c        size_t ret_len = strlen(buf);. G6 q/ j8 x! i
        char *ret_p;
* A" ~7 f1 r) G1 e2 h        if( ret_len>0 ){
  s& v  ]  P9 H( ?; }                ret_p = (char*)calloc(ret_len+1,sizeof(char)); // 加 1 为了存储 '\0'
: b6 |- B  b+ k+ D2 M1 W4 P                memcpy(ret_p, buf, ret_len);3 V) [6 f1 I0 l
        }else ret_p = NULL;
3 B" ]1 }! v' @0 q        return ret_p;
5 _; H) x! e1 }+ U, J! J5 X}
$ ]) P( d" e; ]$ H% ]. y; \- b) A7 N' x
// md5 加密函数,用的是网上一个实现的比较通用的版本。
; g( \+ C  n" V, ^, q8 W1 r7 a$ Gvoid md5(const char* src, size_t size, char* digest)
! g/ w; j8 @7 B1 s+ H5 m0 b{
& X. C2 r/ T/ M5 I7 n3 _6 \        md5_state_t state;
) x. R; s* j% m* \) O! u. G        md5_init(&state);0 ?1 i' B9 \% a" C& g( v3 _8 t
        md5_append(&state,  src, size);* @9 |$ v  O' D
        md5_finish(&state, digest);
; V+ _" ]5 A* x, j, J' Y9 K1 K}
0 F+ R) K5 i: h
2 ]0 [, i4 ?5 r7 S( ^void p(char*s, int len){+ I* M3 K8 D% H7 e; U
        unsigned short i=0;
3 R2 _; G& F) `8 Q        for(i=0; i<len; i++) printf("%c",s[i]);2 h, R$ |; f% Q2 b- X
        printf("%c", '\n');
7 b' l4 U+ g, p}
5 S1 o" M) H6 |8 G" B/ o
  C7 o/ F- ?. wvoid handshake(const char* src, struct handshake* hs){
6 ?: \3 n, W6 g! X        size_t src_len  = strlen(src), i = 0 ;: ^4 o) ~* X( O2 G' ?1 Z3 I
        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前% o1 C* n) d# v7 B  c% W
        hs->host                = match_string(src, "Host: ", '\0');
2 X6 [, ^6 \! }* j2 d) q- I        hs->origin              = match_string(src, "Origin: ", '\0');( ~# ?% T* m% @3 T$ @7 T: p
        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');
0 V' S& F; o5 q# `3 W! i, B        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');
1 l8 A0 H9 @) T+ e        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0');
( n; Y; ]6 w% _$ @% n        // 获取 key3,即最后的8位字符
( _. J6 W9 T$ d. m        char key3[8]="\0";4 a9 w  ^, d, t' u5 U7 w- w
        for (i = 0; i < 8; i++) key3[i] = src[src_len - 8 + i]; : c; |$ Q, D  X6 l
        char digits1[64]="\0", digits2[64]="\0", c='\0';" H9 W, \0 q& U6 }* z/ F
        size_t spaces1 = 0, spaces2 = 0;
& Q, i; ~" n5 ^        size_t key1_len = strlen(hs->key1);
# m8 ^7 Y  b4 f, n- t. U* B( ?        size_t key2_len = strlen(hs->key2);1 K9 p! w$ c1 _! e" A) N3 E- r
        short d1 = 0, d2 = 0;
: N0 e0 e& R* i& [# N        unsigned int result1, result2;
( R5 W# V, z  o1 x        for (i = 0; i < key1_len; i++){ 9 }) p( B" v/ w% A1 t9 {
                c = hs->key1[i];: b5 M: Y8 m" Y/ y! a0 L( {4 x
                if (c == 0x20) spaces1++;
' F: j8 _1 G8 v+ O8 R- c2 l                else if(c>='0' && c<='9') digits1[d1++]=c;
1 v0 q8 O$ ~: w6 I7 q        }
2 d2 L9 o+ Z# P' R  y7 h) o% G6 m        for (i = 0; i < key2_len; i++){
0 M7 G1 s+ C" W) T3 i2 K9 d+ o                c = hs->key2[i];! C& v. y# M" }( R
                if (c == 0x20) spaces2++;
- z+ N' s& S) m# L$ ]( G5 j) U                else if(c>='0' && c<='9') digits2[d2++]=c; & E' [! o0 n1 {2 _$ v" s
        }
+ S" L: l9 _/ j  {) m  q% e        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);
7 w$ c. e* r6 u  ?* r3 ^2 X        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);1 i( h$ I% C* g; x2 z4 E
        printf("ch1:%s\nch2:%s\n",digits1, digits2);! M/ i! X1 c( q' v/ t6 V  Q
        printf("sp1:%d\nsp2:%d\n",spaces1, spaces2);& f$ T6 c4 ~) w, s
        printf("d1:%d\nd2:%d\n"  ,result1, result2); + ^& ^8 i' w3 j* r. I$ c. j7 w
        unsigned char chrkey1[4]="\0", chrkey2[4]="\0";
/ K) o0 F* V+ P( Y        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);  h6 |. \3 Y) k9 Z5 h
        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);
8 n  K3 b; c# c$ @% @        printf("ch-key1:"); p(chrkey1,4);- q% P9 ?. W. D# T
        for(i=0; i<4;i++)printf("0x%02x ",chrkey1[i]);: @$ w1 ^$ j; B5 [6 A3 V
        printf("ch-key2:"); p(chrkey2,4);$ @2 c; x) K: g$ w- G: ?' b( d
        for(i=0; i<4;i++)printf("0x%02x ",chrkey2[i]);
# J% M5 [* X. u) {# [. T6 m, j* a        unsigned char raw[16]="\0", dig[16]="\0";( ~) O& R/ Q, w) A
        memcpy(raw, chrkey1, 4);
/ @" ]: v1 ]& c2 d4 B; t2 B( [2 j1 u        memcpy(&raw[4], chrkey2, 4);
! h. S: j; ^$ O2 l! k; V( J! b        memcpy(&raw[8], key3, 8);. V+ z; l9 \& \6 t4 b8 B1 n
        //计算的md5值) W3 V/ u8 S! n/ b8 @
        printf("\nraw:");5 R5 z% I0 G! g6 ]
        for(i=0; i<16; i++) printf("0x%02x ",raw[i]);' M" o* a2 l6 H3 N% [
        md5(raw, 16, dig);1 p' d/ ~' T9 S# S9 W% T4 m
        printf("\nmd5:");
* }# g) q4 q  r: P% a        for(i=0; i<16; i++) printf("0x%02x ",dig[i]);
/ w- |) X5 j8 }  G! B) }; L}
+ E' u) n# t+ `9 m3 o  T1 G; P: l4 C' L" I+ e! A3 |
, a" c# A  g- m: e2 e8 z5 L9 r" g
int main(), n: j7 a1 z" [6 `+ {
{
9 |1 O0 C# t; @* G8 j# ^        unsigned char msg[512] = "GET /pub/chat?q=me HTTP/1.1\r\n\
% h8 @7 F1 c5 ~6 A6 S! ?        Upgrade: WebSocket\r\n\' S- ~  H' ~+ o+ \6 E8 _
        Connection: Upgrade\r\n\$ A- T! M2 s0 p! `/ `! l
        Host: localhost:4400\r\n\
1 {7 {) g- i# E/ g! l        Origin: null\r\n\. N! W- y" g8 o. U% i5 ~- ]* E: E
        Sec-WebSocket-Protocol: my-custom-chat-protocol\r\n\( ?( z' f* S5 H
        Sec-WebSocket-Key1: x EO2 59186 4  28\\dY 0+\r\n\- B. X0 B& L; e7 r* m
        Sec-WebSocket-Key2: 1  9  3  57695W    0\r\n\r\n";" |, U, ~% \, ]! j. R( D  r
        size_t len = strlen(msg);3 q, A0 R" l1 D
        msg[len]  =0x1f;
4 P$ p' [8 D7 f$ O$ s# R        msg[len+1]=0xf6;
4 S; X, R8 g! N7 t5 T" \5 h: `        msg[len+2]=0xf3;
- m# ?* u- w- Z' O$ N        msg[len+3]=0x3f;
7 h, D% e2 a3 z5 Y        msg[len+4]=0xc7;
' v: J$ x: ^9 K" i: r/ E        msg[len+5]=0x17;1 s0 [& h& B% a9 s. x, q
        msg[len+6]=0x20;/ V4 p2 U7 e7 Y1 c/ v  {7 s8 Q
        msg[len+7]=0x88;
3 Z8 X% `; Q- G        struct handshake hs = {NULL, NULL, NULL, NULL, NULL, NULL};+ D' K3 J, l4 A! e2 ~1 B& V
        handshake(msg, &hs);3 n/ G# T7 h  l* `2 k# ?# V
        free_handshake(&hs);9 G/ w! C/ L$ G
        return 0; : t9 |: n- ]# s  e
}
! s8 p/ m+ k6 \
' c: o" |) C& p: y
8 ?! |: y- k1 Q测试的结果:2 M$ P9 A/ V* F( m& i8 }' M
raw:0x19 0xbf 0x73 0xa4 0x01 0x27 0x5f 0xff 0x1f 0xf6 0xf3 0x3f 0xc7 0x17 0x20 0x88
, p! S  Z. d/ G( L3 xmd5:0x61 0x30 0x1e 0xe8 0x8a 0x17 0xaf 0x39 0xd6 0xad 0xef 0xb9 0x6f 0x00 0x0f 0x68 ! R6 Z( O- j: M; a. p
对比了nodejs的版本,握手部分生成没有错误。
( g( L1 ^  S8 B% x' s: z
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-10-1 23:54 , Processed in 0.031965 second(s), 19 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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