找回密码
 注册
搜索
查看: 5601|回复: 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++庞大的类库和模板。
1 {: q  e8 i* z9 [( f/ }! ]& P完整的windows版本socket握手实现:
0 B3 o9 w5 K& U5 w. U" ~/ b# d+ j. [1 K9 }. D/ p
bool WebSocket::handshake(const char* src, struct handshake* hs){9 K6 d# j- K6 s* g/ W& C
        size_t src_len  = strlen(src), i = 0 ;
# C9 j- Y/ r: H/ S$ m/ W  Q) I        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前
) `& q' E9 E$ b* W/ f- t9 F        hs->host                = match_string(src, "Host: ", '\0');2 b1 U! V. l$ S% z+ ^
        hs->origin              = match_string(src, "Origin: ", '\0');, R& Y$ o. H$ q. z& K  ]
        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');: o) K: K* z  x* f8 S1 I
        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');" o8 y8 g' P$ v1 a$ Z6 ?) j
        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0'); ' ?4 Q  S0 L) W$ B1 `/ E# V
        char key3[8]="\0"; // 获取 key3,即最后的8位字符
( X& D) J! B( D) S        for (i = 0; i < 8; i++) key3[i] = src[src_len-8+i]; + W. y) F$ n% S0 W- A( y0 U* J
        char digits1[64]="\0", digits2[64]="\0", c='\0';* k4 w: l$ W+ G3 d0 X
        size_t spaces1 = 0, spaces2 = 0;
8 l' R! I  Z) E6 j        size_t key1_len = strlen(hs->key1);' s  E2 l; P3 k( P% W1 N  F
        size_t key2_len = strlen(hs->key2);, a* l( H, ]& R  J" b. d# K
        short d1 = 0, d2 = 0;+ @! a; S! b3 T" m
        unsigned int result1, result2;
5 V4 A9 T) e# ]: q        for (i = 0; i < key1_len; i++){ 2 j9 N# Y0 ?  d- C; F( Z5 {1 l$ [
                c = hs->key1[i];
7 m+ ^9 e, p( a* _                if (c == 0x20) spaces1++;
$ F) h# j  S, _6 e  e/ {! D: }                else if(c>='0' && c<='9') digits1[d1++]=c; 0 s4 i2 w) p6 K! I+ ]
        }1 N! Z0 ]: I1 B' ]( [6 T
        for (i = 0; i < key2_len; i++){ + N6 I& Q9 n: }3 {# [2 Q) ~  i
                c = hs->key2[i];
/ U& S7 u0 b- e: ?' U" Z                if (c == 0x20) spaces2++;
- i  ^, X% Y+ W+ ?& H" K- B4 U# G  [                else if(c>='0' && c<='9') digits2[d2++]=c;
7 P6 J  D5 R9 L; @7 J$ G        }
) g" s" T& C. n        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);
/ y, _# r% W- F% |6 z        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);8 A" y$ W7 ]  z0 ^0 x2 N9 Q' D. `
        char chrkey1[4]="\0", chrkey2[4]="\0";: G* I9 p& K) m4 e* N
        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);. p5 a: a# n# W' U
        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);
. ?, Z; M3 i, p- T1 I- d, c        unsigned char raw[16]="\0", dig[16]="\0";# C* p* w* G0 h$ T+ U
        // raw 表示未md5之前的字符串,规则就是前4位key1中的数字/空格数的整数值,- h" M& Y* e; t
        //  连接上key2的最后连接上头信息中的最后8位字符。
2 m. A6 i& ?) Y6 a: v        memcpy(raw, chrkey1, 4);6 V7 ?0 i3 ~; f, t+ \: p1 X
        memcpy(&raw[4], chrkey2, 4);
( }: ]! E# E* {1 ?) F: F  G        memcpy(&raw[8], key3, 8);2 \" a7 [1 p8 n) L
        //计算的md5值
* `7 J6 N. \! ]* q# n: ~5 h        md5_state_t state;3 e5 p2 X) O0 c' s/ Y1 i
        md5_init(&state);
2 m7 C7 ~3 r7 t( ~  e        md5_append(&state, raw, 16);+ S8 J  S1 P- b( \) j5 |- t6 d
        md5_finish(&state, dig);
, x5 s# `3 m$ S6 f6 s. y- |        : u9 T, f& N7 s) K! l% }
        char handshake_str[BUFSIZ];
+ g/ G. U/ s: J6 |: H# M! N8 d. t/ q        memset(handshake_str, 0x00, BUFSIZ);
$ N# F8 ~: `5 |, F& [7 w        char* handshakeFormat = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"( v* Z6 C0 X7 K+ t$ |) \
                "Upgrade: WebSocket\r\n"
  y/ e& C# G% w# ?4 H4 a; |3 }                "Connection: Upgrade\r\n"& J; [3 W! Y6 h2 L, Q$ N6 J
                "Sec-WebSocket-Origin: %s\r\n" : ~8 V3 L2 A  e2 C. b2 r
                "Sec-WebSocket-Location: ws://%s%s\r\n"
+ r8 X* {9 J4 Z- p                "Sec-WebSocket-Protocol: %s\r\n\r\n";$ k: Y; R/ _. V) E& O; W5 y9 g
        sprintf(handshake_str, handshakeFormat, hs->origin, hs->host, hs->resource, hs->protocol);
! _! j0 ?7 Z3 \( p        free_handshake(hs); // 释放handshake指针,已经不用了!
! K# }3 N: B, F' Y% y* ?, o        char response[BUFSIZ];3 u- t0 C3 ?" ]2 e/ x3 H
        memset(response,0,BUFSIZ);
: m# _* n1 b* Y        size_t j=0, handshake_len=strlen(handshake_str);- y* z( J/ \& X5 |7 Q3 v
        for (i = 0; i < handshake_len; i++) response[i] = handshake_str[i];  q6 }4 S6 o; Z9 I2 I) D
        for (j = 0; j < 16; i++, j++) response[i] = dig[j];! H0 g/ u6 U% X  w3 D
        // 这里的clientSocket就是连接好的socket对象了。
- }7 c! f/ F: J        int sent = send(clientSocket, response, strlen(response), 0);) ?, _: ?8 o; ^2 _1 }1 d# d
        return sent>0;
/ \1 i9 Z" V% V  Z' g}' r+ G# G! U7 I' a
目录结构:8 u  m/ J; P2 j1 ?6 K* e9 F
socket/
; s1 g- a' Q) Q6 y9 v$ K. U: ]      socket.c
, L% r4 @1 {+ D: [& S# g$ `      md5/
5 h3 m5 T/ h' b( l! h. P1 v        md5.h% m& Z0 M- @6 D% c
        md5.c0 z% t! M# F0 Q5 p
库文件下载位置: http://sourceforge.net/projects/libmd5-rfc/files/ 总共3个文件,; F/ ]6 h* P7 F, c# T! }
编译命令:  gcc socket.c md5/md5.c -o socket.o  
7 j. J* x; M7 i: z( i2 w! ^ GCC 4.3编译通过,执行 ./socket.o 可以看到打出的raw和md5后的字符串信息。 这里使用了一个网友用nodejs写的websocket实现版本,打出详细的头信息,然后复制到程序里,以测试生成的握手字符串是否一样。
0 m$ M0 g2 ~& c6 G" {" u1 N; S// socket.c 代码。
0 a3 o# J/ m; j$ [6 q// web-socket example 5 \& X9 ^) d6 j
#include <stdio.h>
2 q5 {5 Y  h, j1 p' w% W' k5 s#include <stdlib.h>, `% Y! `1 }/ d% o5 C
#include <string.h>1 ?2 c0 V" u6 }
#include <inttypes.h>
+ K7 [' }# Z' g4 D8 i#include "md5/md5.h"
) n9 o2 r5 n% u+ z$ y& ~4 l# [. s9 ^+ d3 ]
// #define BUFSIZ 5127 e0 F! Z4 _# v) D' l6 F
* P& N5 ?8 a. H% R/ [3 b
//定义handshake结构体变量
' y' f! Y& W) b5 e$ R" Pstruct handshake {
. p) q6 U: Q& m7 a' w        char *resource;
$ a  F# I: C1 o2 Q% s' I8 C1 X. X        char *host;
3 B( y" Q, N4 k6 Z  A        char *origin;" v" t, z3 ?( Y
        char *protocol;# Y; x- h3 X4 f" Q
        char *key1;
! `. G% w5 M( ]7 C# s6 T        char *key2;
; |0 p1 c4 y1 @: u};
( l+ X' J, i: a+ [6 b- t* s
% O5 {. r' t0 r( k" i  `& j  t; V
//释放握手后不再使用的部分变量2 d. e' Z6 m1 ~! ^
void free_handshake(struct handshake* hs){6 q4 N' O+ e2 u2 ?6 Q
        if( hs->resource != NULL )      free(hs->resource);
, Q, R0 d8 p8 w        if( hs->host != NULL )          free(hs->host);/ U( E' g0 C& q# w
        if( hs->origin != NULL )        free(hs->origin);% x8 X4 l0 B% t4 Y# |
        if( hs->protocol != NULL )      free(hs->protocol);7 T+ t, I2 e+ `; W  M3 H. k
        if( hs->key1 != NULL )          free(hs->key1);
) O2 t2 [+ f# d        if( hs->key2 != NULL )          free(hs->key2);& j& s" g+ V) a% x
}
" `# H( U. w) {% L# L9 J  ~- C$ Q0 I) ]8 {
// 这里对上一篇的match_string做了点修改,
" r. ^7 \" S' Q// 增加了一个end参数,这样不必提取出字符串后,再做剔除处理
; e. F! J) `' s" d. m# p" \3 Jchar* match_string(const char* src, const char* pattern, char end){* n. W$ @0 N; ]% K4 h
        char buf[BUFSIZ];* D9 [& |9 \7 v) a3 P. V( k4 p/ ]
        memset(buf, 0, BUFSIZ);
* i8 a# z4 T: g& R/ j        size_t src_len = strlen(src);
  S/ h6 c( m/ B1 e, }2 v        size_t ptn_len = strlen(pattern);5 C; ]# `5 H( ~/ t
        unsigned short b=0, p=0, i=0; " o/ e2 d! l  V- T, v
        char c='\0';- Y- r6 s: k+ S# V4 }
        for(i=0; i<src_len; i++){' J( ^$ |% A9 u& w0 o
                c = src[i];
& F1 b8 h# @4 p8 W* g                if(p==ptn_len){ // p==ptn_len 表示正在匹配中
6 ]: u: N! o" U0 ]8 ]) Y0 x                        if(c=='\r' || c=='\n'  || (end !='\0' && c==end) ) p++; // 匹配结束
- L0 i' `; }/ u: [# D( V3 V4 P; Q                        else buf[b++]=c; // 匹配到的字符
* g  F; d$ O! D                }else if(p<ptn_len){ // 为达到匹配要求
: Q) u2 n" P3 s                        if(c==pattern[p]) p++;
6 ^1 z6 ]1 ?, s$ u" D* k                        else p=0;- X: j2 p' P& p* P- I9 b
                }- F- h) D( S! A# H$ j5 ]
        }
/ F! x9 r' y3 y        size_t ret_len = strlen(buf);
/ E7 g4 ]+ _$ ]3 A0 M& a        char *ret_p; 9 l* z4 s1 r: A* `. j" g
        if( ret_len>0 ){
% C! _. x% ]% Z0 J                ret_p = (char*)calloc(ret_len+1,sizeof(char)); // 加 1 为了存储 '\0'
( o( ?/ B, \9 [" e                memcpy(ret_p, buf, ret_len);
5 j. p7 c3 b  H: s/ L- }- S; }        }else ret_p = NULL;) \/ l! a3 \8 k6 @" w: b' Y
        return ret_p;
1 e* h  p5 X0 W# ]4 f% [# R) f9 M}
: Z& _' E" D* [9 K' Q. s- n1 k' L: D7 s
// md5 加密函数,用的是网上一个实现的比较通用的版本。
! X! w  c3 `! S: m- Q) |- ?void md5(const char* src, size_t size, char* digest)$ C* }, [& k8 {" ^1 C6 G. ?. n
{
% a# s- j0 j5 ?& o        md5_state_t state;) v+ [  E6 N( `8 {: I3 F/ A
        md5_init(&state);
0 F/ Z1 }5 e/ F' x# {5 X, z, i: o' K        md5_append(&state,  src, size);
) g1 C% A/ @1 r# R4 ~0 y        md5_finish(&state, digest);- F- I+ O. ~5 K4 y
}) S5 N/ E2 r2 z; ~- A8 X" M4 a
$ _9 K( p6 D% E( R
void p(char*s, int len){
6 I9 O* F  f* E3 i  J& H5 T. i        unsigned short i=0;
- x' {% D4 }1 \        for(i=0; i<len; i++) printf("%c",s[i]);
; _5 \* u1 f, q/ W/ q. D% z. G% J        printf("%c", '\n');) s5 G$ j; D; F
}1 G8 w8 y: W" d# t
: F) @* K# ]7 T! Z5 M) K5 p7 ]
void handshake(const char* src, struct handshake* hs){
9 r% {. F. A/ U! u1 M2 S        size_t src_len  = strlen(src), i = 0 ;
1 h; F9 A  i* i, n5 }; X' |! j  z        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前4 _) j4 c) S/ c3 [& w/ G/ d' `9 y
        hs->host                = match_string(src, "Host: ", '\0');" U2 H0 W, U, F; e. Z" ~0 r7 s- Z
        hs->origin              = match_string(src, "Origin: ", '\0');
1 b: C3 \% k/ W3 s2 j8 Y" ~2 e8 l        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');6 P6 g, |- K3 f1 K6 p  u
        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');! o: w3 s6 X: u- y. p) T, @
        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0');
* }& N- W8 ^) n/ t% _        // 获取 key3,即最后的8位字符
' \* H9 h8 d2 ~1 ^0 s2 d) b0 E        char key3[8]="\0";
; ]: e- W' l2 h8 `. K        for (i = 0; i < 8; i++) key3[i] = src[src_len - 8 + i];
% E1 F/ d# O% e: o3 R        char digits1[64]="\0", digits2[64]="\0", c='\0';
/ e& ]* V: h4 C% B! b8 K1 Y" e0 C        size_t spaces1 = 0, spaces2 = 0;8 {$ P! s( @8 u) `- t
        size_t key1_len = strlen(hs->key1);
$ C* ?! ~, P' y- c4 c+ x        size_t key2_len = strlen(hs->key2);: I+ I8 n9 r' f. E( j
        short d1 = 0, d2 = 0;2 |; V5 y* q% s+ S
        unsigned int result1, result2;0 V  |! K# @& A! E# \, f' A9 U
        for (i = 0; i < key1_len; i++){ - j9 N  c9 Y! c
                c = hs->key1[i];  ?- T- ^) n( T! q& q& y
                if (c == 0x20) spaces1++;& ^& c4 T8 X8 `1 Y( u. w) E2 ^& C
                else if(c>='0' && c<='9') digits1[d1++]=c;
, G3 ~, F$ o4 l7 `) Z, h        }
$ R0 D! n' E% L: I0 ~        for (i = 0; i < key2_len; i++){ 6 P# @- x5 D& h8 o
                c = hs->key2[i];
5 D7 S' q. |, }  j1 m. c4 o                if (c == 0x20) spaces2++;
) t& p4 r" s& r) ^; s7 X* D! m4 s( O4 {                else if(c>='0' && c<='9') digits2[d2++]=c; $ v( [& f! G6 j& [' u$ T
        }: b( c" D6 n+ I# D3 a3 n! @0 R
        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);; P# `' Z4 k9 S7 V+ F" e5 I
        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);+ O4 Y! c9 O3 f( h: [+ Z
        printf("ch1:%s\nch2:%s\n",digits1, digits2);1 ^' t4 m6 ^! t: e
        printf("sp1:%d\nsp2:%d\n",spaces1, spaces2);
- `9 @' V+ v& x9 Y( g' M- J        printf("d1:%d\nd2:%d\n"  ,result1, result2);
6 e8 n' e3 c3 b5 ?3 p: L        unsigned char chrkey1[4]="\0", chrkey2[4]="\0";+ d% n8 D; Y7 o: }  _; x1 R
        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);
0 T3 b, _9 j0 t5 }( Q        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);& T- {9 O+ B. M) L0 b' S/ S
        printf("ch-key1:"); p(chrkey1,4);
; O! A! U* `4 Q0 F1 x5 H# I) H        for(i=0; i<4;i++)printf("0x%02x ",chrkey1[i]);
( R6 U7 @  B6 ]' p/ T        printf("ch-key2:"); p(chrkey2,4);( N3 _/ ^, \* P, s
        for(i=0; i<4;i++)printf("0x%02x ",chrkey2[i]);7 T3 f8 D  I- E3 R! B! o( {
        unsigned char raw[16]="\0", dig[16]="\0";6 V% t( N3 K2 }; f: m& Q' @
        memcpy(raw, chrkey1, 4);
& A" Q$ L7 N/ [7 K, @( Q, G        memcpy(&raw[4], chrkey2, 4);5 s- b+ z1 k5 F" ]! P- i
        memcpy(&raw[8], key3, 8);5 U/ G/ a7 p) p0 u1 a& m0 q
        //计算的md5值
. S6 I& m( g+ u& m5 G        printf("\nraw:");, Z) w; s) H2 I: R
        for(i=0; i<16; i++) printf("0x%02x ",raw[i]);# R. j+ A0 {6 L
        md5(raw, 16, dig);
/ E0 a1 m6 r* [; k2 n        printf("\nmd5:");% C  i5 o9 k+ Y( p1 {# L* d
        for(i=0; i<16; i++) printf("0x%02x ",dig[i]);
  z  r& z! Y& m3 A}7 x! S, ~6 R8 T
; A0 v& x) D3 \8 a6 R& f/ R% j

, }# t8 p1 r3 ^4 Tint main()
! ?7 i6 s% z/ ]2 ^5 O9 t  Y2 O" S{1 h: G  B2 C2 |7 \% w0 q
        unsigned char msg[512] = "GET /pub/chat?q=me HTTP/1.1\r\n\
6 d# t& _7 z) l6 u- r0 d        Upgrade: WebSocket\r\n\
* ^- {$ F5 z: e( Y        Connection: Upgrade\r\n\8 I7 q  `0 Y; J$ Y
        Host: localhost:4400\r\n\/ w6 z' N) j/ w- h1 c
        Origin: null\r\n\! v/ _0 I7 D% |! M
        Sec-WebSocket-Protocol: my-custom-chat-protocol\r\n\8 A& L  `" Y3 I' ?" W. l6 V7 j8 v
        Sec-WebSocket-Key1: x EO2 59186 4  28\\dY 0+\r\n\" L  a1 V, Y# p2 }' h6 v/ `. `* _
        Sec-WebSocket-Key2: 1  9  3  57695W    0\r\n\r\n";
" Q  i$ `$ P$ R        size_t len = strlen(msg);
( B- F1 G: r' @+ Z4 \9 `        msg[len]  =0x1f;
0 g% p' y" e+ P* `6 t" e& i        msg[len+1]=0xf6;6 t* D; Q7 q7 X
        msg[len+2]=0xf3;
/ n( {9 M1 g6 C2 l2 |        msg[len+3]=0x3f;
, I' z( l: N+ A        msg[len+4]=0xc7;
) m- B8 W" T7 \8 J        msg[len+5]=0x17;* }* F9 v5 B. z* o: B
        msg[len+6]=0x20;
- ^" U4 q. O/ L        msg[len+7]=0x88;: D+ V( m! w) D; `5 A! ?
        struct handshake hs = {NULL, NULL, NULL, NULL, NULL, NULL};& |" _* m  s0 L( v$ I
        handshake(msg, &hs);* X* w0 ]7 [) \. p; U, V% u- Z$ t
        free_handshake(&hs);( J* z* k7 R; z8 A& F
        return 0; . D- w2 \7 _1 b2 o) b% Q* Z7 ~
}
/ m; h3 v6 O% d( @- |
( b  p( ~2 h+ J$ c
/ }& {4 Y1 z! E" p$ u. U$ t测试的结果:
% O0 R% g7 O0 ~  uraw:0x19 0xbf 0x73 0xa4 0x01 0x27 0x5f 0xff 0x1f 0xf6 0xf3 0x3f 0xc7 0x17 0x20 0x88 ! z' E( N: H1 L3 s
md5:0x61 0x30 0x1e 0xe8 0x8a 0x17 0xaf 0x39 0xd6 0xad 0xef 0xb9 0x6f 0x00 0x0f 0x68
, W' ~& R; ]5 H4 o2 }对比了nodejs的版本,握手部分生成没有错误。
0 p* S$ d* R# f7 E0 i. s
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-5-2 08:55 , Processed in 0.026388 second(s), 19 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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