关于Node.js的学习笔记?

关于Node.js的学习笔记?_第1张图片

目录

  • Node.js 网络通信概述
  • 构建TCP服务
  • 构建UPD服务
  • 构建HTTP服务
  • 构建WebSocket服务
  • 构建HTTPS服务

1.Node.js 网络通信概述

关于Node.js的学习笔记?_第2张图片
关于Node.js的学习笔记?_第3张图片

关于Node.js的学习笔记?_第4张图片
关于Node.js的学习笔记?_第5张图片
关于Node.js的学习笔记?_第6张图片
关于Node.js的学习笔记?_第7张图片
关于Node.js的学习笔记?_第8张图片
关于Node.js的学习笔记?_第9张图片
关于Node.js的学习笔记?_第10张图片
关于Node.js的学习笔记?_第11张图片
关于Node.js的学习笔记?_第12张图片
关于Node.js的学习笔记?_第13张图片
关于Node.js的学习笔记?_第14张图片
关于Node.js的学习笔记?_第15张图片
关于Node.js的学习笔记?_第16张图片
关于Node.js的学习笔记?_第17张图片

2.构建TCP服务

关于Node.js的学习笔记?_第18张图片

使用net模块构建TCP服务端(server)和客户端(client)

新建server.js文件
const net = require('net')

const server = net.createServer()

server.on('connection',clientSocket=>{
    console.log('连接成功')
    // 通过clientSocket 给当前连接的客户端发送数据
    clientSocket.write('Connection successful!')
})

server.listen(3000,()=>{
    console.log('Server running 127.0.0.1 3000')
})
新建client.js文件
const net = require('net')

const client = net.createConnection({
    host:'127.0.0.1',
    port:3000
})

client.on('connect',()=>{
    console.log('成功连接到服务器了')
})

// 客户端接收服务端返回的数据
client.on('data',data =>{
    console.log(data.toString())
})

运行代码(先运行服务端后运行客户端,否则会报错)

文件夹路径>nodemon 文件夹名称

服务端打印如下:
关于Node.js的学习笔记?_第19张图片
客户端打印如下:
关于Node.js的学习笔记?_第20张图片
注意:运行nodemon前提是已经全局安装,如遇到下面的情况,说明没有安装。

nodemon : 无法将“nodemon”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路
 
径正确,然后再试一次。
 
所在位置 行:1 字符: 1
 
+ nodemon --version
 
+ ~~~~~~~
 
    + CategoryInfo          : ObjectNotFound: (nodemon:String) [], CommandNotFoundException
 
    + FullyQualifiedErrorId : CommandNotFoundException

解决方法:安装全局的nodemon,在终端执行

npm install -g nodemon

实现TCP服务端(server)和客户端(client)双向通信

更改server.js文件
const net = require('net')

const server = net.createServer()

server.on('connection', clientSocket => {
    console.log('客户端连接成功')

    // 监听clientSocket的data事件
    clientSocket.on('data', data => {
        console.log('客户端:', data.toString())
    })

    // 给客户端发送消息
    // 通过clientSocket 给当前连接的客户端发送数据
    clientSocket.write('我是服务端')
})

server.listen(3000, () => {
    console.log('Server running 127.0.0.1 3000')
})
更改client.js文件
const net = require('net')

const client = net.createConnection({
    host: '127.0.0.1',
    port: 3000
})

client.on('connect', () => {
    console.log('服务端连接成功')

    // 当客户端与服务器连接成功后,客户端就可以给服务端发送消息
    client.write('我是客户端')
})

// 客户端监听data事件
// 客户端接收服务端返回的数据 当服务端发消息过来就会触发改事件
client.on('data', data => {
    console.log('服务端:', data.toString())
})

运行代码结果如下(先运行服务端后运行客户端,否则会报错)
关于Node.js的学习笔记?_第21张图片

实现接收终端数据发送给服务端(接上)

更改client.js文件
const net = require('net')

const client = net.createConnection({
    host: '127.0.0.1',
    port: 3000
})

client.on('connect', () => {
    console.log('服务端连接成功')

    // 当客户端与服务器连接成功后,客户端就可以给服务端发送消息
    client.write('我是客户端')

    // 当客户端与服务端建立连接成功后,我们可以监听终端的输入
    // 将获取终端的输入发送给服务端
    process.stdin.on('data',data=>{
    	// .trim() 去除回车打印显示的空格
        // console.log(data.toString().trim())
        client.write(data.toString().trim())
    })
})

// 客户端监听data事件
// 客户端接收服务端返回的数据 当服务端发消息过来就会触发改事件
client.on('data', data => {
    console.log('服务端:', data.toString())
})

运行代码结果如下(前提是服务端和客户端连接成功,否则会报错)
关于Node.js的学习笔记?_第22张图片

案例:实现TCP终端聊天室

核心需求

  • 用户第一次进来,提示用户输入昵称进行注册(昵称不允许重复)
  • 广播消息(群发)
  • 用户昵称
  • 点对点消息(私聊)

数据格式设计(语言协议)

  • 什么是数据格式
    • 数据格式(data format)是描述数据保存在文件或记录中的规则。可以是字符形式的文本格式,或二进制数据形式的压缩格式
  • 比较常见的数据传输格式
    • JSON
    • XML
    • YAML

第一步:实现群发数据功能

tcp-chatRoom文件夹下新建 server.js文件
const net = require('net')

const server = net.createServer()

const clients = []

server.on('connection',clientSocket=>{
    // 把当前连接的客户端通信接口存储到数据中
    clients.push(clientSocket)

    clientSocket.on('data',data=>{
        console.log('有人说',data.toString())
        //把数据发送给所有的客户端
        clients.forEach(socket=>{
            if (socket !== clientSocket) {
                socket.write(data)
            }
        })
    })

    clientSocket.write("我是服务端")
})

server.listen(3000,()=>{
    console.log('Server running 127.0.0.1 3000')
})
tcp-chatRoom文件夹下新建 client.js文件
const net = require('net')

const client = net.createConnection({
    host:'127.0.0.1',
    port:3000
})

client.on('connect',()=>{
    console.log('客户端与服务端连接成功')

    process.stdin.on('data',data=>{
        data = data.toString().trim()
        client.write(data)
    })
    client.write('我是客户端')
})
client.on('data',data=>{
    console.log(data.toString())
})

运行代码结果如下(前提是服务端和客户端连接成功)
关于Node.js的学习笔记?_第23张图片

第二步:实现用户登录功能

  • 客户端输入昵称发送到服务端
  • 服务端接收数据,校验昵称是否重复
    • 如果已重复,则发送通知告诉客户端
    • 如果可以使用,则将用户昵称及通信 Socket 存储到容器中用于后续使用

用户登录数据格式要求如下:

客户端:

{
  "type": "login",
  "nickName": "xxx"
}

服务端:

{
  "type": "login",
  "success": true | false,
  "message": "登录成功|失败",
  "sumUsers": 10
}
tcp-chatRoom文件夹下新建 types.js文件
module.exports = {
    login:0,
    broadcast:1,
    p2p:2
}
更改tcp-chatRoom文件夹下 server.js文件
const net = require("net");
const types = require("./types");

const server = net.createServer();

//[Socket,Socket,Socket...]用于存储客户端昵称
const users = [];

server.on("connection", clientSocket => {
  clientSocket.on("data", data => {
    data = JSON.parse(data.toString().trim());
    switch (data.type) {
      case types.login:
        if (users.find(item => item.nickName === data.nickName)) {
          return clientSocket.write(
            JSON.stringify({
              type: types.login,
              success: false,
              message: "昵称已重复"
            })
          );
        }

        clientSocket.nickName = data.nickName;
        users.push(clientSocket);
        clientSocket.write(
          JSON.stringify({
            type: types.login,
            success: true,
            message: "登录成功",
            nickName: data.nickName,
            sumUsers: users.length
          })
        );
        break;
      case types.broadcast:
        break;
      case types.p2p:
        break;
      default:
        break;
    }
  });
});

server.listen(3000, () => {
  console.log("Server running 127.0.0.1 3000");
});

更改tcp-chatRoom文件夹下 client.js文件
const net = require("net");
const types = require("./types");

let nickName = null;

const client = net.createConnection({
  host: "127.0.0.1",
  port: 3000
});

client.on("connect", () => {
  console.log("客户端与服务端连接成功");
  process.stdout.write("请输入昵称:");

  process.stdin.on("data", data => {
    // .trim() 去除回车打印显示的空格
    data = data.toString().trim();
    if (!nickName) {
      // write()方法不能直接发送对象,只能发送二进制数据和字符串,所有这里需要转换成字符串发送
      client.write(
        JSON.stringify({
          type: types.login,
          nickName: data
        })
      );
    }
  });
});

// 客户端监听data事件
// 客户端接收服务端返回的数据 当服务端发消息过来就会触发改事件
client.on("data", data => {
  data = JSON.parse(data.toString().trim());
  switch (data.type) {
    case types.login:
      if (!data.success) {
        console.log(`登录失败:${data.message}`);
        process.stdout.write("请输入昵称:");
      } else {
        console.log(`登录成功,当前在线用户:${data.sumUsers}`);
        nickName = data.nickName;
      }
      break;
    case types.broadcast:
      break;
    case types.p2p:
      break;
    default:
      break;
  }
});

运行代码结果如下
关于Node.js的学习笔记?_第24张图片

第三步:实现群发消息功能

  • 客户端输入消息发送到服务端
  • 服务端将消息发送给所有当前连接(也就是存储客户端Socket的容器)的客户端
  • 客户端收到消息,将消息输出到终端

广播消息数据格式要求如下:

客户端:

{
  "type": "broadcast",
  "message": "xxx"
}

服务端:

{
  "type": "broadcast",
  "nickname": "xxx",
  "message": "xxx"
}
更改tcp-chatRoom文件夹下 server.js文件
const net = require("net");
const types = require("./types");

const server = net.createServer();

//[Socket,Socket,Socket...]用于存储客户端昵称
const users = [];

server.on("connection", clientSocket => {
  clientSocket.on("data", data => {
    data = JSON.parse(data.toString().trim());
    switch (data.type) {
      case types.login:
        if (users.find(item => item.nickName === data.nickName)) {
          return clientSocket.write(
            JSON.stringify({
              type: types.login,
              success: false,
              message: "昵称已重复"
            })
          );
        }

        clientSocket.nickName = data.nickName;
        users.push(clientSocket);
        clientSocket.write(
          JSON.stringify({
            type: types.login,
            success: true,
            message: "登录成功",
            nickName: data.nickName,
            sumUsers: users.length
          })
        );
        break;
      case types.broadcast:
        users.forEach(item => {
          item.write(
            JSON.stringify({
              type: types.broadcast,
              nickName: clientSocket.nickName,
              message: data.message
            })
          );
        });
        break;
      case types.p2p:
        break;
      default:
        break;
    }
  });
});

server.listen(3000, () => {
  console.log("Server running 127.0.0.1 3000");
});


更改tcp-chatRoom文件夹下 client.js文件
const net = require("net");
const types = require("./types");

let nickName = null;

const client = net.createConnection({
  host: "127.0.0.1",
  port: 3000
});

client.on("connect", () => {
  console.log("客户端与服务端连接成功");
  process.stdout.write("请输入昵称:");

  process.stdin.on("data", data => {
    // .trim() 去除回车打印显示的空格
    data = data.toString().trim();
    if (!nickName) {
      // write()方法不能直接发送对象,只能发送二进制数据和字符串,所有这里需要转换成字符串发送
      return client.write(
        JSON.stringify({
          type: types.login,
          nickName: data
        })
      );
    }
    //群聊
    client.write(
      JSON.stringify({
        type: types.broadcast,
        message: data
      })
    );
  });
});

// 客户端监听data事件
// 客户端接收服务端返回的数据 当服务端发消息过来就会触发改事件
client.on("data", data => {
  data = JSON.parse(data.toString().trim());
  switch (data.type) {
    case types.login:
      if (!data.success) {
        console.log(`登录失败:${data.message}`);
        process.stdout.write("请输入昵称:");
      } else {
        console.log(`登录成功,当前在线用户:${data.sumUsers}`);
        nickName = data.nickName;
      }
      break;
    case types.broadcast:
      console.log(`${data.nickName}:${data.message}`);
      break;
    case types.p2p:
      break;
    default:
      break;
  }
});

运行代码结果如下:
关于Node.js的学习笔记?_第25张图片

第四步:实现私聊功能

  • 客户端输入消息发送到服务端
  • 服务端根据消息内容从容器中找到对应的通信客户端,然后将消息发送给该客户端
  • 对应的客户端收到消息,将消息输入到终端

点对点消息数据格式要求如下:

客户端:

{
  "type": "p2p",
  "to": "xxx",
  "message": "xxx"
}

服务端:

{
  "type": "p2p",
  "from": "xxx",
  "to": "xxx",
  "message": "xxx"
}
更改tcp-chatRoom文件夹下 server.js文件
const net = require("net");
const types = require("./types");

const server = net.createServer();

//[Socket,Socket,Socket...]用于存储客户端昵称
const users = [];

server.on("connection", clientSocket => {
  clientSocket.on("data", data => {
    data = JSON.parse(data.toString().trim());
    switch (data.type) {
      case types.login:
        if (users.find(item => item.nickName === data.nickName)) {
          return clientSocket.write(
            JSON.stringify({
              type: types.login,
              success: false,
              message: "昵称已重复"
            })
          );
        }

        clientSocket.nickName = data.nickName;
        users.push(clientSocket);
        clientSocket.write(
          JSON.stringify({
            type: types.login,
            success: true,
            message: "登录成功",
            nickName: data.nickName,
            sumUsers: users.length
          })
        );
        break;
      case types.broadcast:
        users.forEach(item => {
          item.write(
            JSON.stringify({
              type: types.broadcast,
              nickName: clientSocket.nickName,
              message: data.message
            })
          );
        });
        break;
      case types.p2p:
        console.log(data);
        const user = users.find(item => item.nickName === data.nickName);
        if (!user) {
          return clientSocket.write(
            JSON.stringify({
              type: types.p2p,
              success: false,
              message: "该用户不存在"
            })
          );
        }

        user.write(
          JSON.stringify({
            type: types.p2p,
            success: true,
            nickName: clientSocket.nickName,
            message: data.message
          })
        );
        break;
      default:
        break;
    }
  });
});

server.listen(3000, () => {
  console.log("Server running 127.0.0.1 3000");
});

更改tcp-chatRoom文件夹下 client.js文件
const net = require("net");
const types = require("./types");

let nickName = null;

const client = net.createConnection({
  host: "127.0.0.1",
  port: 3000
});

client.on("connect", () => {
  console.log("客户端与服务端连接成功");
  process.stdout.write("请输入昵称:");

  process.stdin.on("data", data => {
    // .trim() 去除回车打印显示的空格
    data = data.toString().trim();
    if (!nickName) {
      // write()方法不能直接发送对象,只能发送二进制数据和字符串,所有这里需要转换成字符串发送
      return client.write(
        JSON.stringify({
          type: types.login,
          nickName: data
        })
      );
    }

    // 私聊
    const matches = /^@(\w+)\s(.+)$/.exec(data);
    if (matches) {
      //如果本次的消息符合 @昵称 xxx(消息内容)格式
      return client.write(
        JSON.stringify({
          type: types.p2p,
          nickName: matches[1],
          message: matches[2]
        })
      );
    }

    //群聊
    client.write(
      JSON.stringify({
        type: types.broadcast,
        message: data
      })
    );
  });
});

// 客户端监听data事件
// 客户端接收服务端返回的数据 当服务端发消息过来就会触发改事件
client.on("data", data => {
  data = JSON.parse(data.toString().trim());
  switch (data.type) {
    case types.login:
      if (!data.success) {
        console.log(`登录失败:${data.message}`);
        process.stdout.write("请输入昵称:");
      } else {
        console.log(`登录成功,当前在线用户:${data.sumUsers}`);
        nickName = data.nickName;
      }
      break;
    case types.broadcast:
      console.log(`${data.nickName}:${data.message}`);
      break;
    case types.p2p:
      if (!data.success) {
        return console.log(`发送失败:${data.message}`);
      }
      console.log(`${data.nickName}对你说:${data.message}`);
      break;
    default:
      break;
  }
});

运行代码结果如下:(私聊格式为:@昵称+空号+消息内容)
关于Node.js的学习笔记?_第26张图片

第五步:实现上线|离线通知功能*

  • 上线通知
  • 离线通知

上线|离线通知日志数据格式要求如下:

服务端:

{
  "type": "log",
  "message": "xxx 进入|离开了聊天室,当前在线人数:xx"
}
更改tcp-chatRoom文件夹下 types.js文件
module.exports = {
    login:0,
    broadcast:1,
    p2p:2,
    login:3
}
更改tcp-chatRoom文件夹下 server.js文件
const net = require("net");
const types = require("./types");

const server = net.createServer();

//[Socket,Socket,Socket...]用于存储客户端昵称
const users = [];

server.on("connection", clientSocket => {
  clientSocket.on("data", data => {
    data = JSON.parse(data.toString().trim());
    switch (data.type) {
      case types.login:
        if (users.find(item => item.nickName === data.nickName)) {
          return clientSocket.write(
            JSON.stringify({
              type: types.login,
              success: false,
              message: "昵称已重复"
            })
          );
        }

        clientSocket.nickName = data.nickName;
        users.push(clientSocket);
        clientSocket.write(
          JSON.stringify({
            type: types.login,
            success: true,
            message: "登录成功",
            nickName: data.nickName,
            sumUsers: users.length
          })
        );

        users.forEach(user => {
          if (user !== clientSocket) {
            user.write(
              JSON.stringify({
                type: types.login,
                message: `${data.nickName}进入了聊天室,当前在线用户:${
                  users.length
                }`
              })
            );
          }
        });
        break;
      case types.broadcast:
        users.forEach(item => {
          item.write(
            JSON.stringify({
              type: types.broadcast,
              nickName: clientSocket.nickName,
              message: data.message
            })
          );
        });
        break;
      case types.p2p:
        console.log(data);
        const user = users.find(item => item.nickName === data.nickName);
        if (!user) {
          return clientSocket.write(
            JSON.stringify({
              type: types.p2p,
              success: false,
              message: "该用户不存在"
            })
          );
        }

        user.write(
          JSON.stringify({
            type: types.p2p,
            success: true,
            nickName: clientSocket.nickName,
            message: data.message
          })
        );
        break;
      default:
        break;
    }
  });

  clientSocket.on("end", () => {
    const index = users.findIndex(
      user => user.nickName === clientSocket.nickName
    );
    if (index !== -1) {
      const offlineUser = users[index];
      users.splice(index, -1);
      //广播通知其他用户,xxx用户已离开,当前剩余人数:xxx
      users.forEach(user => {
        user.write(
          JSON.stringify({
            type: types.login,
            message: `${offlineUser.nickname} 离开了聊天室,当前在线用户:${
              users.length
            }`
          })
        );
      });
    }
  });
});

server.listen(3000, () => {
  console.log("Server running 127.0.0.1 3000");
});

更改tcp-chatRoom文件夹下 client.js文件
const net = require("net");
const types = require("./types");

let nickName = null;

const client = net.createConnection({
  host: "127.0.0.1",
  port: 3000
});

client.on("connect", () => {
  console.log("客户端与服务端连接成功");
  process.stdout.write("请输入昵称:");

  process.stdin.on("data", data => {
    // .trim() 去除回车打印显示的空格
    data = data.toString().trim();
    if (!nickName) {
      // write()方法不能直接发送对象,只能发送二进制数据和字符串,所有这里需要转换成字符串发送
      return client.write(
        JSON.stringify({
          type: types.login,
          nickName: data
        })
      );
    }

    // 私聊
    const matches = /^@(\w+)\s(.+)$/.exec(data);
    if (matches) {
      //如果本次的消息符合 @昵称 xxx(消息内容)格式
      return client.write(
        JSON.stringify({
          type: types.p2p,
          nickName: matches[1],
          message: matches[2]
        })
      );
    }

    //群聊
    client.write(
      JSON.stringify({
        type: types.broadcast,
        message: data
      })
    );
  });
});

// 客户端监听data事件
// 客户端接收服务端返回的数据 当服务端发消息过来就会触发改事件
client.on("data", data => {
  data = JSON.parse(data.toString().trim());
  switch (data.type) {
    case types.login:
      if (!data.success) {
        console.log(`登录失败:${data.message}`);
        process.stdout.write("请输入昵称:");
      } else {
        console.log(`登录成功,当前在线用户:${data.sumUsers}`);
        nickName = data.nickName;
      }
      break;
    case types.broadcast:
      console.log(`${data.nickName}:${data.message}`);
      break;
    case types.p2p:
      if (!data.success) {
        return console.log(`发送失败:${data.message}`);
      }
      console.log(`${data.nickName}对你说:${data.message}`);
      break;
    case login:
      console.log(data.message);
      break;
    default:
      break;
  }
});

运行代码结果如下:(下线时显示报错,暂未找到原因,后续再处理)
关于Node.js的学习笔记?_第27张图片
关于Node.js的学习笔记?_第28张图片

TCP小结

  • TCP 必须建立连接(三次握手建立连接)才能通信
    关于Node.js的学习笔记?_第29张图片
  • TCP 只是负责数据的传输,不关心传输的数据格式问题
 {
      "type": "xxx"
    }
  • 如果需要使用 TCP 通信完成某种功能,则需要制定数据格式协议,或者使用第三方协议
  • Socket 就是与之通信的另一端,通过 Socket 接收或是发送数据
  • Socket 通信模型
    关于Node.js的学习笔记?_第30张图片

你可能感兴趣的:(node)