如何用tcp写一个聊天室

976 阅读3分钟

tcp不能通过浏览器访问,因为他并不知道协议的特点,如果用windows开发,需要安装'PuTTY,xsel',mac不需要

tcp属于传输层 => 数据传递 ,传输层有很多协议类似于udp等

具体请参考七层协议

我们最常用的http属于应用层,tcp属于传输层,也就是说应用层建立在传输层上,在node中为了专门实现某种传输手段,提供了一个tcp包

windows需要安装telnet 控制面板=>程序 => telnet√一下 Telnet localhost 3000(不建议)

Telnet协议是TCP/IP协议族中的一员,是Internet远程登陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的能力。在终端使用者的电脑上使用telnet程序,用它连接到服务器。终端使用者可以在telnet程序中输入命令,这些命令会在服务器上运行,就像直接在服务器的控制台上输入一样。

我们用putty (支持中文)

安装后需要配置常用端口.

//node实现tcp协议  给我们提供了一个包 net模块
//tcp 交互也要有服务端和客户端
//客户端: 可以通过任意一个端口号给服务发请求
//服务端:接受并且响应
let net = require('net');

//创建服务端
//socket套接字 会话,http有请求和响应
// let server = net.createServer(function(socket){
//     //请求到来的时候执行以下
//     console.log('客户端链接了服务端');
    
// })
//等同于
let server = net.createServer();
server.on('connection',function(socket){
    //请求到来的时候执行以下
    console.log('客户端链接了服务端');
})
// (端口号,服务器地址默认不写,511(可以接收多少个请求))
//监听成功会自动走回调
let port = 3000; 
server.listen(port,function(){
    console.log(`server start ${port}`);
})

我们在服务端建立端口,同时在cmd上运行,会报错

这个我们最常见的端口冲突,我们想在端口冲突的时候让端口自增1

我们加一行代码就可以解决

server.on('error',function(err){
    if(err.code == 'EADDRINUSE'){
        server.listen(++port)
    }
})
//设置最大连接数
server.maxConnections = 2

socket 是一个Duplex 就是双攻流,可读可写 即客户端一个,服务端一个,两个是同一个人,

server.on('connection',function(socket){
    //请求到来的时候执行以下
    socket.write("欢迎光临")
})

设置当客户端请求时候回应如图

socket.end();//关闭客户端
server.close();//关闭自己  如果触发了close事件就不会在触发新的请求了
server.unref(); //表示关闭  没有客户端链接会自己关闭,不会触发close事件
//接收消息,可以通过流的方式接收到数据
socket.setEncoding('utf8')
socket.on('data' , function(data){
    console.log(data)
});

现在我们来实现一个聊天室, 每次链接服务端socket不是同一个人

  1. 提示当前在线人数多少,总共能容纳多少人
  2. 提示请输入用户名
  3. 广播消息
//创建一个服务
let net = require('net');
let server = net.createServer();
//client存储用户信息
let client = {};
//每次链接服务器 socket每次都会产生新的
server.on('connection',function(socket){ 
    //设置最大连接数,server.getConnections每次连接返回给客户端信息, socket.write读流,通过流的方式接收到数据
    server.maxConnections = 4;
    server.getConnections(function(err,count){
        socket.write(`欢迎光临,当前人数为${count},总容纳数${server.maxConnections}人\r\n`);
        socket.write(`请输入用户名:\r\n`);
    })
    socket.setEncoding('utf8')
    let nickName;//存储用户名
    socket.setEncoding('utf8')
    socket.on('data' , function(chunk){//监听输入
        chunk = chunk.replace(/\r\n/,'');//chunk自带换行回车
        if(nickName){//把说的内容给别人看
            Broadcast(nickName, chunk)
        }else{
            nickName = chunk;
            client[chunk] = socket;
        }
    });
})
function Broadcast(nickName, chunk){
   Object.keys(client).forEach(nick => {
       if(nickName != nick){
            client[nick].write(`${nickName}:${chunk}\r\n`)
       }
   });
}
//监听服务器关闭事件
server.on('close',function(){
    console.log("服务器关闭")
})

下面是我们写的全部内容

//node实现tcp协议  给我们提供了一个包 net模块
//tcp 交互也要有服务端和客户端
//客户端: 可以通过任意一个端口号给服务发请求
//服务端:接受并且响应
let net = require('net');

//创建服务端
//socket套接字 会话,http有请求和响应
// let server = net.createServer(function(socket){
//     //请求到来的时候执行以下
//     console.log('客户端链接了服务端');
    
// })
let server = net.createServer();

let client = {};
//每次链接服务器 socket每次都会产生新的
server.on('connection',function(socket){ 
    //设置最大连接数
    server.maxConnections = 4;
    server.getConnections(function(err,count){
        socket.write(`当前人数为${count},总容纳数${server.maxConnections}人\r\n`);
        socket.write(`请输入用户名:\r\n`);
    })
    //请求到来的时候执行以下  socket是一个Duplex 双攻流,可读可写
    socket.write("欢迎光临");
    //读流,通过流的方式接收到数据
    socket.setEncoding('utf8');
    //监听输入
    let nickName;
    //存储用户名
    socket.setEncoding('utf8')
    socket.on('data' , function(chunk){
        chunk = chunk.replace(/\r\n/,'');
        if(nickName){
            //把说的内容给别人看
            Broadcast(nickName, chunk)

        }else{
            //chunk自带换行回车
            nickName = chunk;
            client[chunk] = socket;
        }
        //server.close();//关闭自己 如果触发了close事件就不会在接收新的请求了,
        //server.unref(); //表示关闭 没有客户端,所有人都退出,链接会自己关闭,不会触发close事件
    });
    //end  服务端把客户端关掉了,,,
    //socket.end();
})
function Broadcast(nickName, chunk){
   Object.keys(client).forEach(nick => {
       if(nickName != nick){
            client[nick].write(`${nickName}:${chunk}\r\n`)
       }
   });
}
// (端口号,服务器地址默认不写,511(可以接收多少个请求))
//监听成功会自动走回调
let port = 3000; 
server.listen(port,function(){
    console.log(`server start ${port}`);
})

//如果端口被占用,我们重启一个端口号
server.on('error',function(err){
    if(err.code == 'EADDRINUSE'){
        server.listen(++port)
    }
})
server.on('close',function(){
    console.log("服务器关闭")
})

多功能聊天室

  • 如果说话的时候富裕一些特殊意义,可以自己增加一些标识

  • look: 看所有的在线人数

  • say:zs: 私聊

  • rename: 重命名

  • all: 广播

  • 我们有个对象 clinet 当前的人{127.0.0.1}

  • 当然都是本地的,所以每个人都是这个,但是不可能同一个端口发出两个socket ,假设第一个端口是{127.0.0.1:8080},另一个是8081,并且我们放歌对象,默认匿名,一级当前是哪个sokect

  • {127.0.0.1:8080:{nickname:'匿名',socket:socket}}

let net = require('net');
let client = {};
let server = net.createServer(function(socket){
    server.maxConnections = 4;
    server.getConnections(function(err,count){
        socket.write(`当前人数为${count},总容纳数${server.maxConnections}人\r\n`);
        socket.write(`请输入用户名:\r\n`);
    })
    let key = socket.remoteAddress + socket.remotePort; // 远程地址和远程端口号
    console.log(key);
    socket.setEncoding('utf8');
    client[key] = {nickName:'匿名',socket:socket}
    socket.on('data' , function(chunk){
        chunk = chunk.replace(/\r\n/,'');
        let char = chunk.split(':')[0];
        let content = chunk.split(':')[1];
        switch(char){
            case 'look':
                showList(socket);
                break;
            case 'say':
                private(content , chunk.split(':')[2],client[key].nickName);
                break;
            case 'rename':
                rename(key, content);
                break;
            case 'all':
                Broadcast(key , chunk, client[key].nickName)
                break;
        }
        // key = socket.remoteAddress + socket.remotePort 
        // client[key] = {nickName:'匿名',socket:socket}
        function showList(socket){
            let users = [];
            Object.keys(client).forEach(key => {
                users.push(client[key].nickName)
            });
            socket.write(`当前用户列表:\r\n${users.join('\r\n')}\r\n`)
        }
        function rename(key , chunk){
            client[key].nickName = chunk;
        }
        function private(nickName, content , n){
            let s;
            Object.keys(client).forEach(key => {
                if(client[key].nickName === nickName){
                    s = client[key].socket
                }
            });
            s.write(`${n}: ${content}\r\n`)
        }
        function Broadcast(nick , chunk , nickName){
            Object.keys(client).forEach(key => {
                console.log(key,nick)
                if(key != nick){
                    client[key].socket.write(`${nickName}:${chunk}\r\n`)
                }
            });
        }
        r.unref();
    });
})
let port = 3000; 
server.listen(port,function(){
    console.log(`server start ${port}`);
})

tcp写服务端

let net = require('net');
let server = net.createServer(function(socket){
    socket.write('hello');
    socket.on('data',function(data){
        console.log(data);
    })
}).listen(3000)

//模拟客户端,并不是通过服务器
let net = require('net');
let socket = net.createConnection({port:3000},function(){
    //同一次请求sockect就是同一个
    socket.write('hi');
    socket.on('data',function(data){
        console.log(data);
    })
})