From 47ba5daa12761c6deb596aede5893a5636225eff Mon Sep 17 00:00:00 2001 From: Shuanglei Tao Date: Thu, 23 Nov 2017 22:01:22 +0800 Subject: [PATCH] protocol: use binary message --- html/css/app.css | 2 +- html/index.html | 1 - html/js/app.js | 39 +++++++++------- html/js/utf8.js | 118 ----------------------------------------------- src/index.html | 5 +- src/protocol.c | 103 ++++++++++++++++++----------------------- src/server.c | 13 ++++-- src/server.h | 18 ++++---- 8 files changed, 90 insertions(+), 209 deletions(-) delete mode 100644 html/js/utf8.js diff --git a/html/css/app.css b/html/css/app.css index fd2e5f7..c93c284 100644 --- a/html/css/app.css +++ b/html/css/app.css @@ -17,7 +17,7 @@ html, body { background-color: #101010; color: #f0f0f0; font-size: 10pt; - font-family: Menlo,Consolas,"DejaVu Sans Mono","Liberation Mono",Courier,monospace; + font-family: "Menlo for Powerline", Menlo,Consolas,"DejaVu Sans Mono","Liberation Mono",Courier,monospace; font-variant-ligatures: none; -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; diff --git a/html/index.html b/html/index.html index d252eaf..f67c919 100644 --- a/html/index.html +++ b/html/index.html @@ -9,7 +9,6 @@ - diff --git a/html/js/app.js b/html/js/app.js index 47a732c..da25092 100644 --- a/html/js/app.js +++ b/html/js/app.js @@ -8,18 +8,30 @@ term, pingTimer, wsError; var openWs = function() { - var ws = new WebSocket(url, protocols); + var ws = new WebSocket(url, protocols), + textDecoder = new TextDecoder(), + textEncoder = new TextEncoder(); var unloadCallback = function(event) { var message = 'Close terminal? this will also terminate the command.'; (event || window.event).returnValue = message; return message; }; + var sendMessage = function (msg) { + if (ws.readyState === WebSocket.OPEN) { + ws.send(textEncoder.encode(msg)); + } + }; + var sendPing = function() { + sendMessage("1"); + }; + + ws.binaryType = 'arraybuffer'; - ws.onopen = function(event) { + ws.onopen = function() { console.log("Websocket connection opened"); wsError = false; - ws.send(JSON.stringify({AuthToken: authToken})); - pingTimer = setInterval(sendPing, 30 * 1000, ws); + sendMessage(JSON.stringify({AuthToken: authToken})); + pingTimer = setInterval(sendPing, 30 * 1000); if (typeof term !== 'undefined') { term.destroy(); @@ -28,18 +40,14 @@ term = new Terminal(); term.on('resize', function(size) { - if (ws.readyState === WebSocket.OPEN) { - ws.send("2" + JSON.stringify({columns: size.cols, rows: size.rows})); - } + sendMessage("2" + JSON.stringify({columns: size.cols, rows: size.rows})); setTimeout(function() { term.showOverlay(size.cols + 'x' + size.rows); }, 500); }); term.on("data", function(data) { - if (ws.readyState === WebSocket.OPEN) { - ws.send("0" + data); - } + sendMessage("0" + data); }); term.on('open', function() { @@ -62,10 +70,11 @@ }; ws.onmessage = function(event) { - var data = event.data.slice(1); - switch(event.data[0]) { + var cmd = String.fromCharCode(new DataView(event.data).getUint8()), + data = textDecoder.decode(event.data.slice(1)); + switch(cmd) { case '0': - term.writeUTF8(window.atob(data)); + term.write(data); break; case '1': // pong break; @@ -104,9 +113,5 @@ }; }; - var sendPing = function(ws) { - ws.send("1"); - }; - openWs(); })(); \ No newline at end of file diff --git a/html/js/utf8.js b/html/js/utf8.js deleted file mode 100644 index 5a10144..0000000 --- a/html/js/utf8.js +++ /dev/null @@ -1,118 +0,0 @@ -// ported from hterm.Terminal.IO.prototype.writeUTF8 -// https://chromium.googlesource.com/apps/libapps/+/master/hterm/js/hterm_terminal_io.js - -UTF8Decoder = function() { - this.bytesLeft = 0; - this.codePoint = 0; - this.lowerBound = 0; -}; - -UTF8Decoder.prototype.decode = function(str) { - var ret = ''; - for (var i = 0; i < str.length; i++) { - var c = str.charCodeAt(i); - if (this.bytesLeft == 0) { - if (c <= 0x7F) { - ret += str.charAt(i); - } else if (0xC0 <= c && c <= 0xDF) { - this.codePoint = c - 0xC0; - this.bytesLeft = 1; - this.lowerBound = 0x80; - } else if (0xE0 <= c && c <= 0xEF) { - this.codePoint = c - 0xE0; - this.bytesLeft = 2; - this.lowerBound = 0x800; - } else if (0xF0 <= c && c <= 0xF7) { - this.codePoint = c - 0xF0; - this.bytesLeft = 3; - this.lowerBound = 0x10000; - } else if (0xF8 <= c && c <= 0xFB) { - this.codePoint = c - 0xF8; - this.bytesLeft = 4; - this.lowerBound = 0x200000; - } else if (0xFC <= c && c <= 0xFD) { - this.codePoint = c - 0xFC; - this.bytesLeft = 5; - this.lowerBound = 0x4000000; - } else { - ret += '\ufffd'; - } - } else { - if (0x80 <= c && c <= 0xBF) { - this.bytesLeft--; - this.codePoint = (this.codePoint << 6) + (c - 0x80); - if (this.bytesLeft == 0) { - var codePoint = this.codePoint; - if (codePoint < this.lowerBound - || (0xD800 <= codePoint && codePoint <= 0xDFFF) - || codePoint > 0x10FFFF) { - ret += '\ufffd'; - } else { - if (codePoint < 0x10000) { - ret += String.fromCharCode(codePoint); - } else { - codePoint -= 0x10000; - ret += String.fromCharCode( - 0xD800 + ((codePoint >>> 10) & 0x3FF), - 0xDC00 + (codePoint & 0x3FF)); - } - } - } - } else { - ret += '\ufffd'; - this.bytesLeft = 0; - i--; - } - } - } - return ret; -}; - -Terminal.prototype.decodeUTF8 = function(str) { - return (new UTF8Decoder()).decode(str); -}; - -Terminal.prototype.encodeUTF8 = function(str) { - var ret = ''; - for (var i = 0; i < str.length; i++) { - var c = str.charCodeAt(i); - if (0xDC00 <= c && c <= 0xDFFF) { - c = 0xFFFD; - } else if (0xD800 <= c && c <= 0xDBFF) { - if (i+1 < str.length) { - var d = str.charCodeAt(i+1); - if (0xDC00 <= d && d <= 0xDFFF) { - c = 0x10000 + ((c & 0x3FF) << 10) + (d & 0x3FF); - i++; - } else { - c = 0xFFFD; - } - } else { - c = 0xFFFD; - } - } - var bytesLeft; - if (c <= 0x7F) { - ret += str.charAt(i); - continue; - } else if (c <= 0x7FF) { - ret += String.fromCharCode(0xC0 | (c >>> 6)); - bytesLeft = 1; - } else if (c <= 0xFFFF) { - ret += String.fromCharCode(0xE0 | (c >>> 12)); - bytesLeft = 2; - } else { - ret += String.fromCharCode(0xF0 | (c >>> 18)); - bytesLeft = 3; - } - while (bytesLeft > 0) { - bytesLeft--; - ret += String.fromCharCode(0x80 | ((c >>> (6 * bytesLeft)) & 0x3F)); - } - } - return ret; -}; - -Terminal.prototype.writeUTF8 = function (str) { - this.write(this.decodeUTF8(str)); -}; diff --git a/src/index.html b/src/index.html index d615286..56abdd5 100644 --- a/src/index.html +++ b/src/index.html @@ -6,18 +6,17 @@ ttyd - Terminal - + -
- + diff --git a/src/protocol.c b/src/protocol.c index 9d3ba1f..70fdc6c 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -1,7 +1,5 @@ #include "server.h" -#define BUF_SIZE 1024 - int send_initial_message(struct lws *wsi) { unsigned char message[LWS_PRE + 256]; @@ -13,17 +11,17 @@ send_initial_message(struct lws *wsi) { // window title n = sprintf((char *) p, "%c%s (%s)", SET_WINDOW_TITLE, server->command, hostname); - if (lws_write(wsi, p, (size_t) n, LWS_WRITE_TEXT) < n) { + if (lws_write(wsi, p, (size_t) n, LWS_WRITE_BINARY) < n) { return -1; } // reconnect time n = sprintf((char *) p, "%c%d", SET_RECONNECT, server->reconnect); - if (lws_write(wsi, p, (size_t) n, LWS_WRITE_TEXT) < n) { + if (lws_write(wsi, p, (size_t) n, LWS_WRITE_BINARY) < n) { return -1; } // client preferences n = sprintf((char *) p, "%c%s", SET_PREFERENCES, server->prefs_json); - if (lws_write(wsi, p, (size_t) n, LWS_WRITE_TEXT) < n) { + if (lws_write(wsi, p, (size_t) n, LWS_WRITE_BINARY) < n) { return -1; } return 0; @@ -86,7 +84,7 @@ check_host_origin(struct lws *wsi) { void tty_client_remove(struct tty_client *client) { - pthread_mutex_lock(&server->lock); + pthread_mutex_lock(&server->mutex); struct tty_client *iterator; LIST_FOREACH(iterator, &server->clients, list) { if (iterator == client) { @@ -95,7 +93,7 @@ tty_client_remove(struct tty_client *client) { break; } } - pthread_mutex_unlock(&server->lock); + pthread_mutex_unlock(&server->mutex); } void @@ -119,6 +117,8 @@ tty_client_destroy(struct tty_client *client) { if (client->buffer != NULL) free(client->buffer); + pthread_mutex_destroy(&client->mutex); + // remove from client list tty_client_remove(client); } @@ -127,8 +127,6 @@ void * thread_run_command(void *args) { struct tty_client *client; int pty; - int bytes; - char buf[BUF_SIZE]; fd_set des_set; client = (struct tty_client *) args; @@ -141,11 +139,11 @@ thread_run_command(void *args) { case 0: /* child */ if (setenv("TERM", "xterm-256color", true) < 0) { perror("setenv"); - exit(1); + pthread_exit((void *) 1); } if (execvp(server->argv[0], server->argv) < 0) { perror("execvp"); - exit(1); + pthread_exit((void *) 1); } break; default: /* parent */ @@ -164,23 +162,25 @@ thread_run_command(void *args) { break; if (FD_ISSET (pty, &des_set)) { - memset(buf, 0, BUF_SIZE); - bytes = (int) read(pty, buf, BUF_SIZE); - struct pty_data *frame = (struct pty_data *) xmalloc(sizeof(struct pty_data)); - frame->len = bytes; - if (bytes > 0) { - frame->data = xmalloc((size_t) bytes); - memcpy(frame->data, buf, bytes); + while (client->running) { + pthread_mutex_lock(&client->mutex); + if (client->state == STATE_READY) { + pthread_mutex_unlock(&client->mutex); + usleep(5); + continue; + } + memset(client->pty_buffer, 0, sizeof(client->pty_buffer)); + client->pty_len = read(pty, client->pty_buffer, sizeof(client->pty_buffer)); + client->state = STATE_READY; + pthread_mutex_unlock(&client->mutex); + break; } - pthread_mutex_lock(&client->lock); - STAILQ_INSERT_TAIL(&client->queue, frame, list); - pthread_mutex_unlock(&client->lock); } } break; } - return 0; + pthread_exit((void *) 0); } int @@ -216,15 +216,17 @@ callback_tty(struct lws *wsi, enum lws_callback_reasons reason, client->authenticated = false; client->wsi = wsi; client->buffer = NULL; + client->state = STATE_INIT; + client->pty_len = 0; + pthread_mutex_init(&client->mutex, NULL); lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), client->hostname, sizeof(client->hostname), client->address, sizeof(client->address)); - STAILQ_INIT(&client->queue); - pthread_mutex_lock(&server->lock); + pthread_mutex_lock(&server->mutex); LIST_INSERT_HEAD(&server->clients, client, list); server->client_count++; - pthread_mutex_unlock(&server->lock); + pthread_mutex_unlock(&server->mutex); lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_GET_URI); lwsl_notice("WS %s - %s (%s), clients: %d\n", buf, client->address, client->hostname, server->client_count); @@ -240,44 +242,31 @@ callback_tty(struct lws *wsi, enum lws_callback_reasons reason, client->initialized = true; break; } + if (client->state != STATE_READY) + break; - pthread_mutex_lock(&client->lock); - while (!STAILQ_EMPTY(&client->queue)) { - struct pty_data *frame = STAILQ_FIRST(&client->queue); - // read error or client exited, close connection - if (frame->len <= 0) { - STAILQ_REMOVE_HEAD(&client->queue, list); - free(frame); - tty_client_remove(client); - lws_close_reason(wsi, - frame->len == 0 ? LWS_CLOSE_STATUS_NORMAL : LWS_CLOSE_STATUS_UNEXPECTED_CONDITION, - NULL, 0); - return -1; - } + // read error or client exited, close connection + if (client->pty_len <= 0) { + tty_client_remove(client); + lws_close_reason(wsi, + client->pty_len == 0 ? LWS_CLOSE_STATUS_NORMAL + : LWS_CLOSE_STATUS_UNEXPECTED_CONDITION, + NULL, 0); + return -1; + } - char *b64_text = base64_encode((const unsigned char *) frame->data, (size_t) frame->len); - size_t msg_len = LWS_PRE + strlen(b64_text) + 1; - unsigned char message[msg_len]; + { + size_t n = (size_t) client->pty_len + 1; + unsigned char message[LWS_PRE + n]; unsigned char *p = &message[LWS_PRE]; - size_t n = sprintf((char *) p, "%c%s", OUTPUT, b64_text); + *p = OUTPUT; + memcpy(p + 1, client->pty_buffer, client->pty_len); + client->state = STATE_DONE; - free(b64_text); - - if (lws_write(wsi, p, n, LWS_WRITE_TEXT) < n) { + if (lws_write(wsi, p, n, LWS_WRITE_BINARY) < n) { lwsl_err("write data to WS\n"); - break; - } - - STAILQ_REMOVE_HEAD(&client->queue, list); - free(frame->data); - free(frame); - - if (lws_partial_buffered(wsi)) { - lws_callback_on_writable(wsi); - break; } } - pthread_mutex_unlock(&client->lock); break; case LWS_CALLBACK_RECEIVE: @@ -321,7 +310,7 @@ callback_tty(struct lws *wsi, enum lws_callback_reasons reason, case PING: { unsigned char c = PONG; - if (lws_write(wsi, &c, 1, LWS_WRITE_TEXT) != 1) { + if (lws_write(wsi, &c, 1, LWS_WRITE_BINARY) != 1) { lwsl_err("send PONG\n"); tty_client_remove(client); lws_close_reason(wsi, LWS_CLOSE_STATUS_UNEXPECTED_CONDITION, NULL, 0); diff --git a/src/server.c b/src/server.c index 3d1cf51..3fcd237 100644 --- a/src/server.c +++ b/src/server.c @@ -140,6 +140,7 @@ tty_server_free(struct tty_server *ts) { unlink(ts->socket_path); } } + pthread_mutex_destroy(&ts->mutex); free(ts); } @@ -203,6 +204,7 @@ main(int argc, char **argv) { int start = calc_command_start(argc, argv); server = tty_server_new(argc, argv, start); + pthread_mutex_init(&server->mutex, NULL); struct lws_context_creation_info info; memset(&info, 0, sizeof(info)); @@ -442,16 +444,19 @@ main(int argc, char **argv) { // libwebsockets main loop while (!force_exit) { - pthread_mutex_lock(&server->lock); + pthread_mutex_lock(&server->mutex); if (!LIST_EMPTY(&server->clients)) { struct tty_client *client; LIST_FOREACH(client, &server->clients, list) { - if (client->running && !STAILQ_EMPTY(&client->queue)) { - lws_callback_on_writable(client->wsi); + if (client->running) { + pthread_mutex_lock(&client->mutex); + if (client->state != STATE_DONE) + lws_callback_on_writable(client->wsi); + pthread_mutex_unlock(&client->mutex); } } } - pthread_mutex_unlock(&server->lock); + pthread_mutex_unlock(&server->mutex); lws_service(context, 10); } diff --git a/src/server.h b/src/server.h index 02bd190..c357315 100644 --- a/src/server.h +++ b/src/server.h @@ -66,14 +66,14 @@ // websocket url path #define WS_PATH "/ws" +#define BUF_SIZE 32768 // 32K + extern volatile bool force_exit; extern struct lws_context *context; extern struct tty_server *server; -struct pty_data { - char *data; - int len; - STAILQ_ENTRY(pty_data) list; +enum pty_state { + STATE_INIT, STATE_READY, STATE_DONE }; struct tty_client { @@ -87,12 +87,14 @@ struct tty_client { struct winsize size; char *buffer; size_t len; + int pid; int pty; + enum pty_state state; + char pty_buffer[BUF_SIZE]; + ssize_t pty_len; pthread_t thread; - - STAILQ_HEAD(pty, pty_data) queue; - pthread_mutex_t lock; + pthread_mutex_t mutex; LIST_ENTRY(tty_client) list; }; @@ -113,7 +115,7 @@ struct tty_server { int max_clients; // maximum clients to support bool once; // whether accept only one client and exit on disconnection char socket_path[255]; // UNIX domain socket path - pthread_mutex_t lock; + pthread_mutex_t mutex; }; extern int -- 2.43.4