1、net模塊基本API
要使用Node.js的net模塊實現一個命令行聊天室,就必須先了解NET模塊的API使用。NET模塊API分為兩大類:Server和Socket類、工廠方法。
Server類如下圖所示:
net.Server類可以用來創建一個TCP或本地服務器,繼承了EventEmitter。
Socket類如下:
net.Socket類一般用創建一個socket客戶端或者是net.Server connection事件的參數。
工廠方法如下:
以上三個圖展示了API的使用,其實NET模塊的內部原理和C++網絡編程差不多的,都是以下步驟。
服務端:
- 創建socket套接字
- 綁定IP和端口
- 啟動監聽
- 等待客戶端連接
- 與客戶端進行通信
- 關閉socket
客戶端:
- 創建socket套接字
- 連接server服務器
- 與服務器進行通信
- 關閉socket
如下圖所示:
2、聊天室的設計和實現
上面學習了NET模塊API的使用,接下來便開始實現命令行聊天室,我們不需要做的很復雜,只需實現如下功能即可:
- 用戶自定義昵稱,不可更改
- 當有新的用戶進入聊天室,或者用戶離開聊天室,廣播給其他用戶
- 用戶發送信息,需廣播給其他用戶
- 客戶端與服務端建立心跳機制
- 用戶輸入'exit'或者'quit'可以退出聊天室
確定功能之后,便開始代碼的編寫。這里我就不一步步分析,直接上代碼了,首先是服務端:
Server:
const net = require('net');
const server = net.createServer();
const clients = {};//保存客戶端的連接
var client = null;//當前客戶連接
var uid = 0;
server.on('connection',(socket)=>{
//啟動心跳機制
var isOnline = !0;
var keepAliveTimer = socket.timer = setInterval(()=>{
if(!isOnline){
client = socket;
quit(socket.nick);
return;
}
if(socket.writable){
isOnline = !1;
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);//啟動監聽
/**
* 處理用戶信息
*/
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);
}
/**
* 用戶首次進入聊天室
*/
function login(msg){
var uuid = '';
uuid = getRndStr(15)+(++uid);//產生用戶ID
client.write(`歡迎你,${msg.nick}:這里總共有${Object.keys(clients).length}個小伙伴在聊天.\r\n`)
client.nick = msg.nick;
client.uuid = uuid;
clients[uuid] = client;
broadcast(`系統:${msg.nick}進入了聊天室.`);
}
/**
* 廣播消息
*/
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}說:${msg.msg}`;
broadcast(message);
}
/**
* 隨機指定長度(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(`請輸入昵稱:`);
//監聽命令行輸入
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則斷開連接並退出
if(msg.toLowerCase() == 'exit' || msg.toLowerCase() == 'quit'){
client.end();
cin.end();
return;
}
cout.write(`你說:${msg}\n\r`);
}
}else{
cout.write(`請輸入昵稱:`);
}
});
function addListener(client) {
client.on('connect', () => {
cout.write(`已連接到服務器\n\r`);
client.write(JSON.stringify({
cmd: 'login',
msg: 'hello server',
nick: nick
}));
});
client.on('end', (chunk) => {
cout.write(`與服務器斷開連接.\n\r`);
});
client.on('data', (chunk) => {
//如果是心跳信息則回應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}`);
});
}
/**
* 創建socket並連接服務器
*/
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);
}
執行結果如下如下:
到此,一個命令行聊天室便做完了。