找回密码
 注册
搜索
查看: 5070|回复: 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++庞大的类库和模板。* q7 E) ~' j5 ~8 I. ^" I' d1 g
完整的windows版本socket握手实现:$ o1 G' \6 o; V# w. Z
0 j. d3 F2 M0 R. s/ I
bool WebSocket::handshake(const char* src, struct handshake* hs){
3 H; a2 E9 p8 t7 K+ `6 P# h        size_t src_len  = strlen(src), i = 0 ;% @/ {- H& f+ u9 k% X
        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前
5 E# ]/ q! G& ]- p        hs->host                = match_string(src, "Host: ", '\0');
) q2 v5 ]: V6 x8 c" e; o6 X        hs->origin              = match_string(src, "Origin: ", '\0');
2 d/ T3 b% X3 e7 z+ F        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');) O7 T8 c. U/ V; N% i* k1 K2 Q
        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');. W# i, w9 B: [; m* s4 e9 X! P* u
        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0'); ! l: M6 ~: x( m# N' L" P, M6 ]& e& D
        char key3[8]="\0"; // 获取 key3,即最后的8位字符+ k- ~' ]+ v1 t
        for (i = 0; i < 8; i++) key3[i] = src[src_len-8+i];
( k2 _. @# F) i! w$ w4 J* g' C        char digits1[64]="\0", digits2[64]="\0", c='\0';
- X, Y: r. ~# U3 T2 o4 ?" G        size_t spaces1 = 0, spaces2 = 0;8 Y2 \# ]+ Z2 N0 s2 b( {7 [
        size_t key1_len = strlen(hs->key1);9 n* u# ?# [& K/ M6 Y
        size_t key2_len = strlen(hs->key2);
% S& d" c, b; [1 ^: |* T5 a        short d1 = 0, d2 = 0;) P5 ]/ l0 J* i) N2 s( X6 \
        unsigned int result1, result2;) ~0 h# v3 M9 `  S2 l* F+ ^4 x5 ?
        for (i = 0; i < key1_len; i++){   q) e0 b( J( ?0 `0 T& K( n
                c = hs->key1[i];
' p. b7 s& N. f/ {3 F; j6 n                if (c == 0x20) spaces1++;9 j5 m* v# S' U6 Z
                else if(c>='0' && c<='9') digits1[d1++]=c; ; e; M! x6 X/ e9 `5 r$ P* i
        }4 f: t2 Z) _4 Q! j$ @/ A6 w
        for (i = 0; i < key2_len; i++){
' e0 \6 T5 v' l  Z2 R- y/ a                c = hs->key2[i];, a* d: H& {$ ?$ [
                if (c == 0x20) spaces2++;
" ~6 W  y: }* _* k                else if(c>='0' && c<='9') digits2[d2++]=c;
  h/ m- n0 N' \- s        }
. y5 Z+ R$ \" @. ^% R        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);) L6 R( O) ^9 b5 M2 p0 K* z( O
        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);
/ w9 N2 X6 n1 w2 _        char chrkey1[4]="\0", chrkey2[4]="\0";
" |6 j+ h3 f& i; m        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);. r: z- Q7 ?0 m! Q, f+ ~, X! G; e
        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);% q* g) C6 Y5 G
        unsigned char raw[16]="\0", dig[16]="\0";# o* u) y* B; v, C# ]
        // raw 表示未md5之前的字符串,规则就是前4位key1中的数字/空格数的整数值,/ n# ^) y5 p: N, O+ B
        //  连接上key2的最后连接上头信息中的最后8位字符。1 i* ~/ T& o& h6 I; \# x
        memcpy(raw, chrkey1, 4);5 E! R  t8 R, A; k/ l# c* b3 p- t
        memcpy(&raw[4], chrkey2, 4);0 l8 o: e, X' V# F" B* G
        memcpy(&raw[8], key3, 8);
1 g/ s' c3 s0 W, a# y' o        //计算的md5值
* U- D8 t8 b& J        md5_state_t state;
5 P4 @# ]( v. x& H: G( O        md5_init(&state);" X) v: [: A9 U- [* _# W( `! k: R8 n
        md5_append(&state, raw, 16);
. k0 @, \5 V& F/ U! V  f        md5_finish(&state, dig);" G5 I. Z5 E! S1 }4 k2 C8 y
        : E, W1 j) v/ F
        char handshake_str[BUFSIZ];
$ g6 s1 X3 |2 f' q) _        memset(handshake_str, 0x00, BUFSIZ);
+ x4 s, @/ k! g5 G+ l  [3 u        char* handshakeFormat = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"6 d, Q6 B0 a1 p! Z
                "Upgrade: WebSocket\r\n"6 P& {7 M! u' a, U- m9 |
                "Connection: Upgrade\r\n"1 E' X2 y. v( Y. a/ |& [2 O9 @& e
                "Sec-WebSocket-Origin: %s\r\n" 4 P* e3 E9 A: b, q
                "Sec-WebSocket-Location: ws://%s%s\r\n"
8 |) W. f7 W* x3 u7 a/ M                "Sec-WebSocket-Protocol: %s\r\n\r\n";8 M9 K  T. s: C5 \) r% ~1 t
        sprintf(handshake_str, handshakeFormat, hs->origin, hs->host, hs->resource, hs->protocol);2 j$ Y5 w% W" E% x1 R! a
        free_handshake(hs); // 释放handshake指针,已经不用了!4 a% Q2 }2 B2 V: q, b8 s; _8 V  N0 `
        char response[BUFSIZ];
- l' Y) V9 \( D: X3 o        memset(response,0,BUFSIZ);
" O4 J: w; |: j  E  h" F2 p        size_t j=0, handshake_len=strlen(handshake_str);# Y9 }% Q4 K9 a+ B
        for (i = 0; i < handshake_len; i++) response[i] = handshake_str[i];; n, g9 [& Y1 L' y5 j& S) Q
        for (j = 0; j < 16; i++, j++) response[i] = dig[j];2 d' S5 A% j, f; [. N& |
        // 这里的clientSocket就是连接好的socket对象了。# i$ J3 Z1 [1 j; b- D! y, A, f
        int sent = send(clientSocket, response, strlen(response), 0);6 F; h$ B7 [: m3 _' P& Q$ ^7 Z* b
        return sent>0;  l  t. {; @2 g) S3 s% ^2 r, o
}+ q0 O& ]! l: V, F2 y! H& \
目录结构:: \5 q/ n$ X% G7 ?0 J% s; n9 n+ N. `
socket/
# v5 w  ]$ u6 q5 U" a      socket.c4 S) Y/ w9 {  }; N- i
      md5/
: S6 r6 y- U1 @* e        md5.h
9 C2 u7 _6 ~7 o8 g/ D        md5.c
0 e& f- c6 P1 |库文件下载位置: http://sourceforge.net/projects/libmd5-rfc/files/ 总共3个文件,
( N: Y, A2 g# a编译命令:  gcc socket.c md5/md5.c -o socket.o  
7 q' ~5 I7 M" A% a; \ GCC 4.3编译通过,执行 ./socket.o 可以看到打出的raw和md5后的字符串信息。 这里使用了一个网友用nodejs写的websocket实现版本,打出详细的头信息,然后复制到程序里,以测试生成的握手字符串是否一样。- o( e, H9 D" D. P# @# A
// socket.c 代码。3 r7 ?# ?+ N- v  G8 W8 c; i+ m
// web-socket example
4 w# q" x! i- y; j9 L# u; N#include <stdio.h>
- p0 b+ I, h2 u% S  d" D. ^#include <stdlib.h>
  l7 {! ]6 E9 K#include <string.h>
- g- f6 N4 y% |6 h1 {#include <inttypes.h>
- G3 N! h$ F( v2 L& E#include "md5/md5.h"% P3 `; H* v* `3 ]/ a& V

+ e$ h& K  r) R; Z9 Y9 C// #define BUFSIZ 512
% L9 s) e# @' c; }
. C" p+ M9 [/ F  o* e//定义handshake结构体变量
- m3 _+ H. e. wstruct handshake {
: ^# f4 o! h; D* h        char *resource;
, E8 `9 q9 P) S2 {' P7 L! X        char *host;4 S3 Q* w9 R, w% \2 |3 [
        char *origin;
" E; u9 k; S9 ~" A$ ]: }' I        char *protocol;. R; R* w- A/ g' j5 X' x
        char *key1;
+ S; }. X6 t; ]& e. `1 I6 f; z        char *key2;
% q1 s3 _, m' n' s, \' j; W};) Z* j: `+ N( Z0 x6 ~9 U2 _4 X
- g% {6 F9 b( t6 r: d1 [3 E
& m7 `9 l8 m0 k8 F: w; ?9 D
//释放握手后不再使用的部分变量3 K+ o% L* G& e5 L* z* ?
void free_handshake(struct handshake* hs){
7 P7 v$ g9 u/ [        if( hs->resource != NULL )      free(hs->resource);
7 e2 J% K9 l5 R" z6 E% e% ]/ F        if( hs->host != NULL )          free(hs->host);. w/ `1 {( @+ }: U, o$ i
        if( hs->origin != NULL )        free(hs->origin);$ G' o+ g5 j' M+ I) s2 B
        if( hs->protocol != NULL )      free(hs->protocol);
* j: m" x4 l) ?) e% H& Z9 R! n        if( hs->key1 != NULL )          free(hs->key1);
5 O# N/ A0 l3 s$ o        if( hs->key2 != NULL )          free(hs->key2);
. k: n, o9 r' h+ a5 d}5 n# f0 ~( c, G9 K+ q" O! i
6 }, l0 ]5 H# h
// 这里对上一篇的match_string做了点修改,0 z: o7 s2 f$ z+ }. x
// 增加了一个end参数,这样不必提取出字符串后,再做剔除处理
  v9 z7 F7 V1 A6 q  L2 p* B2 ]char* match_string(const char* src, const char* pattern, char end){
( [% V3 h2 B0 d4 X. S        char buf[BUFSIZ];5 q/ n$ ^. A/ s7 V3 f$ H. |
        memset(buf, 0, BUFSIZ);# X" k$ I- u. K6 D
        size_t src_len = strlen(src);
/ R+ M2 a+ x! _1 D  v) ^: m* n        size_t ptn_len = strlen(pattern);# n+ D0 c7 o0 |: k
        unsigned short b=0, p=0, i=0; % [$ R9 i& b9 ~
        char c='\0';
, s: k4 v, c& r        for(i=0; i<src_len; i++){# c9 j8 `. l8 b  B1 |6 t
                c = src[i];
' X* U# w9 Q8 N0 l( a4 U                if(p==ptn_len){ // p==ptn_len 表示正在匹配中
- \: r' N9 p* v& q2 N7 x! |  z                        if(c=='\r' || c=='\n'  || (end !='\0' && c==end) ) p++; // 匹配结束- Y) E. z# G( n. U3 |" D) V
                        else buf[b++]=c; // 匹配到的字符
' H1 F' ~: @5 u* M1 X% r. i# b                }else if(p<ptn_len){ // 为达到匹配要求
  x* C3 G0 I( L- d& j& r2 U                        if(c==pattern[p]) p++;
" m/ ?/ w# e  O                        else p=0;6 s5 E2 c( \; x9 c9 _
                }1 P' T- h7 G+ g% a! r$ G, P( @3 z
        }
6 j) b- d: {' a- |, n3 u% r        size_t ret_len = strlen(buf);3 Z6 g! b+ x/ D
        char *ret_p; % d3 z' g# r- q) \  {2 l% g8 b9 \
        if( ret_len>0 ){
! l4 w$ T, }4 e; L0 B                ret_p = (char*)calloc(ret_len+1,sizeof(char)); // 加 1 为了存储 '\0': }- g" Q; H. W& k/ T: B
                memcpy(ret_p, buf, ret_len);
' P9 V. [2 ^" @: {! g        }else ret_p = NULL;! {/ i6 y! o  O* u
        return ret_p;
8 L  E1 u( F+ G& Q}  Q5 X! ~3 v* B0 n% m; t

- {" H, R( S" {6 F9 f. V& A# o// md5 加密函数,用的是网上一个实现的比较通用的版本。- C  T# I, I7 v+ J" p. i
void md5(const char* src, size_t size, char* digest)
1 Y3 m" a; @7 s$ ]{
9 t$ R( ]* i! o! `        md5_state_t state;
! ], H% g& W5 c( @4 H$ |" `! u  s: h        md5_init(&state);
7 S- |% o1 t# `  I5 W        md5_append(&state,  src, size);0 R- }1 ^, j# A" o# Z
        md5_finish(&state, digest);, e1 n& ], a$ f4 e: l/ f
}1 o5 }$ }1 l% ?" L" c0 J' R

9 g2 L( ^( j- l6 n/ o# Nvoid p(char*s, int len){
% G, T2 D1 U) ?3 e# q4 u        unsigned short i=0;
1 c( `; W( D* X, P/ }        for(i=0; i<len; i++) printf("%c",s[i]);
; A8 F, Z& ]7 V; V' h/ Z# X+ J" B  `        printf("%c", '\n');
# V. X* k; r% D( C  D2 H}
* \& y8 b0 q; |! v6 x  N) ~& u& b! T9 Q/ n7 Q. f) o3 m
void handshake(const char* src, struct handshake* hs){
0 x/ L* r+ t, M2 C) {        size_t src_len  = strlen(src), i = 0 ;
. i" z2 E9 E7 k  j        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前
' G3 c2 A5 I* j, y5 [        hs->host                = match_string(src, "Host: ", '\0');, v0 @& [3 ]" v  `# I0 L
        hs->origin              = match_string(src, "Origin: ", '\0');6 p, A+ x1 U8 D: k, X
        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');
6 H2 i! \3 r0 }% A9 B- S' O/ M        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');8 K/ i% b! l) \* s( T' y
        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0'); + F! H4 v& c4 j6 W( R0 M/ I
        // 获取 key3,即最后的8位字符
: u: z" |$ l* H- R" [# s+ O        char key3[8]="\0";
" Y9 W5 i3 v3 d2 P6 k% a+ P( q! O0 b        for (i = 0; i < 8; i++) key3[i] = src[src_len - 8 + i]; 8 c3 c- P  H' @' d
        char digits1[64]="\0", digits2[64]="\0", c='\0';
6 ~8 v; a3 h5 \% v5 }( ^: ]$ G- i        size_t spaces1 = 0, spaces2 = 0;
. `4 p4 o3 z- A# ?# A; I        size_t key1_len = strlen(hs->key1);/ c- o% j/ n5 C1 p- k$ ?
        size_t key2_len = strlen(hs->key2);8 a$ ]5 R. \- A3 c0 d" V4 s
        short d1 = 0, d2 = 0;
- {+ b/ {; e' T, m0 d4 f4 Y5 H8 i        unsigned int result1, result2;
# e; h( [& Y1 b6 v8 G% F" t$ A2 W3 _        for (i = 0; i < key1_len; i++){ . k% o; G- F6 X: B# I1 X
                c = hs->key1[i];% d; l' V6 s9 L, y1 G# e2 z6 M! A
                if (c == 0x20) spaces1++;
) X! R1 B! I' ~- F/ m1 |. C                else if(c>='0' && c<='9') digits1[d1++]=c; $ N3 Z7 ~$ @8 h/ I
        }
; }9 o7 x' O# L" I        for (i = 0; i < key2_len; i++){ % [  ?- a7 ^& W0 l
                c = hs->key2[i];3 C- n! c% X6 f& K! O
                if (c == 0x20) spaces2++;/ f* s; \  h6 X/ C: b/ c/ T
                else if(c>='0' && c<='9') digits2[d2++]=c;
' v8 a8 v+ D, Q8 n: u/ x0 Y& ^        }" P  v" B/ f4 b" R; P( @
        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);' E( L+ J1 M; J) j0 U$ s/ d- T
        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);4 z1 r8 M9 Q9 D5 s/ l4 m) _+ d6 B
        printf("ch1:%s\nch2:%s\n",digits1, digits2);
2 \3 R1 k  j* v        printf("sp1:%d\nsp2:%d\n",spaces1, spaces2);9 j; c8 |: l, @8 ^1 S8 h) _; B. d
        printf("d1:%d\nd2:%d\n"  ,result1, result2); ' I1 ~1 Z# B. W  Y" d/ P' J' `
        unsigned char chrkey1[4]="\0", chrkey2[4]="\0";
" g' m, ?2 g7 t        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);
. ^: T! y/ E; x3 |        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);& {1 a* [- S) f* X2 Q* {/ k! V6 h; G
        printf("ch-key1:"); p(chrkey1,4);
+ ^' ?7 z1 z0 |% S        for(i=0; i<4;i++)printf("0x%02x ",chrkey1[i]);0 r: S- e9 Y/ s7 F1 G- N
        printf("ch-key2:"); p(chrkey2,4);9 \: A3 g# q+ {4 h: c
        for(i=0; i<4;i++)printf("0x%02x ",chrkey2[i]);
" O, B0 n# B7 ^' M/ u        unsigned char raw[16]="\0", dig[16]="\0";* t8 i& Z9 U1 {( W* `# f0 M5 a
        memcpy(raw, chrkey1, 4);
' L1 W$ C- s* k' x( G- r        memcpy(&raw[4], chrkey2, 4);
5 y) v$ @4 g4 @" y        memcpy(&raw[8], key3, 8);  i  D4 V/ X2 r3 b& w
        //计算的md5值! g8 L+ ^- y  l9 J
        printf("\nraw:");. y8 ?4 p  c8 j. j0 m
        for(i=0; i<16; i++) printf("0x%02x ",raw[i]);
' F( n' D9 {7 _" M7 n( p        md5(raw, 16, dig);
( y3 [4 J$ Q5 A9 x. B' V        printf("\nmd5:");
+ U- Y# P# A; X; `4 W# ]        for(i=0; i<16; i++) printf("0x%02x ",dig[i]);
$ n! i* ?) _6 c- G& s. T2 H, S4 {}
. K6 e4 u  B5 q; B; |
, E7 B0 B/ b2 A  S8 t- E/ n: p' T9 R
int main()
3 d& `, E' D, o9 t7 U{
  b1 E$ t; ~1 [: Q        unsigned char msg[512] = "GET /pub/chat?q=me HTTP/1.1\r\n\% b: V4 D. _0 ^' [7 c0 |
        Upgrade: WebSocket\r\n\
/ a' v, x  M; T. S( h1 k' d        Connection: Upgrade\r\n\- ]& [3 J* w3 K$ z/ w6 H
        Host: localhost:4400\r\n\
/ [$ D4 o% q" z* A" Q1 g        Origin: null\r\n\
* o' s$ X. U5 s1 P) t        Sec-WebSocket-Protocol: my-custom-chat-protocol\r\n\! q) I+ e) W0 _/ z' o3 h3 A: u
        Sec-WebSocket-Key1: x EO2 59186 4  28\\dY 0+\r\n\
7 Y5 c8 ^2 N( P/ @" B% s) b9 x        Sec-WebSocket-Key2: 1  9  3  57695W    0\r\n\r\n";
/ J, `1 _0 B% \7 p5 m0 s% {        size_t len = strlen(msg);
8 S# d, v1 G0 z: \* F! Q! _  n        msg[len]  =0x1f;
# }* m" q4 I% R% H, |$ u8 C1 Z        msg[len+1]=0xf6;! z. [3 E0 A$ _% Y2 |5 I
        msg[len+2]=0xf3;" T9 Q; T9 [  `: }9 H. L9 `+ F9 h6 x
        msg[len+3]=0x3f;
8 l1 m; \5 a3 t+ \# w        msg[len+4]=0xc7;
  {: C* y: l8 ~# N        msg[len+5]=0x17;
5 P$ m- P; v9 Q# K% y        msg[len+6]=0x20;8 E6 i% ^% t: O6 K' i" F
        msg[len+7]=0x88;
0 J' m0 e9 |6 L' D% E        struct handshake hs = {NULL, NULL, NULL, NULL, NULL, NULL};5 K$ a/ {1 j: K4 J" h: \( M- a
        handshake(msg, &hs);
$ ~+ i: L9 c& n3 {        free_handshake(&hs);. E& X0 ]% M# B, ^2 V  l
        return 0; " W# Q8 L' t+ d6 }" e1 E
}/ w. C" w: a* d# U. N! w4 T

! T/ n& M1 c" D  b( `" p. ~& G0 B* p) _! G! v. y
测试的结果:$ q+ l" ]- m; ~5 `. q# _/ D+ a
raw:0x19 0xbf 0x73 0xa4 0x01 0x27 0x5f 0xff 0x1f 0xf6 0xf3 0x3f 0xc7 0x17 0x20 0x88 9 G3 r- W3 y4 Y' W
md5:0x61 0x30 0x1e 0xe8 0x8a 0x17 0xaf 0x39 0xd6 0xad 0xef 0xb9 0x6f 0x00 0x0f 0x68 9 k' D9 E! z( \# C3 X
对比了nodejs的版本,握手部分生成没有错误。: y9 P& C1 R1 s! p( ?& s  Y' P
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2025-6-19 07:43 , Processed in 0.022086 second(s), 19 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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