VC++实现的WebSocket服务器端握手协议

本程序是VC++实现windows上程序内嵌WebSocket的部分代码,因为想让浏览器和本地程序直接交互,最好的办法就是websocket,windows的exe程序内嵌一个websocket服务器端程序,浏览器访问localhost,建立交互,这种办法比做成插件更好,所以我采用这种办法来联通桌面程序和浏览器。VC++实现WebSocket的服务器代码,网上还是有示例的,不过基本上不能用,我找到的两个,一个是基于MinGW编译,还有一个是基于VC++2010编译的,还有一个libwebsocket,C语言实现的库,实在是大的惊人,最后,我决定自己实现,实现WebSocket其实不复杂,在普通的socket的服务器上添加一个握手协议,这个握手协议如果用脚本语言实现,非常简单,但用户C++实现就不容易了,我这里实现的基本上是C语言的版本,因为不想使用C++庞大的类库和模板。
完整的windows版本socket握手实现:

bool WebSocket::handshake(const char* src, struct handshake* hs){
    size_t src_len = strlen(src), i = 0 ;
    hs->resource  = match_string(src, "GET ", 0x20); // 提取空格之前
    hs->host        = match_string(src, "Host: ", '\0');
    hs->origin       = match_string(src, "Origin: ", '\0');
    hs->protocol  = match_string(src, "Sec-WebSocket-Protocol: ", '\0');
    hs->key1        = match_string(src, "Sec-WebSocket-Key1: ", '\0');
    hs->key2        = match_string(src, "Sec-WebSocket-Key2: ", '\0'); 
    char key3[8]="\0"; // 获取 key3,即最后的8位字符
    for (i = 0; i < 8; i++) key3[i] = src[src_len-8+i]; 
    char digits1[64]="\0", digits2[64]="\0", c='\0';
    size_t spaces1 = 0, spaces2 = 0;
    size_t key1_len = strlen(hs->key1);
    size_t key2_len = strlen(hs->key2);
    short d1 = 0, d2 = 0;
    unsigned int result1, result2;
    for (i = 0; i < key1_len; i++){ 
        c = hs->key1[i];
        if (c == 0x20) spaces1++;
        else if(c>='0' && c<='9') digits1[d1++]=c; 
    }
    for (i = 0; i < key2_len; i++){ 
        c = hs->key2[i];
        if (c == 0x20) spaces2++;
        else if(c>='0' && c<='9') digits2[d2++]=c; 
    }
    result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);
    result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);
    char chrkey1[4]="\0", chrkey2[4]="\0";
    for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);
    for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);
    unsigned char raw[16]="\0", dig[16]="\0";
    // raw 表示未md5之前的字符串,规则就是前4位key1中的数字/空格数的整数值,
    // 连接上key2的最后连接上头信息中的最后8位字符。
    memcpy(raw, chrkey1, 4);
    memcpy(&raw[4], chrkey2, 4);
    memcpy(&raw[8], key3, 8);
    //计算的md5值
    md5_state_t state;
    md5_init(&state);
    md5_append(&state, raw, 16);
    md5_finish(&state, dig);
    
    char handshake_str[BUFSIZ];
    memset(handshake_str, 0x00, BUFSIZ);
    char* handshakeFormat = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
        "Upgrade: WebSocket\r\n"
        "Connection: Upgrade\r\n"
        "Sec-WebSocket-Origin: %s\r\n" 
        "Sec-WebSocket-Location: ws://%s%s\r\n"
        "Sec-WebSocket-Protocol: %s\r\n\r\n";
    sprintf(handshake_str, handshakeFormat, hs->origin, hs->host, hs->resource, hs->protocol);
    free_handshake(hs); // 释放handshake指针,已经不用了!
    char response[BUFSIZ];
    memset(response,0,BUFSIZ);
    size_t j=0, handshake_len=strlen(handshake_str);
    for (i = 0; i < handshake_len; i++) response[i] = handshake_str[i];
    for (j = 0; j < 16; i++, j++) response[i] = dig[j];
    // 这里的clientSocket就是连接好的socket对象了。
    int sent = send(clientSocket, response, strlen(response), 0);
    return sent>0;
}

目录结构:

socket/
   socket.c
   md5/
    md5.h
    md5.c

库文件下载位置: http://sourceforge.net/projects/libmd5-rfc/files/ 总共3个文件,

编译命令: gcc socket.c md5/md5.c -o socket.o

GCC 4.3编译通过,执行 ./socket.o 可以看到打出的raw和md5后的字符串信息。 这里使用了一个网友用nodejs写的websocket实现版本,打出详细的头信息,然后复制到程序里,以测试生成的握手字符串是否一样。

// socket.c 代码。

// web-socket example 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "md5/md5.h"

// #define BUFSIZ 512

//定义handshake结构体变量
struct handshake {
    char *resource;
    char *host;
    char *origin;
    char *protocol;
    char *key1;
    char *key2;
};


//释放握手后不再使用的部分变量
void free_handshake(struct handshake* hs){
    if( hs->resource != NULL )   free(hs->resource);
    if( hs->host != NULL )     free(hs->host);
    if( hs->origin != NULL )    free(hs->origin);
    if( hs->protocol != NULL )   free(hs->protocol);
    if( hs->key1 != NULL )     free(hs->key1);
    if( hs->key2 != NULL )     free(hs->key2);
}

// 这里对上一篇的match_string做了点修改,
// 增加了一个end参数,这样不必提取出字符串后,再做剔除处理
char* match_string(const char* src, const char* pattern, char end){
    char buf[BUFSIZ];
    memset(buf, 0, BUFSIZ);
    size_t src_len = strlen(src); 
    size_t ptn_len = strlen(pattern);
    unsigned short b=0, p=0, i=0; 
    char c='\0';
    for(i=0; i<src_len; i++){
        c = src[i];
        if(p==ptn_len){ // p==ptn_len 表示正在匹配中
            if(c=='\r' || c=='\n' || (end !='\0' && c==end) ) p++; // 匹配结束
            else buf[b++]=c; // 匹配到的字符 
        }else if(p<ptn_len){ // 为达到匹配要求
            if(c==pattern[p]) p++;
            else p=0;
        }
    }
    size_t ret_len = strlen(buf);
    char *ret_p; 
    if( ret_len>0 ){
        ret_p = (char*)calloc(ret_len+1,sizeof(char)); // 加 1 为了存储 '\0'
        memcpy(ret_p, buf, ret_len);
    }else ret_p = NULL;
    return ret_p; 
}

// md5 加密函数,用的是网上一个实现的比较通用的版本。
void md5(const char* src, size_t size, char* digest)
{
    md5_state_t state;
    md5_init(&state);
    md5_append(&state, src, size);
    md5_finish(&state, digest);
}

void p(char*s, int len){
    unsigned short i=0;
    for(i=0; i<len; i++) printf("%c",s[i]);
    printf("%c", '\n');
}

void handshake(const char* src, struct handshake* hs){
    size_t src_len = strlen(src), i = 0 ;
    hs->resource  = match_string(src, "GET ", 0x20); // 提取空格之前
    hs->host        = match_string(src, "Host: ", '\0');
    hs->origin       = match_string(src, "Origin: ", '\0');
    hs->protocol  = match_string(src, "Sec-WebSocket-Protocol: ", '\0');
    hs->key1        = match_string(src, "Sec-WebSocket-Key1: ", '\0');
    hs->key2        = match_string(src, "Sec-WebSocket-Key2: ", '\0'); 
    // 获取 key3,即最后的8位字符
    char key3[8]="\0";
    for (i = 0; i < 8; i++) key3[i] = src[src_len - 8 + i]; 
    char digits1[64]="\0", digits2[64]="\0", c='\0';
    size_t spaces1 = 0, spaces2 = 0;
    size_t key1_len = strlen(hs->key1);
    size_t key2_len = strlen(hs->key2);
    short d1 = 0, d2 = 0;
    unsigned int result1, result2;
    for (i = 0; i < key1_len; i++){ 
        c = hs->key1[i];
        if (c == 0x20) spaces1++;
        else if(c>='0' && c<='9') digits1[d1++]=c; 
    }
    for (i = 0; i < key2_len; i++){ 
        c = hs->key2[i];
        if (c == 0x20) spaces2++;
        else if(c>='0' && c<='9') digits2[d2++]=c; 
    }
    result1 = (unsigned int) (strtoul(digits1, NULL, 10) / spaces1);
    result2 = (unsigned int) (strtoul(digits2, NULL, 10) / spaces2);
    printf("ch1:%s\nch2:%s\n",digits1, digits2);
    printf("sp1:%d\nsp2:%d\n",spaces1, spaces2);
    printf("d1:%d\nd2:%d\n" ,result1, result2); 
    unsigned char chrkey1[4]="\0", chrkey2[4]="\0";
    for (i = 0; i < 4; i++) chrkey1[i] = result1 << (8 * i) >> (8 * 3);
    for (i = 0; i < 4; i++) chrkey2[i] = result2 << (8 * i) >> (8 * 3);
    printf("ch-key1:"); p(chrkey1,4);
    for(i=0; i<4;i++)printf("0x%02x ",chrkey1[i]);
    printf("ch-key2:"); p(chrkey2,4);
    for(i=0; i<4;i++)printf("0x%02x ",chrkey2[i]);
    unsigned char raw[16]="\0", dig[16]="\0";
    memcpy(raw, chrkey1, 4);
    memcpy(&raw[4], chrkey2, 4);
    memcpy(&raw[8], key3, 8);
    //计算的md5值
    printf("\nraw:");
    for(i=0; i<16; i++) printf("0x%02x ",raw[i]);
    md5(raw, 16, dig);
    printf("\nmd5:");
    for(i=0; i<16; i++) printf("0x%02x ",dig[i]);
}


int main()
{
    unsigned char msg[512] = "GET /pub/chat?q=me HTTP/1.1\r\n\
    Upgrade: WebSocket\r\n\
    Connection: Upgrade\r\n\
    Host: localhost:4400\r\n\
    Origin: null\r\n\
    Sec-WebSocket-Protocol: my-custom-chat-protocol\r\n\
    Sec-WebSocket-Key1: x EO2 59186 4 28\\dY 0+\r\n\
    Sec-WebSocket-Key2: 1 9 3 57695W  0\r\n\r\n";
    size_t len = strlen(msg);
    msg[len] =0x1f;
    msg[len+1]=0xf6;
    msg[len+2]=0xf3;
    msg[len+3]=0x3f;
    msg[len+4]=0xc7;
    msg[len+5]=0x17;
    msg[len+6]=0x20;
    msg[len+7]=0x88;
    struct handshake hs = {NULL, NULL, NULL, NULL, NULL, NULL};
    handshake(msg, &hs);
    free_handshake(&hs);
    return 0; 
}

测试的结果:

raw:0x19 0xbf 0x73 0xa4 0x01 0x27 0x5f 0xff 0x1f 0xf6 0xf3 0x3f 0xc7 0x17 0x20 0x88

md5:0x61 0x30 0x1e 0xe8 0x8a 0x17 0xaf 0x39 0xd6 0xad 0xef 0xb9 0x6f 0x00 0x0f 0x68

对比了nodejs的版本,握手部分生成没有错误。

转自:http://www.kindcent.com/blog/view/vc-plus-plus-websocket-handshake


;