Node TCP /UDP 簡易聊天室

WZZ發表於2018-04-06

Node.js 為實現tcp 提供了一個模組->net 使用時直接require這個模組

建立一個tcp服務

  • net.createServer(callback),回撥函式是連線事件的監聽器,當連線到來時才會執行,它的參 數socket套接字是一個duplex(雙工流)可以支援讀操作和寫操作,socket每次連線時都會產生一個新的socket,每個socket與自己的客戶端通訊
  • server.listen可以設定服務監聽的埠,主機名, backlog服務端處理的最大請求,預設是511,還有回撥函式,當服務啟動成功會呼叫
  • server.maxConnections 配置服務的最大連線數
  • server.getConnections 得到連線時(當請求到來時),會觸發該方法
  • socket.write 在 socket 上傳送資料
  • socket.on('data',callback) 接受客戶端資料
  • socket.on('end',callback)客戶端關閉時呼叫
  • socket.end(); 可以觸發客戶端的關閉事件
  • server.on('close',callbacl) 只有顯示呼叫server.close()時會觸發, close事件表示服務端不再接收新的請求了,當前的連線還能繼續使用,當客戶端連線全部關閉後會執行close事件
  • server.unref() 如果所有客戶端都關閉了,服務端就關閉,如果有新的客戶端連線仍然可以繼續通訊
let net = require('net');
let server = net.createServer(function(socket){
   server.maxConnections = 2;
   server.getConnections(function(err,count){
       socket.write(`當前最大容納${server.maxConnections},現在${count}人`)
   });
   socket.setEncoding('utf8');
   socket.on('data',function(data){
       console.log(data);
       socket.end(); 
       server.close();
       server.unref();
   });
   socket.on('end',function(){
       console.log('客戶端關閉');
   });
});

let port = 8080;
server.listen(port,'localhost',function(){
   console.log(`server start ${port}`)
});
// close事件只有呼叫close方法才會觸發
server.on('close',function(){
   console.log('服務端關閉');
})
//當埠被佔用了,更改埠號
server.on('error',function(err){
   //EADDRINUSE 當前埠號被呼叫
   if(err.code === 'EADDRINUSE'){
       server.listen(++port)
   }
});
複製程式碼

測試

可以通過telnet localhost 8080 (telnet會有亂碼問題,不太好用)
或用putty工具來訪問服務,我的設定如下,開啟一個與伺服器的連線後,回車即傳送內容到服務

Node TCP /UDP 簡易聊天室

例:客戶端輸入的內容寫入1.txt檔案

let net = require('net');
let path = require('path');
let ws = require('fs').createWriteStream(path.join(__dirname,'./1.txt'));
// pipe (readale data不能同時使用)
let server = net.createServer(function(socket){
   socket.pipe(ws,{end:false});
   setTimeout(function(){
        ws.end(); // 關閉可寫流
        socket.unpipe(ws); // 取消管道
    },5000)
});
server.listen(8080);
複製程式碼

利用可讀流pipe方法,邊讀取客戶端輸入的內容,邊寫入檔案,但是當多個客戶端輸入內容時,如果一個客戶端關閉,當前連線的socket就會關閉,同時會關閉ws可寫流,但多個socket用的一個ws,所以引數{end:false} 用於設定這個可寫流不關閉,其他未關閉的客戶端仍然可寫

例:等待客戶端輸入 ,過5s 再列印出來

let net = require('net');

let server = net.createServer(function(socket){ 
   socket.pause(); 
   socket.setTimeout(5000);
   socket.on('data',function(chunk){
       socket.pause();
       console.log(chunk);
   })
   socket.on('timeout',function(){
       //socket.resume();
       socket.end();
   });
});
server.listen(8080);
複製程式碼
  • socket.pause(); 暫停觸發data事件
  • socket.setTimeout(5000);設定超時時間,如果超時,會觸發timeout事件,一般超時會關閉客戶端

客戶端訪問服務端時,服務將一個檔案傳送給客戶端

先建立一個檔案

 let fs = require('fs');
 fs.writeFileSync(__dirname+'/1.txt',Buffer.alloc(1024*1024*10));
複製程式碼
let net = require('net');

let rs = require('fs').createReadStream(__dirname+'/1.txt');
let server = net.createServer(function(socket){
   rs.on('data',function(chunk){
       let flag = socket.write(chunk);
       console.log(flag);
       console.log('快取區的大小'+socket.bufferSize);
   });
   //可寫流的快取區的資料全部寫到目標檔案時觸發
   socket.on('drain',function(){
       console.log('清空快取')
   })
});
server.listen(8080);
複製程式碼

socket.bufferSize : 快取區的大小

例:簡單的聊天室

socket.destroy();//銷燬socket

let net = require('net');

let clients = {}; //儲存{使用者名稱:socket}的對映
// 發言 將聊天內容傳送給其他幾個人
function broadcast(nickname,chunk){
    Object.keys(clients).forEach(key=>{
        //自己聊天的內容,不應該傳送給自己
        if(key!=nickname){
            clients[key].write(`${nickname}:${chunk} \r\n`);
        }
    })
}

let server = net.createServer(function(socket){
    server.maxConnections = 3; //允許同時3個人聊天
    //  當客戶端連線服務端時,提示使用者輸入使用者名稱
    server.getConnections((err,count)=>{
        socket.write(`歡迎來到聊天室 當前使用者數${count}個,請輸入使用者名稱\r\n`);
    });
    let nickname;
    socket.setEncoding('utf8'); 
    //當一個使用者關閉了聊天,銷燬socket,並刪除使用者
    socket.on('end',function(){
        clients[nickname] &&clients[nickname].destroy();
        delete clients[nickname]; // 刪除使用者
    });
    socket.on('data',function(chunk){
        chunk = chunk.replace(/\r\n/,'')
        //如果nickname存在,說明使用者輸入的是聊天內容
        if(nickname){
            // 發言,將聊天內容傳送給其他幾個人
            broadcast(nickname,chunk);
        }else{
        //如果不存在nickname時,使用者輸入的內容就是nickname
            nickname = chunk;
            clients[nickname] = socket;
            socket.write(`您的新使用者名稱是${nickname} \r\n`);
        }
    });
});

server.listen(8080);
複製程式碼

例:聊天室2

該聊天室的功能如下:
1、預設情況下使用者名稱是匿名
2、通過關鍵命令改名, r:使用者名稱
3、支援顯示線上的使用者列表 l
4、廣播的功能,將聊天內容傳送給所有其他線上使用者 b:xxx
5、私聊的功能,只傳送內容給指定使用者 s:使用者名稱:聊天內容

let net = require('net');
let clients  = {};
// 改名 r命令
function rename(key,data,socket){
    clients[key].nickname = data;
    socket.write(`您當前的使用者名稱是${data}\r\n`);
}
// 展示使用者列表 l命令 
function list(socket){
    let str = `當前使用者列表是:\r\n`
    let ls = Object.keys(clients).map(key=>{
        return clients[key].nickname;
    }).join('\r\n');
    socket.write(str+ls+'\r\n');
}
// 私聊 nickname:使用者名稱 content:傳送的內容 key
function private(nickname,content,key){
    let user;
    Object.keys(clients).forEach(function(key){
        if(clients[key].nickname === nickname){
            user = clients[key].socket;
        }
    });
    user.write(clients[key].nickname+":"+content+'\r\n');
}
//廣播
function broadcast(nickname,content){
    Object.keys(clients).forEach(item=>{
        if(clients[item].nickname!= nickname){
            clients[item].socket.write(content+'\r\n')
        }
    })
}

let server = net.createServer(function (socket) {
//使用者預設是匿名的,所以用socket.remoteAddress + socket.remotePort來標識一個使用者
    let key = socket.remoteAddress + socket.remotePort; // 唯一
    clients[key] = {nickname:'匿名',socket}//預設使用者名稱
   
    server.getConnections((err, count) => {
        socket.write(`歡迎來到聊天室 當前使用者${count}個\r\n`);
    });
    socket.setEncoding('utf8');
    socket.on('data', function (chunk) {
        chunk = chunk.replace(/\r\n/, '');
        let chars = chunk.split(':');
        switch (chars[0]) {
            case 'r': // r:zhangsan
                rename(key,chars[1],socket);
                break;
            case 'l':
                list(socket);
                break;
            case 'b': // b:content
                broadcast(key,chars[1]);
                break;
            case 's': // s:使用者名稱:content
                private(chars[1],chars[2],key);
                break;
            default:
                socket.write('當前命令無法解析,重新輸入\r\n')
        }
    });

});
server.listen(8080, function () {
    console.log(`server start 8080`);
})
複製程式碼

例:用net模組建立客戶端

let net = require('net');

let socket = net.createConnection({port:8080},function(){
    socket.write('hello');
    socket.on('data',function(data){
        console.log(data);
    });
});
複製程式碼

服務端測試程式碼

let net = require('net');

let server = net.createServer(function(socket){
    socket.setEncoding('utf8');
    socket.on('data',function(data){
        console.log(data);
    });
});

server.on('connection',function(){
    console.log('客戶端連線')
})
server.listen(8080);
複製程式碼

啟動伺服器,node命令執行客戶端檔案即可看到效果

UDP (引用dgram模組)

客戶端(不需要繫結埠,隨機分配)

let dgram = require('dgram');
//建立socket
let socket = dgram.createSocket('udp4');
//傳送訊息
socket.send('hello',8080,function(){
    console.log('成功')
});
//接收訊息
socket.on('message',function(data){
    console.log(data.toString());
})
複製程式碼

服務端

let dgram = require('dgram');

let socket = dgram.createSocket('udp4');
// 服務端監聽一個埠 資料到來時 可以讀出資訊
socket.bind(8080,'localhost',function(){
    //讀取訊息
    socket.on('message',function(data,rinfo){
        console.log(data.toString());
        //傳送訊息
        socket.send('hello',rinfo.port);
    })
});
複製程式碼

相關文章