找回密码
 注册
查看: 5838|回复: 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++庞大的类库和模板。
3 j  T9 s* }  N" d2 w完整的windows版本socket握手实现:
% x* m% h& Q& O8 d! ^; w6 g- I  I0 J6 V" R, @% ^3 z5 Q2 k1 v# ~
bool WebSocket::handshake(const char* src, struct handshake* hs){
/ g$ T' Q$ o3 \' }: `8 |4 m        size_t src_len  = strlen(src), i = 0 ;+ o& H, W* N- c. I' |
        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前2 I1 M: j7 `1 l+ L! J
        hs->host                = match_string(src, "Host: ", '\0');
0 R9 p* k  x$ z0 r, S        hs->origin              = match_string(src, "Origin: ", '\0');$ \, n7 U! b, m% u" X( F% W8 e% v
        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');
, x2 s* c+ k' T5 f9 B( p* L        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');
4 c; k$ s9 g: T, i7 E+ F        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0'); 9 D7 q" F9 s8 \; }' e+ A; }& c
        char key3[8]="\0"; // 获取 key3,即最后的8位字符
( j7 X' ~* ~" Q& [: I, W        for (i = 0; i < 8; i++) key3[i] = src[src_len-8+i];
5 Y6 J6 `, c0 V( Z4 {( D6 o        char digits1[64]="\0", digits2[64]="\0", c='\0';
' U! O# K- b  U/ z% ]        size_t spaces1 = 0, spaces2 = 0;7 |& n' G& ]6 Q
        size_t key1_len = strlen(hs->key1);8 A9 q- L' E* D& C! L
        size_t key2_len = strlen(hs->key2);
6 N% p. f8 Y1 l+ r7 E$ V        short d1 = 0, d2 = 0;
3 w5 y) ?  ^. g  O8 W) b; D& d        unsigned int result1, result2;2 \3 Y0 R( M$ R1 A: G$ ]- f
        for (i = 0; i < key1_len; i++){
) t$ K0 R8 `) \9 k% x# S                c = hs->key1[i];3 R' q1 \7 Y- K: _' h
                if (c == 0x20) spaces1++;4 f) w7 d9 D- O$ d( ^
                else if(c>='0' && c<='9') digits1[d1++]=c;
3 }2 l  B" q% A8 @* i+ @! V) `        }
5 z. ^- E; ]# d$ C        for (i = 0; i < key2_len; i++){ $ \9 T* U' h( ^1 P
                c = hs->key2[i];1 t( K1 b6 Y/ t# s, A5 R$ d! k
                if (c == 0x20) spaces2++;" c; w" r# O) }6 o; C$ n
                else if(c>='0' && c<='9') digits2[d2++]=c;
* B8 C+ T+ _* O  ^1 ?        }
6 u; k4 a2 `+ H$ P& Z$ [1 U        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);
! P9 x1 }! E1 y% S        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);
3 v1 n* C  a* T* H. h( }        char chrkey1[4]="\0", chrkey2[4]="\0";
4 _8 ~: B% J9 V7 A" u5 M! e        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);
2 r/ b. m" q: ^# o( x' w6 |        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);/ V" _: Q  w  T. [3 x6 o
        unsigned char raw[16]="\0", dig[16]="\0";
, N' M6 `) D2 e4 c" i/ H1 r; I        // raw 表示未md5之前的字符串,规则就是前4位key1中的数字/空格数的整数值,, ~# A  K4 Z& X& O) [; l
        //  连接上key2的最后连接上头信息中的最后8位字符。
1 U! ?* {5 e/ x- X0 R        memcpy(raw, chrkey1, 4);  K% l/ u; B. ?7 J/ T/ {
        memcpy(&raw[4], chrkey2, 4);9 A1 P$ L: r! E( J
        memcpy(&raw[8], key3, 8);& W0 j. o4 [) i& r0 J( F. M0 |0 Z
        //计算的md5值' v; R$ `" e  {- b
        md5_state_t state;* V0 ]& P! {8 D: _% W0 \9 m
        md5_init(&state);
+ \( Z& c( l! x/ P" ~        md5_append(&state, raw, 16);% l0 }, r0 N; X/ s8 x+ ^$ y/ ^
        md5_finish(&state, dig);+ P$ m* s* a/ r2 e: a/ R8 ^" k
        
: J9 D/ S% n0 l# W0 h$ s        char handshake_str[BUFSIZ];
- ]. |* n( [$ M" W* b        memset(handshake_str, 0x00, BUFSIZ);
9 `3 i* G$ {- E# ]! b        char* handshakeFormat = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"/ d1 ^: D, g) M/ F$ B- x
                "Upgrade: WebSocket\r\n"
; D8 n( O; |# h( g                "Connection: Upgrade\r\n"
: y0 y8 }8 m0 I$ g3 \) c                "Sec-WebSocket-Origin: %s\r\n" ) u5 I& \4 M# R
                "Sec-WebSocket-Location: ws://%s%s\r\n". h, V( O: l( L% `  D
                "Sec-WebSocket-Protocol: %s\r\n\r\n";
' k6 T! ^9 d" |        sprintf(handshake_str, handshakeFormat, hs->origin, hs->host, hs->resource, hs->protocol);9 }6 n. U4 o0 ~! Z0 i7 P" |; @7 A
        free_handshake(hs); // 释放handshake指针,已经不用了!
0 s& h* t5 G2 S* M        char response[BUFSIZ];
# S9 ]1 p$ p2 [# C' H        memset(response,0,BUFSIZ);+ a) o; Z8 H& X: e6 ~1 @
        size_t j=0, handshake_len=strlen(handshake_str);
$ `- R, b6 g! u: U. g        for (i = 0; i < handshake_len; i++) response[i] = handshake_str[i];. y0 o3 @! W' T/ N
        for (j = 0; j < 16; i++, j++) response[i] = dig[j];2 x  `3 i7 p4 |$ j( q1 K, `
        // 这里的clientSocket就是连接好的socket对象了。
) N4 m4 Q7 U3 q1 {        int sent = send(clientSocket, response, strlen(response), 0);
6 P) t% d0 A0 g3 r) W  T+ T        return sent>0;% o% ]5 \; O% ]# r
}
. a% M8 \% ?5 N- }/ U0 @目录结构:
" f6 B$ P+ F$ v0 I3 S! E% M5 gsocket/
8 X+ e  p+ x9 s0 X      socket.c6 e- J. n/ t: D5 h$ M' q
      md5/% V- `$ O0 b$ K) i; ^# `' Z2 C( g
        md5.h
+ {3 j$ l  z, \( ]" d' N3 A1 l        md5.c
. v5 k% W" ^1 o1 a- g2 Z库文件下载位置: http://sourceforge.net/projects/libmd5-rfc/files/ 总共3个文件,
( G9 G" ^  W) V7 y编译命令:  gcc socket.c md5/md5.c -o socket.o  
  N) C3 @+ c& n2 {8 g GCC 4.3编译通过,执行 ./socket.o 可以看到打出的raw和md5后的字符串信息。 这里使用了一个网友用nodejs写的websocket实现版本,打出详细的头信息,然后复制到程序里,以测试生成的握手字符串是否一样。' Z  I9 n0 X! }; L
// socket.c 代码。
1 d# \3 W; I0 C2 v* s0 U// web-socket example
3 o* h! U) }0 c#include <stdio.h>- B' w$ p, e' q5 u
#include <stdlib.h>
' b6 q1 u) A5 K9 \* T# W) T, C#include <string.h>
: v/ [; {  }6 b8 q$ `#include <inttypes.h>
% A& Q' y# i/ |#include "md5/md5.h"
/ I* i# e& {! L% f/ v+ E
0 j4 Q' e- a+ O, V& r) p// #define BUFSIZ 512
9 H* O( V6 F2 Q2 d4 p( w! @' ]  O
//定义handshake结构体变量
# M* {$ T6 \3 {struct handshake {) n' B4 m4 t2 N% X
        char *resource;1 Q9 d/ p2 A# e) C
        char *host;  g& X+ L, F6 z, t
        char *origin;5 ^8 h7 c( ?; Z% g
        char *protocol;
8 @$ h& g1 U& O# k0 Z" z1 ~8 k        char *key1;
) z" W+ S7 K5 X2 J% I: E2 C+ z        char *key2;
, o, c. R8 z% E  m+ T5 Y1 c, [};% f; \% U( t4 |# L7 |. q5 _) b1 l

& h. q- X* q+ o/ x% T; ]: I! r* w- b& X+ d' _+ f: w
//释放握手后不再使用的部分变量
4 _0 X! I, B* y+ i9 w/ vvoid free_handshake(struct handshake* hs){! h" ~; x- `+ h0 d2 H' {
        if( hs->resource != NULL )      free(hs->resource);" {* e" W8 B4 U+ }( [
        if( hs->host != NULL )          free(hs->host);
+ ?0 Q4 e$ g4 T% N. r        if( hs->origin != NULL )        free(hs->origin);& h5 W8 U: T  i# r
        if( hs->protocol != NULL )      free(hs->protocol);
: g+ v# `2 f" R        if( hs->key1 != NULL )          free(hs->key1);
" `, U4 z. K% M6 p4 Y+ Y: l        if( hs->key2 != NULL )          free(hs->key2);
) N3 T* O; E% i7 x# U. n}
& `: a: l1 x, ~6 ~4 Z& F) I7 L: u( y4 j" H( Q
// 这里对上一篇的match_string做了点修改,* U7 u" J' \+ H6 T5 R
// 增加了一个end参数,这样不必提取出字符串后,再做剔除处理
% i1 ?5 g+ }$ K6 ichar* match_string(const char* src, const char* pattern, char end){
% a! Q. w0 d# b. v9 L$ G7 i        char buf[BUFSIZ];* z2 W1 W1 e* d3 V$ f, q
        memset(buf, 0, BUFSIZ);
& w! D3 Z9 |0 u7 y        size_t src_len = strlen(src); * n  \7 q; ~- N7 L
        size_t ptn_len = strlen(pattern);
0 s2 h+ Q- K. N+ i$ x        unsigned short b=0, p=0, i=0; 0 t! M& S$ p* ?5 \
        char c='\0';2 Z9 }6 f) g7 M6 k' i/ g
        for(i=0; i<src_len; i++){
/ R! d3 t+ w% M7 K                c = src[i];6 i4 ?" ]# j% k* [) w" V) i
                if(p==ptn_len){ // p==ptn_len 表示正在匹配中
4 U9 H2 n7 ]$ B7 l                        if(c=='\r' || c=='\n'  || (end !='\0' && c==end) ) p++; // 匹配结束
3 k, C, h, |8 ]3 K% d8 [1 z( R                        else buf[b++]=c; // 匹配到的字符
5 B7 C8 r7 E0 t                }else if(p<ptn_len){ // 为达到匹配要求. n5 f5 G. w5 u0 a& d
                        if(c==pattern[p]) p++;  ^: @8 C% J+ s
                        else p=0;3 e1 {) u3 q' z7 E
                }
5 R* \0 `# D  N# |( a# Y$ f        }2 R$ B5 A! o' i# e5 R
        size_t ret_len = strlen(buf);* t0 ]8 O+ h) z7 \
        char *ret_p; % ~2 i7 B1 r; V9 C+ A
        if( ret_len>0 ){1 ?7 r  |  h8 C4 o: F
                ret_p = (char*)calloc(ret_len+1,sizeof(char)); // 加 1 为了存储 '\0'/ T/ i/ j7 `, _; }5 Z
                memcpy(ret_p, buf, ret_len);6 k4 v& Q/ r  B8 n
        }else ret_p = NULL;: N2 K" s+ N6 z! l* h7 E0 e& g* A8 x
        return ret_p; 7 `8 s1 F3 W- M" A
}
) ]; o, [8 \  M# u( A
8 b" ]: k( D& i9 O// md5 加密函数,用的是网上一个实现的比较通用的版本。1 q6 y/ s; \. j! z
void md5(const char* src, size_t size, char* digest)
; M0 e, N) Z: ~! ]* }{
" ]9 s+ y- h0 S3 a. j% n9 @        md5_state_t state;
0 x/ B/ g1 U; n        md5_init(&state);2 f0 D, a6 Y3 L  w/ V
        md5_append(&state,  src, size);
4 I: h6 x% z+ ?5 D$ r1 I9 b7 s        md5_finish(&state, digest);5 D) H* A( l( C/ Q) R, e
}9 s  R6 _. d9 }! @6 O5 Y
3 X# D: N* B6 g  A
void p(char*s, int len){5 S7 q! E7 d, M- n% r
        unsigned short i=0;
" X1 l# ~, D$ U# a        for(i=0; i<len; i++) printf("%c",s[i]);6 @8 O# A6 u/ `8 V
        printf("%c", '\n');
) P6 n. Y) @) s) ~# C}
6 @- |5 t- f' q* D( k& ^
/ R5 \8 B- G+ l0 x* {, Q  y6 ?: K  Yvoid handshake(const char* src, struct handshake* hs){, ]. G1 U$ q# E$ m  m9 s% I
        size_t src_len  = strlen(src), i = 0 ;
5 H* e2 d( P$ _+ t$ }        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前* b7 F9 y& G; K7 r# c! P
        hs->host                = match_string(src, "Host: ", '\0');8 w4 M" _. x: T4 O' q% z
        hs->origin              = match_string(src, "Origin: ", '\0');' P# e+ U& u# L. D! o0 I" o: }$ J
        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');
" F5 z" G- N7 l/ n8 Z. P3 Q; y7 |        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');, l! y$ k& \4 m
        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0'); 7 n2 l8 A0 z" c9 w3 _
        // 获取 key3,即最后的8位字符
6 c4 ~  J% c; z  r9 q        char key3[8]="\0";! u  k" Z- c8 @2 I$ U
        for (i = 0; i < 8; i++) key3[i] = src[src_len - 8 + i];
8 w0 j# k! T. N; h        char digits1[64]="\0", digits2[64]="\0", c='\0';/ V- T* ?. ]3 P. q: O
        size_t spaces1 = 0, spaces2 = 0;6 k2 F8 b: p9 A* P) s
        size_t key1_len = strlen(hs->key1);
% e( ]) B- S5 Y4 Z        size_t key2_len = strlen(hs->key2);
9 m) b* v/ @' f/ E; h" C        short d1 = 0, d2 = 0;
5 i3 j. u. S" S) w. q$ \9 f" S3 [" u        unsigned int result1, result2;' d0 W- q6 E! ~+ f6 Z
        for (i = 0; i < key1_len; i++){ ) s* e! x" o( h
                c = hs->key1[i];. I4 t+ [" I+ V/ z+ g; t& W
                if (c == 0x20) spaces1++;
7 V/ \0 S" Z+ l, ^                else if(c>='0' && c<='9') digits1[d1++]=c;
5 M- b, Q8 l% d2 W, y* t. }        }
" d7 J0 \8 q: z2 S% r5 {        for (i = 0; i < key2_len; i++){
3 o  G- E; M7 N$ M0 n( e$ g                c = hs->key2[i];- V: Q! q3 H+ G. V+ y5 `! P- J
                if (c == 0x20) spaces2++;
8 d7 B0 u3 ?; O3 S* A2 V  L' V$ N                else if(c>='0' && c<='9') digits2[d2++]=c;
1 z' a9 M9 G. B/ T        }6 d3 p  d% F, [! g; ~! w2 Q
        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);1 G5 ~, K1 J/ d) f& v
        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);* }* \! G. z+ o( _2 o4 }9 M
        printf("ch1:%s\nch2:%s\n",digits1, digits2);8 ]0 f; o& _+ O& V# d; P" e3 O0 }
        printf("sp1:%d\nsp2:%d\n",spaces1, spaces2);, T; v9 p& }2 {, p! n
        printf("d1:%d\nd2:%d\n"  ,result1, result2);
# t; b( e+ W) @9 E        unsigned char chrkey1[4]="\0", chrkey2[4]="\0";
% _, n( y& y: g/ t        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);/ E) I" G& g! p" |, }% E3 V
        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);; R1 S# W; w% D' V/ t6 r
        printf("ch-key1:"); p(chrkey1,4);
, f% d( d9 ^2 u3 U* I/ w" O        for(i=0; i<4;i++)printf("0x%02x ",chrkey1[i]);  ?; ?. Z, [% K: B7 ^
        printf("ch-key2:"); p(chrkey2,4);
3 D! c- T5 w+ e6 u        for(i=0; i<4;i++)printf("0x%02x ",chrkey2[i]);
1 G; ?2 R4 O8 }; P2 l        unsigned char raw[16]="\0", dig[16]="\0";) U& ?2 {; @3 w; J, v9 k
        memcpy(raw, chrkey1, 4);, s* v; p% J& _% D# r8 e0 Q, A
        memcpy(&raw[4], chrkey2, 4);  |6 H( k( v* A
        memcpy(&raw[8], key3, 8);& ?- m( ]/ x* p3 @
        //计算的md5值
( |/ X7 b7 h$ f, P" O6 O+ |4 l8 w        printf("\nraw:");
% O4 [; E7 o5 |1 u' r! I        for(i=0; i<16; i++) printf("0x%02x ",raw[i]);; A7 D& p* X4 g' H
        md5(raw, 16, dig);
: y& \; g$ |7 _& y" J        printf("\nmd5:");& ^. }1 j; M7 J  w0 i8 I
        for(i=0; i<16; i++) printf("0x%02x ",dig[i]);
. ?! Y7 s6 N. z7 }$ O}  F1 q1 C# A8 U1 c

& i( |/ C9 f- \  h  J6 l, N- O) \
int main()* U: z  w# i% E5 P
{
4 n( C( t8 u4 d+ T6 y, r# Q        unsigned char msg[512] = "GET /pub/chat?q=me HTTP/1.1\r\n\  Q: o% }3 J! b! w5 q: z
        Upgrade: WebSocket\r\n\, ]4 W$ ^& p  [, O* a0 L
        Connection: Upgrade\r\n\
  A! W. i$ U+ _+ }' n3 d* Z        Host: localhost:4400\r\n\: U, V- ~$ S0 v. d4 f
        Origin: null\r\n\3 `' l2 r) P4 _; D3 D9 j
        Sec-WebSocket-Protocol: my-custom-chat-protocol\r\n\5 g+ Y" ?1 l6 g/ d' R6 n" {
        Sec-WebSocket-Key1: x EO2 59186 4  28\\dY 0+\r\n\
  N) |+ ~  p" s- z        Sec-WebSocket-Key2: 1  9  3  57695W    0\r\n\r\n";+ e/ Q3 L  E. g0 t
        size_t len = strlen(msg);2 A6 E" o; F5 l2 y
        msg[len]  =0x1f;
8 p! B8 Q8 [$ v. n" A1 G0 {        msg[len+1]=0xf6;" }9 x4 J$ c8 N% j8 ^, `. }: H
        msg[len+2]=0xf3;* S- l/ C- B& V% Q5 ~6 y$ b" ]
        msg[len+3]=0x3f;6 h6 q1 X- w$ r) S/ \
        msg[len+4]=0xc7;
, a+ L' k' Z% Y7 g# y9 u        msg[len+5]=0x17;
0 X+ a' |- \3 R+ Y$ z3 z        msg[len+6]=0x20;
( Q7 `; `5 {: `+ _" D0 S' x        msg[len+7]=0x88;5 G' `$ m5 j% o
        struct handshake hs = {NULL, NULL, NULL, NULL, NULL, NULL};
3 [& g3 W* O& j+ D1 Z        handshake(msg, &hs);+ @, @( D. f! Q2 x: d8 y+ M
        free_handshake(&hs);, n+ c* O* k$ v0 h. |' T
        return 0;
0 G& ~( z7 q, ~; Q}" g- d: v9 }. e+ i( d
5 Z; M1 E6 F: `* c
+ m" J9 p5 }& Y/ U5 ?
测试的结果:
( n7 O6 e$ o$ x/ m* p" l7 ^raw:0x19 0xbf 0x73 0xa4 0x01 0x27 0x5f 0xff 0x1f 0xf6 0xf3 0x3f 0xc7 0x17 0x20 0x88 ) |( u3 p' f, G6 c& [% A; @& G$ n$ }5 c
md5:0x61 0x30 0x1e 0xe8 0x8a 0x17 0xaf 0x39 0xd6 0xad 0xef 0xb9 0x6f 0x00 0x0f 0x68 * y+ n5 l  O6 ]2 b  D( [2 Z
对比了nodejs的版本,握手部分生成没有错误。
: g- d1 |  h  l* k3 H; C7 W1 G8 B0 F
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-10-2 00:12 , Processed in 0.017947 second(s), 17 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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