本程序是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