使用nodejs在终端实现聊天室(服务端+客户端)

200 阅读3分钟

创建服务端

const server = net.createServer(); //HACK 返回net.Server

创建客户端

//HACK 返回net.Server
// socket是双工的 指的是A ——- B之间的连接. 如果有100个客户端 就会建立100个socket 
const socket = net.createConnection({ host: HOST, port: PORT }, async () => {
  console.log('connected to the server!');

用nodejs读取命令行界面(TTY模块,readline模块) 然后写到socket中

  1. 用nodejs readline读取命令行界面
const net = require('net');
const readline = require('readline/promises'); //提供接口读取readable stream中的内容

const PORT = 3099;
const HOST = '127.0.0.1';

// 用nodejs readline读取命令行界面
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  // process.stdin返回一个双工的流 可读的stream
});
  1. 写到socket中 这里的ask方法就是在终端给用户提示,让用户输入
const socket = net.createConnection({ host: HOST, port: PORT }, async () => {
  console.log('connected to the server!');

  const ask = async () => {
    const message = await rl.question('Enter a message > ');
   
    socket.write(`-${message}`);
  };
  ask();

socket是双工的 指的是A ——- B之间的连接. 如果有100个客户端 就会建立100个socket 可以用数组存储起来 让每个客户端都能收到彼此的信息。

而且我们要知道谁发送了这个信息?就要为连接到的每个客户端分配id

为连接到的每个客户端分配id

server.js
// 如果有100个客户端 就会建立100个socket 可以用数组存储起来 让每个客户端都能收到彼此的信息
const clients = [];

server.on('connection', (socket) => {
  console.log('A new Connection to the server');

  // 谁发送了这个信息?为连接到的每个客户端分配id 
  const clientId = clients.length + 1;

  // 同时广播它的id
  clients.map((client) => {
    client.socket.write(`User ${clientId} joined`); //这个是在所有的客户端都会收到的:除了刚刚加入的这台客户端它自己不会收到 广播
  });
  // 告诉客户端被分配到了哪个id 
  socket.write(`id-${clientId}`); //这个和上面的有什么差别 这个是没有写到client的socket里面的 //这个只会在建立连接的socket收到

  // 有客户端连接就把他的id和socket一并推到clients数组中
  // socket是双工的 指的是A ——- B之间的连接
  //  如果有100个客户端 就会建立100个socket
  // 可以用数组存储起来 让每个客户端都能收到彼此的信息
  clients.push({ id: clientId.toString(), socket });
});


同时在客户端用户输入的时候要传入Id,改写ask方法,这里的moveCursor和clearLine只是为了让ui更好看,具体可以看node官方文档

  const ask = async () => {
    const message = await rl.question('Enter a message > ');
    // 用户输入完了 我们也得到message了 就移动光标得到Enter a message > 这一行
    await moveCursor(0, -1);
    await clearLine(0);//再删除这一行
    // 在发送信息的时候要告诉服务器是哪个id
    socket.write(`${id}-message-${message}`);
  };
  ask();
const clearLine = (dir) => {
  // dir <number>
  // -1: to the left from cursor
  // 1: to the right from cursor
  // 0: the entire line
  return new Promise((resolve, reject) => {
    process.stdout.clearLine(dir, () => {
      resolve();
    });
  });
};

const moveCursor = (dx, dy) => {
  return new Promise((resolve, reject) => {
    process.stdout.moveCursor(dx, dy, () => {
      resolve(); //这里为什么是process.stdout 然后这一步的作用是
    });
  });
};

最后,服务端收客户端的输入就要广播

 socket.on('data', (data) => {
    const dataString = data.toString('utf-8');
    const id = dataString.substring(0, dataString.indexOf('-'));
    const message = dataString.substring(dataString.indexOf('-message-') + 9);

    clients.map((client) => {
      client.socket.write(`> User ${id} : ${message}`);
    });
  });

再加上 有人加入或退出就广播

  socket.on('end', () => {
    clients.map((client) => {
      client.socket.write(`User ${clientId} left!`); //有人断开socket连接 为什么这里要去map呢 //因为要广播,告诉所有的客户端 //怎么找到对应的clientId的?//socket是一对一的
    });
  });

客户端收到数据的处理


  socket.on("data",async(data) => {
    // console.log();
    // await moveCursor(0,-1);//这个不是多此一举吗 
    // await clearLine(0);
    if(data.toString("utf-8").substring(0,2) ==="id"){
        id = data.toString("utf-8").substring(3);//截取到最后一位的话不会把-message-${message}也
        console.log(`your id is ${id}`)
    }else{
        console.log(data.toString("utf-8"))
    }
    ask()
  })

完整代码:

客户端


const net = require('net');
const readline = require('readline/promises'); //提供接口读取readable stream中的内容

const PORT = 3099;
const HOST = '127.0.0.1';

// 用nodejs readline读取命令行界面
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  // process.stdin返回一个双工的流 可读的stream
});

const clearLine = (dir) => {
  // dir <number>
  // -1: to the left from cursor
  // 1: to the right from cursor
  // 0: the entire line
  return new Promise((resolve, reject) => {
    process.stdout.clearLine(dir, () => {
      resolve();
    });
  });
};

const moveCursor = (dx, dy) => {
  return new Promise((resolve, reject) => {
    process.stdout.moveCursor(dx, dy, () => {
      resolve(); //这里为什么是process.stdout 然后这一步的作用是
    });
  });
};

let id;
//HACK 返回net.Server
// socket是双工的 指的是A ——- B之间的连接. 如果有100个客户端 就会建立100个socket 
const socket = net.createConnection({ host: HOST, port: PORT }, async () => {
  console.log('connected to the server!');

  const ask = async () => {
    const message = await rl.question('Enter a message > ');
    // 用户输入完了 我们也得到message了 就移动光标得到Enter a message > 这一行
    await moveCursor(0, -1);
    await clearLine(0);//再删除这一行
    // 在发送信息的时候要告诉服务器是哪个id
    socket.write(`${id}-message-${message}`);
  };
  ask();

  socket.on("data",async(data) => {
    // console.log();
    // await moveCursor(0,-1);//这个不是多此一举吗 
    // await clearLine(0);
    if(data.toString("utf-8").substring(0,2) ==="id"){
        id = data.toString("utf-8").substring(3);//截取到最后一位的话不会把-message-${message}也
        console.log(`your id is ${id}`)
    }else{
        console.log(data.toString("utf-8"))
    }
    ask()
  })
});
socket.on("end", () => {
    console.log("Connection was ended!");
  });


服务端:

const net = require('net');

const PORT = 3099;
const HOST = '127.0.0.1';

const server = net.createServer(); //HACK 返回net.Server

// 如果有100个客户端 就会建立100个socket 可以用数组存储起来 让每个客户端都能收到彼此的信息
const clients = [];

server.on('connection', (socket) => {
  console.log('A new Connection to the server');

  // 谁发送了这个信息?为连接到的每个客户端分配id 
  const clientId = clients.length + 1;

  // 同时广播它的id
  clients.map((client) => {
    client.socket.write(`User ${clientId} joined`); //这个是在所有的客户端都会收到的:除了刚刚加入的这台客户端它自己不会收到 广播
  });
  // 告诉客户端被分配到了哪个id 
  socket.write(`id-${clientId}`); //这个和上面的有什么差别 这个是没有写到client的socket里面的 //这个只会在建立连接的socket收到

  socket.on('data', (data) => {
    const dataString = data.toString('utf-8');
    const id = dataString.substring(0, dataString.indexOf('-'));
    const message = dataString.substring(dataString.indexOf('-message-') + 9);

    clients.map((client) => {
      client.socket.write(`> User ${id} : ${message}`);
    });
  });

  // 有人加入或退出就广播 
  socket.on('end', () => {
    clients.map((client) => {
      client.socket.write(`User ${clientId} left!`); //有人断开socket连接 为什么这里要去map呢 //因为要广播,告诉所有的客户端 //怎么找到对应的clientId的?//socket是一对一的
    });
  });

  // 有客户端连接就把他的id和socket一并推到clients数组中
  // socket是双工的 指的是A ——- B之间的连接
  //  如果有100个客户端 就会建立100个socket
  // 可以用数组存储起来 让每个客户端都能收到彼此的信息
  clients.push({ id: clientId.toString(), socket });
});

server.listen(PORT, HOST, () => {
  console.log('open server on', server.address());
});