zoukankan      html  css  js  c++  java
  • 为libevent添加websocket支持(上)

    在跨平台网络基础库中,libevent与asio近年来使用比较广泛。asio对boost的依赖太大,个人认为发展前途堪忧,尤其asio对http没有很好的支持也是缺点之一。

    libevent对http有天生支持,含有服务与客户两个部分,是做web服务的好特性。

    libevent随对http支持很优秀,但并不支持html5标准的websocket,这有些与时代脱轨。如果你熟悉websocket协议,像自己扩展libevent,很遗憾,libevent的http部分并不支持逻辑层扩展。所以我想,还是通过源码级扩展比较好。注:git上有代码级扩展,但不是在http功能上的扩展。

    正文:

    libevent的http支持核心代码都在http.c中,包含了几个相关头文件,包括http.h、http-internal.h、http_struct.h、http_compat.h。

    libevent的主要容器是列表,由一系列宏进行操作。包括http request,callback函数,http connection,输入头信息,输出头信息均被列表容器管理。回调函数的搜索匹配,evkeyxxx相关的头信息搜索,均需要在列表中遍历,会有些许性能损耗。

    libevent有两个方法设置http事件回调函数:evhttp_set_cb,evhttp_set_gencb.我本计划用开关的方式来决定是否开启websocket的连接升级(Connection: Upgrade)功能,后来觉得与libevent的原始架构有些不一致,最终决定用类似设置回调函数的方法设置哪些路径接收WebSocket升级:evhttp_set_ws,evhttp_del_ws。

    处理头信息:

    evhttp_read_header是接管websocket升级的好地方,我在EVHTTP_REQUEST case的地方添加处理代码,根据WebSocket标准文档,先在header中寻找升级Key(Sec-WebSocket-Key),进行Hash(SHA1->BASE64)返回给客户端即可完成升级。至于hash代码,在windows下可方便的用加解密相关函数(Crypt开头)解决,在Linux就要用openssl了。

    libevent默认在读完header后会关闭bufferevent的读取事件,这会影响之后我们websocket的通讯,为此我写了一个新的写缓冲函数,不停止读取事件:evhttp_write_buffer_nostop_read。只需要复制evhttp_write_buffer函数,删除设置缓冲cb的代码即可。

    libevent http connection有个state枚举,用来只是当前读取状态,我为此枚举添加了一个状态:EVCON_READING_WSDATA,在提升Websocket完成后,将state设置为EVCON_READING_WSDATA,并且为evhttp_read_cb添加一个对应case,处理websocket的数据。

    代码

    注:代码按照libevent源码风格进行编写,除了大括号后置,基本就是本人的风格了。

    先做到协议提升与协议解析,下次再讨论发送的问题以及数据类型的问题。

    websocket客户端key处理代码:

    Linux需要这些头文件<openssl/sha.h>,<openssl/bio.h>,<openssl/evp.h>,<string.h>,<openssl/buffer.h>,前提你应该有openssl的devel版被安装。

    Linux总归会麻烦一些,忍咯!Windows需要引用crypt32.lib,Linu需要引用libcrypto.lib(gcc:lcrypto)。

    Windows已测试,Linux只进行了片段代码测试。

     1 const char*
     2 ws_hash(const char* client_key) {
     3     static char result[50];
     4     if (strlen(client_key) > 37)
     5         return NULL;
     6 
     7     const char* uuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
     8     char src[100];
     9     strcpy(src, client_key);
    10     strcat(src, uuid);
    11     size_t src_len = strlen(src);
    12 
    13 #ifdef _WIN64 or _WIN32
    14     HCRYPTPROV hCryptProv;
    15     if (CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, 0)){
    16         HCRYPTHASH hHash;
    17         if (CryptCreateHash(hCryptProv, CALG_SHA1, 0, 0, &hHash)){
    18             if (CryptHashData(hHash, (BYTE*)src, src_len, 0)){
    19                 BYTE hash_result[50];
    20                 DWORD out_len = sizeof(hash_result);
    21                 if (CryptGetHashParam(hHash, HP_HASHVAL, hash_result, &out_len, 0)){
    22                     DWORD crypt_out_len = 100;
    23                     CryptBinaryToStringA(hash_result, out_len, CRYPT_STRING_BASE64, result, &crypt_out_len);
    24                     result[crypt_out_len - 2] = 0;
    25                     return result;
    26                 }
    27             }
    28         }
    29     }
    30 #else
    31     unsigned char* value = SHA1((unsigned char*)src, src_len, out);
    32     BIO *bm = NULL, *bio = NULL;
    33     bio = BIO_new(BIO_f_base64());
    34     if (bio) {
    35         bm = BIO_new(BIO_s_mem());
    36         if (bm) {
    37             BIO_push(bio, bm);
    38             BIO_write(bio, value, strlen((const char*)value));
    39             BIO_flush(bio);
    40             BUF_MEM *buf;
    41             BIO_get_mem_ptr(bio, &buf);
    42             strcpy(result, buf->data);
    43             BIO_free_all(bio);
    44         }
    45     }
    46 #endif
    47     return NULL;
    48 }

    websocket提升代码:

    主要处理头信息并写回客户端与状态。

     1     case EVHTTP_REQUEST: {
     2         /* handle the websocket upgrade key */
     3         const char* seckey = evhttp_find_header(req->input_headers, "Sec-WebSocket-Key");
     4         if (seckey) {
     5             struct evhttp_wsup* wsup;
     6             char* translated;
     7             /* Test for different URLs */
     8             const char* path = evhttp_uri_get_path(req->uri_elems);
     9             size_t offset = strlen(path);
    10             if ((translated = mm_malloc(offset + 1)) == NULL)
    11                 return;
    12             evhttp_decode_uri_internal(path, offset, translated,
    13                 0 /* decode_plus */);
    14             TAILQ_FOREACH(wsup, &evcon->http_server->websocket_upgrades, next) {
    15                 if (!stricmp(wsup->what, translated)) {
    16                     evhttp_add_header(req->output_headers, "Connection", "Upgrade");
    17                     evhttp_add_header(req->output_headers, "Upgrade", "WebSocket");
    18                     evhttp_add_header(req->output_headers, "Sec-WebSocket-Accept", ws_hash(seckey));
    19                     req->websocket = 1;
    20                     evhttp_response_code(req, 101, "SwitchProtocol");
    21                     evhttp_make_header(req->evcon, req);
    22                     evhttp_write_buffer_nostop_read(req->evcon, NULL, NULL);
    23                     evcon->state = EVCON_READING_WSDATA;
    24                     bufferevent_enable(evcon->bufev, EV_READ);
    25                     break;
    26                 }
    27             }
    28             mm_free(translated);
    29             return;
    30         }

    处理websocket协议的代码:

     1 int
     2 process_buffer(unsigned char* buff, size_t data_len)
     3 {
     4     if (data_len < 2)
     5         return 0;
     6     switch (buff[0] & 0xF){
     7     case 8:
     8         return 1;
     9     case 9:
    10     case 10:{
    11         auto len = buff[1] & 0x7F;
    12         auto mask = (buff[1] & 0x80) > 0;
    13         if (len > 0)
    14             return -1;
    15 
    16         if (mask)
    17             return 6;
    18         else
    19             return 2;
    20         break;
    21     }
    22     case 1:{
    23         auto len = buff[1] & 0x7F;
    24         auto mask = (buff[1] & 0x80) > 0;
    25         int head_len = 0;
    26         if (len == 127)
    27             head_len = mask ? 14 : 10;
    28         else if (len == 126)
    29             head_len = mask ? 8 : 4;
    30         else
    31             head_len = mask ? 6 : 2;
    32         if (data_len < head_len)
    33             return 0;
    34 
    35         int tail_len = 0;
    36         if (len == 127)
    37             tail_len = (int)ntohll((unsigned long long)(buff + 2));
    38         else if (len == 126)
    39             tail_len = ntohs((u_short)(buff + 2));
    40         else
    41             tail_len = len;
    42 
    43         if (data_len < head_len + tail_len)
    44             return 0;
    45 
    46         if (mask)
    47             for (int i = head_len, j = 0; j < tail_len; i++, j++)
    48                 buff[i] = buff[i] ^ buff[head_len - 4 + j % 4];
    49 
    50         char* utf8_text = buff + head_len;
    51 
    52         return head_len + tail_len;
    53     }
    54     }
    55     return 0;
    56 }
    57 
    58 static void
    59 evhttp_read_wsdata(struct evhttp_connection *evcon, struct evhttp_request *req)
    60 {
    61     struct evbuffer *buf = bufferevent_get_input(evcon->bufev);
    62 
    63     size_t buflen = evbuffer_get_length(buf);
    64     if (buflen == 0)
    65         return;
    66 
    67     size_t drain_len = 0;
    68     unsigned char* data = evbuffer_pullup(buf, buflen);
    69     while (buflen>0) {
    70         int result = process_buffer(data, buflen);
    71         if (result < 0) {
    72             evhttp_connection_free(evcon);
    73             return;
    74         }
    75         else if (result > 0) {
    76             if (result > buflen) {
    77                 evhttp_connection_free(evcon);
    78                 return;
    79             }
    80             drain_len += result;
    81             data += result;
    82             buflen -= result;
    83         }
    84         else
    85             break;
    86     }
    87     if(drain_len>0)evbuffer_drain(buf, drain_len);
    88 
    89     /* Read more! */
    90     bufferevent_enable(evcon->bufev, EV_READ);
    91 }
  • 相关阅读:
    Asp.net Mvc 中的模型绑定
    ActionResult 之HttpGet HttpPost
    Asp.net MVC 之 ActionResult
    Amazon MWS 上传数据 (三) 提交请求
    Amazon MWS 上传数据 (二) 构造请求
    Amazon MWS 上传数据 (一) 设置服务
    linq常用
    Nacos入门
    Post 方法参数写在body中和写在url中有什么区别
    强密码(必须包含字母、数字、特殊字符,长度8到16位)
  • 原文地址:https://www.cnblogs.com/fyter/p/websocket_for_libevent.html
Copyright © 2011-2022 走看看