Chat in tempo reale con Readline & Socket.io di Node.js

Cosa starai creando

Node.js ha un modulo sottovalutato nella sua libreria standard che è sorprendentemente utile. Il modulo Readline fa quello che dice sulla scatola: legge una riga di input dal terminale. Questo può essere usato per chiedere all'utente una o due domande, o per creare un prompt nella parte inferiore dello schermo. In questo tutorial, intendo mostrare la capacità di Readline e creare una chat room CLI in tempo reale supportata da Socket.io. Il client invierà non solo messaggi semplici, ma anche comandi per emote con /me, messaggi privati ​​con / msg, e consente di modificare i nickname con / nick.

A Little About Readline

Questo è probabilmente l'uso più semplice di Readline:

var readline = require ('readline'); var rl = readline.createInterface (process.stdin, process.stdout); rl.question ("Qual è il tuo nome?", function (answer) console.log ("Hello," + answer); rl.close (););

Includiamo il modulo, creiamo l'interfaccia Readline con gli stream standard di input e output, quindi chiediamo all'utente una domanda una tantum. Questo è il primo utilizzo di Readline: fare domande. Se hai bisogno di confermare qualcosa con un utente, magari nella forma del sempre popolare, "Vuoi farlo? (Y / n)", che pervade gli strumenti CLI, readline.question () è il modo per farlo.

L'altra funzionalità fornita da Readline è il prompt, che può essere personalizzato dal suo valore predefinito ">"carattere e temporaneamente sospeso per impedire l'input.Per il nostro client di chat readline, questa sarà la nostra interfaccia principale. Ci sarà una singola occorrenza di readline.question () chiedere all'utente un soprannome, ma tutto il resto lo sarà readline.prompt ().

Gestire le tue dipendenze

Iniziamo con la parte noiosa: le dipendenze. Questo progetto farà uso di socket.io, il socket.io-client pacchetto e ANSI-colore. Il tuo packages.json il file dovrebbe apparire in questo modo:

"name": "ReadlineChatExample", "version": "1.0.0", "description": "chat CLI con readline e socket.io", "author": "Matt Harzewski", "dependencies": "socket .io ":" latest "," socket.io-client ":" latest "," ansi-color ":" latest "," private ": true

Correre installazione di npm e dovresti essere bravo ad andare.

Il server

Per questo tutorial, utilizzeremo un server Socket.io incredibilmente semplice. Non ha niente di più fondamentale di questo:

var socketio = require ('socket.io'); // Listen on port 3636 var io = socketio.listen (3636); io.sockets.on ('connection', function (socket) // Trasmette il messaggio di un utente a tutti gli altri nella stanza socket.on ('send', function (data) io.sockets.emit ('message', data);););

Tutto ciò che fa è prendere un messaggio in arrivo da un client e passarlo a tutti gli altri. Il server sarebbe probabilmente più robusto per un'applicazione su più larga scala, ma per questo semplice esempio dovrebbe essere sufficiente.

Questo dovrebbe essere salvato nella directory del progetto come server.js.

Il client: include e installazione

Prima di arrivare alla parte divertente, dobbiamo includere le nostre dipendenze, definire alcune variabili e avviare l'interfaccia Readline e la connessione socket.

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);

Il codice è praticamente auto esplicativo a questo punto. Abbiamo la nostra variabile soprannome, la connessione socket (attraverso il socket.io-client pacchetto) e la nostra interfaccia Readline.

Socket.io si connetterà a localhost sulla porta 3636 in questo esempio, ovviamente questo sarebbe cambiato nel dominio e nella porta del tuo server, se stavi creando un'app di chat di produzione. (Non ha molto senso chiacchierare con te stesso!)

Il cliente: chiedere il nome dell'utente

Ora per il nostro primo utilizzo di Readline! Vogliamo chiedere all'utente la sua scelta di nickname, che li identificherà nella chat. Per questo, useremo Readline domanda() metodo.

// Imposta il nome utente rl.question ("Inserisci un nickname:", funzione (nome) nick = nome; var msg = nick + "è entrato nella chat"; socket.emit ("invia", tipo: " notice ', message: msg); rl.prompt (true););

Impostiamo la variabile nick da prima, al valore raccolto dall'utente, inviamo un messaggio al server (che verrà inoltrato agli altri client) che il nostro utente ha aggiunto alla chat, quindi riaccedi l'interfaccia Readline alla modalità prompt. Il vero valore passato a richiesta() assicura che il carattere prompt sia visualizzato correttamente. (Altrimenti il ​​cursore può spostarsi sulla posizione zero sulla linea e sul>"non verrà mostrato.)

Sfortunatamente, Readline ha un problema frustrante con richiesta() metodo. Non funziona bene con console.log (), che produrrà il testo sulla stessa riga del carattere prompt, lasciando il randagio ">"personaggi ovunque e altre stranezze. Per rimediare a questo, non useremo console.log ovunque in questa applicazione, salva per un posto. Invece, l'output dovrebbe essere passato a questa funzione:

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

Questo leggermente La soluzione hacky garantisce che la riga corrente nella console sia vuota e che il cursore si trovi nella posizione zero prima di stampare l'output. Quindi richiede esplicitamente che il prompt venga emesso nuovamente, in seguito.

Quindi per il resto di questo tutorial, vedrai console_out () invece di console.log ().

Il client: gestione dell'input

Ci sono due tipi di input che un utente può inserire: chat e comandi. Sappiamo che i comandi sono preceduti da una barra, quindi è facile distinguere tra i due.

Readline ha diversi gestori di eventi, ma il più importante è senza dubbio linea. Ogni volta che viene rilevato un carattere di nuova riga nel flusso di input (dal tasto Invio o Invio), questo evento viene attivato. Quindi dobbiamo collegarci linea per il nostro gestore di input.

rl.on ('line', function (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 // invia messaggio di chat socket.emit ('invia', tipo: 'chat', messaggio: line, nick: nick); rl.prompt (true););

Se il primo carattere della riga di input è una barra, sappiamo che è un comando, che richiederà più elaborazione. In caso contrario, inviamo semplicemente un messaggio di chat regolare e ripristiniamo il prompt. Notare la differenza tra i dati inviati sul socket qui e per il messaggio di join nel passaggio precedente. Sta usando un diverso genere, quindi il client ricevente sa come formattare il messaggio e noi passiamo il tacca variabile pure.

Il nome del comando (cmd) e il testo che segue (arg) sono isolati con una piccola regex e una sottostringa magica, quindi li passiamo a una funzione che elabora il comando.

function chat_command (cmd, arg) switch (cmd) case 'nick': var notice = nick + "ha cambiato il loro nome in" + arg; nick = arg; socket.emit ('send', type: 'notice', message: notice); rompere; 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, a: to, from: nick); rompere; case 'me': var emote = nick + "" + arg; socket.emit ('send', type: 'emote', messaggio: emote); rompere; default: console_out ("Questo non è un comando valido."); 

Se l'utente digita / nick gollum, il tacca la variabile è resettata per essere gollum, dove potrebbe essere stato Smeagol prima e un avviso viene inviato al server.

Se l'utente digita / msg bilbo Dove è il prezioso?, la stessa regex viene utilizzata per separare il destinatario e il messaggio, quindi un oggetto con il tipo di raccontare viene inviato al server. Questo verrà visualizzato in modo leggermente diverso da un messaggio normale e non dovrebbe essere visibile agli altri utenti. Certo, il nostro server troppo semplice sposterà ciecamente il messaggio a tutti, ma il client ignorerà tell che non è indirizzato al nickname corretto. Un server più robusto potrebbe essere più discreto.

Il comando emote viene utilizzato sotto forma di / io sto mangiando la seconda colazione. Il nickname è preposto alla emote in un modo che dovrebbe essere familiare a chiunque abbia usato IRC o giocato a un gioco di ruolo multiplayer, quindi è passato al server.

Il client: gestione dei messaggi in entrata

Ora il cliente ha bisogno di un modo per ricevere messaggi. Tutto quello che dobbiamo fare è agganciare il client Socket.io Messaggio evento e formattare i dati in modo appropriato per l'output.

socket.on ('message', function (data) var leader; if (data.type == 'chat' && data.nick! = nick) leader = colore ("<"+data.nick+"> "," verde "); console_out (leader + data.message); else if (data.type ==" notice ") console_out (color (data.message, 'cyan')); else if (data. scrivi == "tell" && data.to == nick) leader = colore ("[" + data.from + "->" + data.to + "]", "rosso"); console_out (leader + data.message ); else if (data.type == "emote") console_out (color (data.message, "cyan")););

Messaggi con un tipo di Chiacchierare quello non erano inviati dal client utilizzando il nostro nickname vengono visualizzati con il nickname e il testo della chat. L'utente può già vedere ciò che ha digitato nella readline, quindi non ha senso riprodurlo di nuovo. Qui sto usando il ANSI-colore pacchetto per colorare un po 'l'output. Non è strettamente necessario, ma rende la chat più facile da seguire.

Messaggi con un tipo di Avviso o emote sono stampati così come sono, anche se colorati come ciano.

Se il messaggio è a raccontare e il nickname è uguale al nome corrente di questo client, l'output assume la forma di [Qualcuno-> Tu] Ciao!. Certo, questo non è terribilmente privato. Se volevi vedere ognuno è messaggi, tutto quello che dovresti fare è estrarre il && data.to == nick parte. Idealmente, il server dovrebbe sapere a quale client inviare il messaggio e non inviarlo ai client che non ne hanno bisogno. Ma ciò aggiunge complessità inutile che va oltre lo scopo di questo tutorial.

Fire It Up!

Ora vediamo se tutto funziona. Per testarlo, avvia il server eseguendo nodo server.js e quindi aprire un paio di nuove finestre di terminale. Nelle nuove finestre, corri nodo client.js e inserisci un soprannome. Dovresti quindi essere in grado di chattare tra loro, assumendo che tutto vada bene.

.