Echtzeit-Chat mit Readline & Socket.io von Node.js

Was Sie erstellen werden

Node.js hat in seiner Standardbibliothek ein unterbewertetes Modul, das überraschend nützlich ist. Das Readline-Modul macht, was es auf der Box sagt: Es liest eine Eingabezeile vom Terminal. Dies kann verwendet werden, um dem Benutzer eine oder zwei Fragen zu stellen oder eine Eingabeaufforderung am unteren Bildschirmrand zu erstellen. In diesem Lernprogramm möchte ich die Leistungsfähigkeit von Readline demonstrieren und einen Echtzeit-CLI-Chatroom erstellen, der von Socket.io unterstützt wird. Der Client sendet nicht nur einfache Nachrichten, sondern verfügt auch über Befehle für Emotes /mir, private Nachrichten mit / msg, und erlauben, dass Spitznamen mit geändert werden / nick.

Ein wenig über Readline

Dies ist wahrscheinlich die einfachste Verwendung von Readline:

var readline = required ('readline'); var rl = readline.createInterface (process.stdin, process.stdout); rl.question ("Wie heißt du?") function (answer) console.log ("Hallo" + Antwort); rl.close (););

Wir schließen das Modul ein, erstellen die Readline-Schnittstelle mit den Standard-Eingabe- und -Ausgabeströmen und stellen dem Benutzer eine einmalige Frage. Dies ist die erste Verwendung von Readline: Fragen stellen. Wenn Sie etwas mit einem Benutzer bestätigen müssen, möglicherweise in der Form des beliebten "Wollen Sie das tun? (J / n)", das CLI-Tools durchdringt, readline.question () ist der Weg, es zu tun.

Die andere Funktionalität, die Readline bietet, ist die Eingabeaufforderung, die von ihrem Standard aus angepasst werden kann. ">"Zeichen und vorübergehend angehalten, um Eingaben zu verhindern. Für unseren Readline-Chat-Client wird dies unsere primäre Schnittstelle sein. Es wird ein einziges Vorkommen von readline.question () den Benutzer nach einem Spitznamen fragen, aber alles andere wird sein readline.prompt ().

Verwalten Sie Ihre Abhängigkeiten

Beginnen wir mit dem langweiligen Teil: Abhängigkeiten. Dieses Projekt macht Gebrauch von socket.io, das socket.io-client Paket und ansi-color. Ihre packages.json Die Datei sollte ungefähr so ​​aussehen:

"name": "ReadlineChatExample", "version": "1.0.0", "description": "CLI-Chat mit readline und socket.io", "author": "Matt Harzewski", "abhängigkeiten": "socket .io ":" latest "," socket.io-client ":" latest "," ansi-color ":" latest "," private ": true

Lauf npm installieren und du solltest gut sein.

Der Server

Für dieses Tutorial verwenden wir einen unglaublich einfachen Socket.io-Server. Einfacher geht es nicht:

var socketio = erfordern ('socket.io'); // Listen an Port 3636 var io = socketio.listen (3636); io.sockets.on ('Verbindung', Funktion (Socket) // Sendet die Nachricht eines Benutzers an alle anderen Personen im Raum Socket.on ('Senden', Funktion (Daten) io.sockets.emit ('Nachricht', Daten); ); );

Sie müssen lediglich eine eingehende Nachricht von einem Client übernehmen und an alle anderen weiterleiten. Der Server wäre wahrscheinlich robuster für eine größere Anwendung, aber für dieses einfache Beispiel sollte es ausreichend sein.

Dies sollte im Projektverzeichnis als gespeichert werden server.js.

Der Client: Inklusive & Einrichtung

Bevor wir zum unterhaltsamen Teil kommen, müssen wir unsere Abhängigkeiten einbeziehen, einige Variablen definieren und die Readline-Schnittstelle und die Socket-Verbindung starten.

var readline = required ('readline'), socketio = requir ('socket.io-client'), util = requir ('util'), color = requir ("ansi-color"). set; var nick; var socket = socketio.connect ('localhost', port: 3636); var rl = readline.createInterface (process.stdin, process.stdout);

Der Code ist an dieser Stelle ziemlich selbsterklärend. Wir haben unsere Nickname-Variable, die Socket-Verbindung (über die socket.io-client Paket) und unsere Readline-Schnittstelle.

Socket.io stellt über Port eine Verbindung zu localhost her 3636 In diesem Beispiel würde dies natürlich in die Domäne und den Port Ihres eigenen Servers geändert, wenn Sie eine Produktions-Chat-App erstellen. (Es hat nicht viel Sinn, mit sich selbst zu chatten!)

Der Kunde: Nach dem Namen des Benutzers fragen

Nun zur ersten Verwendung von Readline! Wir möchten den Benutzer nach der Wahl des Spitznamens fragen, wodurch er im Chatroom identifiziert wird. Dafür verwenden wir Readline's Frage() Methode.

// Setze den Benutzernamen rl.question ("Bitte gib einen Spitznamen ein:", function (name) nick = name; var msg = nick + "hat sich dem Chat angeschlossen"; socket.emit ('send', type: ' notice ', message: msg); rl.prompt (true););

Wir setzen die Nick-Variable von vorher auf den vom Benutzer erfassten Wert, senden eine Nachricht an den Server (die an die anderen Clients weitergeleitet wird), dass unser Benutzer dem Chat beigetreten ist, und wechseln dann die Readline-Schnittstelle wieder in den Aufforderungsmodus. Das wahr Wert übergeben an Prompt() stellt sicher, dass das Eingabeaufforderungszeichen richtig angezeigt wird. (Andernfalls kann sich der Cursor an die Nullposition in der Zeile bewegen und>"wird nicht angezeigt.)

Leider hat Readline ein frustrierendes Problem mit der Prompt() Methode. Es spielt sich nicht gut mit console.log (), die den Text in der gleichen Zeile wie das Eingabeaufforderungszeichen ausgeben wird.>"Charaktere überall und andere Verrückte. Um dies zu beheben, werden wir sie nicht verwenden console.log an einem beliebigen Ort in dieser Anwendung. Stattdessen sollte die Ausgabe an diese Funktion übergeben werden:

function console_out (msg) process.stdout.clearLine (); process.stdout.cursorTo (0); console.log (msg); rl.prompt (true); 

Diese leicht Hacky Solution stellt sicher, dass die aktuelle Zeile in der Konsole leer ist und sich der Cursor in der Nullposition befindet, bevor die Ausgabe gedruckt wird. Dann wird explizit verlangt, dass die Eingabeaufforderung anschließend erneut ausgegeben wird.

Für den Rest dieses Tutorials werden Sie sehen console_out () anstatt console.log ().

Der Kunde: Umgang mit Eingaben

Es gibt zwei Arten von Eingaben, die ein Benutzer eingeben kann: Chat und Befehle. Wir wissen, dass Befehlen ein Schrägstrich vorangestellt ist, so dass man leicht zwischen den beiden unterscheiden kann.

Readline hat mehrere Event-Handler, aber der wichtigste ist zweifellos Linie. Jedes Mal, wenn im Eingabestrom ein Zeilenvorschubzeichen (von der Return- oder der Eingabetaste) erkannt wird, wird dieses Ereignis ausgelöst. Wir müssen uns also einhaken Linie für unseren Eingabehandler.

rl.on ('line', Funktion (line) if (line [0] == "/" && line.length> 1) var cmd = line.match (/ [az] + \ b /) [0 ]; var arg = line.substr (cmd.length + 2, line.length); chat_command (cmd, arg); else // Chatnachricht senden socket.emit ('send', type: 'chat', message: line, nick: nick); rl.prompt (true););

Wenn das erste Zeichen der Eingabezeile ein Schrägstrich ist, wissen wir, dass es sich um einen Befehl handelt, für den mehr Verarbeitung erforderlich ist. Ansonsten senden wir einfach eine normale Chat-Nachricht und setzen die Eingabeaufforderung zurück. Beachten Sie den Unterschied zwischen den Daten, die hier über den Socket gesendet werden, und für die Join-Nachricht im vorherigen Schritt. Es verwendet eine andere Art, Der empfangende Client weiß also, wie er die Nachricht formatiert, und wir übergeben die Nick auch variabel.

Der Befehlsname (cmd) und der folgende Text (arg) werden mit etwas Regex und Teilstringmagie isoliert, dann geben wir sie an eine Funktion weiter, die den Befehl verarbeitet.

function chat_command (cmd, arg) switch (cmd) case 'nick': var notice = nick + "änderte ihren Namen in" + arg; nick = arg; socket.emit ('send', type: 'notice', message: notice); brechen; case 'msg': var to = arg.match (/ [a-z] + \ b /) [0]; var message = arg.substr (bis.Länge, Arg.Länge); socket.emit ('send', type: 'tell', message: message, an: an, von: nick); brechen; case 'me': var emote = nick + "" + arg; socket.emit ('send', type: 'emote', message: emote); brechen; default: console_out ("Dies ist kein gültiger Befehl."); 

Wenn der Benutzer eingibt / Nick Gollum, das Nick Variable wird auf zurückgesetzt Gollum, wo könnte es gewesen sein Smeagol vorher und ein Hinweis wird an den Server gesendet.

Wenn der Benutzer eingibt / msg bilbo Wo ist das wertvolle?, Der gleiche reguläre Ausdruck wird verwendet, um den Empfänger und die Nachricht zu trennen, dann ein Objekt mit dem Typ von sagen wird an den Server geschoben. Diese wird etwas anders angezeigt als eine normale Nachricht und sollte für andere Benutzer nicht sichtbar sein. Zwar wird unser übermäßig einfacher Server die Nachricht blind an alle weiterleiten, aber der Client ignoriert Meldungen, die nicht an den richtigen Benutzernamen adressiert sind. Ein robusterer Server könnte diskreter sein.

Der Emote-Befehl wird in der Form von verwendet Ich esse ein zweites Frühstück. Der Spitzname wird dem Emote auf eine Weise vorangestellt, die jedem bekannt sein sollte, der IRC oder ein Multiplayer-Rollenspiel gespielt hat. Dann wird er auf den Server übertragen.

Der Kunde: Umgang mit eingehenden Nachrichten

Nun benötigt der Client eine Möglichkeit, Nachrichten zu empfangen. Alles, was wir tun müssen, ist das Socket.io-Client Botschaft Ereignis und formatieren Sie die Daten entsprechend für die Ausgabe.

socket.on ('message', Funktion (data) var leader; if (data.type == 'chat' && data.nick! = nick) leader = color ("<"+data.nick+"> "," grün "); console_out (leader + data.message); else if (data.type ==" notice ") console_out (color (data.message, 'cyan')); else if (data. type == "tell" && data.to == nick) leader = color ("[" + data.from + "->" + data.to + "]", "red"); console_out (führer + data.message ; else if (data.type == "emote") console_out (color (data.message, "cyan")););

Nachrichten mit einem Typ von Plaudern Das waren nicht Der Client, der unseren Benutzernamen verwendet, wird mit dem Benutzernamen und dem Chat-Text angezeigt. Der Benutzer kann bereits sehen, was er in die Readline eingetippt hat. Es hat also keinen Sinn, ihn erneut auszugeben. Hier verwende ich die ansi-color Paket, um die Ausgabe ein wenig zu färben. Es ist nicht unbedingt notwendig, aber es macht den Chat leichter zu folgen.

Nachrichten mit einem Typ von beachten oder emote werden unverändert gedruckt, obwohl sie in Cyan gefärbt sind.

Wenn die Nachricht ein ist sagen und der Kurzname entspricht dem aktuellen Namen dieses Clients. Die Ausgabe hat die Form von [Jemand-> Du] Hallo!. Das ist natürlich nicht besonders privat. Wenn du sehen wolltest jedermanns Nachrichten, alles, was Sie tun müssten, ist die && data.to == Nickname Teil. Im Idealfall sollte der Server wissen, an welchen Client die Nachricht gesendet werden soll, und sollte sie nicht an Clients senden, die sie nicht benötigen. Dies führt jedoch zu unnötiger Komplexität, die den Rahmen dieses Tutorials sprengt.

Feuer es auf!

Mal sehen, ob alles funktioniert. Starten Sie zum Testen den Server, indem Sie ihn ausführen Knoten server.js und dann ein paar neue Terminalfenster öffnen. In den neuen Fenstern ausführen Knoten client.js und geben Sie einen Spitznamen ein. Sie sollten dann in der Lage sein, zwischen ihnen zu chatten, vorausgesetzt, alles läuft gut.

.