找回密码
 注册
搜索
查看: 5137|回复: 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++庞大的类库和模板。
! O& y/ v- h6 K0 m: g6 A2 P完整的windows版本socket握手实现:7 k4 [& R0 z# y+ P, o+ O0 \3 S; O$ m
5 b4 E& T( F1 b9 u
bool WebSocket::handshake(const char* src, struct handshake* hs){) K/ _/ i7 w3 o& b! |4 G
        size_t src_len  = strlen(src), i = 0 ;
6 U1 `, c& o( U) `        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前
7 r$ M1 D+ b$ j6 Q% R# A+ C, Z  b        hs->host                = match_string(src, "Host: ", '\0');
) G6 t5 A" T& L; _- y7 G  g3 K! U0 S8 ^        hs->origin              = match_string(src, "Origin: ", '\0');1 j, p' i2 ?3 ]* T1 A" r
        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');0 ]! a  r+ k9 y. e
        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');
" j4 K4 D' P$ c. g9 J' t# x2 _- f# r        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0');
  e* d" h7 Q! Q! z, y* n        char key3[8]="\0"; // 获取 key3,即最后的8位字符
0 U4 t2 a, w3 @5 X1 }9 ^% U        for (i = 0; i < 8; i++) key3[i] = src[src_len-8+i]; 4 ^9 {1 v7 H" \4 Z8 m( C) s4 K
        char digits1[64]="\0", digits2[64]="\0", c='\0';
. ~) [) e4 S  F        size_t spaces1 = 0, spaces2 = 0;) S. G& j% K0 q2 _9 t3 u
        size_t key1_len = strlen(hs->key1);+ D! I% C. O8 i# @( }, i
        size_t key2_len = strlen(hs->key2);
8 D" X* l4 f2 S8 Q' H3 ~/ _        short d1 = 0, d2 = 0;0 H7 Z4 E" w- H# }/ B7 Z: E3 t1 D
        unsigned int result1, result2;! D) w; ^* S& Z' m- Y0 g
        for (i = 0; i < key1_len; i++){
+ u5 n: i# K$ b" B# F  q8 P" E                c = hs->key1[i];
) w5 k! K' q9 X$ ]& n+ E$ x                if (c == 0x20) spaces1++;2 d1 m$ x+ c3 \& k% a4 y
                else if(c>='0' && c<='9') digits1[d1++]=c;
9 \& o. Y/ H8 l* Q- R$ t! e# ~        }
' Z' }- t' _8 B        for (i = 0; i < key2_len; i++){
9 Z2 q$ D: K3 V) W                c = hs->key2[i];
1 e. O( r/ W3 C                if (c == 0x20) spaces2++;# b( n. ?; U7 D4 C: v
                else if(c>='0' && c<='9') digits2[d2++]=c;
; I2 P1 Z: O' i& F' h" D7 g/ M        }3 v% R* \2 P& r7 `$ W; E
        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);% d) |+ {& q0 L7 W8 [3 q4 Z
        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);
& r8 Z4 e  m% B* s* s$ ^( d        char chrkey1[4]="\0", chrkey2[4]="\0";
" L( K7 B9 Q( O* o' j/ E* o        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);
  H; i& w) u+ C5 }- P6 ?, a        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);
% v$ p3 d; ~( W3 g! q2 R/ r        unsigned char raw[16]="\0", dig[16]="\0";
9 h$ @& v+ o$ f  U; P        // raw 表示未md5之前的字符串,规则就是前4位key1中的数字/空格数的整数值,+ i" H) S5 Q8 W' w" m% ]( t
        //  连接上key2的最后连接上头信息中的最后8位字符。# `7 m% {& m' r& o& a
        memcpy(raw, chrkey1, 4);
; l  f& ~+ F9 ^! }/ O$ z. \        memcpy(&raw[4], chrkey2, 4);
; A/ E3 c' U; P        memcpy(&raw[8], key3, 8);
' e. N. n! @  I& R" F9 d        //计算的md5值
# ]+ ^* u8 `4 D        md5_state_t state;' x) x& f8 X: T* f" E
        md5_init(&state);6 E/ K1 n& V  W6 x! q. m
        md5_append(&state, raw, 16);* s3 R7 g) X  N/ I( X
        md5_finish(&state, dig);
! C! }# B$ b" a        
! e! z+ J6 e) E( c$ Z- F) |        char handshake_str[BUFSIZ];
; ]! F  t6 b2 P: |        memset(handshake_str, 0x00, BUFSIZ);
6 x! w6 N% A/ _3 |9 R        char* handshakeFormat = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
; \' J6 ?% A! w+ t5 D: h$ `) P& X                "Upgrade: WebSocket\r\n") i$ @0 L: Z# \3 L* ~; g0 G
                "Connection: Upgrade\r\n"
0 `$ c5 V9 W9 Y* w                "Sec-WebSocket-Origin: %s\r\n"
; @: R, n* y+ F                "Sec-WebSocket-Location: ws://%s%s\r\n"
  ]' g2 k) s+ t5 j( T. |                "Sec-WebSocket-Protocol: %s\r\n\r\n";5 X1 J* P: a1 _# }7 I. }: H
        sprintf(handshake_str, handshakeFormat, hs->origin, hs->host, hs->resource, hs->protocol);
7 d" C; J0 s4 j4 y9 \        free_handshake(hs); // 释放handshake指针,已经不用了!
: ^: |0 d" R$ Z1 x* q2 k        char response[BUFSIZ];
) ^% }  X; ^9 p  u        memset(response,0,BUFSIZ);3 y% e+ [. O% o8 r1 ]. J* U6 c" `: T) h
        size_t j=0, handshake_len=strlen(handshake_str);
2 i7 k  ^8 U, l( e0 T, L        for (i = 0; i < handshake_len; i++) response[i] = handshake_str[i];( G4 e* D$ {* F/ T, a; [- D
        for (j = 0; j < 16; i++, j++) response[i] = dig[j];
7 C' L" }$ W  Q& x+ q/ g5 g! t9 a0 R2 p        // 这里的clientSocket就是连接好的socket对象了。  u& ?$ ?( b5 l( _
        int sent = send(clientSocket, response, strlen(response), 0);: m, T9 x! L" K# N: Y9 x5 [* V! _
        return sent>0;. V1 q& z2 |4 K' p8 o/ a
}
  k: ~# Q, R- q% U- o目录结构:* k1 N* j( d4 y+ V% y7 w5 z
socket/
+ @, s! k$ v1 G7 Q4 @6 U% J0 Y$ }      socket.c* Q+ R6 e. [) |9 D2 W3 n
      md5/, U( X5 d5 f3 a9 _1 h' I% B7 ]" m
        md5.h
' W" [/ M' ^, ?, }( c4 n! u        md5.c
! _/ \& X! V: }* g* u* c/ r3 Y& o% C库文件下载位置: http://sourceforge.net/projects/libmd5-rfc/files/ 总共3个文件,
- T# D- ?7 i9 r9 E) n5 t编译命令:  gcc socket.c md5/md5.c -o socket.o  9 b$ J% I- A, Y
GCC 4.3编译通过,执行 ./socket.o 可以看到打出的raw和md5后的字符串信息。 这里使用了一个网友用nodejs写的websocket实现版本,打出详细的头信息,然后复制到程序里,以测试生成的握手字符串是否一样。
/ v% A- j! b1 x4 ?2 ?. W// socket.c 代码。; }( R/ H; K  H/ T
// web-socket example
1 k" e; n8 O0 q, T& }#include <stdio.h>
$ D) ]9 }) [: d; v9 ^; _#include <stdlib.h>4 \& {5 ~6 }' t* \) c, c
#include <string.h>
* \6 O* [: D. r. T5 I5 [4 K4 F#include <inttypes.h>
7 l5 E) P5 d- T- y7 t* ?. F; ~#include "md5/md5.h"
% s/ y% W" ~8 _' k' Z2 ^% N' b8 M9 v2 a( {3 s/ G
// #define BUFSIZ 512- E* W' N: H1 @% e' ~

4 s8 m, u6 K/ U. a# O//定义handshake结构体变量1 P( V6 g# f+ {* t6 `
struct handshake {* Y, m2 T, ]( R
        char *resource;* c9 p8 B" N$ ?$ J* _/ j7 C
        char *host;
1 C: v1 [( L. ]2 S/ @" Z. b* P( q        char *origin;
2 A4 l1 n# J4 G! Z8 ~% C' F        char *protocol;
6 H' a7 N9 i& F' v        char *key1;
2 o6 `3 R1 M4 b! E) _        char *key2;
' w# P- W, C, h9 Y& S' J5 V};! j  C  I, M$ e! @- i, s
" S# v+ `9 D- c/ {
. r5 S  j+ {8 F( V
//释放握手后不再使用的部分变量, n8 ~0 U" ]8 R4 P+ m9 ^# k
void free_handshake(struct handshake* hs){) x$ T9 h, q! G
        if( hs->resource != NULL )      free(hs->resource);
2 ?% p3 ^1 S- R/ U3 l+ n$ t        if( hs->host != NULL )          free(hs->host);9 }  B6 L- Y- J6 J; P
        if( hs->origin != NULL )        free(hs->origin);( i8 N2 M  F. [8 ^  D  J" P% [; C# T# D
        if( hs->protocol != NULL )      free(hs->protocol);. D% F2 G- p" R) [
        if( hs->key1 != NULL )          free(hs->key1);' M2 X5 f6 Z) Q! C: T& f
        if( hs->key2 != NULL )          free(hs->key2);* ^5 ^  r1 A: q- @" z
}  s: S% q) e, M" d! p

2 K1 l& L- U- _/ b  F// 这里对上一篇的match_string做了点修改,( Z" B2 D1 c. f; ^, I- N7 T: [: d
// 增加了一个end参数,这样不必提取出字符串后,再做剔除处理; s/ K  _, E- ]2 N0 a/ z
char* match_string(const char* src, const char* pattern, char end){) ^" k& T# Y' e) P7 m
        char buf[BUFSIZ];
1 F5 D7 |8 D$ G3 \        memset(buf, 0, BUFSIZ);
* W' c) H" U: G        size_t src_len = strlen(src); 0 E5 z+ W* K" u& m) [' a! M
        size_t ptn_len = strlen(pattern);/ O+ n4 [4 Q$ m" p8 r  K& R( x
        unsigned short b=0, p=0, i=0;
% Y: R0 T3 l  K9 r3 J        char c='\0';
% S" Y5 k' [7 o- z& E! U0 v        for(i=0; i<src_len; i++){/ V7 j( V- U; M; S% ]
                c = src[i];  a/ W: |& D& \$ F" L  k
                if(p==ptn_len){ // p==ptn_len 表示正在匹配中
" ~! O! G- o# b* H; S                        if(c=='\r' || c=='\n'  || (end !='\0' && c==end) ) p++; // 匹配结束( g. g5 p. ]9 u* d( u2 e- x' w1 u  S
                        else buf[b++]=c; // 匹配到的字符
' G7 S8 P& B$ @; G/ J* n( h( Y                }else if(p<ptn_len){ // 为达到匹配要求; s% V, F, a9 o$ p  W4 T
                        if(c==pattern[p]) p++;8 Q6 Z1 U) S6 P5 a5 s
                        else p=0;
& B' x4 @3 W3 P6 D7 \- P7 b                }
. P) J9 K+ X' Q+ W0 m; G$ Z& }        }2 ?/ D$ L; Y6 L  Y; r: U. k
        size_t ret_len = strlen(buf);' X" W! C/ p5 {& h8 d
        char *ret_p;
7 B/ P9 B$ y$ d" f) |  b# U* A9 J        if( ret_len>0 ){" z0 o; w% H0 C0 h1 l* j/ t6 {) R; O
                ret_p = (char*)calloc(ret_len+1,sizeof(char)); // 加 1 为了存储 '\0'
# N$ X8 l$ E8 L# B: w3 y* ~                memcpy(ret_p, buf, ret_len);' g$ @# f" n& w6 \3 K
        }else ret_p = NULL;
- u5 X3 M: e4 C0 J8 ~. e. N        return ret_p;
2 c# p- d# Q+ Q, p4 W* O}
$ e0 z& i/ ^. V5 m9 ?% V
& p  V2 L6 d4 l3 V+ N2 Q7 y// md5 加密函数,用的是网上一个实现的比较通用的版本。- j0 f( `8 T, z% f
void md5(const char* src, size_t size, char* digest)& J  s% [5 O) |2 R$ t- S
{
' E' o4 n& u5 j# r! C& c1 j* c        md5_state_t state;
7 e& Z2 z4 Y6 G* u+ b9 x        md5_init(&state);0 c: v  c- i) X$ O( j+ K; V& a
        md5_append(&state,  src, size);
0 p0 h3 X' j  n) w- R  D        md5_finish(&state, digest);
  P$ l4 T) }- |2 g9 {}
; Y) M- x) j7 A% E. W6 h+ P+ m& f
6 l7 N/ [0 K; @( y  ~void p(char*s, int len){8 @1 s1 z1 S, ?; t. O* t- F3 Q9 @( z
        unsigned short i=0;
" Z# i* Y/ J& Q1 }, n# l) T4 U        for(i=0; i<len; i++) printf("%c",s[i]);
6 a3 h, N* z3 t+ u% @. o        printf("%c", '\n');3 G# c" S& F6 J7 S6 p0 n1 p' b" j
}( Z5 J9 w# [5 H) T/ H

( O1 g/ m! b: Y) u3 Fvoid handshake(const char* src, struct handshake* hs){' D+ S2 M) w" t* J. t
        size_t src_len  = strlen(src), i = 0 ;
, v6 b$ S+ P& w, _/ M6 d        hs->resource    = match_string(src, "GET ", 0x20); // 提取空格之前9 ?" e! f' w# K% m
        hs->host                = match_string(src, "Host: ", '\0');3 i! _6 Z$ a5 |. Q& u
        hs->origin              = match_string(src, "Origin: ", '\0');8 ]7 u" ?) \& n- M
        hs->protocol    = match_string(src, "Sec-WebSocket-Protocol: ", '\0');. r( g  T7 D# B- v- w
        hs->key1                = match_string(src, "Sec-WebSocket-Key1: ", '\0');
4 }5 T* F7 M0 m8 c  s- r        hs->key2                = match_string(src, "Sec-WebSocket-Key2: ", '\0'); / I# M; T, y7 o5 `) _
        // 获取 key3,即最后的8位字符
( U5 S6 v8 T6 M, a1 ~        char key3[8]="\0";7 I- i) S! V3 P6 [7 `+ _+ e) b
        for (i = 0; i < 8; i++) key3[i] = src[src_len - 8 + i]; # {$ D* m& \2 S% A7 M2 F
        char digits1[64]="\0", digits2[64]="\0", c='\0';
2 U4 S% A6 P% i: G        size_t spaces1 = 0, spaces2 = 0;. p0 T) a& g+ }
        size_t key1_len = strlen(hs->key1);6 l. m+ L& s! J% {7 l
        size_t key2_len = strlen(hs->key2);
% G! A! I8 x. [8 d& \  E# ~        short d1 = 0, d2 = 0;3 X6 j7 n( K1 A/ A0 c
        unsigned int result1, result2;6 @, y7 v: y8 Z4 N7 M- ]
        for (i = 0; i < key1_len; i++){
. [6 ?& N; ^' D  Z                c = hs->key1[i];
! q$ \" z1 Q% A% a5 K3 T- e                if (c == 0x20) spaces1++;
5 ~8 S( z' n1 _0 `/ [( {) x                else if(c>='0' && c<='9') digits1[d1++]=c;
7 {+ T) D! }! {        }6 X$ y  b* R7 F" E9 G1 b8 Q* I
        for (i = 0; i < key2_len; i++){ 8 f% J6 `: j: B9 J6 K
                c = hs->key2[i];$ v' W# \, i5 Y( S, f5 M! Q
                if (c == 0x20) spaces2++;' U8 V8 p. ?4 ^; }
                else if(c>='0' && c<='9') digits2[d2++]=c;
7 G1 K1 i) {& n5 Z$ O* `- {        }
3 a; K$ ~0 R7 Q3 f, f        result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);
0 T4 U8 Y! d# k8 n$ O        result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);5 S2 l+ Z+ O8 q' I% W5 C. v* [# t
        printf("ch1:%s\nch2:%s\n",digits1, digits2);
; k& v2 r- z) |        printf("sp1:%d\nsp2:%d\n",spaces1, spaces2);
1 J8 N7 }  L: P$ a' v        printf("d1:%d\nd2:%d\n"  ,result1, result2);
% K. N9 |' n$ B        unsigned char chrkey1[4]="\0", chrkey2[4]="\0";
# Z2 @& F) \& O1 K. u, r" v: O6 j        for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);0 Y: |7 A# X0 i+ Q
        for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);
0 A' C7 c5 t0 E9 l        printf("ch-key1:"); p(chrkey1,4);
' e9 r' q1 r" n! T  V        for(i=0; i<4;i++)printf("0x%02x ",chrkey1[i]);1 F' g3 f$ D$ o+ p) U& O
        printf("ch-key2:"); p(chrkey2,4);! @6 ^. Q; |1 W! p/ m! d4 a9 c
        for(i=0; i<4;i++)printf("0x%02x ",chrkey2[i]);
& x7 l+ A8 \4 F2 |2 `6 ]        unsigned char raw[16]="\0", dig[16]="\0";2 B' Q# U) E2 X8 a' D3 s2 R" O2 ?
        memcpy(raw, chrkey1, 4);
9 r6 ?, D" h1 D' q* ]: W        memcpy(&raw[4], chrkey2, 4);
- U# [$ j' G  v/ `% K- n        memcpy(&raw[8], key3, 8);
' r, R2 V* Y4 T8 P* L& }/ P        //计算的md5值# i4 B- c7 f1 c- m% J( I/ d
        printf("\nraw:");
& O! ^4 f" r% [3 B; l8 ?2 b        for(i=0; i<16; i++) printf("0x%02x ",raw[i]);- b: Z7 j; j: \$ y5 q- M- o: x/ ^
        md5(raw, 16, dig);
' ?9 q; ~; e7 ~( \: d        printf("\nmd5:");: N# i8 c) ~8 h: H, a2 }- A  e
        for(i=0; i<16; i++) printf("0x%02x ",dig[i]);
/ k8 T: q0 U. e+ L4 V# f' \  p2 C}) j, M6 G* ?$ c. V, D& i# ]4 ]
# {! S  P1 i4 T* h+ c) D9 C
6 v. W' y7 i) U+ C
int main()
5 \8 A9 g, X' a6 o0 x- _( ~( e{
, o* z& `! K1 [, _$ E- _+ |        unsigned char msg[512] = "GET /pub/chat?q=me HTTP/1.1\r\n\
  I7 B$ w6 [6 n/ e        Upgrade: WebSocket\r\n\4 n1 C* l1 X* X$ Y" F( f0 c
        Connection: Upgrade\r\n\
4 h0 O, G0 L8 [5 q        Host: localhost:4400\r\n\: {! J1 V9 ~# d( n2 H% W
        Origin: null\r\n\# X& w& m5 }7 S1 w. L# G8 C
        Sec-WebSocket-Protocol: my-custom-chat-protocol\r\n\
9 }% `5 w1 d2 C! j; l2 @        Sec-WebSocket-Key1: x EO2 59186 4  28\\dY 0+\r\n\$ T: K- v* H# a
        Sec-WebSocket-Key2: 1  9  3  57695W    0\r\n\r\n";" t% f/ y" ?7 m& y
        size_t len = strlen(msg);
3 h3 I* X. h% {/ `. S* y2 K        msg[len]  =0x1f;: @( s3 y% T# q. e
        msg[len+1]=0xf6;
0 ?; ~! t& V9 y) b& t        msg[len+2]=0xf3;& v) t' i8 o/ t9 ?1 l5 a
        msg[len+3]=0x3f;
  n' X& X. }' }3 T% `: F5 E        msg[len+4]=0xc7;1 o) j; e" s: K3 n& }- J9 ?9 k
        msg[len+5]=0x17;, z( C2 F' Z5 S
        msg[len+6]=0x20;8 q# v5 L2 |* Z% g
        msg[len+7]=0x88;
1 U/ t' z( K1 q# d9 W2 H        struct handshake hs = {NULL, NULL, NULL, NULL, NULL, NULL};. {3 ]/ O; m: [' q: k
        handshake(msg, &hs);
' i6 a0 R: n( d. j& T! ]        free_handshake(&hs);
4 ]4 P0 f" P; g2 `        return 0;
8 Q* b# L0 P1 |6 l8 |}. Z( Y9 G  V# W% E  U) |( E

- h" n) \4 f3 H1 n) i' k' o4 t: S3 E
测试的结果:
, J  o+ Z/ V" z" j3 craw:0x19 0xbf 0x73 0xa4 0x01 0x27 0x5f 0xff 0x1f 0xf6 0xf3 0x3f 0xc7 0x17 0x20 0x88 6 W- M3 ~9 H9 j9 W
md5:0x61 0x30 0x1e 0xe8 0x8a 0x17 0xaf 0x39 0xd6 0xad 0xef 0xb9 0x6f 0x00 0x0f 0x68
& }7 ~" y3 j/ @( B3 b# B对比了nodejs的版本,握手部分生成没有错误。0 c0 U& S- O- s; l  z3 }+ l9 w
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2025-8-8 23:23 , Processed in 0.034643 second(s), 17 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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