libevent 增加 websocket协议支持
- 原理:
1 通过send_reply_start发送的消息,不会关闭req;
2 bufferevent 接管
API调用
//计算KEY
static void ws_handshake(const char *key, char* ac) {
const char* magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
unsigned char sha[20]; // , b64_sha[30];
//base64(sha1(key+magic))
_sha1_ctx sha_ctx;
_sha1_init(&sha_ctx);
_sha1_update(&sha_ctx, (unsigned char*)key, strlen(key));
_sha1_update(&sha_ctx, (unsigned char*)magic, 36);
_sha1_final(sha, &sha_ctx);
base64_encode(sha, sizeof(sha), (char*)ac);
}
//evhttp_set_gencb(http, _api_wstest, NULL);
void _api_wstest(struct evhttp_request *req, void *arg)
{
const char* seckey = evhttp_find_header(evhttp_request_get_input_headers(req), "Sec-WebSocket-Key");
if (seckey) {
struct evhttp_connection* evcon = evhttp_request_get_connection(req);
//evhttp_connection_
evhttp_connection_set_closecb(evcon, on_evcnclose_cb, req);
evhttp_request_set_on_complete_cb(req, req_on_complete_cb, NULL);
char acstr[100];
ws_handshake(seckey, acstr);
evhttp_add_header(evhttp_request_get_output_headers(req), "Connection", "Upgrade");
evhttp_add_header(evhttp_request_get_output_headers(req), "Upgrade", "WebSocket");
evhttp_add_header(evhttp_request_get_output_headers(req), "Sec-WebSocket-Accept", acstr);
//evhttp_add_header(evhttp_request_get_output_headers(req), "Connection", "Keep-Alive");
//通过send_reply_start发送的消息,不会关闭req
//evhttp_send_reply_start(req, 101, "Switching Protocols");
//方法2: 自己接管bufferevent
struct bufferevent* bufev = evhttp_connection_get_bufferevent(evcon);
struct evbuffer* output = bufferevent_get_output(bufev);
evbuffer_add_printf(output, "HTTP/%d.%d %d %s\r\n", req->major, req->minor, 101, "Switching Protocols");
TAILQ_FOREACH(header, evhttp_request_get_output_headers(req), next) {
evbuffer_add_printf(output, "%s: %s\r\n",
header->key, header->value);
}
evbuffer_add(output, "\r\n", 2);
bufferevent_setcb(bufev,
ev_read_cb,
ev_write_cb,
ev_error_cb,
req);
bufferevent_enable(bufev, EV_READ | EV_WRITE);
}
}
bufferevent回调(读,写,错误)
//写回调
static void ev_write_cb(struct bufferevent* bufev, void* arg)
{
struct evhttp_request* req = (struct evhttp_request*)arg;
struct evhttp_connection* evcon = evhttp_request_get_connection(req);
printf("evhttp_write_cb\n");
/* Activate our call back */
if (evcon->cb != NULL)
(*evcon->cb)(evcon, evcon->cb_arg);
}
//读回调
static void ev_read_cb(struct bufferevent* bufev, void* arg)
{
struct evhttp_request* req = (struct evhttp_request*)arg;
struct evhttp_connection* evcon = evhttp_request_get_connection(req);
printf("evhttp_read_cb\n");
struct evbuffer* buffer = bufferevent_get_input(evcon->bufev);
size_t buflen = evbuffer_get_length(buffer);
struct ws_msg msg;
unsigned char* buf = evbuffer_pullup(buffer, buflen);
int ofs = 0;
while (ws_process(buf + ofs, buflen - ofs, &msg) > 0) {
char* s = (char*)buf + ofs + msg.header_len;
size_t len = msg.header_len + msg.data_len;
printf("ws_process> flag=%02x\n", msg.flags & 15);
if (len > 0) {
//这里就是websocket接收消息
printf("[%.*s]\n", msg.data_len, s);
}
ofs += len;
}
if (ofs > 0)
evbuffer_drain(buffer, ofs);
}
static void ev_error_cb(struct bufferevent* bufev, short what, void* arg)
{
struct evhttp_request* req = (struct evhttp_request*)arg;
struct evhttp_connection* evcon = evhttp_request_get_connection(req);
printf("evhttp_error_cb\n");
evhttp_send_reply_end(req);
}
WS消息解析
struct ws_msg {
uint8_t flags;
size_t header_len;
size_t data_len;
};
static size_t ws_process(uint8_t* buf, size_t len, struct ws_msg* msg) {
size_t i, n = 0, mask_len = 0;
memset(msg, 0, sizeof(*msg));
if (len >= 2) {
n = buf[1] & 0x7f; // Frame length
mask_len = buf[1] & 128 ? 4 : 0; // last bit is a mask bit
msg->flags = buf[0];
if (n < 126 && len >= mask_len) {
msg->data_len = n;
msg->header_len = 2 + mask_len;
}
else if (n == 126 && len >= 4 + mask_len) {
msg->header_len = 4 + mask_len;
msg->data_len = ntohs(*(uint16_t*)&buf[2]);
}
else if (len >= 10 + mask_len) {
msg->header_len = 10 + mask_len;
msg->data_len =
(size_t)(((uint64_t)ntohl(*(uint32_t*)&buf[2])) << 32) +
ntohl(*(uint32_t*)&buf[6]);
}
}
// Sanity check, and integer overflow protection for the boundary check below
// data_len should not be larger than 1 Gb
if (msg->data_len > 1024 * 1024 * 1024) return 0;
if (msg->header_len + msg->data_len > len) return 0;
if (mask_len > 0) {
uint8_t* p = buf + msg->header_len, * m = p - mask_len;
for (i = 0; i < msg->data_len; i++) p[i] ^= m[i & 3];
}
return msg->header_len + msg->data_len;
}
如何主动关闭?
- 设定超时时间:
evhttp_connection_set_timeout(evhttp_request_get_connection(req), 1);