找回密码
 注册
搜索
查看: 5276|回复: 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++庞大的类库和模板。! O1 N4 }9 Q/ U
完整的windows版本socket握手实现:
: ~# b! ~$ m3 W9 f* ^2 U6 F$ |6 ]: z% h- i, ]2 B3 W9 T. p
bool WebSocket::handshake(const char* src, struct handshake* hs){
- M0 a2 N& \5 f9 l6 Q9 [4 u4 c+ w        size_t src_len  = strlen(src), i = 0 ;
0 F- ?! @5 q  @; [0 E5 y2 h  Z/ i2 p        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前
! }; e2 m! ~& y! Z        hs->host                = match_string(src, "Host: ", '\0');3 f8 L% l0 x$ b
        hs->origin              = match_string(src, "Origin: ", '\0');
3 Q$ `6 M. B5 ^/ S' A        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');2 k) w1 s( y) [& s0 q* g1 q  m
        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');
$ s7 _2 f, r% Z1 E. h7 h! d        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0'); / F/ P  }0 N# r6 ?
        char key3[8]="\0"; // 获取 key3,即最后的8位字符
! ^5 ^  N/ m* c: ~        for (i = 0; i < 8; i++) key3[i] = src[src_len-8+i]; & c: G, C! z& N
        char digits1[64]="\0", digits2[64]="\0", c='\0';3 z9 {) N4 N+ N; z' d1 t" u6 c1 V* }
        size_t spaces1 = 0, spaces2 = 0;
  s0 \) e  G" |, g4 _        size_t key1_len = strlen(hs->key1);
+ ~8 X: r2 n3 s, M/ q        size_t key2_len = strlen(hs->key2);
& Q: W$ b( |( S3 c* B0 C        short d1 = 0, d2 = 0;
) g5 y# Q) o6 M1 w, W- }. ~        unsigned int result1, result2;+ V; Q9 E7 A5 W! S; e7 O
        for (i = 0; i < key1_len; i++){
( `3 M  }; g& O, y$ m1 R4 s, f                c = hs->key1[i];
' ]2 A. N3 w9 c' ^1 B2 d; s                if (c == 0x20) spaces1++;/ {0 q. w7 O) Q6 n% B
                else if(c>='0' && c<='9') digits1[d1++]=c; $ V9 b9 z& I2 y3 Y, q
        }
8 \. r$ t, m  P# @' O        for (i = 0; i < key2_len; i++){ ' k9 Y) q  q9 W, x( t& `& `& i
                c = hs->key2[i];7 c; I6 T! L9 w8 a2 M  o+ H
                if (c == 0x20) spaces2++;- w( T$ _8 A: g$ S4 f# x
                else if(c>='0' && c<='9') digits2[d2++]=c;
$ R% f: T$ b) J( j4 J0 o. u/ m1 \! C- v        }
% X5 G$ P! n3 T) L+ A' Q' y, ]        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);! K4 N8 O+ g2 t: ?
        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);
* N7 ?, R; V8 F( L4 o, m        char chrkey1[4]="\0", chrkey2[4]="\0";
  K4 {% Y, G5 y0 K. Y. R7 f1 w        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);6 A  J3 l; ]/ M' y2 B
        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);- I! J& U( j% R: x* I
        unsigned char raw[16]="\0", dig[16]="\0";
% H& {6 `0 r7 o5 O- E! Q/ }, k        // raw 表示未md5之前的字符串,规则就是前4位key1中的数字/空格数的整数值,
* C3 u9 j6 m* \% \7 e( B% D3 y2 m        //  连接上key2的最后连接上头信息中的最后8位字符。
! g; d2 A; T2 {6 k2 A9 U/ u5 @        memcpy(raw, chrkey1, 4);
# o4 z$ K6 ~- K  t* y        memcpy(&raw[4], chrkey2, 4);$ p+ ^+ E- Y( B% J+ K
        memcpy(&raw[8], key3, 8);
8 F6 _" @) U  Z3 t, x        //计算的md5值
/ |  R% I9 L- A7 {% W        md5_state_t state;
0 x6 l4 j  Z! j* _4 p' q        md5_init(&state);
/ |9 v, h& D/ j% B0 \& v) `8 f        md5_append(&state, raw, 16);
. f. _* l* U4 }8 d! u        md5_finish(&state, dig);  c, G8 B: n* Q8 X5 A9 B" R
        , i# x8 [5 d5 E# E
        char handshake_str[BUFSIZ];
, e- @0 S1 P! H% q3 J- b6 v        memset(handshake_str, 0x00, BUFSIZ);; K5 w7 k) N3 ^) V" s' X
        char* handshakeFormat = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"2 Q; w8 m2 S8 c# c0 {5 P
                "Upgrade: WebSocket\r\n") ~- z# c# M: O" p
                "Connection: Upgrade\r\n"
3 {- d6 J  {5 |' G: q% {+ u                "Sec-WebSocket-Origin: %s\r\n"
( v4 @; x' d8 K$ X                "Sec-WebSocket-Location: ws://%s%s\r\n"
" [$ c8 g( s# D! Q                "Sec-WebSocket-Protocol: %s\r\n\r\n";
" J$ k, z$ n* y4 y" }$ b( }$ @        sprintf(handshake_str, handshakeFormat, hs->origin, hs->host, hs->resource, hs->protocol);& `1 J. Z/ b+ ^! D6 ]! v& m
        free_handshake(hs); // 释放handshake指针,已经不用了!
3 {9 e8 B: Y) r8 m( x$ H* P$ u        char response[BUFSIZ];) e4 b( R) u! \1 m9 M
        memset(response,0,BUFSIZ);: G, O3 R8 i+ Q" x9 t( C9 m/ g
        size_t j=0, handshake_len=strlen(handshake_str);
! P4 `# |7 n0 u        for (i = 0; i < handshake_len; i++) response[i] = handshake_str[i];
" n3 B/ v' E5 j0 q4 j        for (j = 0; j < 16; i++, j++) response[i] = dig[j];$ [$ i5 R$ k6 k/ q2 t2 W
        // 这里的clientSocket就是连接好的socket对象了。* @7 Q6 L; Z% }2 I5 ]- E/ B" U
        int sent = send(clientSocket, response, strlen(response), 0);% s- Z2 o, v" a6 ?
        return sent>0;9 P8 K8 s+ l* M% V" v8 y- ?5 }
}
! B3 N. j( v  O9 B( ?% b  x/ s4 d( g目录结构:8 w, s6 ^; m8 e
socket/
  U) n8 ?$ X+ a, g7 Z      socket.c
# V0 I/ h+ z9 @9 f      md5/
( k* {2 J4 h: b, n, d, \4 c        md5.h
7 J. E* u4 }3 w( z        md5.c
# ^2 j9 X2 g' r库文件下载位置: http://sourceforge.net/projects/libmd5-rfc/files/ 总共3个文件,
& U- P! h. a; g8 f3 w编译命令:  gcc socket.c md5/md5.c -o socket.o  
  t/ k# _6 s/ l GCC 4.3编译通过,执行 ./socket.o 可以看到打出的raw和md5后的字符串信息。 这里使用了一个网友用nodejs写的websocket实现版本,打出详细的头信息,然后复制到程序里,以测试生成的握手字符串是否一样。
( M! W1 }+ D* p/ m; j9 K- c// socket.c 代码。4 J; _7 ^4 |: M% t8 b4 B; Y% @
// web-socket example ) [5 R3 l/ M" C6 C. f8 N- ^
#include <stdio.h>! Q; g" j% [- w" R/ D4 d' p
#include <stdlib.h>: _* b' @+ x7 O+ I
#include <string.h>$ H- v% B" F, w# Q
#include <inttypes.h>3 j( W% w& g5 w& q
#include "md5/md5.h"
' X) ?4 e4 x% s+ H6 [: k0 w& d3 n
+ N& M) J! L! l% K3 O7 U1 o& H// #define BUFSIZ 5123 U; ~* G9 z6 z9 X: W- ?1 {* Z! z
+ ^: H% `# c1 x5 e: G
//定义handshake结构体变量
0 b; B, D1 L& [- c! H) Istruct handshake {% l2 k, F9 w- C3 _
        char *resource;
6 t* m' ]9 ~) {        char *host;2 ]$ p1 c5 V8 `* m+ W9 i4 E
        char *origin;
8 @2 k  J/ _' P+ s        char *protocol;
! ^# Q  G" w, S1 d0 B        char *key1;
4 w. `4 W1 K6 l' N; ^5 U        char *key2;1 r& s* o# n7 Z' i9 U
};
; \" V! X/ ^: \& f# `" B0 e' y; C

( n+ n$ Z5 l6 ~6 c8 c//释放握手后不再使用的部分变量
1 N* P! d8 a0 o9 Evoid free_handshake(struct handshake* hs){& Q+ S% c! y/ W& A8 v
        if( hs->resource != NULL )      free(hs->resource);+ x* e+ Y. ?6 A  e4 ~$ J
        if( hs->host != NULL )          free(hs->host);
8 m5 \3 T4 r2 y' B2 \        if( hs->origin != NULL )        free(hs->origin);0 X5 U; y% `& o' c- t+ l
        if( hs->protocol != NULL )      free(hs->protocol);
+ L4 X8 x) S2 \( [9 m        if( hs->key1 != NULL )          free(hs->key1);
7 A  @9 \7 I3 j) A! m8 L( o! V4 r5 G        if( hs->key2 != NULL )          free(hs->key2);
! x2 o8 `6 j- _& O, X" [}
& x, w  w7 V& v! j/ w, U2 r; C% B9 c# C+ E$ u
// 这里对上一篇的match_string做了点修改,
9 B' n# e$ `8 k% r" u: Q: k  U// 增加了一个end参数,这样不必提取出字符串后,再做剔除处理( j! \: `& S9 ~# [( y' ]' ]
char* match_string(const char* src, const char* pattern, char end){( Y9 e3 N, O# D. n
        char buf[BUFSIZ];! _+ M5 q" M8 y. B7 N$ y# N
        memset(buf, 0, BUFSIZ);
3 E* Y2 b* u7 ^9 l( v        size_t src_len = strlen(src);
- K5 p5 E& v, w% U9 L( `; s' @        size_t ptn_len = strlen(pattern);) n0 O$ j$ P' ?) `# b% U) |$ K
        unsigned short b=0, p=0, i=0;
& K# {# ~; w: h        char c='\0';
4 {! d. e! k8 K  c% x7 h+ `" [        for(i=0; i<src_len; i++){/ L5 _& g5 ^7 W& |" U8 l% r
                c = src[i];9 z7 D8 L* e( R1 `
                if(p==ptn_len){ // p==ptn_len 表示正在匹配中' N2 B5 K# ]* r8 @4 E
                        if(c=='\r' || c=='\n'  || (end !='\0' && c==end) ) p++; // 匹配结束: X  ~; w" ]' t
                        else buf[b++]=c; // 匹配到的字符 8 I0 O# r5 H3 B- {; u
                }else if(p<ptn_len){ // 为达到匹配要求, x+ E, D; r4 @
                        if(c==pattern[p]) p++;/ O5 x  V  W: k' Y& F2 j
                        else p=0;4 K- k1 f+ Y9 T) s
                }& z7 C+ m6 h" c
        }9 k7 E7 H, g2 N: S
        size_t ret_len = strlen(buf);
0 b  J" ]6 t  L        char *ret_p;
: l/ ^6 Q% j* m! x( [4 V& x        if( ret_len>0 ){% p1 t# D: i- T8 U6 Z
                ret_p = (char*)calloc(ret_len+1,sizeof(char)); // 加 1 为了存储 '\0'/ u6 j5 j" x: T/ `) l" _
                memcpy(ret_p, buf, ret_len);( G1 ^3 T& C4 \" @
        }else ret_p = NULL;6 _$ j" _2 p4 q' s. Q
        return ret_p;
4 f' W* q2 E% J/ ?6 O: P. l}
. T3 I' D' G7 ?$ {3 D8 R
$ W: P: {5 u- T2 x9 e# x// md5 加密函数,用的是网上一个实现的比较通用的版本。+ c5 b) z# V: o" N& l
void md5(const char* src, size_t size, char* digest)
6 y* }- s5 p- D' T5 j/ h{
$ k0 X3 O0 @* \$ R! B        md5_state_t state;
* ?  m6 y+ z1 o% y# L9 _        md5_init(&state);* |% r% g- m* p- |
        md5_append(&state,  src, size);
5 v8 S2 y) {, N! M) {0 z. V        md5_finish(&state, digest);
; Q8 e+ c+ a; _$ j4 d' h}/ e2 i' h9 |- o

* M8 e( d- @, \2 [4 N; {void p(char*s, int len){
% H+ N$ u( }) o        unsigned short i=0;! x) u( L/ B& L9 d+ o+ o9 k
        for(i=0; i<len; i++) printf("%c",s[i]);' n; l$ D  C, W: T, K* Q/ |5 d
        printf("%c", '\n');
. a1 s" }* L( [* Y# C. R}, y4 c& x- u% l3 s4 J) Z, M2 c

: w! I+ r2 u: g% k4 R) B8 v  a5 |& {void handshake(const char* src, struct handshake* hs){5 A. W% M% Y0 w
        size_t src_len  = strlen(src), i = 0 ;& `  g% j  i. Y( h1 q  |
        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前. P8 F0 {) K' r8 r
        hs->host                = match_string(src, "Host: ", '\0');
! ~" Z7 x2 U& }- w; s' p! F8 W        hs->origin              = match_string(src, "Origin: ", '\0');4 C" Z+ P5 l% G5 E. h! H9 s% o
        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');
2 s, ^* p6 }0 s* U% V* ?" n, P7 `        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');) d3 I9 A& W3 J) u. T6 h' I8 T
        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0'); 3 \4 }8 b/ s3 p2 Q( X" `
        // 获取 key3,即最后的8位字符
& x; x/ w2 M) ~) C8 r9 k+ R        char key3[8]="\0";2 r. R& R% Q3 y0 _9 h4 [
        for (i = 0; i < 8; i++) key3[i] = src[src_len - 8 + i];
* x$ @8 U6 o) V2 [8 n4 B        char digits1[64]="\0", digits2[64]="\0", c='\0';
! y- [# K! k  q: l        size_t spaces1 = 0, spaces2 = 0;$ B, y9 K7 g5 X. r. E# K
        size_t key1_len = strlen(hs->key1);+ }0 u; E4 \) u" m; H) ]. z- @# J" W
        size_t key2_len = strlen(hs->key2);
- ^) o" w* n/ H; W9 m1 D        short d1 = 0, d2 = 0;
  p, |5 o1 x) j/ C        unsigned int result1, result2;* ?, [% x7 B7 i! d: g
        for (i = 0; i < key1_len; i++){ , N8 }- C6 S5 I  F
                c = hs->key1[i];
6 F. h5 o& g4 g+ J* O                if (c == 0x20) spaces1++;
3 B8 ]% t3 f, f# O2 q                else if(c>='0' && c<='9') digits1[d1++]=c;
- H' {5 }( r: b0 p+ F1 W        }8 k/ t0 k) F6 Y! ?
        for (i = 0; i < key2_len; i++){ / {: Q1 i# t' h  H
                c = hs->key2[i];
% }; p. J2 Q+ w. U                if (c == 0x20) spaces2++;- @( r/ d; \: R, y" D
                else if(c>='0' && c<='9') digits2[d2++]=c;
/ Y; G1 ~' b6 V        }
8 Y+ l% h! l8 `        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);' |$ z- p6 F# S
        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);
  X/ c+ A6 D9 N! s$ q6 \        printf("ch1:%s\nch2:%s\n",digits1, digits2);3 }5 [/ d, l& k* I  S) Y# s
        printf("sp1:%d\nsp2:%d\n",spaces1, spaces2);, \3 E; n$ W, I3 L; G
        printf("d1:%d\nd2:%d\n"  ,result1, result2); ' H) t& L8 W8 ~* f* W' z
        unsigned char chrkey1[4]="\0", chrkey2[4]="\0";
9 f; A9 I6 u) m2 M$ z2 q7 {        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);
- r" q9 j; O: K- h        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);
9 |$ ~4 }# R) L1 a( F        printf("ch-key1:"); p(chrkey1,4);+ W/ \3 i4 i8 S9 ~- n7 \8 q! W; \! O+ Y
        for(i=0; i<4;i++)printf("0x%02x ",chrkey1[i]);
7 l* ~$ M9 L" S2 @; y+ T        printf("ch-key2:"); p(chrkey2,4);7 z0 c. I! Y7 t) _' m2 D5 I1 t
        for(i=0; i<4;i++)printf("0x%02x ",chrkey2[i]);
& [0 @  A6 f5 U+ |$ E+ z- ^        unsigned char raw[16]="\0", dig[16]="\0";
; a! r4 y3 Y$ W! i2 m( z9 W2 W        memcpy(raw, chrkey1, 4);
" T% T! Q# I% Q" E, a& Z/ n0 z        memcpy(&raw[4], chrkey2, 4);
* O" P4 o/ k2 _3 P& p% m( T        memcpy(&raw[8], key3, 8);
+ r6 w* L" w5 x: v8 e/ v        //计算的md5值: k% t" U- |! h4 x- i) T/ m
        printf("\nraw:");1 z, g( u$ D+ m- o3 _% o
        for(i=0; i<16; i++) printf("0x%02x ",raw[i]);
9 Y8 F9 A& i+ I/ [4 p/ f+ n        md5(raw, 16, dig);+ P+ L3 W4 I' X" ~! G
        printf("\nmd5:");6 ]: t5 |4 e0 z9 z2 D0 [
        for(i=0; i<16; i++) printf("0x%02x ",dig[i]);
! x5 C! F1 h( L! `}) ~; \# ]+ F" x" O

/ [7 G8 S: G0 |/ e( l2 T/ m- w& V% o) z/ X+ ]
int main()$ e* p! V1 \% ?1 L- c& ?
{6 l3 R7 t' \, o& v, f& c5 ?
        unsigned char msg[512] = "GET /pub/chat?q=me HTTP/1.1\r\n\+ D3 [0 Z) Y" T5 v/ {
        Upgrade: WebSocket\r\n\7 C8 [1 ^4 O/ F" F/ g
        Connection: Upgrade\r\n\  b2 E; X6 b* g3 W9 T) W' C
        Host: localhost:4400\r\n\" \8 b+ K4 j% R& F, ?5 }4 l
        Origin: null\r\n\
3 m) y+ R7 f6 @# N" e        Sec-WebSocket-Protocol: my-custom-chat-protocol\r\n\
" f& p9 N8 O: `1 G# ?" ^        Sec-WebSocket-Key1: x EO2 59186 4  28\\dY 0+\r\n\
3 f; V. f8 x& ?3 c# [        Sec-WebSocket-Key2: 1  9  3  57695W    0\r\n\r\n";- X8 p3 i9 i$ {: x. i
        size_t len = strlen(msg);* h1 Z% {$ j% e7 ^
        msg[len]  =0x1f;
% d3 w" m6 x' I% ~  S* `        msg[len+1]=0xf6;
3 b: u& W" p1 F! X* n        msg[len+2]=0xf3;, t% {$ [6 g! d$ @
        msg[len+3]=0x3f;& j5 f( Y- [% v6 w6 X) ]4 P
        msg[len+4]=0xc7;
  C. l, ?$ J& W7 C/ [2 Z# {* f  D        msg[len+5]=0x17;! \5 q& l0 {1 t) w
        msg[len+6]=0x20;) I* n2 n' _; |
        msg[len+7]=0x88;
, d  l# U/ l+ g6 e        struct handshake hs = {NULL, NULL, NULL, NULL, NULL, NULL};
9 Y! o+ n+ Z' o5 D        handshake(msg, &hs);  J# `; S2 [6 n7 J
        free_handshake(&hs);' r: h9 I9 x6 {% I) E: Z
        return 0;
; j1 g, D- n( v. j}! V; L- ^3 I! t' `
5 q% \0 p% _5 e

& d. V% r8 W( x; j  @3 B测试的结果:
+ D* ]# S. X. K" ]raw:0x19 0xbf 0x73 0xa4 0x01 0x27 0x5f 0xff 0x1f 0xf6 0xf3 0x3f 0xc7 0x17 0x20 0x88 2 I) y1 B; B& A* h1 N
md5:0x61 0x30 0x1e 0xe8 0x8a 0x17 0xaf 0x39 0xd6 0xad 0xef 0xb9 0x6f 0x00 0x0f 0x68 ; _' x9 w! L1 o/ h2 d' P
对比了nodejs的版本,握手部分生成没有错误。
- E* U1 ~! s) C; [1 l
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2025-11-14 16:27 , Processed in 0.024396 second(s), 19 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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