Node.js 在其標準函式庫中有一個未被充分重視的模組,但卻非常有用。 Readline 模組依照包裝盒上的說明執行操作:從終端機讀取一行輸入。這可用於詢問使用者一兩個問題,或在螢幕底部建立提示。在本教程中,我打算展示 Readline 的功能並製作一個由 Socket.io 支援的即時 CLI 聊天室。客戶端不僅可以發送簡單的訊息,還可以使用 /me
發送表情命令,使用 /msg
發送私人訊息,並允許使用 /nick
。
這可能是 Readline 最簡單的用法:
var readline = require('readline'); var rl = readline.createInterface(process.stdin, process.stdout); rl.question("What is your name? ", function(answer) { console.log("Hello, " + answer ); rl.close(); });
我們包含該模組,使用標準輸入和輸出流創建 Readline 接口,然後向用戶詢問一個一次性問題。這是Readline的第一個用法:問問題。如果您需要與使用者確認某些內容,可能採用流行的形式,“您想這樣做嗎?(y/n)”,這種形式普遍存在於CLI 工具中,readline.question ()
就是這樣做的方法。
#Readline 提供的另一個功能是提示,可以根據其預設的「>」字元進行自訂,並暫時暫停以防止輸入。對於我們的 Readline 聊天用戶端,這將是我們的主要介面。將會出現一次 readline.question()
來詢問使用者暱稱,但其他所有內容都會是 readline.prompt()
。
讓我們從無聊的部分開始:依賴關係。專案將使用 socket.io
、socket.io-client
套件和 ansi-color
。您的 packages.json
檔案應如下所示:
{ "name": "ReadlineChatExample", "version": "1.0.0", "description": "CLI chat with readline and socket.io", "author": "Matt Harzewski", "dependencies": { "socket.io": "latest", "socket.io-client": "latest", "ansi-color": "latest" }, "private": true }
運行 npm install
就可以了。
在本教學中,我們將使用一個非常簡單的 Socket.io 伺服器。沒有比這更基本的了:
var socketio = require('socket.io'); // Listen on port 3636 var io = socketio.listen(3636); io.sockets.on('connection', function (socket) { // Broadcast a user's message to everyone else in the room socket.on('send', function (data) { io.sockets.emit('message', data); }); });
它所做的只是從一個客戶端接收傳入訊息並將其傳遞給其他所有人。對於更大規模的應用程式來說,伺服器可能會更加健壯,但對於這個簡單的範例來說,它應該足夠了。
#這應該保存在專案目錄中,名稱為 server.js
。
#在我們開始有趣的部分之前,我們需要包含我們的依賴項,定義一些變量,並啟動 Readline 介面和套接字連接。
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);
此時,程式碼幾乎是不言自明的。我們已經有了暱稱變數、套接字連接(透過 socket.io-client
套件)和 Readline 介面。
在此範例中,Socket.io 將透過連接埠 3636
連接到本機,當然,如果您正在製作生產聊天應用程序,這將變更為您自己的伺服器的網域和連接埠。 (與自己聊天沒有太大意義!)
#現在我們第一次使用 Readline!我們想詢問用戶選擇的暱稱,這將在聊天室中識別他們。為此,我們將使用 Readline 的 question()
方法。
// Set the username rl.question("Please enter a nickname: ", function(name) { nick = name; var msg = nick + " has joined the chat"; socket.emit('send', { type: 'notice', message: msg }); rl.prompt(true); });
我們將先前的 nick 變數設定為從使用者收集的值,向伺服器發送訊息(該訊息將轉發到其他客戶端)我們的使用者已加入聊天,然後將 Readline 介面切換回提示模式。傳遞給 prompt()
的 true
值可確保正確顯示提示符號。 (否則遊標可能會移動到該行的零位置,並且不會顯示“>”。)
不幸的是,Readline 在 prompt()
方法方面有令人沮喪的問題。它與console.log()
配合得不太好,它會將文字輸出到與提示字元相同的行,從而在各處留下雜散的“>”字符和其他字符怪異。為了解決這個問題,我們不會在此應用程式中的任何位置使用 console.log
,僅保留一個位置。相反,輸出應該傳遞給此函數:
function console_out(msg) { process.stdout.clearLine(); process.stdout.cursorTo(0); console.log(msg); rl.prompt(true); }
這個稍微的hacky解決方案可確保控制台中的當前行為空,並且在列印輸出之前遊標位於零位置。然後它明確要求再次輸出提示。
#因此,在本教學的其餘部分中,您將看到 console_out()
而不是 console.log()
。
使用者可以輸入兩種類型的輸入:聊天和命令。我們知道命令前面有斜杠,因此很容易區分兩者。
#Readline 有几个事件处理程序,但最重要的无疑是 line
。每当在输入流中检测到换行符(通过 return 或 Enter 键)时,就会触发此事件。因此,我们需要为我们的输入处理程序挂钩 line
。
rl.on('line', function (line) { if (line[0] == "/" && line.length > 1) { var cmd = line.match(/[a-z]+\b/)[0]; var arg = line.substr(cmd.length+2, line.length); chat_command(cmd, arg); } else { // send chat message socket.emit('send', { type: 'chat', message: line, nick: nick }); rl.prompt(true); } });
如果输入行的第一个字符是斜杠,我们就知道这是一个命令,这将需要更多的处理。否则,我们只是发送常规聊天消息并重置提示。请注意此处通过套接字发送的数据与上一步中的加入消息之间的差异。它使用不同的 type
,因此接收客户端知道如何格式化消息,并且我们还传递 nick
变量。
命令名称(cmd
)和后面的文本(arg
)用一些正则表达式和子字符串魔术隔离,然后我们将它们传递给处理函数命令。
function chat_command(cmd, arg) { switch (cmd) { case 'nick': var notice = nick + " changed their name to " + arg; nick = arg; socket.emit('send', { type: 'notice', message: notice }); break; 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 }); break; case 'me': var emote = nick + " " + arg; socket.emit('send', { type: 'emote', message: emote }); break; default: console_out("That is not a valid command."); } }
如果用户输入 /nick gollum
,则 nick
变量将重置为 gollum
,它可能是 smeagol
之前,并将通知推送到服务器。
如果用户输入 /msg bilbo 珍贵在哪里?
,使用相同的正则表达式来分隔接收者和消息,然后是一个类型为 tell
被推送到服务器。这将与普通消息的显示方式略有不同,并且其他用户不应该看到。诚然,我们过于简单的服务器会盲目地将消息推送给每个人,但客户端会忽略未发送到正确昵称的通知。更强大的服务器可以更加离散。
表情命令的使用形式为/我正在吃第二顿早餐
。昵称以任何使用过 IRC 或玩过多人角色扮演游戏的人都应该熟悉的方式添加到表情符号中,然后将其推送到服务器。
现在客户端需要一种接收消息的方法。我们需要做的就是挂钩 Socket.io 客户端的 message
事件并适当地格式化数据以进行输出。
socket.on('message', function (data) { var leader; if (data.type == 'chat' && data.nick != nick) { leader = color("<"+data.nick+"> ", "green"); 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")); } });
客户端使用我们的昵称发送的类型为 chat
的消息将与昵称和聊天文本一起显示。用户已经可以看到他们在 Readline 中输入的内容,因此没有必要再次输出它。在这里,我使用 ansi-color
包对输出进行一点着色。这并不是绝对必要的,但它使聊天更容易理解。
类型为 notice
或 emote
的消息按原样打印,但颜色为青色。
如果消息是 tell
,且昵称等于此客户端的当前名称,则输出采用 [Somebody->You] Hi!
的形式。当然,这并不是非常私密的事情。如果您想查看每个人的消息,您只需取出 && data.to == nick
部分即可。理想情况下,服务器应该知道将消息推送到哪个客户端,而不是将其发送给不需要它的客户端。但这增加了不必要的复杂性,超出了本教程的范围。
现在让我们看看是否一切正常。要对其进行测试,请通过运行 node server.js
启动服务器,然后打开几个新的终端窗口。在新窗口中,运行 node client.js
并输入昵称。假设一切顺利,那么您应该能够在他们之间聊天。
希望本教程向您展示了 Readline 模块的入门是多么容易。您可能想尝试向聊天应用程序添加更多功能,以进行更多练习。最后,查看 Readline 文档以获取完整的 API。
以上是使用Node.js的Readline和Socket.io實現即時聊天的詳細內容。更多資訊請關注PHP中文網其他相關文章!