在跨平台网络基础库中,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只进行了片段代码测试。

 const char*
ws_hash(const char* client_key) {
static char result[];
if (strlen(client_key) > )
return NULL; const char* uuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
char src[];
strcpy(src, client_key);
strcat(src, uuid);
size_t src_len = strlen(src); #ifdef _WIN64 or _WIN32
HCRYPTPROV hCryptProv;
if (CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, )){
HCRYPTHASH hHash;
if (CryptCreateHash(hCryptProv, CALG_SHA1, , , &hHash)){
if (CryptHashData(hHash, (BYTE*)src, src_len, )){
BYTE hash_result[];
DWORD out_len = sizeof(hash_result);
if (CryptGetHashParam(hHash, HP_HASHVAL, hash_result, &out_len, )){
DWORD crypt_out_len = ;
CryptBinaryToStringA(hash_result, out_len, CRYPT_STRING_BASE64, result, &crypt_out_len);
result[crypt_out_len - ] = ;
return result;
}
}
}
}
#else
unsigned char* value = SHA1((unsigned char*)src, src_len, out);
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, strlen((const char*)value));
BIO_flush(bio);
BUF_MEM *buf;
BIO_get_mem_ptr(bio, &buf);
strcpy(result, buf->data);
BIO_free_all(bio);
}
}
#endif
return NULL;
}

websocket提升代码:

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

     case EVHTTP_REQUEST: {
/* handle the websocket upgrade key */
const char* seckey = evhttp_find_header(req->input_headers, "Sec-WebSocket-Key");
if (seckey) {
struct evhttp_wsup* wsup;
char* translated;
/* Test for different URLs */
const char* path = evhttp_uri_get_path(req->uri_elems);
size_t offset = strlen(path);
if ((translated = mm_malloc(offset + )) == NULL)
return;
evhttp_decode_uri_internal(path, offset, translated,
/* decode_plus */);
TAILQ_FOREACH(wsup, &evcon->http_server->websocket_upgrades, next) {
if (!stricmp(wsup->what, translated)) {
evhttp_add_header(req->output_headers, "Connection", "Upgrade");
evhttp_add_header(req->output_headers, "Upgrade", "WebSocket");
evhttp_add_header(req->output_headers, "Sec-WebSocket-Accept", ws_hash(seckey));
req->websocket = ;
evhttp_response_code(req, , "SwitchProtocol");
evhttp_make_header(req->evcon, req);
evhttp_write_buffer_nostop_read(req->evcon, NULL, NULL);
evcon->state = EVCON_READING_WSDATA;
bufferevent_enable(evcon->bufev, EV_READ);
break;
}
}
mm_free(translated);
return;
}

处理websocket协议的代码:

 int
process_buffer(unsigned char* buff, size_t data_len)
{
if (data_len < )
return ;
switch (buff[] & 0xF){
case :
return ;
case :
case :{
auto len = buff[] & 0x7F;
auto mask = (buff[] & 0x80) > ;
if (len > )
return -; if (mask)
return ;
else
return ;
break;
}
case :{
auto len = buff[] & 0x7F;
auto mask = (buff[] & 0x80) > ;
int head_len = ;
if (len == )
head_len = mask ? : ;
else if (len == )
head_len = mask ? : ;
else
head_len = mask ? : ;
if (data_len < head_len)
return ; int tail_len = ;
if (len == )
tail_len = (int)ntohll((unsigned long long)(buff + ));
else if (len == )
tail_len = ntohs((u_short)(buff + ));
else
tail_len = len; if (data_len < head_len + tail_len)
return ; if (mask)
for (int i = head_len, j = ; j < tail_len; i++, j++)
buff[i] = buff[i] ^ buff[head_len - + j % ]; char* utf8_text = buff + head_len; return head_len + tail_len;
}
}
return ;
} static void
evhttp_read_wsdata(struct evhttp_connection *evcon, struct evhttp_request *req)
{
struct evbuffer *buf = bufferevent_get_input(evcon->bufev); size_t buflen = evbuffer_get_length(buf);
if (buflen == )
return; size_t drain_len = ;
unsigned char* data = evbuffer_pullup(buf, buflen);
while (buflen>) {
int result = process_buffer(data, buflen);
if (result < ) {
evhttp_connection_free(evcon);
return;
}
else if (result > ) {
if (result > buflen) {
evhttp_connection_free(evcon);
return;
}
drain_len += result;
data += result;
buflen -= result;
}
else
break;
}
if(drain_len>)evbuffer_drain(buf, drain_len); /* Read more! */
bufferevent_enable(evcon->bufev, EV_READ);
}

为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. springBoot(6)---过滤器,监听器,拦截器

    过滤器,监听器,拦截器 一.理解它们 看里十几篇博客,总算有点小明白,总的来讲,两张图可以让我看明白点. 通过两幅图我们可以理解拦截器和过滤器的特点 1.过滤器 过滤器是在请求进入tomcat容器后, ...

  2. python multiprocessing深度解析

    在写python多线程代码的时候,会用到multiprocessing这个包,这篇文章总结了一些这个包在多进程管理方面的一些原理和代码分析. 1. 问题一:是否需要显式调用pool的close和joi ...

  3. 导入项目报错【Minimum supported Gradle version is 3.3. Current version is 2.14.1】

    问题描述 导入项目的时候,因为同事的开发环境是Android Studio 2.3.2  Gradle3.3.而我的开发环境是Android Studio 2.2.2 Gradle2.14.1. 所以 ...

  4. 编写高质量代码改善java程序的151个建议——[110-117]异常及Web项目中异常处理

    原创地址:http://www.cnblogs.com/Alandre/(泥沙砖瓦浆木匠),需要转载的,保留下! 文章宗旨:Talk is cheap show me the code. 大成若缺,其 ...

  5. Spring Boot (四)模板引擎Thymeleaf集成

    一.Thymeleaf介绍 Thymeleaf是一种Java XML / XHTML / HTML5模板引擎,可以在Web和非Web环境中使用.它更适合在基于MVC的Web应用程序的视图层提供XHTM ...

  6. 深入理解redis数据类型

    转载请注明出处:https://www.cnblogs.com/wenjunwei/p/9720033.html redis的存储模型 redis不是普通的键值对存储,它实际上是一个数据结构存储服务器 ...

  7. Python系列:五、异常处理-技术流ken

    至今为止还没有进一步的谈论过错误信息,不过在你已经试验过的那些例子中,可能已经遇到过一些.Python 中(至少)有两种错误:语法错误和异常( syntax errors 和 exceptions ) ...

  8. 基于SSM框架贺州学院校园二手交易平台设计与实现

    前言 这个是我当时的毕业论文,分享出来,给同学们参考. 绪论 随着中国新四大发明的诞生,网购成了千千万万网友们购物的新方式,新的购物方式促进商业的发展,但随着人们生活水平的提高,许多新购置的物品用了没 ...

  9. 错误提示:The project was not built since its build path is incomplete. Cannot find the class file for java.lang.Object. Fix the build path then try building this project The type java.lang.Object cannot b

    原文:http://www.cnblogs.com/mmzs/p/7662863.html 错误类型: 搞了很久才找到原因.解决办法写出来分享: 出现以上错误的原因是玩耍maven时多装了个jre.本 ...

  10. [转]centos6.5修改yum安装的mysql默认目录

    本文转自:https://www.cnblogs.com/fefjay/p/6044444.html 0.说明 Linux下更改yum默认安装的mysql路径datadir.linux下,MySQL默 ...