Node.js利用Net模塊實(shí)現(xiàn)多人命令行聊天室的方法
這篇文章介紹的是Node.js利用Net模塊實(shí)現(xiàn)命令行式的多人聊天室,下面話不多說(shuō),來(lái)看看詳細(xì)的介紹吧。
1、net模塊基本API
要使用Node.js的net模塊實(shí)現(xiàn)一個(gè)命令行聊天室,就必須先了解NET模塊的API使用。NET模塊API分為兩大類:
Server和Socket類、工廠方法。
Server類如下圖所示:
net.Server類可以用來(lái)創(chuàng)建一個(gè)TCP或本地服務(wù)器,繼承了EventEmitter。
Socket類如下:
net.Socket類一般用創(chuàng)建一個(gè)socket客戶端或者是net.Server connection事件的參數(shù)。
工廠方法如下:
以上三個(gè)圖展示了API的使用,其實(shí)NET模塊的內(nèi)部原理和C++網(wǎng)絡(luò)編程差不多的,都是以下步驟。
服務(wù)端:
- 創(chuàng)建socket套接字
- 綁定IP和端口
- 啟動(dòng)監(jiān)聽(tīng)
- 等待客戶端連接
- 與客戶端進(jìn)行通信
- 關(guān)閉socket
客戶端:
- 創(chuàng)建socket套接字
- 連接server服務(wù)器
- 與服務(wù)器進(jìn)行通信
- 關(guān)閉socket
如下圖所示:
2、聊天室的設(shè)計(jì)和實(shí)現(xiàn)
上面學(xué)習(xí)了NET模塊API的使用,接下來(lái)便開(kāi)始實(shí)現(xiàn)命令行聊天室,我們不需要做的很復(fù)雜,只需實(shí)現(xiàn)如下功能即可:
- 用戶自定義昵稱,不可更改
- 當(dāng)有新的用戶進(jìn)入聊天室,或者用戶離開(kāi)聊天室,廣播給其他用戶
- 用戶發(fā)送信息,需廣播給其他用戶
- 客戶端與服務(wù)端建立心跳機(jī)制
- 用戶輸入'exit'或者'quit'可以退出聊天室
確定功能之后,便開(kāi)始代碼的編寫。這里我就不一步步分析,直接上代碼了,首先是服務(wù)端:
Server:
const net = require('net'); const server = net.createServer(); const clients = {};//保存客戶端的連接 var client = null;//當(dāng)前客戶連接 var uid = 0; server.on('connection',(socket)=>{ //啟動(dòng)心跳機(jī)制 var isOnline = !0; var keepAliveTimer = socket.timer = setInterval(()=>{ if(!isOnline){ isOnline = !1; client = socket; quit(socket.nick); return; } if(socket.writable){ socket.write('::'); }else{ client = socket; quit(socket.nick); } },3000); socket.on('end',()=>{ console.log(`client disconnected.\n\r`); socket.destroy(); }); socket.on('error',(error)=>{ console.log(error.message); }); socket.on('data',(chunk)=>{ client = socket; var msg = JSON.parse(chunk.toString()); if(msg.cmd=='keep'){ isOnline = !0; return; } dealMsg(msg); }); }); server.on('error',(err)=>{ console.log(err); }); server.on('listening',()=>{ console.log(`listening on ${server.address().address}:${server.address().port}\n\r`); }); server.listen(8060);//啟動(dòng)監(jiān)聽(tīng) /** * 處理用戶信息 */ function dealMsg(msg){ const cmd = msg.cmd; const funs = { 'login':login, 'chat':chat, 'quit':quit, 'exit':quit }; if(typeof funs[cmd] !== 'function') return !1; funs[cmd](msg); } /** * 釋放連接資源 */ function freeConn(conn){ conn.end(); delete clients[conn.uuid]; conn.timer&&clearInterval(conn.timer); } /** * 用戶首次進(jìn)入聊天室 */ function login(msg){ var uuid = ''; uuid = getRndStr(15)+(++uid);//產(chǎn)生用戶ID client.write(`歡迎你,${msg.nick}:這里總共有${Object.keys(clients).length}個(gè)小伙伴在聊天.\r\n`) client.nick = msg.nick; client.uuid = uuid; clients[uuid] = client; broadcast(`系統(tǒng):${msg.nick}進(jìn)入了聊天室.`); } /** * 廣播消息 */ function broadcast(msg){ Object.keys(clients).forEach((uuid)=>{ if((clients[uuid]!=client)& clients[uuid].writable){ clients[uuid].write(msg); } }); } /** * 退出聊天室 */ function quit(nick){ var message = `小伙伴${nick}退出了聊天室.`; broadcast(message); freeConn(client); } function chat(msg){ if(msg.msg.toLowerCase()=='quit'||msg.msg.toLowerCase()=='exit'){ quit(msg.nick); return ; } var message = `${msg.nick}說(shuō):${msg.msg}`; broadcast(message); } /** * 隨機(jī)指定長(zhǎng)度(len)的字符串 */ function getRndStr(len=1){ var rndStr = ''; for (; rndStr.length < len; rndStr += Math.random().toString(36).substr(2)); return rndStr.substr(0, len); }
客戶端代碼如下:
client:
const net = require('net'); const cout = process.stdout; const cin = process.stdin; var client = null; var nick = ''; cout.write(`請(qǐng)輸入昵稱:`); //監(jiān)聽(tīng)命令行輸入 cin.on('data',(chunk)=>{ if(chunk.toString()!='\r\n'){ if(client === null){ nick = (chunk+'').replace(/[\r\n]/ig,""); createClient(); }else{ msg = (chunk+'').replace(/[\r\n]/ig,""); client.write(JSON.stringify({ cmd: 'chat', msg: msg, nick: nick })); //如果輸入是exit或quit則斷開(kāi)連接并退出 if(msg.toLowerCase() == 'exit' || msg.toLowerCase() == 'quit'){ client.end(); cin.end(); return; } cout.write(`你說(shuō):${msg}\n\r`); } }else{ cout.write(`請(qǐng)輸入昵稱:`); } }); function addListener(client) { client.on('connect', () => { cout.write(`已連接到服務(wù)器\n\r`); client.write(JSON.stringify({ cmd: 'login', msg: 'hello server', nick: nick })); }); client.on('end', (chunk) => { cout.write(`與服務(wù)器斷開(kāi)連接.\n\r`); }); client.on('data', (chunk) => { //如果是心跳信息則回應(yīng)keep命令 if(chunk.toString()=='::'){ client.write(JSON.stringify({ cmd: 'keep', msg: '', nick: nick })); return ; } cout.write(`${chunk}\n\r`); }); client.on('error', (err) => { cout.write(`an error has occured.\n\r${err}`); }); } /** * 創(chuàng)建socket并連接服務(wù)器 */ function createClient(){ console.log('\033[2J');//清屏操作 cout.write(`輸入'EXIT OR QUIT'退出聊天室.\r\n`); client = new net.Socket() client.connect({port:8060/*,host:'1.1.1.69'*/}); addListener(client); }
執(zhí)行結(jié)果如下如下:
到此,一個(gè)命令行聊天室便做完了。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流。
相關(guān)文章
node項(xiàng)目使用http模塊發(fā)送get-post請(qǐng)求方式
這篇文章主要介紹了node項(xiàng)目使用http模塊發(fā)送get-post請(qǐng)求方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-09-09nodeJS(express4.x)+vue(vue-cli)構(gòu)建前后端分離實(shí)例(帶跨域)
這篇文章主要介紹了nodeJS(express4.x)+vue(vue-cli)構(gòu)建前后端分離實(shí)例(帶跨域) ,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07使用nodejs搭建一個(gè)簡(jiǎn)易HTTP服務(wù)的實(shí)現(xiàn)示例
本文主要介紹了使用nodejs搭建一個(gè)簡(jiǎn)易HTTP服務(wù)的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05Node.js中的package.json與cnpm命令行工具介紹
這篇文章介紹了Node.js中的package.json與cnpm命令行工具,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06Node.js中package.json中庫(kù)的版本號(hào)(~和^)
這篇文章主要介紹了Node.js中package.json中庫(kù)的版本號(hào)(~和^),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-04-04Nodejs使用express連接數(shù)據(jù)庫(kù)mongoose的示例
數(shù)據(jù)庫(kù)并進(jìn)行操作通常需要使用第三方庫(kù),其中最流行的是mongoose,本文主要介紹了Nodejs使用express連接數(shù)據(jù)庫(kù)mongoose的示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-06-06淺談Node.js 子進(jìn)程與應(yīng)用場(chǎng)景
這篇文章主要介紹了淺談Node.js 子進(jìn)程與應(yīng)用場(chǎng)景,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01