为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只进行了片段代码测试。
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支持(上)的更多相关文章
- Netty 框架学习 —— 添加 WebSocket 支持
WebSocket 简介 WebSocket 协议是完全重新设计的协议,旨在为 Web 上的双向数据传输问题提供一个切实可行的解决方案,使得客户端和服务器之间可以在任意时刻传输消息 Netty 对于 ...
- 在.net core web网站中添加webSocket支持
注意:前置条件,操作系统 windows 8 以上,IIS Express 8.0 以上. 第1步:在Startup.cs文件的头部添加如下引用: using System.Net.WebSocket ...
- 把自己Github上的代码添加Cocoapods支持
转载请注明原链接:http://www.cnblogs.com/zhanggui/p/6003481.html 一.前言 这两天被cocoapods折磨的心力憔悴.看cocoapods官网的添加支持, ...
- 把上传Github的代码添加Cocoapods支持
开始 这里我将从最初的开始进行介绍,包括Github上创建项目已经上传项目,到最后的支持Cocoapods. 步骤如下: 代码上传Github 创建podspec文件,并验证是否通过 在Github上 ...
- Spring 4 官方文档学习(十四)WebSocket支持
个人提示:如果需要用到页面推送,高频且要低延迟,WebSocket无疑是最佳选择.否则还是轮询和long polling吧. 做了一个小demo放在码云上,有兴趣的可以看一下,简单易懂:websock ...
- ASP.NET Core 中的 WebSocket 支持(转自MSDN)
本文介绍 ASP.NET Core 中 WebSocket 的入门方法. WebSocket (RFC 6455) 是一个协议,支持通过 TCP 连接建立持久的双向信道. 它用于从快速实时通信中获益的 ...
- Spring Boot 添加Shiro支持
前言: Shiro是一个权限.会话管理的开源Java安全框架:Spring Boot集成Shiro后可以方便的使用Session: 工程概述: (工程结构图) 一.建立Spring Boot工程 参照 ...
- 将自己库添加Cocoapods支持
给库添加Cocoapods支持, 使这个工具使用起来更加方便, 更好的使用Cocoapods, 助力iOS程序开发, 下面进入正题, 想要实现这个过程, 绝对不虚此读. 首先写好一个要添加Cocoap ...
- 为python-sproto添加map支持
上个月太忙了,做完这个修改还没写博客,现在补一下.. 之前使用protobuf做协议打包的时候,经常会有个痛点,没法用具体数据的值作为key来索引数据.比如现在客户端上传了造兵协议,协议大概长这样: ...
随机推荐
- vue 解决无法设置滚动位置的问题
问题描述 在实现锚点定位的时候发现无法设置滚动条的位置. 在Vue中,使用 document.body.scrollTop=952 无法设置滚动条的高度. document.body.scrollTo ...
- linux的tar命令
Linux下的tar压缩解压缩命令详解 tar -c: 建立压缩档案 -x:解压 -t:查看内容 -r:向压缩归档文件末尾追加文件 -u:更新原压缩包中的文件 这五个是独立的命令,压缩解压都要用到其中 ...
- Python快速学习01:Eclipse上配置PyDev & 'Hello World !'
前言 系列文章:[传送门] 答应了Vamei,帮他传文章,Python,顺自己学学. 很喜欢这种黏黏的语言 突然发现--我用的GoAgent(谷歌FQ软件),竟然是Python编的. 简介 Pytho ...
- Ubuntu 下 Galera cluster for MySQL 集群安装
mysql galera cluster官网:http://galeracluster.com/documentation-webpages/ 相关安装教程:(不一定管用) http://blog.c ...
- es6入门2--对象解构赋值
解构赋值:ES6允许按照一定规则从数组或对象中提取值,并对变量进行赋值.说直白点,等号两边的结构相同,右边的值会赋给左边的变量. 一.数组的解构赋值: 1.基本用法 let [a, b, c] = [ ...
- Eclipse中Maven插件的使用技巧及原理
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6698699.html 题目有点大,这里只是自己对Maven插件的一些使用总结,可能会涉及到 ...
- Python系列:三、流程控制循环语句--技术流ken
Python条件语句 Python条件语句是通过一条或多条语句的执行结果(True或者False)来决定执行的代码块. 可以通过下图来简单了解条件语句的执行过程: Python程序语言指定任何非0和非 ...
- linux学习基础1
简介 包含计算机组成,发行.核心思想.主要目录,一些命令ifconfig.echo.tty.startx.export.pwd.history.shutdown.poweroff.reboot.hwc ...
- java.lang.NoSuchMethodException: tk.mybatis.mapper.provider.SpecialProvider.<init>()
Caused by: org.apache.ibatis.builder.BuilderException: Error invoking SqlProvider method (tk.mybatis ...
- webpack-dev-server不是内部或外部命令
参考:https://segmentfault.com/q/1010000006939078 错误报告:webpack-dev-server不是内部或外部命令 错误原因: 当执行命令: npm run ...