websocket binary 数据解析_如何高效的在libuv中处理http及websocket

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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值