Realtime chat met Readline & Socket.io van Node.js

Wat je gaat creëren

Node.js heeft een minder gewaardeerde module in zijn standaardbibliotheek die verrassend nuttig is. De Readline-module doet wat hij zegt op de doos: hij leest een invoerregel van de terminal. Dit kan worden gebruikt om de gebruiker een paar vragen te stellen, of om een ​​vraag onderaan het scherm te maken. In deze zelfstudie wil ik laten zien hoe goed Readline is en maak een real-time CLI-chatruimte die wordt ondersteund door Socket.io. De client verzendt niet alleen eenvoudige berichten, maar heeft ook commando's voor emotes met /me, privéberichten met / msg, en laat bijnamen veranderen met /Nick.

Een beetje over Readline

Dit is waarschijnlijk het eenvoudigste gebruik van Readline:

var readline = require ('readline'); var rl = readline.createInterface (process.stdin, process.stdout); rl.question ("Wat is uw naam?", functie (antwoord) console.log ("Hallo", + antwoord); rl.close (););

We nemen de module op, maken de Readline-interface met de standaard invoer- en uitvoerstromen en vragen de gebruiker een eenmalige vraag. Dit is het eerste gebruik van Readline: vragen stellen. Als u iets met een gebruiker wilt bevestigen, misschien in de vorm van het immer populaire: "Wilt u dit doen? (J / n)", waarmee CLI-tools zijn geperfectioneerd, readline.question () is de manier om het te doen.

De andere functionaliteit die Readline biedt, is de prompt, die kan worden aangepast vanuit de standaard ">"karakter en tijdelijk gepauzeerd om invoer te voorkomen. Voor onze Readline-chatclient zal dit onze primaire interface zijn. Er zal een enkele keer voorkomen dat readline.question () om de gebruiker om een ​​bijnaam te vragen, maar al het andere zal zijn readline.prompt ().

Je afhankelijkheden beheren

Laten we beginnen met het saaie deel: afhankelijkheden. Dit project zal gebruik maken van socket.io, de socket.io-client pakket en ansi-color. Jouw packages.json bestand zou er ongeveer zo uit moeten zien:

"naam": "ReadlineChatExample", "version": "1.0.0", "description": "CLI-chat met readline en socket.io", "auteur": "Matt Harzewski", "afhankelijkheden": "socket .io ":" latest "," socket.io-client ":" latest "," ansi-color ":" latest "," private ": true

Rennen npm installeren en je zou goed moeten zijn om te gaan.

De server

Voor deze zelfstudie gebruiken we een ongelooflijk eenvoudige Socket.io-server. Het wordt niet eenvoudiger dan dit:

var socketio = require ('socket.io'); // Luister naar poort 3636 var io = socketio.listen (3636); io.sockets.on ('connection', function (socket) // Broadcast een gebruikersbericht naar iedereen in de room socket.on ('send', function (data) io.sockets.emit ('message', data);););

Het enige dat het doet is een inkomend bericht van de ene klant ontvangen en doorgeven aan de anderen. De server zou waarschijnlijk robuuster zijn voor een toepassing op grotere schaal, maar voor dit eenvoudige voorbeeld zou het voldoende moeten zijn.

Dit zou in de projectdirectory moeten worden opgeslagen als server.js.

De klant: omvat & stelt in

Voordat we bij het leuke gedeelte komen, moeten we onze afhankelijkheden opnemen, een aantal variabelen definiëren en de Readline-interface en socketverbinding starten.

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

De code spreekt op dit punt vrijwel vanzelf. We hebben onze bijnaam variabele, de socket-verbinding (via de socket.io-client pakket) en onze Readline-interface.

Socket.io maakt verbinding met localhost via poort 3636 in dit voorbeeld zou dit natuurlijk worden gewijzigd naar het domein en de poort van je eigen server, als je een productiechat-app maakt. (Het heeft niet veel zin om met jezelf te praten!)

De klant: vragen om de gebruikersnaam

Nu voor ons eerste gebruik van Readline! We willen de gebruiker vragen naar hun bijnaam, die hen in de chatroom zal identificeren. Hiervoor gebruiken we Readline's vraag() methode.

// Stel de gebruikersnaam rl.question in ("Voer een bijnaam in:", functie (naam) nick = naam; var msg = nick + "is lid geworden van de chat"; socket.emit ('send', type: ' notice ', message: msg); rl.prompt (true););

We hebben de nick-variabele van tevoren ingesteld op de waarde die door de gebruiker is verzameld, sturen een bericht naar de server (die wordt doorgestuurd naar de andere clients) dat onze gebruiker lid is geworden van de chat en vervolgens de Readline-interface terugzetten naar de prompt-modus. De waar waarde doorgegeven aan prompt () zorgt ervoor dat het snelle teken correct wordt weergegeven. (Anders kan de cursor naar de nulpositie op de regel gaan en de ">"zal niet getoond worden.)

Helaas heeft Readline een frustrerend probleem met de prompt () methode. Het speelt niet leuk met console.log (), die tekst zal uitvoeren op dezelfde regel als het promptteken, waardoor verdwaald blijft ">"karakters overal en andere gekheid. Om dit te verhelpen, zullen we het niet gebruiken console.log overal in deze applicatie, op één plaats opslaan. In plaats daarvan moet de uitvoer aan deze functie worden doorgegeven:

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

Deze licht hacky-oplossing zorgt ervoor dat de huidige regel in de console leeg is en dat de cursor in de nulpositie staat voordat de uitvoer wordt afgedrukt. Vervolgens wordt expliciet gevraagd om de prompt later opnieuw uit te voeren.

Dus voor de rest van deze tutorial zul je het zien console_out () in plaats van console.log ().

De klant: invoer verwerken

Er zijn twee soorten invoer die een gebruiker kan invoeren: chat en opdrachten. We weten dat opdrachten worden voorafgegaan door een schuine streep, dus het is gemakkelijk om onderscheid te maken tussen deze twee.

Readline heeft verschillende event handlers, maar de belangrijkste is ongetwijfeld lijn. Telkens wanneer een nieuwlijnteken wordt gedetecteerd in de invoerstroom (van de return- of de enter-toets), wordt deze gebeurtenis geactiveerd. Dus we moeten inhaken lijn voor onze invoerbehandelaar.

rl.on ('regel', functie (regel) if (regel [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 // stuur chatbericht socket.emit ('send', type: 'chat', message: line, nick: nick); rl.prompt (true););

Als het eerste teken van de invoerregel een schuine streep is, weten we dat het een opdracht is die meer verwerking vereist. Anders sturen we gewoon een normaal chatbericht en stellen we de prompt opnieuw in. Let op het verschil tussen de gegevens die hier via de socket worden verzonden en voor het join-bericht in de vorige stap. Het gebruikt een ander type, dus de ontvangende klant weet hoe het bericht moet worden geformatteerd en we passeren het Nick variabel ook.

De opdrachtnaam (cmd) en de tekst die volgt (arg) worden geïsoleerd met een beetje regex en ondermijning van magie, dan geven we ze door aan een functie die het commando verwerkt.

function chat_command (cmd, arg) switch (cmd) case 'nick': var notice = nick + "heeft zijn naam gewijzigd in" + arg; nick = arg; socket.emit ('send', type: 'notice', message: notice); breken; case 'msg': var to = arg.match (/ [a-z] + \ b /) [0]; var message = arg.substr (to.length, arg.length); socket.emit ('send', type: 'tell', message: message, to: to, from: nick); breken; case 'me': var emote = nick + "" + arg; socket.emit ('send', type: 'emote', message: emote); breken; standaard: console_out ("Dat is geen geldige opdracht."); 

Als de gebruiker typt / nick gollum, de Nick variabele wordt gereset om te zijn gollum, waar het zou kunnen zijn Smeagol ervoor en er wordt een bericht naar de server gestuurd.

Als de gebruiker typt / msg bilbo Waar is het kostbare?, dezelfde regex wordt gebruikt om de ontvanger en het bericht te scheiden, en vervolgens een object met het type vertellen wordt naar de server geduwd. Dit wordt een beetje anders weergegeven dan een normaal bericht en zou niet zichtbaar moeten zijn voor andere gebruikers. Toegegeven, onze overdreven eenvoudige server zal de boodschap blindelings naar iedereen pushen, maar de klant zal de meldingen negeren die niet zijn geadresseerd aan de juiste bijnaam. Een robuustere server kan discreter zijn.

Het commando emote wordt gebruikt in de vorm van / ik eet tweede ontbijt. De bijnaam is vooraf aan de emote toegevoegd op een manier die vertrouwd zou moeten zijn voor iedereen die IRC heeft gebruikt of een multiplayer roleplaying-spel heeft gespeeld, waarna deze naar de server is geduwd.

De klant: inkomende berichten afhandelen

Nu heeft de klant een manier nodig om berichten te ontvangen. Alles wat we moeten doen is inhaken op de Socket.io-client bericht gebeurtenis en formatteer de gegevens op de juiste manier voor de uitvoer.

socket.on ('message', function (data) var leader; if (data.type == 'chat' && data.nick! = nick) leader = color ("<"+data.nick+"> "," groen "); 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 (leader + data.message ); else if (data.type == "emote") console_out (color (data.message, "cyan")););

Berichten met een type babbelen dat waren niet verzonden door de client met behulp van onze bijnaam worden weergegeven met de bijnaam en chat-tekst. De gebruiker kan al zien wat ze in de leesregel hebben getypt, dus het heeft geen zin om het opnieuw uit te voeren. Hier gebruik ik de ansi-color pakket om de uitvoer een beetje in te kleuren. Het is niet strikt noodzakelijk, maar het maakt de chat gemakkelijker te volgen.

Berichten met een type merk op of emote zijn gedrukt zoals het is, hoewel gekleurd cyaan.

Als het bericht een is vertellen en de bijnaam is gelijk aan de huidige naam van deze klant, de uitvoer heeft de vorm van [Iemand-> Jij] Hallo!. Natuurlijk is dit niet erg privé. Als je het wilde zien van iedereen berichten, alles wat je zou moeten doen is de && data.to == nick een deel. Idealiter zou de server moeten weten naar welke client het bericht moet worden verzonden en niet naar clients moeten worden verzonden die deze niet nodig hebben. Maar dat voegt nodeloze complexiteit toe die buiten het bestek van deze tutorial valt.

Fire It Up!

Laten we nu kijken of alles werkt. Om het uit te testen, start u de server door te draaien knooppunt server.js en open vervolgens een paar nieuwe terminalvensters. In de nieuwe vensters, uitvoeren knoop client.js en voer een bijnaam in. Je zou dan tussen hen moeten kunnen chatten, ervan uitgaande dat alles goed gaat.

.