Node TCP /UDP 簡易聊天室

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

例:客戶端輸入的內容寫入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事件,通常超時會關閉客戶端

客戶端訪問服務端時,服務將一個文件發送給客戶端bash

先建立一個文件服務器

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

例:簡單的聊天室

socket.destroy();//銷燬sockettcp

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

該聊天室的功能以下:
一、默認狀況下用戶名是匿名
二、經過關鍵命令更名, r:用戶名
三、支持顯示在線的用戶列表 l
四、廣播的功能,將聊天內容發送給全部其餘在線用戶 b:xxx
五、私聊的功能,只發送內容給指定用戶 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);
    })
});
複製代碼
相關文章
相關標籤/搜索