利用Node.js的Net模塊實現一個命令行多人聊天室

一、net模塊基本API
要使用Node.js的net模塊實現一個命令行聊天室,就必須先了解NET模塊的API使用。NET模塊API分爲兩大類:Server和Socket類、工廠方法。
Server類以下圖所示:
server類
net.Server類能夠用來建立一個TCP或本地服務器,繼承了EventEmitter。
Socket類以下:
socket類
net.Socket類通常用建立一個socket客戶端或者是net.Server connection事件的參數。
工廠方法以下:

以上三個圖展現了API的使用,其實NET模塊的內部原理和C++網絡編程差很少的,都是如下步驟。
服務端:javascript

  1. 建立socket套接字
  2. 綁定IP和端口
  3. 啓動監聽
  4. 等待客戶端鏈接
  5. 與客戶端進行通訊
  6. 關閉socket

客戶端:java

  1. 建立socket套接字
  2. 鏈接server服務器
  3. 與服務器進行通訊
  4. 關閉socket

以下圖所示:
編程

二、聊天室的設計和實現
上面學習了NET模塊API的使用,接下來便開始實現命令行聊天室,咱們不須要作的很複雜,只需實現以下功能便可:服務器

  1. 用戶自定義暱稱,不可更改
  2. 當有新的用戶進入聊天室,或者用戶離開聊天室,廣播給其餘用戶
  3. 用戶發送信息,需廣播給其餘用戶
  4. 客戶端與服務端創建心跳機制
  5. 用戶輸入'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:dom

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

執行結果以下以下:
socket

到此,一個命令行聊天室便作完了。學習

相關文章
相關標籤/搜索