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工具来访问服务,我的设置如下,打开一个与服务器的连接后,回车即发送内容到服务

例:客户端输入的内容写入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);
    })
});
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值