在跨平台网络基础库中,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[];
  4. if (strlen(client_key) > )
  5. return NULL;
  6.  
  7. const char* uuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
  8. char src[];
  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, )){
  16. HCRYPTHASH hHash;
  17. if (CryptCreateHash(hCryptProv, CALG_SHA1, , , &hHash)){
  18. if (CryptHashData(hHash, (BYTE*)src, src_len, )){
  19. BYTE hash_result[];
  20. DWORD out_len = sizeof(hash_result);
  21. if (CryptGetHashParam(hHash, HP_HASHVAL, hash_result, &out_len, )){
  22. DWORD crypt_out_len = ;
  23. CryptBinaryToStringA(hash_result, out_len, CRYPT_STRING_BASE64, result, &crypt_out_len);
  24. result[crypt_out_len - ] = ;
  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 + )) == NULL)
  11. return;
  12. evhttp_decode_uri_internal(path, offset, translated,
  13. /* 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 = ;
  20. evhttp_response_code(req, , "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 < )
  5. return ;
  6. switch (buff[] & 0xF){
  7. case :
  8. return ;
  9. case :
  10. case :{
  11. auto len = buff[] & 0x7F;
  12. auto mask = (buff[] & 0x80) > ;
  13. if (len > )
  14. return -;
  15.  
  16. if (mask)
  17. return ;
  18. else
  19. return ;
  20. break;
  21. }
  22. case :{
  23. auto len = buff[] & 0x7F;
  24. auto mask = (buff[] & 0x80) > ;
  25. int head_len = ;
  26. if (len == )
  27. head_len = mask ? : ;
  28. else if (len == )
  29. head_len = mask ? : ;
  30. else
  31. head_len = mask ? : ;
  32. if (data_len < head_len)
  33. return ;
  34.  
  35. int tail_len = ;
  36. if (len == )
  37. tail_len = (int)ntohll((unsigned long long)(buff + ));
  38. else if (len == )
  39. tail_len = ntohs((u_short)(buff + ));
  40. else
  41. tail_len = len;
  42.  
  43. if (data_len < head_len + tail_len)
  44. return ;
  45.  
  46. if (mask)
  47. for (int i = head_len, j = ; j < tail_len; i++, j++)
  48. buff[i] = buff[i] ^ buff[head_len - + j % ];
  49.  
  50. char* utf8_text = buff + head_len;
  51.  
  52. return head_len + tail_len;
  53. }
  54. }
  55. return ;
  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 == )
  65. return;
  66.  
  67. size_t drain_len = ;
  68. unsigned char* data = evbuffer_pullup(buf, buflen);
  69. while (buflen>) {
  70. int result = process_buffer(data, buflen);
  71. if (result < ) {
  72. evhttp_connection_free(evcon);
  73. return;
  74. }
  75. else if (result > ) {
  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>)evbuffer_drain(buf, drain_len);
  88.  
  89. /* Read more! */
  90. bufferevent_enable(evcon->bufev, EV_READ);
  91. }

为libevent添加websocket支持(上)的更多相关文章

  1. Netty 框架学习 —— 添加 WebSocket 支持

    WebSocket 简介 WebSocket 协议是完全重新设计的协议,旨在为 Web 上的双向数据传输问题提供一个切实可行的解决方案,使得客户端和服务器之间可以在任意时刻传输消息 Netty 对于 ...

  2. 在.net core web网站中添加webSocket支持

    注意:前置条件,操作系统 windows 8 以上,IIS Express 8.0 以上. 第1步:在Startup.cs文件的头部添加如下引用: using System.Net.WebSocket ...

  3. 把自己Github上的代码添加Cocoapods支持

    转载请注明原链接:http://www.cnblogs.com/zhanggui/p/6003481.html 一.前言 这两天被cocoapods折磨的心力憔悴.看cocoapods官网的添加支持, ...

  4. 把上传Github的代码添加Cocoapods支持

    开始 这里我将从最初的开始进行介绍,包括Github上创建项目已经上传项目,到最后的支持Cocoapods. 步骤如下: 代码上传Github 创建podspec文件,并验证是否通过 在Github上 ...

  5. Spring 4 官方文档学习(十四)WebSocket支持

    个人提示:如果需要用到页面推送,高频且要低延迟,WebSocket无疑是最佳选择.否则还是轮询和long polling吧. 做了一个小demo放在码云上,有兴趣的可以看一下,简单易懂:websock ...

  6. ASP.NET Core 中的 WebSocket 支持(转自MSDN)

    本文介绍 ASP.NET Core 中 WebSocket 的入门方法. WebSocket (RFC 6455) 是一个协议,支持通过 TCP 连接建立持久的双向信道. 它用于从快速实时通信中获益的 ...

  7. Spring Boot 添加Shiro支持

    前言: Shiro是一个权限.会话管理的开源Java安全框架:Spring Boot集成Shiro后可以方便的使用Session: 工程概述: (工程结构图) 一.建立Spring Boot工程 参照 ...

  8. 将自己库添加Cocoapods支持

    给库添加Cocoapods支持, 使这个工具使用起来更加方便, 更好的使用Cocoapods, 助力iOS程序开发, 下面进入正题, 想要实现这个过程, 绝对不虚此读. 首先写好一个要添加Cocoap ...

  9. 为python-sproto添加map支持

    上个月太忙了,做完这个修改还没写博客,现在补一下.. 之前使用protobuf做协议打包的时候,经常会有个痛点,没法用具体数据的值作为key来索引数据.比如现在客户端上传了造兵协议,协议大概长这样: ...

随机推荐

  1. 200 行代码使用 C# 实现区块链

    文章原文来自:Code your own blockchain in less than 200 lines of Go!,原始文章是通过 Go 语言来实现自己的区块链的,这里我们参照该文章来使用 C ...

  2. ionic3 实现扫码功能

    ionic3 通过插件phonegap-plugin-barcodescanner,调用机器硬件摄像头实现扫码功能. 首先当然先了解下 phonegap-plugin-barcodescanner,这 ...

  3. Ansible系列之roles使用说明

    roles(角色)介绍 ansible自1.2版本开始引入的新特性,用于层次性,结构化地组织playbook.roles能够根据层次型结构自动装载变量文件.tasks以及handlers等.要使用ro ...

  4. 搭建 MobileNet-SSD 开发环境并使用 VOC 数据集训练 TensorFlow 模型

    原文地址:搭建 MobileNet-SSD 开发环境并使用 VOC 数据集训练 TensorFlow 模型 0x00 环境 OS: Ubuntu 1810 x64 Anaconda: 4.6.12 P ...

  5. leetcode — permutations-ii

    import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Source : https://o ...

  6. TCP/IP原理浅析

    TCP/IP概述 TCP/IP起源于1969年美国国防部(DOD:The United States Department Of Defense)高级研究项目管理局(APRA:AdvancedRese ...

  7. python的Web框架,Django自定义过滤器及标签

    代码布局 有的时候框架给的过滤器不够用,需要自定义一些过滤器,所以就需要我们自己来定义一些过滤器等 自定义代码放置的路径 某个app特用(独有)的 - app 目录下的 templatetags文件夹 ...

  8. Python和Java编程题(六)

    1.题目:猴子吃桃问题:猴子第一天摘下若干个桃子,当即吃了一半,还不瘾,又多吃了一个 第二天早上又将剩下的桃子吃掉一半,又多吃了一个.以后每天早上都吃了前一天剩下的一半零一个.到第10天早上想再吃时, ...

  9. centos7安装遇到的坑

    1.安装中遇到what is the location of the gcc program on your machine 直接输入 no.意思就是跳过gcc的安装了.但是系统虽然安装了vmware ...

  10. MySQL中间件之ProxySQL(5):线程、线程池、连接池

    返回ProxySQL系列文章:http://www.cnblogs.com/f-ck-need-u/p/7586194.html 1.ProxySQL的线程 ProxySQL由多个模块组成,是一个多线 ...