找回密码
 注册
搜索
查看: 5599|回复: 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++庞大的类库和模板。0 M, M, j5 P: Y) @8 a4 j
完整的windows版本socket握手实现:# G5 Z: @. X, e) q. T

" ~/ ~3 I- g3 W9 A+ y) O% Ebool WebSocket::handshake(const char* src, struct handshake* hs){
2 r0 V. i; S; V1 d: U" h: V. p        size_t src_len  = strlen(src), i = 0 ;: S$ z- X' j- L; i/ i
        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前
6 _+ R  {7 ]0 Z. F' v/ ]        hs->host                = match_string(src, "Host: ", '\0');
# V0 o( M2 A. P6 u        hs->origin              = match_string(src, "Origin: ", '\0');
, E, ]$ f8 v1 G' \4 H" j        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');
* q; d& ~* u4 }3 `- G        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');
# }3 m  X( P4 E        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0');
3 S% [4 |1 B/ K# b4 w9 W9 `        char key3[8]="\0"; // 获取 key3,即最后的8位字符9 y2 L$ w+ y- E) |
        for (i = 0; i < 8; i++) key3[i] = src[src_len-8+i]; ( u8 \7 F! q. U% L; L
        char digits1[64]="\0", digits2[64]="\0", c='\0';
* \+ w0 E% o& G: h% M$ T        size_t spaces1 = 0, spaces2 = 0;- Z  B6 O, X5 u$ t# S( \8 u
        size_t key1_len = strlen(hs->key1);
4 ~  k2 \, S4 p        size_t key2_len = strlen(hs->key2);
6 `' ]7 D0 \" l" V( c5 g        short d1 = 0, d2 = 0;1 i/ R7 |# Z0 U  W! }. F
        unsigned int result1, result2;' r; Q/ U- G9 O! D$ E; @
        for (i = 0; i < key1_len; i++){
3 L" X# w8 h* l3 _+ ^. E% [                c = hs->key1[i];
. i4 ^# T% P' o' ^) H( n6 d' |                if (c == 0x20) spaces1++;& i) |' l- a0 u* X: F% W( W8 u
                else if(c>='0' && c<='9') digits1[d1++]=c; % m; g' a! F9 @, p) T* J" H4 Q
        }
* ?6 Q  _, n; |4 a, _, y0 j        for (i = 0; i < key2_len; i++){
! O) ~6 A5 b' n, Y/ _' ^                c = hs->key2[i];" u3 m8 F" [/ w, E; |( ]+ x! D9 X! W6 p
                if (c == 0x20) spaces2++;
) ~1 g8 g% M1 c! P8 {                else if(c>='0' && c<='9') digits2[d2++]=c;
$ e2 X( [6 P& L4 M9 Q8 Z4 `, R        }4 `+ ?4 z7 `- {
        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);4 `5 B$ R" R5 L
        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);
, U) f2 [$ F% m& ^6 {- N7 j        char chrkey1[4]="\0", chrkey2[4]="\0";
# u3 G% E" z- f        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);
- E8 x% b2 J! A, |0 x        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);
: Q$ @$ K* O1 Z# g5 ~        unsigned char raw[16]="\0", dig[16]="\0";9 H: r! E9 R* p" I% t, j; G
        // raw 表示未md5之前的字符串,规则就是前4位key1中的数字/空格数的整数值,0 C5 N$ @, N. G) T$ I/ @7 k" \
        //  连接上key2的最后连接上头信息中的最后8位字符。
+ h5 R: N- e$ M, B# ^        memcpy(raw, chrkey1, 4);
7 j# D* ^$ O4 _: P* P' {1 Z        memcpy(&raw[4], chrkey2, 4);9 ~$ s- z- g) h
        memcpy(&raw[8], key3, 8);7 I8 q. \, Z% H3 Z) X0 K. B1 `4 ^* j
        //计算的md5值/ S5 q- W! S+ N9 j
        md5_state_t state;% {" {) D( E$ K" w# ?. C
        md5_init(&state);4 t0 I0 i' _7 O! W
        md5_append(&state, raw, 16);, u+ p& e0 w6 D9 R
        md5_finish(&state, dig);$ i' w% n' _1 n: V3 _/ C! k" y& ]" G
        
2 E% }* @. c* W1 g7 ?        char handshake_str[BUFSIZ];
+ d: g8 m/ p: g+ p& e        memset(handshake_str, 0x00, BUFSIZ);
4 O5 d& v- }; ~+ p+ q        char* handshakeFormat = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
) d8 {3 [6 i0 r3 ^                "Upgrade: WebSocket\r\n"- L" \1 \  t3 y- Z
                "Connection: Upgrade\r\n"
! U9 Z% b5 D' N$ Z1 n3 ]                "Sec-WebSocket-Origin: %s\r\n"
; z. ~2 E$ @" I* @2 R                "Sec-WebSocket-Location: ws://%s%s\r\n"4 L9 _9 R$ p, G
                "Sec-WebSocket-Protocol: %s\r\n\r\n";, S# s4 d2 V" J, p
        sprintf(handshake_str, handshakeFormat, hs->origin, hs->host, hs->resource, hs->protocol);
/ U. j* d" Q4 M. h$ N        free_handshake(hs); // 释放handshake指针,已经不用了!
" c$ Y9 R- \/ N        char response[BUFSIZ];4 n1 W: H# ?, g8 T& r* e
        memset(response,0,BUFSIZ);; h  E: B. ^+ F. `/ R& W, F9 F
        size_t j=0, handshake_len=strlen(handshake_str);- W% O' [+ D; C5 P! R
        for (i = 0; i < handshake_len; i++) response[i] = handshake_str[i];
, o' N! x. {0 J7 J0 S0 p/ m        for (j = 0; j < 16; i++, j++) response[i] = dig[j];
  i& g9 u$ V/ |' |        // 这里的clientSocket就是连接好的socket对象了。
( n- Y% S/ @) B9 S) Z( `        int sent = send(clientSocket, response, strlen(response), 0);! Y. k& ?  A, V+ h! |
        return sent>0;
1 n; [5 k8 ~# G- A9 r}* P, e( k0 G& d$ _
目录结构:9 a6 V* z, W; S# H- L) P- L
socket/
. L! i6 I  C- T      socket.c
7 A8 C7 }8 Y/ |% F# O+ `; }      md5/% G8 w1 \+ T" z3 [& n1 N
        md5.h
- V  ^! [* g- p. A+ H        md5.c0 x) F9 ?9 o+ \. N$ _5 x' v
库文件下载位置: http://sourceforge.net/projects/libmd5-rfc/files/ 总共3个文件,( c. t- ~% `6 K( @; g: O8 m
编译命令:  gcc socket.c md5/md5.c -o socket.o  
+ G5 d' |& H2 ^ GCC 4.3编译通过,执行 ./socket.o 可以看到打出的raw和md5后的字符串信息。 这里使用了一个网友用nodejs写的websocket实现版本,打出详细的头信息,然后复制到程序里,以测试生成的握手字符串是否一样。
% Y7 A( q4 |  A0 d// socket.c 代码。2 r6 E7 h, z7 ?/ ]( G
// web-socket example
$ N4 G" _, k6 z* `#include <stdio.h>0 K; x) }; [) R' v' y1 ~% }6 F
#include <stdlib.h>
6 U4 |8 {3 P2 Y4 I' y' d#include <string.h>
: b2 @5 w9 a: I7 z#include <inttypes.h># R/ E1 U. v. `5 ^# H
#include "md5/md5.h"% k3 t. r( `. C: H5 O- b5 d2 g2 u

& A7 j) X; J1 Y* L2 e( I// #define BUFSIZ 5127 \3 w0 A$ V0 H" z: o

& i8 H9 l, t' ~% t! Y. W//定义handshake结构体变量1 u. S- d6 U; U, a
struct handshake {
+ K: L: F! V3 L1 w' }; h9 U  N        char *resource;
/ O* w  Y6 L- B8 K5 V/ B        char *host;$ k. G, a/ u& D9 H
        char *origin;
. E2 N7 C" ]- @1 R+ X        char *protocol;5 j, E5 d0 k' ^7 @, P5 ^
        char *key1;) l; v4 o5 D; b, {5 q0 c
        char *key2;8 N7 P6 h+ }0 @+ ]+ b& }8 ]
};( I7 q( y6 h' U( L
# K9 ]& v9 i( S* F# D/ ^2 S

) d3 L$ A3 ?. b! |//释放握手后不再使用的部分变量0 M5 Y) q; U  J: U# P( a$ t
void free_handshake(struct handshake* hs){
9 N; K9 a& V, J/ X$ p4 Q        if( hs->resource != NULL )      free(hs->resource);+ g; Y6 B: Q5 t, c4 U1 j+ X
        if( hs->host != NULL )          free(hs->host);' T0 w( M: K7 ~# d% i: @" \6 N; J
        if( hs->origin != NULL )        free(hs->origin);2 G4 I& P& t4 ]. v
        if( hs->protocol != NULL )      free(hs->protocol);
; f; ]$ m/ L, j6 v: H* ?& }* |        if( hs->key1 != NULL )          free(hs->key1);
5 _8 Y% K! W0 M" `        if( hs->key2 != NULL )          free(hs->key2);- T- b8 _8 m8 p- l. H
}
; s5 Q- I2 P+ `4 {9 J% I: s1 T# m3 l6 U, k0 X
// 这里对上一篇的match_string做了点修改,* |+ {1 O: `, m/ [
// 增加了一个end参数,这样不必提取出字符串后,再做剔除处理
! t3 T6 u0 @( S) v, f4 `0 Q- D2 Zchar* match_string(const char* src, const char* pattern, char end){( I0 ~3 \" P8 {- Y7 e  e! t
        char buf[BUFSIZ];
! e0 S* B0 K( M' A& q6 W. g        memset(buf, 0, BUFSIZ);
7 c4 ~3 d; {# v        size_t src_len = strlen(src); * ]9 v# o) u' c6 e) n
        size_t ptn_len = strlen(pattern);+ V% O5 z" {' \# H9 G' A( S: [
        unsigned short b=0, p=0, i=0;
( ?5 _4 q4 ~, s2 U3 f        char c='\0';
6 R8 I( L4 A. R6 ?; o        for(i=0; i<src_len; i++){
, U* @& {* t: |/ O                c = src[i];9 z2 y8 k, u3 j
                if(p==ptn_len){ // p==ptn_len 表示正在匹配中
( a; l# \$ X0 t$ ^                        if(c=='\r' || c=='\n'  || (end !='\0' && c==end) ) p++; // 匹配结束
6 a& B+ Y8 P6 k' i                        else buf[b++]=c; // 匹配到的字符   F3 w( i8 a6 V5 g! T( P( W
                }else if(p<ptn_len){ // 为达到匹配要求" \. q1 G+ {# S6 B* u
                        if(c==pattern[p]) p++;% U( s+ V" A3 {# y4 B+ d
                        else p=0;6 _0 O; [( E, u0 H  \# t7 b0 W
                }5 x4 M4 g5 i  G, W0 e$ e3 @9 q& O+ J
        }$ o: K6 U+ i% u" r' J# g/ l3 B
        size_t ret_len = strlen(buf);0 C( X% C0 q7 h: q% [
        char *ret_p;
; R& h7 j1 x" y; `6 M        if( ret_len>0 ){+ K/ ~8 ?( ~) Q) T
                ret_p = (char*)calloc(ret_len+1,sizeof(char)); // 加 1 为了存储 '\0'  |2 Y" o+ v/ J8 }2 c9 b/ O# @
                memcpy(ret_p, buf, ret_len);& ^( Y; k2 a2 ?1 S1 j+ d
        }else ret_p = NULL;
, q% l8 V3 A  A2 U$ D& Z' s        return ret_p;
) F0 s. H7 `' k1 D' e. \. ?6 y}
) J' \) N7 i( m% T$ K4 i# l: j4 u4 r& f1 H% b. R# J5 P  C$ I
// md5 加密函数,用的是网上一个实现的比较通用的版本。6 B% ^! ~1 Z3 U
void md5(const char* src, size_t size, char* digest)
3 o. p2 U0 G0 z{
& x, B7 W4 O, I7 Z2 a) {) Y1 C5 K        md5_state_t state;
2 `8 ?2 j6 `% M! K$ b4 _6 n        md5_init(&state);
5 d" }6 @1 f9 X# Q3 T5 t$ Z+ I        md5_append(&state,  src, size);! b  I( f& _' y! y  p9 d- w0 j" k
        md5_finish(&state, digest);. P4 i5 {. L1 J/ [3 j6 q* e
}) Z6 m. _! g3 K4 ]; S, h# `
: j& t; P* P& s1 W8 u4 O: C
void p(char*s, int len){
0 T- W: Z& F/ \& r  P( I        unsigned short i=0;
+ `& M6 Q! f( t( ?0 e        for(i=0; i<len; i++) printf("%c",s[i]);
6 f* z/ O2 x% r7 u3 B        printf("%c", '\n');
) G' E$ x- |7 @6 H! B}
) z2 w$ m( r  p/ |2 y- ?8 q: J9 {1 D! q
void handshake(const char* src, struct handshake* hs){6 N3 x8 i1 q- g! E5 t9 g( f
        size_t src_len  = strlen(src), i = 0 ;/ j% P) s" A) Y+ v
        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前) A5 t4 L2 z7 A. T- ~" ^  V
        hs->host                = match_string(src, "Host: ", '\0');0 `7 d7 j+ _2 h* @% r
        hs->origin              = match_string(src, "Origin: ", '\0');
0 C, o- e. M0 ?  P7 K, o8 _' d        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');1 U2 v# w8 N# |; D
        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');
1 ~6 e9 w% Q+ t& y& @        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0');
/ S. g. T& v0 ~2 X, [/ l* K6 e8 ?  }        // 获取 key3,即最后的8位字符
5 i  O9 F3 U) {  Z9 q        char key3[8]="\0";5 d$ u  h- w# p- ]: O- a( Z  J
        for (i = 0; i < 8; i++) key3[i] = src[src_len - 8 + i];
* c7 x$ @* x" x# P        char digits1[64]="\0", digits2[64]="\0", c='\0';3 ?6 _1 l+ U( P
        size_t spaces1 = 0, spaces2 = 0;. _4 Y- S3 R0 V& [  W
        size_t key1_len = strlen(hs->key1);
' a1 m; }2 X: q% a* M        size_t key2_len = strlen(hs->key2);8 _4 s: S. m6 i6 c
        short d1 = 0, d2 = 0;
& S" Z8 L* E9 L6 S3 U1 s# T        unsigned int result1, result2;5 B  }% p5 A: j; h$ Q. Z
        for (i = 0; i < key1_len; i++){
$ @/ s/ a( ^- v, t% N1 X                c = hs->key1[i];- E" W8 [' X( m
                if (c == 0x20) spaces1++;: R+ h% r/ V7 k5 M
                else if(c>='0' && c<='9') digits1[d1++]=c;
# ~, C5 c/ w( e9 P; @" u        }5 Q7 V( z& d6 u. H. R8 g$ W
        for (i = 0; i < key2_len; i++){ & L' c$ y6 \' M5 R  E! }9 {
                c = hs->key2[i];+ `0 ~% i9 m4 m* a% y8 }
                if (c == 0x20) spaces2++;
5 r% X  c: u9 i                else if(c>='0' && c<='9') digits2[d2++]=c;
' d0 A8 J( ~# ?7 O; o' ~        }; a5 s( T+ K( j5 Q
        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);
. c+ g9 T( S9 ]: D2 \5 d        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);
+ a$ p2 }  J% N# [        printf("ch1:%s\nch2:%s\n",digits1, digits2);
' i+ r6 Z! M0 ]9 M        printf("sp1:%d\nsp2:%d\n",spaces1, spaces2);+ L6 y7 D1 N2 Z  K; F
        printf("d1:%d\nd2:%d\n"  ,result1, result2);
+ S! |) O0 b6 i( {1 g# U" s        unsigned char chrkey1[4]="\0", chrkey2[4]="\0";; R, H& R3 K$ O$ K. ?/ s# C# i8 S
        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);  b; [  b4 M; T: I$ G' ]$ L2 \% F
        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);
# w. R$ O$ D) n        printf("ch-key1:"); p(chrkey1,4);  s" M* ?0 u! [  X8 h) }' Z% |
        for(i=0; i<4;i++)printf("0x%02x ",chrkey1[i]);
$ c% o7 N) [( O: _# q' o        printf("ch-key2:"); p(chrkey2,4);: P8 N* a6 h" G/ p
        for(i=0; i<4;i++)printf("0x%02x ",chrkey2[i]);( m5 r9 ?) C: ^
        unsigned char raw[16]="\0", dig[16]="\0";
- h) _( u3 Y7 f+ M1 i        memcpy(raw, chrkey1, 4);% k/ {  L/ m4 z
        memcpy(&raw[4], chrkey2, 4);( P* A! d+ t! W
        memcpy(&raw[8], key3, 8);
- q1 H$ f1 t$ C6 H& k2 ~" T        //计算的md5值6 J1 F9 f! ?, O* s
        printf("\nraw:");# ]" H" _$ s3 u' B$ r8 @
        for(i=0; i<16; i++) printf("0x%02x ",raw[i]);9 \7 q9 J# U2 P* J3 R$ T8 p
        md5(raw, 16, dig);
3 n$ q' n, v7 o1 R        printf("\nmd5:");. C( f/ `8 }6 N- C, }
        for(i=0; i<16; i++) printf("0x%02x ",dig[i]);
, ]2 z* e) M5 T/ X  ]$ M}
3 A' w; _+ q8 r$ A- A: O$ ~1 N
/ V( l3 _- l1 x, E; {. ?( }% Z& L. N9 g
int main()
2 b: n, w2 a; W5 B{
( p' M, {8 d# T2 t0 ^, |" p        unsigned char msg[512] = "GET /pub/chat?q=me HTTP/1.1\r\n\
) ^/ g( |( P7 Q! U/ ~. ]4 O4 g        Upgrade: WebSocket\r\n\# V" _! U5 v& i) N+ j# z
        Connection: Upgrade\r\n\
3 ]/ W, r- B- M/ O* ?6 {* z        Host: localhost:4400\r\n\
0 }: i2 x: l- h; }. t: X        Origin: null\r\n\% ]9 o  z7 |' v
        Sec-WebSocket-Protocol: my-custom-chat-protocol\r\n\
) s- q: ^: i  V/ _1 i+ \        Sec-WebSocket-Key1: x EO2 59186 4  28\\dY 0+\r\n\
. u: Y) ~2 C" R: T% b- z# \        Sec-WebSocket-Key2: 1  9  3  57695W    0\r\n\r\n";
  Z8 u' g2 _; R8 p" z. `        size_t len = strlen(msg);) W+ w+ p3 u$ u3 Z
        msg[len]  =0x1f;
; }: L4 A# R/ h8 N# n7 W. Y$ n) s* g) G        msg[len+1]=0xf6;9 w  s6 D) C2 [+ i* g8 I8 Y  J  d
        msg[len+2]=0xf3;/ v8 w& s! M0 V9 j
        msg[len+3]=0x3f;  Y  n, U, k+ l# y7 u  Q% f
        msg[len+4]=0xc7;
$ C# P$ x9 `/ o& e        msg[len+5]=0x17;3 s/ V0 U7 ?& p
        msg[len+6]=0x20;. Z5 J  y, ^3 `* q+ x8 V9 |( F
        msg[len+7]=0x88;
: }  o. h" ^( e- P8 T        struct handshake hs = {NULL, NULL, NULL, NULL, NULL, NULL};5 w2 r9 X2 ?- Q, I
        handshake(msg, &hs);9 `% m0 K: c$ g. {: Y  }& \5 ^
        free_handshake(&hs);
$ m3 G1 e; J4 y        return 0; ) {! a* _- W! E  B6 t
}3 t- i; C- `0 j! \
& P6 i0 I- F# a# q5 e

' x  {& L) R& `5 {% Y测试的结果:/ x$ M" j. y. @5 }% w- o
raw:0x19 0xbf 0x73 0xa4 0x01 0x27 0x5f 0xff 0x1f 0xf6 0xf3 0x3f 0xc7 0x17 0x20 0x88
/ V. c9 R! H  g* Q1 \md5:0x61 0x30 0x1e 0xe8 0x8a 0x17 0xaf 0x39 0xd6 0xad 0xef 0xb9 0x6f 0x00 0x0f 0x68
! b6 O2 n& R2 o& r对比了nodejs的版本,握手部分生成没有错误。6 E+ g) h  ~. @8 p. @8 u
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-5-2 07:37 , Processed in 0.026118 second(s), 19 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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