|
|
|
@ -1,4 +1,4 @@
|
|
|
|
|
/* http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 */
|
|
|
|
|
/* https://datatracker.ietf.org/doc/html/rfc6455 */
|
|
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
@ -19,6 +19,13 @@ struct host_info {
|
|
|
|
|
short port;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum worker_state {
|
|
|
|
|
WS_INITIAL,
|
|
|
|
|
WS_SENT_HANDSHAKE,
|
|
|
|
|
WS_RECEIVED_RESPONSE,
|
|
|
|
|
WS_COMPLETE
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* worker_thread, with counter of remaining messages */
|
|
|
|
|
struct worker_thread {
|
|
|
|
|
struct host_info *hi;
|
|
|
|
@ -29,14 +36,24 @@ struct worker_thread {
|
|
|
|
|
int msg_sent;
|
|
|
|
|
int byte_count;
|
|
|
|
|
pthread_t thread;
|
|
|
|
|
enum worker_state state;
|
|
|
|
|
|
|
|
|
|
struct evbuffer *buffer;
|
|
|
|
|
struct evbuffer *rbuffer;
|
|
|
|
|
int got_header;
|
|
|
|
|
|
|
|
|
|
struct evbuffer *wbuffer;
|
|
|
|
|
|
|
|
|
|
int verbose;
|
|
|
|
|
int fd;
|
|
|
|
|
struct event ev_r;
|
|
|
|
|
struct event ev_w;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wait_for_possible_read(struct worker_thread *wt);
|
|
|
|
|
static void
|
|
|
|
|
wait_for_possible_write(struct worker_thread *wt);
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
process_message(struct worker_thread *wt, size_t sz) {
|
|
|
|
|
|
|
|
|
@ -55,15 +72,37 @@ process_message(struct worker_thread *wt, size_t sz) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Called when we can write to the socket.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
websocket_write(int fd, short event, void *ptr) {
|
|
|
|
|
websocket_can_write(int fd, short event, void *ptr) {
|
|
|
|
|
int ret;
|
|
|
|
|
struct worker_thread *wt = ptr;
|
|
|
|
|
printf("%s (wt=%p, fd=%d)\n", __func__, wt, fd);
|
|
|
|
|
|
|
|
|
|
if(event != EV_WRITE) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
switch (wt->state)
|
|
|
|
|
{
|
|
|
|
|
case WS_INITIAL: /* still sending initial HTTP request */
|
|
|
|
|
ret = evbuffer_write(wt->wbuffer, fd);
|
|
|
|
|
printf("evbuffer_write returned %d\n", ret);
|
|
|
|
|
printf("evbuffer_get_length returned %d\n", evbuffer_get_length(wt->wbuffer));
|
|
|
|
|
if (evbuffer_get_length(wt->wbuffer) != 0) { /* not all written */
|
|
|
|
|
wait_for_possible_write(wt);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
/* otherwise, we've sent the full request, time to read the response */
|
|
|
|
|
wt->state = WS_SENT_HANDSHAKE;
|
|
|
|
|
wait_for_possible_read(wt);
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
#if 0
|
|
|
|
|
char message[] = "\x00[\"SET\",\"key\",\"value\"]\xff\x00[\"GET\",\"key\"]\xff";
|
|
|
|
|
ret = write(fd, message, sizeof(message)-1);
|
|
|
|
|
if(ret != sizeof(message)-1) {
|
|
|
|
@ -73,25 +112,31 @@ websocket_write(int fd, short event, void *ptr) {
|
|
|
|
|
|
|
|
|
|
wt->msg_sent += 2;
|
|
|
|
|
if(wt->msg_sent < wt->msg_target) {
|
|
|
|
|
event_set(&wt->ev_w, fd, EV_WRITE, websocket_write, wt);
|
|
|
|
|
event_set(&wt->ev_w, fd, EV_WRITE, websocket_can_write, wt);
|
|
|
|
|
event_base_set(wt->base, &wt->ev_w);
|
|
|
|
|
ret = event_add(&wt->ev_w, NULL);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
websocket_read(int fd, short event, void *ptr) {
|
|
|
|
|
websocket_can_read(int fd, short event, void *ptr) {
|
|
|
|
|
char packet[2048], *pos;
|
|
|
|
|
int ret, success = 1;
|
|
|
|
|
|
|
|
|
|
struct worker_thread *wt = ptr;
|
|
|
|
|
printf("%s (wt=%p)\n", __func__, wt);
|
|
|
|
|
|
|
|
|
|
if(event != EV_READ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* read message */
|
|
|
|
|
ret = read(fd, packet, sizeof(packet));
|
|
|
|
|
ret = evbuffer_read(wt->rbuffer, fd, 65536);
|
|
|
|
|
printf("evbuffer_read() returned %d\n", ret);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
|
pos = packet;
|
|
|
|
|
if(ret > 0) {
|
|
|
|
|
char *data, *last;
|
|
|
|
@ -103,22 +148,22 @@ websocket_read(int fd, short event, void *ptr) {
|
|
|
|
|
return; /* not yet */
|
|
|
|
|
} else { /* start monitoring possible writes */
|
|
|
|
|
printf("start monitoring possible writes\n");
|
|
|
|
|
evbuffer_add(wt->buffer, frame_start + 2, ret - (frame_start + 2 - packet));
|
|
|
|
|
evbuffer_add(wt->rbuffer, frame_start + 2, ret - (frame_start + 2 - packet));
|
|
|
|
|
|
|
|
|
|
wt->got_header = 1;
|
|
|
|
|
event_set(&wt->ev_w, fd, EV_WRITE,
|
|
|
|
|
websocket_write, wt);
|
|
|
|
|
websocket_can_write, wt);
|
|
|
|
|
event_base_set(wt->base, &wt->ev_w);
|
|
|
|
|
ret = event_add(&wt->ev_w, NULL);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
/* we've had the header already, now bufffer data. */
|
|
|
|
|
evbuffer_add(wt->buffer, packet, ret);
|
|
|
|
|
evbuffer_add(wt->rbuffer, packet, ret);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while(1) {
|
|
|
|
|
data = (char*)EVBUFFER_DATA(wt->buffer);
|
|
|
|
|
sz = EVBUFFER_LENGTH(wt->buffer);
|
|
|
|
|
data = (char*)EVBUFFER_DATA(wt->rbuffer);
|
|
|
|
|
sz = EVBUFFER_LENGTH(wt->rbuffer);
|
|
|
|
|
|
|
|
|
|
if(sz == 0) { /* no data */
|
|
|
|
|
break;
|
|
|
|
@ -136,7 +181,7 @@ websocket_read(int fd, short event, void *ptr) {
|
|
|
|
|
process_message(ptr, msg_sz); /* record packet */
|
|
|
|
|
|
|
|
|
|
/* drain including frame delimiters (+2 bytes) */
|
|
|
|
|
evbuffer_drain(wt->buffer, msg_sz + 2);
|
|
|
|
|
evbuffer_drain(wt->rbuffer, msg_sz + 2);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
printf("ret=%d\n", ret);
|
|
|
|
@ -147,6 +192,24 @@ websocket_read(int fd, short event, void *ptr) {
|
|
|
|
|
close(fd);
|
|
|
|
|
event_base_loopexit(wt->base, NULL);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wait_for_possible_read(struct worker_thread *wt) {
|
|
|
|
|
printf("%s (wt=%p)\n", __func__, wt);
|
|
|
|
|
event_set(&wt->ev_r, wt->fd, EV_READ, websocket_can_read, wt);
|
|
|
|
|
event_base_set(wt->base, &wt->ev_r);
|
|
|
|
|
event_add(&wt->ev_r, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wait_for_possible_write(struct worker_thread *wt) {
|
|
|
|
|
printf("%s (wt=%p)\n", __func__, wt);
|
|
|
|
|
event_set(&wt->ev_r, wt->fd, EV_WRITE, websocket_can_write, wt);
|
|
|
|
|
event_base_set(wt->base, &wt->ev_r);
|
|
|
|
|
event_add(&wt->ev_r, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void*
|
|
|
|
@ -157,10 +220,9 @@ worker_main(void *ptr) {
|
|
|
|
|
"Connection: Upgrade\r\n"
|
|
|
|
|
"Upgrade: WebSocket\r\n"
|
|
|
|
|
"Origin: http://%s:%d\r\n"
|
|
|
|
|
"Sec-WebSocket-Key1: 18x 6]8vM;54 *(5: { U1]8 z [ 8\r\n"
|
|
|
|
|
"Sec-WebSocket-Key2: 1_ tx7X d < nw 334J702) 7]o}` 0\r\n"
|
|
|
|
|
"Sec-WebSocket-Key: webdis-websocket-test-key\r\n"
|
|
|
|
|
"\r\n"
|
|
|
|
|
"Tm[K T2u";
|
|
|
|
|
;
|
|
|
|
|
|
|
|
|
|
struct worker_thread *wt = ptr;
|
|
|
|
|
|
|
|
|
@ -184,27 +246,29 @@ worker_main(void *ptr) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* initialize worker thread */
|
|
|
|
|
wt->fd = fd;
|
|
|
|
|
wt->base = event_base_new();
|
|
|
|
|
wt->buffer = evbuffer_new();
|
|
|
|
|
wt->rbuffer = evbuffer_new();
|
|
|
|
|
wt->wbuffer = evbuffer_new(); /* write buffer */
|
|
|
|
|
wt->byte_count = 0;
|
|
|
|
|
wt->got_header = 0;
|
|
|
|
|
|
|
|
|
|
/* send handshake */
|
|
|
|
|
/* build handshake buffer */
|
|
|
|
|
/*
|
|
|
|
|
ws_handshake_sz = sizeof(ws_handshake)
|
|
|
|
|
+ 2*strlen(wt->hi->host) + 500;
|
|
|
|
|
ws_handshake = calloc(ws_handshake_sz, 1);
|
|
|
|
|
ws_handshake_sz = (size_t)sprintf(ws_handshake, ws_template,
|
|
|
|
|
wt->hi->host, wt->hi->port,
|
|
|
|
|
wt->hi->host, wt->hi->port);
|
|
|
|
|
ret = write(fd, ws_handshake, ws_handshake_sz);
|
|
|
|
|
|
|
|
|
|
struct event ev_r;
|
|
|
|
|
event_set(&ev_r, fd, EV_READ | EV_PERSIST, websocket_read, wt);
|
|
|
|
|
event_base_set(wt->base, &ev_r);
|
|
|
|
|
event_add(&ev_r, NULL);
|
|
|
|
|
*/
|
|
|
|
|
int added = evbuffer_add_printf(wt->wbuffer, ws_template, wt->hi->host, wt->hi->port,
|
|
|
|
|
wt->hi->host, wt->hi->port);
|
|
|
|
|
wait_for_possible_write(wt); /* request callback */
|
|
|
|
|
|
|
|
|
|
/* go! */
|
|
|
|
|
event_base_dispatch(wt->base);
|
|
|
|
|
printf("event_base_dispatch returned\n");
|
|
|
|
|
event_base_free(wt->base);
|
|
|
|
|
free(ws_handshake);
|
|
|
|
|
return NULL;
|
|
|
|
@ -240,14 +304,14 @@ main(int argc, char *argv[]) {
|
|
|
|
|
int i, opt;
|
|
|
|
|
char *colon;
|
|
|
|
|
double total = 0, total_bytes = 0;
|
|
|
|
|
int verbose = 0;
|
|
|
|
|
int verbose = 0, single = 0;
|
|
|
|
|
|
|
|
|
|
struct host_info hi = {host_default, port_default};
|
|
|
|
|
|
|
|
|
|
struct worker_thread *workers;
|
|
|
|
|
|
|
|
|
|
/* getopt */
|
|
|
|
|
while ((opt = getopt(argc, argv, "h:p:c:n:v")) != -1) {
|
|
|
|
|
while ((opt = getopt(argc, argv, "h:p:c:n:vs")) != -1) {
|
|
|
|
|
switch (opt) {
|
|
|
|
|
case 'h':
|
|
|
|
|
colon = strchr(optarg, ':');
|
|
|
|
@ -277,6 +341,11 @@ main(int argc, char *argv[]) {
|
|
|
|
|
case 'v':
|
|
|
|
|
verbose = 1;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 's':
|
|
|
|
|
single = 1;
|
|
|
|
|
thread_count = 1;
|
|
|
|
|
break;
|
|
|
|
|
default: /* '?' */
|
|
|
|
|
usage(argv[0], host_default, port_default,
|
|
|
|
|
thread_count_default,
|
|
|
|
@ -289,10 +358,19 @@ main(int argc, char *argv[]) {
|
|
|
|
|
workers = calloc(sizeof(struct worker_thread), thread_count);
|
|
|
|
|
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &t0);
|
|
|
|
|
if (single) {
|
|
|
|
|
printf("Single-threaded mode\n");
|
|
|
|
|
workers[0].msg_target = msg_target;
|
|
|
|
|
workers[0].hi = &hi;
|
|
|
|
|
workers[0].verbose = verbose;
|
|
|
|
|
workers[0].state = WS_INITIAL;
|
|
|
|
|
worker_main(&workers[0]);
|
|
|
|
|
} else {
|
|
|
|
|
for (i = 0; i < thread_count; ++i) {
|
|
|
|
|
workers[i].msg_target = msg_target;
|
|
|
|
|
workers[i].hi = &hi;
|
|
|
|
|
workers[i].verbose = verbose;
|
|
|
|
|
workers[i].state = WS_INITIAL;
|
|
|
|
|
|
|
|
|
|
pthread_create(&workers[i].thread, NULL,
|
|
|
|
|
worker_main, &workers[i]);
|
|
|
|
@ -304,6 +382,7 @@ main(int argc, char *argv[]) {
|
|
|
|
|
total += workers[i].msg_received;
|
|
|
|
|
total_bytes += workers[i].byte_count;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* timing */
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &t1);
|
|
|
|
|