为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 }
时间: 2024-11-05 21:44:10

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

SpringBoot入门二十,添加Websocket支持

项目基本配置参考SpringBoot入门一,使用myEclipse新建一个SpringBoot项目,使用myEclipse新建一个SpringBoot项目即可.此示例springboot的版本已经升级到2.2.1.RELEASE,具体步骤如下: 1. pom.xml添加以下配置信息 <!-- 4. 引入websocket支持 --> <dependency> <groupId>org.springframework.boot</groupId> <ar

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

开始 这里我将从最初的开始进行介绍,包括Github上创建项目已经上传项目,到最后的支持Cocoapods. 步骤如下: 代码上传Github 创建podspec文件,并验证是否通过 在Github上创建release版本 注册CocoaPods账号 上传代码到CocoaPods 检查上传是否成功 1 代码上传Github 首先我们打开github.com,然后创建自己的项目工程: 这里注意那个MIT License,在后面添加Cocoapods支持的时候会用到(稍后介绍).然后点击创建即可.

把自己github上的代码添加cocoapods支持

一.前言 这两天被cocoapods折磨的心力憔悴.看cocoapods官网的添加支持,但是介绍的(ying)比(yu)较(tai)简(cha)单,而且有的步骤也没有写上,导致看着官方文档也没有成功,后来查阅了简书.CocoaChina等等,还是已经接近崩溃.没有一个完整的介绍.索性多个文档对比测试,最后终于成功的让自己Github上的库成功支持Cocoapods安装. 二.开始 这里我将从最初的开始进行介绍,包括Github上创建项目已经上传项目,到最后的支持Cocoapods. 步骤如下:

为Phonegap Android平台增加websocket支持,使默认成为socket.io首选通

为Phonegap Android平台增加websocket支持,使默认成为socket.io首选通道选择 广而告之 使用socket.io作为跨浏览器平台的实时推送首选,经测试在各个主流浏览器上测试都确实具有良好的下实时表现.这里为推广socketio-netty服务器端实现哈,做次广告,同时预热一下: socketio-netty : 又一款socket.io服务器端实现,兼容0.9-1.0版本~ 示范目的 我们要构建一个在市面上常见浏览器上都可以正常运行的集体聊天应用,保证在IE6+,Fi

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

本文介绍 ASP.NET Core 中 WebSocket 的入门方法. WebSocket (RFC 6455) 是一个协议,支持通过 TCP 连接建立持久的双向信道. 它用于从快速实时通信中获益的应用,如聊天.仪表板和游戏应用. 如果不明白什么是WebSocket可以参考这篇文章 系统必备 ASP.NET Core 1.1 或更高版本 支持 ASP.NET Core 的任何操作系统: Windows 7/Windows Server 2008 或更高版本 Linux macOS 如果应用在安

为UiAutomatorViewer添加xpath支持

UiAutomatorViewer是Android SDK自带的测试工具,用来查看手机或模拟器上的界面元素,小巧,简单,开箱即用,十分方便.美中不足之处在于,它不能获取界面元素的xpath. 写自动化测试脚本时,xpath是一种非常方便的定位方式.Appium等一些成熟的工具框架可以获取到界面元素xpath,但使用起来稍有点重量级.那么是否也可以给UiAutomatorViewer添加xpath支持呢? 答案是肯定的. 首先下载UiAutomatorView源代码,我用的地址是https://a

Qt国际化(Q_DECLARE_TR_FUNCTIONS() 宏给非Qt类添加翻译支持,以前没见过QTextEncoder和QTextDecoder和QLibraryInfo::location()和QEvent::LanguageChange)

Internationalization with Qt 应用程序的国际化就是使得程序能在国际间可用而不仅仅是在本国可用的过程. Relevant Qt Classes andAPIs 以下的类支持Qt的国际化. QTextCodec QTextDecoder QTextEncoder QTranslator QLocale Languages and WritingSystems 有时,国际化是比较简单的,例如,把美国的应用程序让澳大利亚或英国的用户可访问,只需要简单的改变拼写.但是,把美国的

Mybatis添加Ehcache支持

1.Mybatis默认的缓存配置 MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制. Mybatis缓存包含全局的缓存和局部的缓存,全局的缓存可以讲主配置文件的setting属性的参数cacheEnabled设置为true(好吧,默认为true), 局部的二级缓存默认情况下是没有开启的,要开启二级缓存,你需要在你的 SQL 映射文件中添加一行: <cache/> eviction(回收策略) 默认的是 LRU.可选择项有FIFO,SOFT,WEAK flushInte

java-cef系列视频第三集:添加flash支持

上一集我们介绍了如何搭建java-cef调试环境. 本视频介绍如何给java-cef客户端添加flashplayer支持 本作品采用知识共享署名-非商业性使用-禁止演绎 3.0 中国大陆许可协议进行许可.