找回密码
 注册
搜索
查看: 5201|回复: 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++庞大的类库和模板。" s$ G  V/ I( d8 V0 N
完整的windows版本socket握手实现:1 S( [4 b, s; m  M9 z# g

9 [& H3 e. ^3 ?1 _, Ebool WebSocket::handshake(const char* src, struct handshake* hs){  ^0 `2 I/ C' p: v! ?7 |
        size_t src_len  = strlen(src), i = 0 ;
# p  [: R' w9 c& P: A- M' v0 n        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前& k8 m2 T9 Q  k" }
        hs->host                = match_string(src, "Host: ", '\0');
4 I4 l) p" p4 V0 I. V( r        hs->origin              = match_string(src, "Origin: ", '\0');7 a& H+ B# A1 Q" s. I8 \5 h+ W
        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');$ X$ `1 q& K# E8 U4 x. K
        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');0 p7 r2 X) o( W% C% F; l
        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0'); # E: h5 u4 [. y& o! U0 B
        char key3[8]="\0"; // 获取 key3,即最后的8位字符
; O7 n, _& K$ N( b% G        for (i = 0; i < 8; i++) key3[i] = src[src_len-8+i];
' T; E" |- W7 ]# G4 w3 I$ F9 I/ s        char digits1[64]="\0", digits2[64]="\0", c='\0';1 E5 i! O0 r' f2 o- t! F
        size_t spaces1 = 0, spaces2 = 0;
0 Y2 V: J  N; a1 e( R/ k        size_t key1_len = strlen(hs->key1);5 `* r' ?6 D4 V6 x; `6 V* |! G
        size_t key2_len = strlen(hs->key2);
; A. ~5 D" G, S' z        short d1 = 0, d2 = 0;2 y* x5 c, O% P; f0 r5 W
        unsigned int result1, result2;5 E; G% S9 [7 {2 d; I3 b! n
        for (i = 0; i < key1_len; i++){
" N% L- F5 F1 j. O8 }  g                c = hs->key1[i];
* G* M; P3 l, H- n6 L                if (c == 0x20) spaces1++;
; j3 X6 ~! V1 P. o                else if(c>='0' && c<='9') digits1[d1++]=c;
; L; q# D( F9 Y' E, w- }        }- d0 e* v9 V& j  p; g
        for (i = 0; i < key2_len; i++){
5 O- e- f3 v  H$ u" x8 @2 n& h                c = hs->key2[i];
) @3 K5 Q% z6 U# D/ {* O1 M1 P& t* W                if (c == 0x20) spaces2++;$ y  F1 X6 ^+ W# B7 ?+ }7 H
                else if(c>='0' && c<='9') digits2[d2++]=c;
0 R( a1 i* }+ F' p  x0 X$ J$ U        }
1 W  e! o! F- B9 e3 W        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);
" I6 C6 i0 Y8 r" \        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);
: t& l  E6 m8 V        char chrkey1[4]="\0", chrkey2[4]="\0";5 H9 q9 ]: E% h( X. Y
        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);( D; N- V2 @- C- V# h1 z. q
        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);/ ^' J" x1 c# s& y) T  {; ~7 p6 D
        unsigned char raw[16]="\0", dig[16]="\0";
+ D1 ?- t# Q/ q7 `% j  c        // raw 表示未md5之前的字符串,规则就是前4位key1中的数字/空格数的整数值,
6 S: p" Z1 H7 f8 Z        //  连接上key2的最后连接上头信息中的最后8位字符。
" l( O/ J5 R8 v' A1 k" T        memcpy(raw, chrkey1, 4);
7 z5 p( M0 `2 |. n        memcpy(&raw[4], chrkey2, 4);8 D3 C0 V# d) u( E8 g
        memcpy(&raw[8], key3, 8);
4 u$ s/ u. A9 D        //计算的md5值4 o/ d6 D5 ]/ U) B  c
        md5_state_t state;
- X0 c- M9 X1 o. N        md5_init(&state);
5 m! }9 b- N4 Z$ C  f2 I1 Z        md5_append(&state, raw, 16);: s1 f* @4 O* L
        md5_finish(&state, dig);& r& f& X  }# v+ w) k* X$ v0 A
        2 Z2 ^9 c6 ~* w& r1 t
        char handshake_str[BUFSIZ];
: h$ h7 H/ c6 b% k) J        memset(handshake_str, 0x00, BUFSIZ);! o& [+ h3 L, Y
        char* handshakeFormat = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
8 m( F5 E) P% Q7 n" v+ z                "Upgrade: WebSocket\r\n"
2 F8 Q& e. |4 n% m& ?9 @: G                "Connection: Upgrade\r\n"$ \# m3 Z' W2 L2 \7 V+ k4 P
                "Sec-WebSocket-Origin: %s\r\n"
! S. j6 `. b5 I) ~                "Sec-WebSocket-Location: ws://%s%s\r\n"
- C9 j- [  n3 Z6 f- l0 l- R  W2 p% M                "Sec-WebSocket-Protocol: %s\r\n\r\n";8 f8 l8 W$ A7 P6 }
        sprintf(handshake_str, handshakeFormat, hs->origin, hs->host, hs->resource, hs->protocol);
5 a4 [! Z( M/ f4 `7 ?) Y        free_handshake(hs); // 释放handshake指针,已经不用了!2 t3 Z5 `( p: T, \
        char response[BUFSIZ];+ w* H! i; L+ }& I! x& I
        memset(response,0,BUFSIZ);, ]" r1 v" E$ z# B% h+ \
        size_t j=0, handshake_len=strlen(handshake_str);
+ A/ m; F! g, d/ e9 m4 a        for (i = 0; i < handshake_len; i++) response[i] = handshake_str[i];7 H- {+ g- P2 O2 g. W/ T+ e; S
        for (j = 0; j < 16; i++, j++) response[i] = dig[j];1 b( \* P# M2 r: B1 |# ?% o4 E  n
        // 这里的clientSocket就是连接好的socket对象了。
0 e0 G- Z1 ^+ k5 S) {        int sent = send(clientSocket, response, strlen(response), 0);1 ]) l1 W5 e1 s$ F( W( W6 @. q' F
        return sent>0;
& D# s' |  B/ q4 f9 X+ N9 g}
& \7 ], {& E( S* h' _/ L$ H目录结构:: C2 b7 O9 u/ z9 `9 f/ y
socket/
- w- h, s5 N" @9 i% R: }      socket.c
7 s# k: J. }1 E* D, e" g$ ?1 n      md5/  ^& M8 ?' C. X! V
        md5.h0 f. g- I% R: C5 D1 c% Y
        md5.c
0 J  [5 C( U$ t库文件下载位置: http://sourceforge.net/projects/libmd5-rfc/files/ 总共3个文件,
5 K8 j% B( H- f6 y编译命令:  gcc socket.c md5/md5.c -o socket.o  
  S& x9 X% U- A( |$ ` GCC 4.3编译通过,执行 ./socket.o 可以看到打出的raw和md5后的字符串信息。 这里使用了一个网友用nodejs写的websocket实现版本,打出详细的头信息,然后复制到程序里,以测试生成的握手字符串是否一样。
- I) z/ Q3 {- p8 F// socket.c 代码。4 H' q  y9 h: N9 ], n1 P
// web-socket example   H7 X" q- E! Q! ~( y2 D9 {4 b
#include <stdio.h>- q1 i7 @; X2 N7 @) v
#include <stdlib.h>
3 h* I* u* Z1 C" I. b#include <string.h># G: O% v: j4 u1 G9 Q0 N) y
#include <inttypes.h>
; P! O8 ?. D4 @#include "md5/md5.h"
5 v! X+ E* L6 O: [+ ]7 m, r+ t) A& k# f" M8 a% v
// #define BUFSIZ 512
7 D. J+ M# p8 D' d1 d
+ r% t5 k  Y3 s3 A( O. T; q//定义handshake结构体变量9 q/ ]' c6 ^5 \, u
struct handshake {+ a: w& M, H# O) h* n# V
        char *resource;
5 o8 `% m: `! A) H+ H        char *host;' T, j0 U$ j0 O" z: {6 G  C
        char *origin;' W$ d) N+ q* a
        char *protocol;7 q( W* a, Q: H4 N: U4 t9 F" e
        char *key1;8 n0 o9 V0 w6 U2 ~4 ]4 P9 l' C3 ^
        char *key2;2 w& t  B% y" B& I
};# m! S3 a7 v2 L: P3 B4 w( `/ i& q  e

& X6 w% g2 |$ j3 w. ~1 e; w
  H& M. V  |4 A8 G//释放握手后不再使用的部分变量
$ F. U" f  ?! x% b. d  Tvoid free_handshake(struct handshake* hs){
& {- O8 y9 R2 v4 f9 I% e        if( hs->resource != NULL )      free(hs->resource);
/ E2 A, ?) e1 Z! x1 Z" e        if( hs->host != NULL )          free(hs->host);
; ^* u0 l3 a8 |' W        if( hs->origin != NULL )        free(hs->origin);
" J. }( }* f# I2 y* Z9 H        if( hs->protocol != NULL )      free(hs->protocol);
+ z( E7 {& w) E! k        if( hs->key1 != NULL )          free(hs->key1);
( A5 L% P, x' A6 V1 i$ T        if( hs->key2 != NULL )          free(hs->key2);1 L2 {) B5 _2 y* H! z; p
}1 n# c/ p8 o; D( u- A1 d
! b5 L7 h5 X6 C# h6 A1 {
// 这里对上一篇的match_string做了点修改,1 B3 w" _( c* w) m' C/ N
// 增加了一个end参数,这样不必提取出字符串后,再做剔除处理$ C' C; N" X$ K) Z1 ]
char* match_string(const char* src, const char* pattern, char end){* |: Z$ f" k- V  z3 b: c
        char buf[BUFSIZ];
' e& B7 Z7 E) q  k* q& g        memset(buf, 0, BUFSIZ);4 b' _, Q8 _1 r& z8 j1 Q
        size_t src_len = strlen(src); 8 I$ t; J& Z0 x% p  g' i
        size_t ptn_len = strlen(pattern);
( _! c) f; X: [4 W+ N        unsigned short b=0, p=0, i=0; # A. H$ }! _0 F& ]  r+ P
        char c='\0';
. l$ J8 G8 |0 n" ^        for(i=0; i<src_len; i++){
3 @! v  I& E* u0 Z3 c0 M/ ?% T( D                c = src[i];7 ]9 U6 N; T! \  U( V5 L
                if(p==ptn_len){ // p==ptn_len 表示正在匹配中  [8 N: c$ C: Y
                        if(c=='\r' || c=='\n'  || (end !='\0' && c==end) ) p++; // 匹配结束
1 n! V* B0 Q1 g- p7 A% ^                        else buf[b++]=c; // 匹配到的字符
8 N* i2 T0 |; ]. A                }else if(p<ptn_len){ // 为达到匹配要求% E  M* }8 [* J( O
                        if(c==pattern[p]) p++;
3 g) M: D! _2 }                        else p=0;  K) U" {" G6 r- k+ o  K8 @3 \% l. Z
                }5 D; s# g2 n- s6 f7 Z4 x
        }2 }4 l8 @5 }. V9 c/ v) n
        size_t ret_len = strlen(buf);  W: H9 n& k4 u1 f/ h* u
        char *ret_p;
) f" Y4 V0 \, R# j' T        if( ret_len>0 ){
. S( E& N- d2 I9 T) f9 a" a                ret_p = (char*)calloc(ret_len+1,sizeof(char)); // 加 1 为了存储 '\0'
4 t3 M( ]! c9 m3 f$ A! B: W                memcpy(ret_p, buf, ret_len);
% z% Z: X! h9 L. m. J- `/ z        }else ret_p = NULL;+ `/ h7 y5 F) j
        return ret_p; 7 Q6 N- e8 Q) E# Y1 `  ]! y" |! ~/ M
}- a/ W5 X5 o6 m2 A7 X

$ w# B2 o/ b' d8 H, b( N- Y// md5 加密函数,用的是网上一个实现的比较通用的版本。
4 I5 m, b+ ?: @7 p% Kvoid md5(const char* src, size_t size, char* digest)& }: F* |/ |. V& t8 H0 F# E0 E
{& J5 f# Z: d8 L3 O
        md5_state_t state;# \8 Q6 o$ @4 ?$ \
        md5_init(&state);
/ B, d8 P; q+ n1 t2 z4 N        md5_append(&state,  src, size);
1 h& S# [* V" @0 u% h        md5_finish(&state, digest);
. D" s2 z% |5 _6 ~5 d}
4 ?6 m" v4 X* J) `
' t, l/ v3 J( x$ I. g9 h7 B3 B' Bvoid p(char*s, int len){0 X5 v! @$ n% S
        unsigned short i=0;& j% |/ P7 J% c. T& E
        for(i=0; i<len; i++) printf("%c",s[i]);  a* ~, x/ F$ D/ M0 B
        printf("%c", '\n');7 |2 e( {5 B+ a0 L7 e$ X, e
}
) _( J6 @3 ]' F) a* v: W; J: g7 f8 Q( i% w- o
void handshake(const char* src, struct handshake* hs){) m: ~- y* c  b( K% l
        size_t src_len  = strlen(src), i = 0 ;* t$ t+ ~) p5 }% J4 O0 ~; b
        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前
, w; y2 k6 L  h" L) A! o5 u        hs->host                = match_string(src, "Host: ", '\0');
! K1 x$ r8 B, u1 T        hs->origin              = match_string(src, "Origin: ", '\0');
7 x# D! z5 z( ]" z) @% f- N7 {        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');# P/ j  A! j. S/ E$ {* @
        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');
% _9 A& d/ O. q3 u+ h        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0'); + l# e& q& n( d! S# F
        // 获取 key3,即最后的8位字符
# f6 ~: [, A- p. o$ X: V( R        char key3[8]="\0";
* X% V6 ]" F7 d" B7 S7 B7 I, q' q        for (i = 0; i < 8; i++) key3[i] = src[src_len - 8 + i];
6 o8 \9 C. [4 [3 h* S        char digits1[64]="\0", digits2[64]="\0", c='\0';
% G: r. I* J1 S' Z# ^5 y7 g        size_t spaces1 = 0, spaces2 = 0;
% ?' ^' u9 D* [& M; x        size_t key1_len = strlen(hs->key1);
1 w/ N, g& Z. [  x        size_t key2_len = strlen(hs->key2);7 M% s, o+ \0 F  A) k
        short d1 = 0, d2 = 0;
  p# ^% C5 T1 x  D3 l        unsigned int result1, result2;
+ {8 ~, v) ?- \( d9 D7 K) |        for (i = 0; i < key1_len; i++){ 7 B) |. [0 G0 f$ g1 ~. d
                c = hs->key1[i];$ V  o8 F/ C  X2 o/ m- y. p1 m9 A
                if (c == 0x20) spaces1++;4 f6 Q! s7 h: j
                else if(c>='0' && c<='9') digits1[d1++]=c; 6 k5 S! f. P5 A! m& n) Q
        }
: t3 w7 t! w6 ?6 \' N9 v        for (i = 0; i < key2_len; i++){ - ]1 Q% r% z' L! p+ R! M
                c = hs->key2[i];
& f. i9 c9 b: C! g0 W) c                if (c == 0x20) spaces2++;- M1 b$ C4 @$ J8 R; C
                else if(c>='0' && c<='9') digits2[d2++]=c; 1 Z( U! b3 R. r5 o; _" u2 \
        }) z( d) \. }5 s! O5 u
        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);
' _7 `0 _4 \/ F' n% m2 m        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);  L6 U; a3 i2 z7 ]/ n! V
        printf("ch1:%s\nch2:%s\n",digits1, digits2);
6 z  e) z- o% |2 G2 b        printf("sp1:%d\nsp2:%d\n",spaces1, spaces2);& r0 n. k( L5 S' F6 j& }+ Q! H
        printf("d1:%d\nd2:%d\n"  ,result1, result2); % ~# r0 Y& Q2 l2 ?4 _8 Y. y
        unsigned char chrkey1[4]="\0", chrkey2[4]="\0";
# n! Z+ r9 z  b0 {+ k8 N; W* _        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);
$ G5 ^% K8 o! O+ N' [. n        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);
, c' e8 S( e# g3 o: O5 L  X        printf("ch-key1:"); p(chrkey1,4);0 U2 F  w8 F0 ^8 f& f
        for(i=0; i<4;i++)printf("0x%02x ",chrkey1[i]);
4 {- j3 G3 Q2 X2 Z: \( [        printf("ch-key2:"); p(chrkey2,4);
1 {9 x  |. s  E0 l        for(i=0; i<4;i++)printf("0x%02x ",chrkey2[i]);
3 c9 K/ L  t# c* l% M" {  }        unsigned char raw[16]="\0", dig[16]="\0";
) Z2 p, u7 E" }; i5 T        memcpy(raw, chrkey1, 4);
% T9 W1 [. P# |4 Z. g; A        memcpy(&raw[4], chrkey2, 4);! m7 {, m$ U7 ^0 c/ I7 z
        memcpy(&raw[8], key3, 8);( }0 u  g$ g% S+ @6 x5 J* s9 E/ |
        //计算的md5值- z1 O! r. f+ D
        printf("\nraw:");
% |0 ?. ~# o4 Z+ f# K- H2 S        for(i=0; i<16; i++) printf("0x%02x ",raw[i]);' B* j, Y" p6 G; Y0 j" f4 W8 q
        md5(raw, 16, dig);. A3 `1 n4 R8 D
        printf("\nmd5:");
, R/ y" s" j4 M1 B. w8 ?        for(i=0; i<16; i++) printf("0x%02x ",dig[i]);9 S1 ~* v7 Q# T4 z3 I
}
) B( E! e% O8 q3 t
- k( [- n8 I. S# w% d( ~& f0 r) |, \0 K7 k3 u' i4 u
int main()* \* ^6 E6 K' w
{
2 p9 A( H: a. [! s3 K$ m        unsigned char msg[512] = "GET /pub/chat?q=me HTTP/1.1\r\n\
! O$ S7 g% W/ a5 X& a+ q2 W7 j$ m        Upgrade: WebSocket\r\n\2 D+ K# j6 U8 v4 o* z* y
        Connection: Upgrade\r\n\
4 H3 h  p' Z& e2 i        Host: localhost:4400\r\n\
6 Z- u$ q' ^+ K& d        Origin: null\r\n\' ~: I5 n7 e; q! h
        Sec-WebSocket-Protocol: my-custom-chat-protocol\r\n\
2 z6 e% g" n2 t$ [8 X0 k; F' H7 E4 v        Sec-WebSocket-Key1: x EO2 59186 4  28\\dY 0+\r\n\1 H# D( h7 u/ V% F
        Sec-WebSocket-Key2: 1  9  3  57695W    0\r\n\r\n";+ |' `; n5 N% u2 @
        size_t len = strlen(msg);* [/ n* x: R' i7 @7 `; U' l& ~8 V
        msg[len]  =0x1f;# B' r" X+ k) ]' M: f% u
        msg[len+1]=0xf6;+ z, {" m3 j+ V* q) K9 [$ w
        msg[len+2]=0xf3;6 X2 v& ]3 k- p
        msg[len+3]=0x3f;% o1 q; G2 i7 Y: O3 G
        msg[len+4]=0xc7;6 i' H! F* z, X1 E
        msg[len+5]=0x17;- f+ q  L' p# ~5 V# b2 G* }
        msg[len+6]=0x20;
3 X: t; H2 Z# l9 _4 p% {6 @& q# s        msg[len+7]=0x88;
+ `$ j8 V; D( k5 b$ p+ G2 n        struct handshake hs = {NULL, NULL, NULL, NULL, NULL, NULL};
, t0 e" u1 ~9 I$ t        handshake(msg, &hs);
$ z8 i& s. d9 Q  y4 Q- h        free_handshake(&hs);- \0 i/ o( I! ~# f
        return 0; ; q7 M1 l0 R& ^2 T
}
0 |& ^7 i8 h2 \  ]. {% }; w( u, A/ h% `
- C/ R3 P6 e! w& \# v, U$ E1 Z& u2 N3 v, p. a$ z
测试的结果:. ~! j* _$ o0 m% C  e5 Z
raw:0x19 0xbf 0x73 0xa4 0x01 0x27 0x5f 0xff 0x1f 0xf6 0xf3 0x3f 0xc7 0x17 0x20 0x88
& [) g3 r* u' j0 Amd5:0x61 0x30 0x1e 0xe8 0x8a 0x17 0xaf 0x39 0xd6 0xad 0xef 0xb9 0x6f 0x00 0x0f 0x68
. T* r: T: }$ v8 {3 f( D对比了nodejs的版本,握手部分生成没有错误。% i/ w) C1 p; V  P. W! t' |; k7 |
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2025-9-30 08:58 , Processed in 0.043093 second(s), 19 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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