学以致玩【Node.js实战】-从0搭建WebSocket实时聊天项目,用Vscode摸鱼聊天

一个简单的WebSocket小练习,适合像我一样的小白。和开发同事用Vscode终端聊天,摸鱼大师。

先来粗略整理一下WebSocket的相关知识点:

WebSocket

WebSocket是HTML5提供的一种协议,与传统的HTTP请求-响应模型不同,WebSocket允许在客户端和服务器之间建立持久连接,双方可进行双向通信,直到连接关闭。

应用场景 由于WebSocket持久连接、双向通信、低延迟的特点,常用的应用场景:实时聊天、实时通知、多人在线游戏、实时协作工具等。

服务端初步搭建

初始化项目

  1. 创建项目文件夹
  2. 在文件夹目录下执行npm init初始化package.json文件
  3. 创建index.js,作为服务器
  4. 安装Node.js的ws库:npm install ws

创建WebSocket服务器 创建服务器:new WebSocketServer(options[, callback])

js 复制代码
// 引入WebSocketServer类
const { WebSocketServer } = require('ws');

// 自定义端口号
const PORT = 8124;

// 创建并启动WebSocket服务器,监听PORT端口号
const wsServer = new WebSocketServer({ port: PORT }, () => {
    console.log("WebSocket服务端创建成功,地址为 ws://127.0.0.1:8124");
})

服务器监听连接事件 connection事件,回调函数参数:

  • websocket: WebSocket实例(服务器与接入的客户端之间的连接)
  • request: 客户端发送的http请求信息,本例中未用到
js 复制代码
// 服务器监听connection事件,有客户端成功连接时触发
wsServer.on("connection", (ws) => {

})

监听消息 对于每个接入的客户端(webSocket实例,即回调函数中的ws),我们监听其messageerrorclose事件,以实现消息接收、错误处理、连接关闭。

js 复制代码
// 服务器监听connection事件,有客户端成功连接时触发
wsServer.on("connection", (ws) => {
    // 监听消息事件
    ws.on('message', (message) => {
        console.log('接收消息:', message);
    });

    // 监听关闭事件
    ws.on('close', () => {
        console.log('客户端关闭!');
    });

    // 监听错误事件
    ws.on('error', (error) => {
        console.log('WebSocket error:', error);
    });
})

至此,简单的服务器搭建好了,我们先来创建客户端,双方能够连接成功再进行下一步。

客户端初步搭建

初始化项目

  1. 另外创建项目文件夹
  2. 其他步骤同服务器端

创建WebSocket连接 客户端创建连接:new WebSocket(address,[, protocols][, options]) 此处的address(连接地址)为服务端的IP地址及端口号,其他可选参数本例未用到。

js 复制代码
// 引入WebSocket类
const { WebSocket } = require("ws");

// 创建webSocket
const ws = new WebSocket("ws://127.0.0.1:8124");

监听open事件 连接成功建立时,会触发open事件。

js 复制代码
// 连接成功建立
ws.on('open', () => {
    console.log('连接成功!');
})

监听消息 接收服务端发来的消息,通过监听message事件,回调函数参数:data为Buffer类型。

Buffer用于处理二进制数据,通过toLocaleString()将其转换为字符串

js 复制代码
ws.on("message", function message(data) {
    console.log(data.toLocaleString());
});

// 监听错误
ws.on("error", console.error);

测试连接

完成以上步骤后,我们先测试连接是否能成功建立:先运行服务端index.js,创建服务器,再运行客户端的index.js,连接到服务器。注意IP地址和端口号保持一致。

如果连接成功,服务端终端:

客户端:

这里成功开启了连接,但双方还不能发送消息,接下来我们实现发送消息的部分。

客户端发送消息

客户端通过命令行来进行输入,首先我们需要先获取用户输入。此处我们使用node内置模块readline获取用户输入。

创建readline实例

  • input 属性指定了从哪里读取输入,这里使用了 process.stdin,表示从标准输入流中读取用户的输入
  • output 属性指定了输出到哪里,这里使用了 process.stdout,表示向标准输出流中输出信息,通常是在控制台中显示
js 复制代码
// 引入内置模块readline
const readline = require('readline');

// 创建readline实例
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
})

获取用户输入 在连接成功的回调内,监听readline的'line'事件,获取用户输入,并发送给服务器:

js 复制代码
// 连接成功建立
ws.on('open', () => {
    console.log('连接成功!');
    // 读取输入
    rl.on('line', (input) => {
        // 将用户输入发送给服务器
        if (input) {
            ws.send(input)
        }
    })
})

创建昵称 这样虽然可以发送消息,但不知道是谁发送的,所以在发送之前,我们先要求每个用户为自己创建昵称。 通过readline向命令行输出问题,并获取用户输入。 之后修改上面的写法,在每次发送消息时,捎带上用户名。

js 复制代码
let userName = '匿名';
// 连接成功建立
ws.on('open', () => {
    console.log('连接成功!');
    rl.question("请输入昵称:", (name) => {
        // 输入有效,则将用户输入的昵称存入全局变量
        name && (userName = name);
        // 读取输入
        rl.on('line', (input) => {
            // 将用户输入发送给服务器,每次捎带上用户名
            if (input) {
                ws.send(userName + ":" + input);
            }
        })
    })
})

服务器转发消息

转发消息 服务器收到某一客户端的消息后,需要将其转发,以便其他所有客户端能看到这条消息,其中ws代表当前发来消息的客户端,它不需要再被转发。

js 复制代码
// 服务器监听connection事件,有客户端成功连接时触发
wsServer.on("connection", (ws) => {
    // 监听消息事件
    ws.on('message', (message) => {
        wsServer.clients.forEach((client) => {
            if (client !== ws && client.readyState === 1) {
                client.send(message);
            }
        });
    });
})

测试消息收发

重新运行服务端程序,开启多个客户端程序,测试某个客户端发消息,其他客户端是否能收到消息。

简易版完整代码

以上是具备基础功能的命令行聊天工具,开发测试完成后,运行起服务端程序,把client文件夹甩给局域网内的其他人,就可以一起聊天啦!注意连接地址的IP为服务器IP地址。

以下是这个简易版程序的完整代码。

github项目地址:github.com/LightBoatA/...

服务端

js 复制代码
// 引入WebSocketServer类
const { WebSocketServer } = require('ws');

// 自定义端口号
const PORT = 8124;

// 创建并启动WebSocket服务器,监听PORT端口号
const wsServer = new WebSocketServer({ port: PORT }, () => {
    console.log("WebSocket服务端创建成功,地址为 ws://127.0.0.1:8124");
})

// 服务器监听connection事件,有客户端成功连接时触发
wsServer.on("connection", (ws) => {
    // 监听消息事件
    ws.on('message', (message) => {
        wsServer.clients.forEach((client) => {
            if (client !== ws && client.readyState === 1) {
                client.send(message);
            }
        });
    });

    // 监听关闭事件
    ws.on('close', () => {
        console.log('客户端关闭!');
    });

    // 监听错误事件
    ws.on('error', (error) => {
        console.log('WebSocket error:', error);
    });
})

客户端

js 复制代码
// 引入WebSocket类
const { WebSocket } = require("ws");
// 引入内置模块readline
const readline = require('readline');

// 创建readline实例
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
})

// 创建webSocket
const ws = new WebSocket("ws://127.0.0.1:8124");

let userName = '匿名';
// 连接成功建立
ws.on('open', () => {
    console.log('连接成功!');
    rl.question("请输入昵称:", (name) => {
        // 输入有效,则将用户输入的昵称存入全局变量
        name && (userName = name);
        // 读取输入
        rl.on('line', (input) => {
            // 将用户输入发送给服务器,每次捎带上用户名
            if (input) {
                ws.send(userName + ":" + input);
            }
        })
    })

})

// 监听消息
ws.on("message", function message(data) {
    console.log(data.toLocaleString());
});

// 监听错误
ws.on("error", console.error);

花里胡哨版

在简易版基础上添加了用户上线通知、下线通知、当前用户查看、服务器通知等功能。 github项目地址:github.com/LightBoatA/...

服务端

js 复制代码
const { WebSocketServer } = require("ws");

const NOTICE_PERFIX = '--- 通知☆ --- ';
const REPLY_PERFIX = '--- 服务器回复☆ --- ';
let onlineUsers = [];

const wss = new WebSocketServer({ port: 8124 }, () => {
  console.log("success:ws://127.0.0.1:8124");
});

wss.on("connection", function connection(ws) {
  ws.on("error", console.error);
  ws.on("message", function message(data) {
    try {
      const msg = JSON.parse(data);
      switch (msg.type) {
        case 'add_user':
          addUser(msg.data, ws);
          break;
        case 'user_list':
          sendUserList(ws);
          break;
        case 'message':
          broadcastUsers(msg.data, ws);
          break;
        default:
          break;
      }
    } catch (error) {
      broadcastUsers(getServerErrorMsg(error))
    }
  });
  ws.on("close", () => {
    removeUser(ws)
  });
});

const getAllOnlineUsersMsg = (perfix) => {
  return (perfix || NOTICE_PERFIX) + '当前在线用户:' + onlineUsers.map(user => user.username).join('、');
}

const getAddUserMsg = (username) => {
  return NOTICE_PERFIX + username + '上线了!' + '\n' + getAllOnlineUsersMsg();
}

const getRemoveUserMsg = (username) => {
  return NOTICE_PERFIX + username + '下线了!' + '\n' + getAllOnlineUsersMsg();
}

const getServerErrorMsg = (error) => {
  return NOTICE_PERFIX + '服务器故障:' + error;
}

const addUser = (username, ws) => {
  onlineUsers.push({ username, socket: ws });
  const data = getAddUserMsg(username)
  broadcastUsers(data);
  console.log('用户加入');
}

const removeUser = (ws) => {
  const offlineUser = onlineUsers.find(user => user.socket === ws);
  if (offlineUser) {
    onlineUsers = onlineUsers.filter(user => user.socket !== ws)
    const data = getRemoveUserMsg(offlineUser.username);
    broadcastUsers(data);
    console.log('用户离开');
  }
}

const sendUserList = (ws) => {
  const data = getAllOnlineUsersMsg(REPLY_PERFIX);
  ws.send(data);
}

const broadcastUsers = (data, ws) => {
  wss.clients.forEach((client) => {
    if (client !== ws && client.readyState === 1) {
      client.send(data);
    }
  });
}

客户端

js 复制代码
const { WebSocket } = require("ws");
const ws = new WebSocket("ws://127.0.0.1:8124");
const readline = require("readline").createInterface({
  input: process.stdin,
  output: process.stdout,
});

let name = "匿名用户";

const onLine = () => {
  readline.on("line", (input) => {
    if (input === "close") {
      ws.close();
      readline.close();
    } else if (input === "user list") {
      ws.send(JSON.stringify({ type: "user_list"}))
    }else {
      // 否则将输入发送给服务器
      ws.send(JSON.stringify({ type: "message", data: name + ":" + input}));
    }
  });
};

ws.on("error", console.error);

ws.on("open", function open() {
  console.log("连接成功");
  readline.question("请输入昵称:", (input) => {
    input && (name = input);
    ws.send(JSON.stringify({ type: "add_user", data: name }))
    onLine();
  });
});

ws.on("message", function message(data) {
  console.log(data.toLocaleString());
});
相关推荐
龙少95432 天前
【Http,Netty,Socket,WebSocket的应用场景和区别】
java·后端·websocket·网络协议·http
m0_748232922 天前
前端在WebSocket中加入Token
前端·websocket·网络协议
等一场春雨2 天前
react websocket 全局访问和响应
前端·websocket·react.js
流穿2 天前
WebSocket vs SSE:实时通信技术的对比与选择
网络·websocket·网络协议·大语言模型·sse
_.Switch2 天前
FastAPI 的进阶应用与扩展技术:异步编程与协程、websocket、celery
网络·数据库·python·websocket·网络协议·性能优化·fastapi
Onlooker1292 天前
实现 WebSocket 接入文心一言
网络·websocket·网络协议
kikyo哎哟喂2 天前
计算机网络基础--WebSocket
websocket·网络协议·计算机网络
G丶AEOM3 天前
WebSocket了解
网络·websocket·网络协议
m0_748230943 天前
websocket 局域网 webrtc 一对一 多对多 视频通话 的示例
websocket·音视频·webrtc
m0_748238924 天前
web网页端使用webSocket实现语音通话功能(SpringBoot+VUE)
前端·spring boot·websocket