找回密码
 注册
搜索
查看: 5763|回复: 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++庞大的类库和模板。
+ g! M, B0 f5 }9 X, {- x完整的windows版本socket握手实现:4 w% o6 z( J; b% r( J7 b  ?

3 c" y- R1 k4 J7 L" `, `6 jbool WebSocket::handshake(const char* src, struct handshake* hs){
4 l. A# r; l; j) `7 @* B        size_t src_len  = strlen(src), i = 0 ;
# n# q  p5 I5 }& o0 ~/ t+ }) O) a  Z5 e        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前: w* R6 d* j4 V: l0 ?
        hs->host                = match_string(src, "Host: ", '\0');
, E; \8 r5 _. l/ @6 Q$ I        hs->origin              = match_string(src, "Origin: ", '\0');3 Z/ m8 c$ j/ V( t, p: q7 b
        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');
4 q  M  h( C5 r2 B1 t: B. i        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');
* n1 G/ L% `  m/ S3 z        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0'); ) ?- T/ o# j* ]4 k3 x6 k
        char key3[8]="\0"; // 获取 key3,即最后的8位字符/ V7 @0 V. r1 I) ~* Y
        for (i = 0; i < 8; i++) key3[i] = src[src_len-8+i]; + H- ~3 r& C: i6 }
        char digits1[64]="\0", digits2[64]="\0", c='\0';
% {! K+ V7 }6 y6 ^$ G" p& k        size_t spaces1 = 0, spaces2 = 0;
! S& e9 L9 A5 g        size_t key1_len = strlen(hs->key1);8 T! Y2 s( b0 U
        size_t key2_len = strlen(hs->key2);4 H5 d; r0 Y$ m3 n
        short d1 = 0, d2 = 0;
) n) Y1 R* x0 B) b        unsigned int result1, result2;
  W7 d7 x7 @5 K+ x5 V( J: r        for (i = 0; i < key1_len; i++){
: w* A/ k$ o7 k4 r2 R                c = hs->key1[i];
5 J' d6 }* @* ^; g3 Y' g                if (c == 0x20) spaces1++;" `( O- t# @$ \( x; S# p' T
                else if(c>='0' && c<='9') digits1[d1++]=c; . R7 H4 D0 H+ l: V1 ~
        }5 P1 [  R7 N$ {7 L
        for (i = 0; i < key2_len; i++){
1 Y# ^9 s/ G6 w2 |+ R. [3 w                c = hs->key2[i];9 _6 }: c* |& S8 n, O1 g# ~! g  z
                if (c == 0x20) spaces2++;& e0 d7 k2 h4 s: [: T% d1 P/ [
                else if(c>='0' && c<='9') digits2[d2++]=c; : C/ t; W2 ^0 m, `* ^  ~) ?
        }& G' f$ q" s7 d% H5 p( p0 \
        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);5 x8 v! P' Q0 f' U2 ?1 s
        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);7 B/ V' s4 x3 n; a6 O# E$ [: x
        char chrkey1[4]="\0", chrkey2[4]="\0";
5 v7 R. x  X$ R$ m* ^; y; s        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);
- a: a( F0 H4 c3 G9 q- F- _        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);1 s' N/ ?. M  ?8 P9 q2 i! ^
        unsigned char raw[16]="\0", dig[16]="\0";* r9 s9 h7 o: z) Q
        // raw 表示未md5之前的字符串,规则就是前4位key1中的数字/空格数的整数值,, ~$ O$ T% \  y1 N9 K
        //  连接上key2的最后连接上头信息中的最后8位字符。
) J4 [+ m' D% z5 H( `        memcpy(raw, chrkey1, 4);% e0 E! ~/ M" I4 C
        memcpy(&raw[4], chrkey2, 4);
$ y  l( [; Z; f9 |        memcpy(&raw[8], key3, 8);- P$ I8 {/ b3 i" n3 p
        //计算的md5值1 @+ [% N6 x$ A( s+ p' ]# C. N
        md5_state_t state;7 ~* W' w4 y4 h, g$ U" L4 V/ c
        md5_init(&state);
! T3 g9 W9 @6 K. @! l1 l' F        md5_append(&state, raw, 16);3 H8 h: a1 y8 W3 _0 h" I5 V
        md5_finish(&state, dig);
. W# A7 B9 I( h0 [0 F+ j' A2 H        
$ T$ v# c4 u( D2 @% w' V        char handshake_str[BUFSIZ];5 c: V, ~/ O6 t; X; u
        memset(handshake_str, 0x00, BUFSIZ);
8 Q9 V( Z7 ^) P0 M, c        char* handshakeFormat = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"6 X3 a+ V. i+ F) h
                "Upgrade: WebSocket\r\n"7 h% v* f5 k8 O8 h- z0 K: n
                "Connection: Upgrade\r\n"5 [3 P( Z" Y; c% ~& }9 q
                "Sec-WebSocket-Origin: %s\r\n" & p$ ^  ^6 |7 ?0 u
                "Sec-WebSocket-Location: ws://%s%s\r\n"' o& w$ d! N( e5 ~# J
                "Sec-WebSocket-Protocol: %s\r\n\r\n";
# {- c  t1 i( y        sprintf(handshake_str, handshakeFormat, hs->origin, hs->host, hs->resource, hs->protocol);
# x9 `' o+ j/ K5 s7 i  q, D" K4 N        free_handshake(hs); // 释放handshake指针,已经不用了!
8 }" b1 H/ I2 t6 i7 T2 n/ }        char response[BUFSIZ];
. N( a2 o4 s8 M  }/ N6 O' i        memset(response,0,BUFSIZ);
2 g' ]% ^! ]( ]# [        size_t j=0, handshake_len=strlen(handshake_str);' D; k5 L- d! a! h) ], P5 {' m1 ?
        for (i = 0; i < handshake_len; i++) response[i] = handshake_str[i];# M, N$ F8 l5 ?/ q+ A
        for (j = 0; j < 16; i++, j++) response[i] = dig[j];% U/ l1 C2 a7 D
        // 这里的clientSocket就是连接好的socket对象了。
. v  k$ r6 v( M        int sent = send(clientSocket, response, strlen(response), 0);
: H# X- z! L% `, u) a8 r        return sent>0;
0 W% r9 H1 S2 i9 t2 e4 k}# w% u# s9 s! U7 J
目录结构:
8 P( \# H' f, G# q9 dsocket/
3 F+ J+ y1 u0 {) M4 G' e2 |( m      socket.c
% S$ M% A, t9 k      md5/
2 ]5 b: Q( @0 ]9 y0 d        md5.h
% g7 G& ^8 [6 ~5 Q0 [% Y        md5.c
* C, |5 R. O2 m/ Z- l( {( A库文件下载位置: http://sourceforge.net/projects/libmd5-rfc/files/ 总共3个文件,
" F' U; M& @. a4 r+ }编译命令:  gcc socket.c md5/md5.c -o socket.o  
: y- T9 k% t  H/ x) D& L+ ]$ b0 a* r GCC 4.3编译通过,执行 ./socket.o 可以看到打出的raw和md5后的字符串信息。 这里使用了一个网友用nodejs写的websocket实现版本,打出详细的头信息,然后复制到程序里,以测试生成的握手字符串是否一样。' L) ^% p& ]) w: }
// socket.c 代码。
! z( l- N% o, y3 r- a// web-socket example
. O' m( J+ K+ C6 C#include <stdio.h>
7 p$ |# J" z1 U6 v- H#include <stdlib.h>
+ k, f$ `& \/ j4 I#include <string.h>
2 ~- i" l8 H2 s! b, I% \% p$ q#include <inttypes.h>7 L7 W! q# V' l* @" @
#include "md5/md5.h"
1 l" v4 z" Z2 |8 o
) z9 s) a/ D7 o8 s# D// #define BUFSIZ 512' X- \! E$ M, n; c! ?2 t. {

  w4 W2 w6 o' a7 U//定义handshake结构体变量# t6 K$ j, C& B& c, k! q" F3 ^+ @
struct handshake {
* t; D) V% D/ M3 i. g: I. z/ F        char *resource;3 b( Z$ ^% h: Z& P& _
        char *host;5 f! R8 C8 z- H  o7 G6 _, T
        char *origin;
! S& Z7 x/ H* M        char *protocol;
3 p4 ]- v4 A& K) F7 ?        char *key1;/ Q# j; W, R) ]( h
        char *key2;+ I0 `9 P( D- I6 X7 H: p
};/ D! Y& f7 u% p: o, l
6 R' V& u4 j/ c; ~3 m$ ^4 u( c* m5 C

" [& J' G3 D; U( `, |8 I' F//释放握手后不再使用的部分变量
/ k/ Z# V' `0 n- Gvoid free_handshake(struct handshake* hs){8 B  F7 v& v& d3 k7 Q! Q; |/ p& _( _5 V
        if( hs->resource != NULL )      free(hs->resource);
  @0 i; G( X& ?0 z& B1 r, Z        if( hs->host != NULL )          free(hs->host);
) T0 K4 ?4 C' l  G, x, h$ s- T& v7 }/ w        if( hs->origin != NULL )        free(hs->origin);! `/ T# Y, V& D4 l! T8 }9 Y
        if( hs->protocol != NULL )      free(hs->protocol);
; V4 {! u% [! y        if( hs->key1 != NULL )          free(hs->key1);
" C+ G( D- g1 J# T1 W2 G        if( hs->key2 != NULL )          free(hs->key2);
* G& o- W" s0 t; N! f9 L* B}
/ ~! F. [% i, L! ?5 t9 x1 F; c
9 X* P/ ~$ c5 j3 h% F// 这里对上一篇的match_string做了点修改,. k1 Y$ o# p9 z. s. P7 g' W! S- ]2 f
// 增加了一个end参数,这样不必提取出字符串后,再做剔除处理/ j% c6 Q, n: r
char* match_string(const char* src, const char* pattern, char end){
8 P" B/ L" i7 W/ m+ X        char buf[BUFSIZ];
: g$ E* `& @# Y! [& k        memset(buf, 0, BUFSIZ);! Z  y( N3 N- c
        size_t src_len = strlen(src);
5 B4 S" p9 ~1 D: d" v% }2 G        size_t ptn_len = strlen(pattern);" B# y: o9 f1 g7 X: ~
        unsigned short b=0, p=0, i=0; $ ^( v6 p% o+ K" V: P) i
        char c='\0';
( k, p) B  A6 Y        for(i=0; i<src_len; i++){/ \! ]: J( F! d. h! `
                c = src[i];
) n% V4 G- ]1 X/ b; N$ q2 ~: o                if(p==ptn_len){ // p==ptn_len 表示正在匹配中& X- m4 t4 w/ b0 k
                        if(c=='\r' || c=='\n'  || (end !='\0' && c==end) ) p++; // 匹配结束
% Y# y: g' S6 `9 K2 b, {                        else buf[b++]=c; // 匹配到的字符 6 V. O+ j  ?7 [) g
                }else if(p<ptn_len){ // 为达到匹配要求
+ ~; e/ C& ?2 b( q* p7 g' D                        if(c==pattern[p]) p++;* ~. r  H, W7 L! R* b
                        else p=0;* Q$ |; y6 H) q: B: E" G% `9 P
                }9 N. X( Q5 h) Z! n1 ^& Q2 x
        }2 i* u3 W$ \' K7 g% i# C
        size_t ret_len = strlen(buf);
. b' K6 Z& {; n1 O) R        char *ret_p;
; p) r* D( f( d" d0 ~( F( E        if( ret_len>0 ){/ S& A3 W. R" C4 k0 e. A1 l
                ret_p = (char*)calloc(ret_len+1,sizeof(char)); // 加 1 为了存储 '\0'
/ X; a* U# L4 A4 Q4 [                memcpy(ret_p, buf, ret_len);6 z2 ]  |5 r4 k4 t# U$ s% E
        }else ret_p = NULL;; }- u% {! z' o
        return ret_p;
! a4 c' p7 D" N/ l' V7 w3 w" \}# }) K& m: p8 B
# o, f* O4 Z0 b" F8 Z' O$ H1 W
// md5 加密函数,用的是网上一个实现的比较通用的版本。
$ [$ `' X- B& Z. hvoid md5(const char* src, size_t size, char* digest)+ z' i3 k1 J, e7 F1 }
{, k0 Z& E) t/ p$ [
        md5_state_t state;; ^* A9 k6 e% a  I# ?4 \* m' W
        md5_init(&state);
# R' C+ \* d, {        md5_append(&state,  src, size);
% M9 a0 P7 t/ Z$ q        md5_finish(&state, digest);, e# v( L9 M7 X6 I: T, b, n
}
3 f) D# d, |% l1 q1 L" ]
' Q1 m: W8 u! Z" Svoid p(char*s, int len){* j& a* J' k% F& O+ Z0 x) ?
        unsigned short i=0;, ?( A4 o, j* ^9 \
        for(i=0; i<len; i++) printf("%c",s[i]);0 z* R$ q7 G/ v; L4 p  |/ {3 n
        printf("%c", '\n');3 g  Q7 ~# J$ l
}; O' `: n7 O9 N9 p- O8 c

1 I$ _! F) ?/ w8 s+ b5 ]$ Kvoid handshake(const char* src, struct handshake* hs){
: s9 m$ |7 b3 `7 W7 g7 c        size_t src_len  = strlen(src), i = 0 ;8 B- M( x! X& x* k! f3 o
        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前. g% g# m( K. m& S: H
        hs->host                = match_string(src, "Host: ", '\0');; ?+ U: k, Y' F' x8 V! N. q0 m* O' F
        hs->origin              = match_string(src, "Origin: ", '\0');4 _; ~- G0 u3 f0 O
        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');
' ]4 p/ f; x4 x, K# J$ m- @        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');
3 M$ v1 E3 B$ @2 D/ o" l$ U        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0');
+ ]4 \, {5 Z. i8 m0 I        // 获取 key3,即最后的8位字符' j: G" N( S- W- n
        char key3[8]="\0";. Y% A5 K8 h* ?" Q$ J2 V
        for (i = 0; i < 8; i++) key3[i] = src[src_len - 8 + i];
1 O9 |, Z7 O. V$ [        char digits1[64]="\0", digits2[64]="\0", c='\0';
' W. R9 H3 v: y+ @% x        size_t spaces1 = 0, spaces2 = 0;
4 g# R; H9 F9 d& X        size_t key1_len = strlen(hs->key1);# a+ X" L* L8 D7 b! L$ U: i# D
        size_t key2_len = strlen(hs->key2);' ~, s+ x! T5 {$ _: t3 X
        short d1 = 0, d2 = 0;4 ^) V, g- a8 @- t/ W! ]
        unsigned int result1, result2;
3 k* S0 I' b0 x        for (i = 0; i < key1_len; i++){
$ R% s+ S7 P8 I6 x& T! W% }! U                c = hs->key1[i];
7 R$ C$ D0 R# u$ O0 K% b; m                if (c == 0x20) spaces1++;
( z; h! B5 n* g! K                else if(c>='0' && c<='9') digits1[d1++]=c; 4 z  B) A- h6 v' |% y
        }- A' {# o+ X) Q8 f
        for (i = 0; i < key2_len; i++){ , L! s) {6 f5 C# X
                c = hs->key2[i];* F% K9 `+ M- e
                if (c == 0x20) spaces2++;9 X/ J: e. T3 x7 K( B
                else if(c>='0' && c<='9') digits2[d2++]=c; 7 @! L8 @) y+ U( g6 g: B! D  X' W2 Y8 t
        }+ Z" ?6 u+ y: m: D
        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);
* c, y  ~; l7 |4 Z* j* I" H        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);
+ B7 M: I% m" z7 N2 w2 `7 p* w& U$ [        printf("ch1:%s\nch2:%s\n",digits1, digits2);
% g' m" e( V7 w1 A        printf("sp1:%d\nsp2:%d\n",spaces1, spaces2);
2 i( x$ k' V8 s" a/ T0 k9 o        printf("d1:%d\nd2:%d\n"  ,result1, result2); ' a% T  O# v) }1 ~; d- N" r
        unsigned char chrkey1[4]="\0", chrkey2[4]="\0";8 _) f: e0 q; g5 \7 K
        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);6 p" _0 J5 x0 }$ q
        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);4 [, Y) ~/ a' ]9 W2 j
        printf("ch-key1:"); p(chrkey1,4);1 F# s# ^% o, p+ R" E0 [
        for(i=0; i<4;i++)printf("0x%02x ",chrkey1[i]);
  D4 S8 M- O' e; y        printf("ch-key2:"); p(chrkey2,4);
, e, m' X+ T- Y        for(i=0; i<4;i++)printf("0x%02x ",chrkey2[i]);
; I. N) j4 P$ w: F- Q7 e& v        unsigned char raw[16]="\0", dig[16]="\0";
/ N. z. _  L* K        memcpy(raw, chrkey1, 4);1 R! |* I. m3 L6 X: ~& f
        memcpy(&raw[4], chrkey2, 4);
! h9 G# @& [, N; v. l        memcpy(&raw[8], key3, 8);' O8 w0 Y7 |+ G2 g* M
        //计算的md5值9 Y* B5 T" n: j
        printf("\nraw:");9 T/ d; t, I0 _; A
        for(i=0; i<16; i++) printf("0x%02x ",raw[i]);; D  }6 T$ q* k8 i
        md5(raw, 16, dig);
$ G3 k) j6 Z4 j# k2 v        printf("\nmd5:");. w1 }2 l. y" z* s( N5 E" ~
        for(i=0; i<16; i++) printf("0x%02x ",dig[i]);
( m5 z: Q1 q) n* O}
. u* B. X: V# t- \6 G2 f0 }+ M* C6 V
4 ~* S4 R5 A6 ~! i* t
int main()) f# d& ^2 |. O( P3 H
{8 \- V7 }4 u- B1 |& ?7 F" _
        unsigned char msg[512] = "GET /pub/chat?q=me HTTP/1.1\r\n\5 @& M4 u8 j7 f0 z6 L9 }! Z' V+ `' s
        Upgrade: WebSocket\r\n\: a6 ^+ T8 r& X1 l- F2 u
        Connection: Upgrade\r\n\
/ V; T3 {$ ^, n, O2 ~        Host: localhost:4400\r\n\
* }* B! c* D4 [        Origin: null\r\n\& \8 N7 {! Z! V' o
        Sec-WebSocket-Protocol: my-custom-chat-protocol\r\n\$ p0 k3 L1 N  V' D. O5 @9 ^' c
        Sec-WebSocket-Key1: x EO2 59186 4  28\\dY 0+\r\n\2 t3 H/ K% D6 z
        Sec-WebSocket-Key2: 1  9  3  57695W    0\r\n\r\n";
: z& o( M5 E5 _0 q  Y  Q+ X! d        size_t len = strlen(msg);# A2 j& h" |3 ?& m( f4 |
        msg[len]  =0x1f;8 P! E/ }6 O1 f' V/ p
        msg[len+1]=0xf6;
. v( f6 g6 i# R* x% Z3 F        msg[len+2]=0xf3;# l, Y- S* O- l& a
        msg[len+3]=0x3f;
( V7 e+ z& }: o( x        msg[len+4]=0xc7;+ u3 \  w& r# }6 h  ?' b
        msg[len+5]=0x17;8 @$ h0 d& u3 ^/ y4 C1 s/ E
        msg[len+6]=0x20;
3 C% f' Q# p5 P; L- ?        msg[len+7]=0x88;: i8 C! z. s- B: h- P9 N6 ~
        struct handshake hs = {NULL, NULL, NULL, NULL, NULL, NULL};
' ~% f9 y) Q1 M. j        handshake(msg, &hs);
  B3 U. X* ?9 j; F/ d, ~5 R  q        free_handshake(&hs);8 k: \# G& r5 r! P" j
        return 0;
# I- U6 P) j3 s! {+ r! ~/ [}+ K2 i. ?) o" I4 i+ S6 C

4 b- ^8 i9 e! e( Q  V3 T% }" Y2 F9 E$ b" e* G
测试的结果:! q4 w. t$ b+ p2 j8 H# J& e! A
raw:0x19 0xbf 0x73 0xa4 0x01 0x27 0x5f 0xff 0x1f 0xf6 0xf3 0x3f 0xc7 0x17 0x20 0x88
  j5 h. \% d0 W$ o: e( S3 bmd5:0x61 0x30 0x1e 0xe8 0x8a 0x17 0xaf 0x39 0xd6 0xad 0xef 0xb9 0x6f 0x00 0x0f 0x68 5 F3 a0 \0 n+ ~& P+ n5 ~, x7 {
对比了nodejs的版本,握手部分生成没有错误。* N" I! {$ {: a4 d
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-8-14 01:13 , Processed in 0.021030 second(s), 17 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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