![5e3ec96c6509ca69ba535d178e17f812.png](https://img-blog.csdnimg.cn/img_convert/5e3ec96c6509ca69ba535d178e17f812.png)
参考等级:★★★⛤⛤
概述
libuv是一个纯异步IO事件库,并不像libevent那样带有http功能。需要http,自己加。需要 websocket自己加。需要pop、smtp,自己加。
http_parser 是一个http头解释器,与libuv一样,都是nodejs才用的库,可以用来从tcp协议数据中分析出http请求信息,是个http高性能实现,结构占用32字节内存。
如果你用http_parser在以编码的方式只取感兴趣的头信息,其他信息略过,性能将是几乎完美的,没有过多的性能冗余。
如果你用http_parser先把所有头信息都存起来,像http服务器那样,到逻辑处理时再根据需求取用,那么性能必然或多或少受到影响。
ws_parser和websocket_parser是两个websocket头解析器,(并不负责http头,也不处理握手,仅处理websocket的帧数据),解析期间前者占用16字节,后者占用48个字节。虽然都叫parser,但前者没有发送函数,所以是个不完整的库。
上面所有库都没有处理websocket的握手、升级。我会在后面给出握手代码。
http_parser的集成
直接把http_parser.c、http_parser.h两个文件复制到libuv项目中。
uv_read_start(..., after_read);注册事件后,after_read中调用http_parser_execute,依次将初始化好的http_parser实例指针传入,http_parser_settings实例指针传入,欲解析的http请求字符串指针传入,及其长度传入。
http_parser_settings中保存了消息开始事件回调,url解析完成回调,请求头字段回调,请求头值回调等,可以通过这些回调处理数据,也可以根据返回值指示http_parser要不要继续解析。
int header_field_cb(http_parser* p, const char* buf, size_t len)
int header_value_cb(http_parser* p, const char* buf, size_t len)
int request_url_cb(http_parser* p, const char* buf, size_t len)
{
……
return HPE_OK;
}
static http_parser_settings settings =
{ message_begin_cb, request_url_cb, response_status_cb, header_field_cb, header_value_cb
, headers_complete_cb, body_cb, message_complete_cb, chunk_header_cb, chunk_complete_cb};
static void after_read(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf)
{
……
http_parser_execute((http_parser*)handle->data, &settings, buf->base, nread);
……
}
websocket集成
选择websocket_parser
websocket握手
客户端会在HTTP头中带升级信息,Connection: Upgrade,Upgrade: websocket,以及Sec-WebSocket-Key后面跟一个随机的base64编码字符串。
服务器需要对这个字符串拼接一个固定GUID(258EAFA5-E914-47DA-95CA-C5AB0DC85B11)进行SHA1哈希再得到base64字符串,设置到HTTP响应头的Sec-WebSocket-Accept字段中。
HTTP的响应代码是101 Switching Protocols。Upgrade: websocket也需要返回给客户端。
将客户端base64编码转为响应字符串的跨平台函数我写好了,请自行优化(Win做了错误判断,Linux没有做。也没有做缓存等),Windows链接Crypt32.lib,Linux链接crypto(你需要libopenssl-dev):
bool ws_hash(const char* client_key, size_t key_len, char* output, size_t& output_len)
{
const char* uuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
char src[100] = { 0 };
memcpy(src, client_key, key_len);
memcpy(src+key_len, uuid, 36);
size_t src_len = key_len + 36;
#ifdef _WIN32
HCRYPTPROV hCryptProv;
if (!CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, NULL))
if (!CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET))
return false;
HCRYPTHASH hHash;
if (!CryptCreateHash(hCryptProv, CALG_SHA1, 0, 0, &hHash)) {
CryptReleaseContext(hCryptProv, 0);
return false;
}
if (!CryptHashData(hHash, (BYTE*)src, src_len, 0)) {
CryptDestroyHash(hHash);
CryptReleaseContext(hCryptProv, 0);
return false;
}
BYTE hash_result[50];
DWORD out_len = sizeof(hash_result);
if (!CryptGetHashParam(hHash, HP_HASHVAL, hash_result, &out_len, 0)) {
CryptDestroyHash(hHash);
CryptReleaseContext(hCryptProv, 0);
return false;
}
DWORD crypt_out_len = 100;
CryptBinaryToStringA(hash_result, out_len, CRYPT_STRING_BASE64, output, &crypt_out_len);
output[crypt_out_len - 2] = 0;
CryptDestroyHash(hHash);
CryptReleaseContext(hCryptProv, 0);
output_len = crypt_out_len-2;
return true;
#else
unsigned char md[SHA_DIGEST_LENGTH];
unsigned char* value = SHA1((const unsigned char*)src, src_len, md);
//base64_encode((const char*)md, strlen(md), output, 50);
BIO *bm = NULL, *bio = NULL;
bio = BIO_new(BIO_f_base64());
if (bio) {
bm = BIO_new(BIO_s_mem());
if (bm) {
BIO_push(bio, bm);
BIO_write(bio, value, SHA_DIGEST_LENGTH);
BIO_flush(bio);
BUF_MEM *buf;
BIO_get_mem_ptr(bio, &buf);
strcpy(output, buf->data);
output_len = buf->length-1;
BIO_free_all(bio);
return true;
}
}
#endif
return false;
}
握手之后就可以传输数据了
websocket_parser与http_parser类似,针对每个http请求,先初始化(websocket_parser_init),再执行(websocket_parser_execute)。初始化需要把回调函数设置一下。
websocket_parser默认没有在出发事件前给你把遮蔽码去掉,还要调用websocket_parser_has_mask看下是否有遮蔽码,如果有还要调用websocket_parser_decode进行解码,这真的是脱裤子放屁。在我的代码中,我把这两个移入了websocket_parser.cpp内部。
需要注意:ws数据的回调函数中的文本都是原始数据,需要解一下utf8编码。
友情提醒:std::codecvt在c++11出现,在c++17作废,所以win下还是用MultiByteToWideChar函数吧,linux用iconv。
下一篇我将讨论一下如何自己写http服务器,性能超过nginx。