找回密码
 注册
搜索
查看: 5679|回复: 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++庞大的类库和模板。, H0 y: Y$ S/ C
完整的windows版本socket握手实现:8 H: q' u! M4 Y% R' f/ ^: p
$ Z4 r, p- R* u+ L% t4 L
bool WebSocket::handshake(const char* src, struct handshake* hs){# {, R. q  A( P0 x
        size_t src_len  = strlen(src), i = 0 ;
% J+ f- X7 k3 V9 s7 B" z1 _        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前1 ^$ q8 o9 ^; m8 u5 m; R
        hs->host                = match_string(src, "Host: ", '\0');
+ j8 x. w, J) Y- z        hs->origin              = match_string(src, "Origin: ", '\0');
* Q9 Y5 D: `6 T        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');6 O+ k# ^5 r3 Z/ g: g$ D) H
        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');. F5 B) Q6 i1 i, Z; l8 a
        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0'); + Y) v: m2 A. ~/ H2 o* ^  E7 {
        char key3[8]="\0"; // 获取 key3,即最后的8位字符; L+ q1 c( \( H/ S3 K; X. C
        for (i = 0; i < 8; i++) key3[i] = src[src_len-8+i];
  |) d+ k" l1 P% Q6 I7 g        char digits1[64]="\0", digits2[64]="\0", c='\0';+ D3 r: |3 e! H9 g. o
        size_t spaces1 = 0, spaces2 = 0;
9 G3 I; ], ~" k. h& A8 G6 M        size_t key1_len = strlen(hs->key1);5 ]! ^+ q/ n7 R( L3 z, h
        size_t key2_len = strlen(hs->key2);$ M4 A5 k# ^) _2 R% [
        short d1 = 0, d2 = 0;
$ C/ ]& Z# Y9 }' @; k5 |8 l  `& z! P        unsigned int result1, result2;* Y2 A! _7 z2 p9 V6 E
        for (i = 0; i < key1_len; i++){
) ?! K7 ~4 [( R1 O( k, J/ V( E                c = hs->key1[i];
' R, E$ a, ]6 P: l                if (c == 0x20) spaces1++;( j% K+ V) H, s' E6 d
                else if(c>='0' && c<='9') digits1[d1++]=c;
0 o9 X3 L8 X( ]) i, G; E5 W        }- L  T2 l/ s$ m0 g) v5 O- [
        for (i = 0; i < key2_len; i++){ $ z9 q- z8 x' c0 I8 }3 n& b
                c = hs->key2[i];
7 G% }" K8 S% Q! i, i: n( D                if (c == 0x20) spaces2++;
$ u. ]1 j& \1 J6 f5 c0 H5 ?. \1 v6 m                else if(c>='0' && c<='9') digits2[d2++]=c; 0 \1 g5 [  |' Y
        }/ [5 E4 e% U: q* D% l
        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);
$ D( ]$ q. w. ~" A7 Z" h0 n        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);
0 T% [, q: f7 X2 X* m$ d; _        char chrkey1[4]="\0", chrkey2[4]="\0";
" ^& g$ K- F' ^) F" m( r5 l1 C* r        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);! l! _( s- f2 X
        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);
- u5 ?: w2 L. t3 b+ D8 \0 k        unsigned char raw[16]="\0", dig[16]="\0";3 v  a: V1 Q% l
        // raw 表示未md5之前的字符串,规则就是前4位key1中的数字/空格数的整数值,
6 h& {% f% ?! H! n        //  连接上key2的最后连接上头信息中的最后8位字符。
; r" `' U7 I2 K7 n4 n/ l9 E        memcpy(raw, chrkey1, 4);
% P( {! U% |3 ], M7 X. F* j        memcpy(&raw[4], chrkey2, 4);
/ v5 q8 k/ u* c" ]: {0 l; Y. ~# ^. D        memcpy(&raw[8], key3, 8);
# Z$ C$ E$ ]. s        //计算的md5值" J( v) P4 g! b
        md5_state_t state;: d, z! i: w4 }6 z0 J; _: O9 Q8 c
        md5_init(&state);7 u. a) Y/ G; V
        md5_append(&state, raw, 16);
" T0 p: ], q! @4 L' w( S        md5_finish(&state, dig);4 f1 L8 d4 ?( p1 f4 l& {' a
        $ a1 ~+ v! I5 ^& g2 s- e
        char handshake_str[BUFSIZ];
3 t. {- R2 o& @# u& L        memset(handshake_str, 0x00, BUFSIZ);
% h# w2 }- n0 `7 G# @" J        char* handshakeFormat = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"7 j' h) b1 h4 g) K: X, g, I' N
                "Upgrade: WebSocket\r\n"% t; i" `4 ?. J
                "Connection: Upgrade\r\n"& p) N3 s# Q- _& y& W9 L3 \* S
                "Sec-WebSocket-Origin: %s\r\n" 0 h1 @2 Q" S! A
                "Sec-WebSocket-Location: ws://%s%s\r\n"' R# v# t! F( E/ s3 I$ s" [
                "Sec-WebSocket-Protocol: %s\r\n\r\n";; q: E4 D  w2 u) A
        sprintf(handshake_str, handshakeFormat, hs->origin, hs->host, hs->resource, hs->protocol);- D, |$ z: r. W/ z( U9 g3 z; V
        free_handshake(hs); // 释放handshake指针,已经不用了!
' n3 ~9 h: Y1 m, ~$ S  @        char response[BUFSIZ];" n/ Z; N& F5 I8 L6 t& V
        memset(response,0,BUFSIZ);0 b& e9 q4 f4 D; V+ A* n- H6 ~
        size_t j=0, handshake_len=strlen(handshake_str);1 a! U; b+ _3 q$ g- t! B3 U
        for (i = 0; i < handshake_len; i++) response[i] = handshake_str[i];
3 M2 j! L' k1 d0 }' w7 g" d+ s0 ]8 h        for (j = 0; j < 16; i++, j++) response[i] = dig[j];
! o; K4 `5 q" e/ }: y. N, p& i# L        // 这里的clientSocket就是连接好的socket对象了。
: s# t7 k" z+ K        int sent = send(clientSocket, response, strlen(response), 0);& ^3 E5 H* L1 g0 p& J
        return sent>0;) h6 n! R% D: d) j: r6 a# m
}' x" C, Y) [$ Z' ~- c  a7 T
目录结构:; @" i- A# W) M: H7 y) o
socket// A1 `% }5 k# r6 `: T/ ^* p
      socket.c
: h& f/ Z9 }3 X0 \( [/ l5 W      md5/3 ^& u' ~: K# X& Q0 E
        md5.h" g4 s0 T! p' A; G4 j% H
        md5.c
. n# T7 s9 O# c库文件下载位置: http://sourceforge.net/projects/libmd5-rfc/files/ 总共3个文件,
8 `4 u6 p: T6 C% J" b( N编译命令:  gcc socket.c md5/md5.c -o socket.o  $ m+ E. c0 A2 Z+ K$ x6 W: I
GCC 4.3编译通过,执行 ./socket.o 可以看到打出的raw和md5后的字符串信息。 这里使用了一个网友用nodejs写的websocket实现版本,打出详细的头信息,然后复制到程序里,以测试生成的握手字符串是否一样。; t2 ^# O1 E' ~. P( a: h2 s
// socket.c 代码。: n: ^0 n. j# n. ]5 |
// web-socket example
7 o& e" D7 D. @% Q+ G* ?! j7 a#include <stdio.h>
$ @3 Y( ]8 X) H( C& f- n* ~#include <stdlib.h>
$ x( Y& ^, D+ y#include <string.h>
: V7 C4 m/ j! W#include <inttypes.h>
& h4 ~+ i) H) i" ]7 q! ?#include "md5/md5.h"
& Z1 G3 J7 m0 T4 ?' L- A* Q4 J
0 S4 l0 J- M% t+ M" h; |, c8 z" K- i// #define BUFSIZ 512, Z9 ?# c3 b( G* G$ |
. Y( F: ^7 g# T
//定义handshake结构体变量
2 [7 X0 H3 g0 ?! pstruct handshake {2 W) N. t' U: Q7 w
        char *resource;
0 k+ h- u8 w0 e0 k! G7 y        char *host;
4 ^5 s* p& N) b        char *origin;
' Q- _- [9 n% c$ \8 s        char *protocol;
5 q# H# o1 {- z* P        char *key1;
3 R3 W* L2 b* \2 n! S$ O& Y+ B        char *key2;
" @+ g7 r: k" P6 R; _( S};: a! Z" p9 X( X
! p/ W7 x& N& n. U3 G5 P9 u: O
- r, D) ~% Q, r: r
//释放握手后不再使用的部分变量
+ h- g8 J6 n6 ?4 L2 Q% n, D" \# bvoid free_handshake(struct handshake* hs){
! ]4 Z+ S, C2 [/ {8 L        if( hs->resource != NULL )      free(hs->resource);  ]# `) W/ `. g
        if( hs->host != NULL )          free(hs->host);
& M* j" y& M/ h        if( hs->origin != NULL )        free(hs->origin);- Q4 `+ W& }: \9 c! I! u
        if( hs->protocol != NULL )      free(hs->protocol);
4 Q) e( ^4 v- z3 ?        if( hs->key1 != NULL )          free(hs->key1);% `0 q$ H6 y5 f- y
        if( hs->key2 != NULL )          free(hs->key2);
  Y: r. s+ b7 s) Y4 @/ D& A}! D3 T, q' p# H& x

! A4 O9 H' k! c  {5 _6 P% H// 这里对上一篇的match_string做了点修改,/ q8 T$ P2 i  G- k0 y: }  k# \
// 增加了一个end参数,这样不必提取出字符串后,再做剔除处理
% ?# v% H5 ?$ Q: ^: Tchar* match_string(const char* src, const char* pattern, char end){/ D, X$ x3 ^: v5 ?' l) y' y
        char buf[BUFSIZ];
) h+ `# m% D4 B- _" Q, L        memset(buf, 0, BUFSIZ);
5 y9 k# N' R$ f) N( N        size_t src_len = strlen(src);
9 H" C4 y7 {# B1 A" W        size_t ptn_len = strlen(pattern);8 I6 W' [$ G( R% ^. Z- k
        unsigned short b=0, p=0, i=0;
9 n5 s, {6 G+ s5 ^        char c='\0';
8 B8 @) l! S! w. a        for(i=0; i<src_len; i++){
. I! X. ^4 N8 f9 @- H7 ]6 R/ g                c = src[i];5 ]9 d. W0 C# o3 Z- i5 e% H
                if(p==ptn_len){ // p==ptn_len 表示正在匹配中
6 w. T$ \1 W4 `1 l- o0 b- o                        if(c=='\r' || c=='\n'  || (end !='\0' && c==end) ) p++; // 匹配结束
- b3 d4 X# A2 ~( D4 t2 r                        else buf[b++]=c; // 匹配到的字符 3 P1 ]- b: @% }% K2 k6 Z5 N8 H
                }else if(p<ptn_len){ // 为达到匹配要求8 ?( z  W5 E+ @2 p: \' C" K8 Z
                        if(c==pattern[p]) p++;  Z/ ?, _! z& G; p  y( a
                        else p=0;3 w9 Q& f6 X: B" ~4 ^8 j  Y
                }. ]3 ~( w% s  b0 G) M" r8 N
        }
1 p9 f# y! L3 V9 a        size_t ret_len = strlen(buf);
) a# T1 h4 K- w6 [$ P7 P2 t        char *ret_p; - c2 Q; o% s6 `* I0 v' Q  Z
        if( ret_len>0 ){
# q2 z3 e% i: H2 }5 s                ret_p = (char*)calloc(ret_len+1,sizeof(char)); // 加 1 为了存储 '\0'
* G% K& C1 U1 Z( V4 A                memcpy(ret_p, buf, ret_len);
2 Q( n" E% Q/ n' ]: R+ Y        }else ret_p = NULL;
/ P5 ?0 {" c. o+ t* H# q. s        return ret_p;
( d: J, B( `* h  W6 M}
, h5 C- `- T* |" ~3 \) h' s6 q8 @( c4 t# M* U; M
// md5 加密函数,用的是网上一个实现的比较通用的版本。* k1 q: j! z! z* N' o, i0 k
void md5(const char* src, size_t size, char* digest)$ B8 J' k& t3 e4 C% W
{0 w, Z5 U5 ]) H+ M7 p
        md5_state_t state;
$ X  @* @9 g7 V, A6 D        md5_init(&state);% O6 d% J$ k6 m7 l2 n' a
        md5_append(&state,  src, size);0 F$ P( a0 a& T" x) B
        md5_finish(&state, digest);; I& I4 f! h: `: Z
}
, e, \# X+ z. [  C+ o) P: y& q1 O
$ K. p0 ?( F  w' s5 J0 {void p(char*s, int len){, J' a3 H3 n( }
        unsigned short i=0;0 l+ b- N, J$ i! c9 e4 I
        for(i=0; i<len; i++) printf("%c",s[i]);% _: ^6 f) ?( j0 w- ~" b+ U8 {3 P
        printf("%c", '\n');
9 s" p* S1 ]& i}2 C0 s. a0 M, t( a. o1 C$ A
6 G# X2 K* W: |
void handshake(const char* src, struct handshake* hs){
* V# h" O$ a4 v+ j. i        size_t src_len  = strlen(src), i = 0 ;
4 T8 u* F8 k( Z1 W( C        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前
, j! i/ G3 S! E8 C; W$ V( y  \        hs->host                = match_string(src, "Host: ", '\0');* O- r1 W% Y# L5 X" C. T$ p4 h
        hs->origin              = match_string(src, "Origin: ", '\0');3 d8 h9 K8 @( f7 s
        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');
2 [, Z% Y" w! k! U, B        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');* T  x7 a5 m! R$ G9 W) ?0 b+ R/ g
        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0');
$ F2 R. o+ o. i: L8 s2 |4 C8 b        // 获取 key3,即最后的8位字符
: t0 Q* U& n0 y2 k0 S        char key3[8]="\0";
* H) F* [" ~: m% Q; A        for (i = 0; i < 8; i++) key3[i] = src[src_len - 8 + i]; 1 _3 }+ d  `0 Z* m. j$ j" ]( d
        char digits1[64]="\0", digits2[64]="\0", c='\0';* T0 `6 [$ M( n1 |& e3 f2 i
        size_t spaces1 = 0, spaces2 = 0;8 J5 y) @. ~8 @$ ~3 E9 U2 F
        size_t key1_len = strlen(hs->key1);
6 ~' e; Z' [; \; F( ?; k' j/ }        size_t key2_len = strlen(hs->key2);* {0 N( w# \$ k
        short d1 = 0, d2 = 0;' f* c/ b5 X7 L5 C! y) O* ]
        unsigned int result1, result2;' e/ v9 |2 @+ D7 }
        for (i = 0; i < key1_len; i++){   m# S) m, R0 g
                c = hs->key1[i];
; [, c3 S- C: v! j                if (c == 0x20) spaces1++;
1 @3 L6 N; I' f' a2 Y                else if(c>='0' && c<='9') digits1[d1++]=c; ) U. a+ N, K8 ^; g* Z* c& T
        }
  Z: X. }+ _. V  N9 S        for (i = 0; i < key2_len; i++){   H; j' |) Q. e
                c = hs->key2[i];  V1 e0 p- k' \  ?8 ~+ `
                if (c == 0x20) spaces2++;* n4 S, p) k( \# K% n9 X
                else if(c>='0' && c<='9') digits2[d2++]=c; 2 A( h0 L. D3 |8 d: f
        }
5 P2 o2 r  \5 h        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);
$ G' ^5 v$ n- m        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);! X  e9 T% H! p" V4 G
        printf("ch1:%s\nch2:%s\n",digits1, digits2);( E, {( |) e- y. s
        printf("sp1:%d\nsp2:%d\n",spaces1, spaces2);3 l/ {  J, N; k1 N
        printf("d1:%d\nd2:%d\n"  ,result1, result2);
$ p8 ^6 U0 M! a2 M6 y        unsigned char chrkey1[4]="\0", chrkey2[4]="\0";0 Q) y( h: s* Y9 h2 v
        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);/ h: {( ?; |0 X  w
        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);
! W3 U8 m; l- M' U  [% ]# e        printf("ch-key1:"); p(chrkey1,4);
' n/ x9 _1 b* U$ H        for(i=0; i<4;i++)printf("0x%02x ",chrkey1[i]);. Y5 d. O7 ^0 e* o, I8 n8 P7 p
        printf("ch-key2:"); p(chrkey2,4);
. ]5 q- B9 }5 L/ v4 \2 D        for(i=0; i<4;i++)printf("0x%02x ",chrkey2[i]);. S% {4 a' J% t( m0 W5 k1 ]! F* E
        unsigned char raw[16]="\0", dig[16]="\0";
' x: Y; L# \- K, w. m. s. G        memcpy(raw, chrkey1, 4);
% z) ~- u& B& q) H0 ]3 y& T3 E        memcpy(&raw[4], chrkey2, 4);" P4 F0 a. {/ ?. _" }# `6 @2 c
        memcpy(&raw[8], key3, 8);
6 n# |+ d* t% _9 m& e% ]        //计算的md5值
/ C. a5 ]' j, d2 ]        printf("\nraw:");
8 R, q2 G1 h7 Y4 M: r" R9 P        for(i=0; i<16; i++) printf("0x%02x ",raw[i]);' e9 I9 _8 u* l8 I! F" ^% }
        md5(raw, 16, dig);
% {# v- Y5 q$ u( ~2 @        printf("\nmd5:");
- Q% o2 N; |% Q7 Q$ p0 ]! Z6 c) N        for(i=0; i<16; i++) printf("0x%02x ",dig[i]);
8 J  n& [4 o& x, c) Z$ j}& y4 T' v5 Y, _! |* \9 v8 R$ ]
) _6 Y: A9 ?0 k

* D' L9 [5 q4 v+ r" H2 W/ c  _( oint main()
/ W: ^# m' e2 @& |{+ K7 j/ J  A0 ?: k( _( _
        unsigned char msg[512] = "GET /pub/chat?q=me HTTP/1.1\r\n\
" x1 N6 |4 T" _3 W9 a! ]0 U: ^- a  u        Upgrade: WebSocket\r\n\
' U/ ~$ a/ t+ \- ]$ A* ~% K$ J        Connection: Upgrade\r\n\
( |+ V# o. P# t! V( d# e        Host: localhost:4400\r\n\8 a3 T  w8 `2 f- j: n' P
        Origin: null\r\n\: j% V6 ?9 q( L0 w) C
        Sec-WebSocket-Protocol: my-custom-chat-protocol\r\n\
+ t  l5 Q/ z! {# h. d8 d        Sec-WebSocket-Key1: x EO2 59186 4  28\\dY 0+\r\n\
$ Z1 e* W1 V) |$ y. ^. ?: U        Sec-WebSocket-Key2: 1  9  3  57695W    0\r\n\r\n";
: _" r$ I0 Q5 F1 u        size_t len = strlen(msg);
5 P+ D' g' n6 M        msg[len]  =0x1f;
) V/ c$ ~: }: @* B/ ^        msg[len+1]=0xf6;
9 C& I! B" f2 F: S$ b6 c        msg[len+2]=0xf3;7 j( Y. r, k. V2 l* c# ^% ~
        msg[len+3]=0x3f;0 |" Y6 F: `0 S# F9 t' I
        msg[len+4]=0xc7;% R: S( r# O5 d* V' l% t
        msg[len+5]=0x17;& ]0 x" J( ?+ ^0 E/ ~' H
        msg[len+6]=0x20;
5 B0 h- j' l% c3 E- f4 M        msg[len+7]=0x88;9 z! g8 A' W1 d1 u& h
        struct handshake hs = {NULL, NULL, NULL, NULL, NULL, NULL};" G+ h2 u0 d& |) D0 G" _
        handshake(msg, &hs);
) a- E7 v) f& X' w+ g) c* c' n        free_handshake(&hs);) T" K2 V8 ~+ @
        return 0;
9 y/ i: K2 T7 w4 `( n  Y}* r2 g8 i+ s5 S, N/ {0 [9 C
* p) k+ Z: o. h1 R

) V, }$ i5 H: R, a( W' n测试的结果:
! E0 k8 T2 N. P! s( M5 ~raw:0x19 0xbf 0x73 0xa4 0x01 0x27 0x5f 0xff 0x1f 0xf6 0xf3 0x3f 0xc7 0x17 0x20 0x88 / ^8 {, g$ {) q8 N0 a  x, u
md5:0x61 0x30 0x1e 0xe8 0x8a 0x17 0xaf 0x39 0xd6 0xad 0xef 0xb9 0x6f 0x00 0x0f 0x68 & p; G* d( S# V% E
对比了nodejs的版本,握手部分生成没有错误。6 B8 Y  t* G$ ^1 r/ T7 F' t
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-6-18 07:58 , Processed in 0.023809 second(s), 19 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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