1. 具体代码在需要的下载

https://gitee.com/zyqwasd/socket

效果:

2. package.json文件

1. 下载基本的模块  修改了start 脚本  nodemon 需要先单独下载 npm install nodemon  开启服务器直接nodemon就好

  1. 1 {
  2. 2 "name": "socketio",
  3. 3 "version": "1.0.0",
  4. 4 "description": "",
  5. 5 "main": "app.js",
  6. 6 "scripts": {
  7. 7 "test": "echo \"Error: no test specified\" && exit 1",
  8. 8 "start": "nodemon app.js"
  9. 9 },
  10. 10 "author": "",
  11. 11 "license": "ISC",
  12. 12 "dependencies": {
  13. 13 "jsonwebtoken": "^8.5.1",
  14. 14 "koa": "^2.13.4",
  15. 15 "koa-bodyparser": "^4.3.0",
  16. 16 "koa-router": "^12.0.0",
  17. 17 "koa-static": "^5.0.0",
  18. 18 "mysql2": "^2.3.3",
  19. 19 "socket.io": "^4.5.1"
  20. 20 }
  21. 21 }

3. 搭建简单的koa服务器

  1.  
const koa = require("koa");     //koa模块
const static = require("koa-static");  //静态路由模块  
const path = require("path");    //path处理地址模块
const bodyParser = require("koa-bodyparser");// 解析post传参模块
const indexRouter = require("./routers/indexRouter");// 自己定义的路由模块 
const { createServer } = require("http");// 因为socket.io 用到http 中creareServer 方法
const app = new koa();// 实例化 koa 
const httpServer = createServer(app.callback());   
//引入socketServer模块
const socketServer = require("./service/socketService");
// 把需要的httpServer传进去 这个是后面的方法 因为里面要用到httpServer 我就传了进去
socketServer(httpServer);
// 获取post数据
app.use(bodyParser());
// 静态资源 
app.use(static(path.join(__dirname, "public")));
// 路由
app.use(indexRouter.routes());
// 监听
httpServer.listen(3000, () => {
  console.log("server start");
});

4.indexRouter路由 主要是管理和分配其他路由模块

  1. const indexRouter = require("koa-router");//
  2. const loginRouter = require("./loginRouter");
  3. const chatRouter = require("./chatRouter");
  4.  
  5. const router = new indexRouter(); //实例化路由模块
  6.  
  7. router.use("/login", loginRouter.routes(), loginRouter.allowedMethods());// 如果访问关于 /login 就进入这个
  8. router.use("/", chatRouter.routes(), chatRouter.allowedMethods());// 如果访问 / 就走这个 allowedMethods()可以很友好的告诉客户端服务端响应的是什么请求
  9. module.exports = router; // 导出模块

5. loginRouter 路由 主要是验证用户名和密码来设置token的

  1. const loginRouter = require("koa-router");
  2. const router = new loginRouter();
  3. const controller = require("../controller/loginController");
  4. // 访问/login 就指向/login.html页面
  5. router.get("/", (cxt, next) => {
  6. cxt.redirect("/login.html");
  7. });
    // 如果post 请求来了就传到 controller 模块中的find 方法 这个封装的方法
  8. router.post("/", controller.find);
  9.  
  10. module.exports = router;

6. chat 路由 主要是指向页面的

  1. const chatRouter = require("koa-router");
  2.  
  3. const router = new chatRouter();
  4. // 访问/chat 就返回 /chat.html 页面
  5. router.get("/", (cxt, next) => {
  6. cxt.redirect("/chat.html");
  7. });
  8.  
  9. module.exports = router;

7.login中的controller模块 用来验证用户名密码给前端传token

  1. const service = require("../service/loginService");//导入模块主要用来去数据库查找
  2. const jwt = require("../uilt/JWT");// 这个主要用来加密生成token 方法在下面有说明
  3. const controller = {
  4. async find(cxt, next) {// 定义响应给服务器的函数
  5. const { password, username } = cxt.request.body;// 把数据解构出来
  6. // 去数据库查找
  7. let [data] = await service.find(username, password);// 去service模块中去查找数据 等待数据返回 这里用到了 async await 解决异步问题
  8. if (data.length) {//返回的是数组 如果找到到了length就不为0
  9. let token = jwt.generate(data[0], "1d"); // 利用封装的方法 传入数据生成token
  10. cxt.set("Authorization", token);//设置响应头字段为Authorization 给前端返回token
  11. cxt.body = { OK: 1 };// 返回前端一个 ok:1
  12. } else {// 不成功就返回一个ok:0
  13. cxt.body = { OK: 0 };
  14. }
  15. },
  16. };
  17. module.exports = controller;

8. 封装的jwt 模块

  1. const jwt = require("jsonwebtoken");//导入安装好的jsonwebtoken模块
  2. const secret = "chatjsonwebtoken"; // 设置秘钥
  3. myjwt = {
    //写一个生成token 的方法传入值和过期时间就能生成token
  4. generate(value, expires) {
  5. return jwt.sign(value, secret, { expiresIn: expires });
  6. },
    //生成解密的办法 因为这个token 解密失败会阻断服务器 所以用trycath 来处理 成功返回数据 不成功返回false
  7. verify(token) {
  8. try {
  9. return jwt.verify(token, secret);
  10. } catch (error) {
  11. return false;
  12. }
  13. },
  14. };
  15.  
  16. module.exports = myjwt;

9.定义service方法来链接数据库这里用到的是mysql 数据库和mysql2模块

  1. const mysql = require("mysql2");
  2. // 创建一个对象 导出这个对象就好 方法写在这个大对象中
  3. const service = {
    //接收传来的数据
  4. find(username, password) {
    //因为这个找不到也会报错阻止浏览器运行 所以用到了 trycath
  5. try {//创建连接 因为这个是异步的所以用到了promise()
  6. const promisePool = mysql.createPool(getDBConfig()).promise();
          //sql 语句查询
  7. return promisePool.query(
  8. `select username from user where username=? && password=?`,
  9. [username, password]// 这个是传参 对应的是里面的?号
  10. );
  11. } catch (error) {//找不到就返回 【】
  12. return [];
  13. }
  14. },
  15. };
  16.  
  17. module.exports = service;
    // 定义连接数据
  18. function getDBConfig() {
  19. return {
  20. host: "127.0.0.1",//地址
  21. user: "root",//用户名字
  22. port: "3306",// 端口 一般都是3306 如果换了就换这个端口就行
  23. password: "",// 密码因为我是本地的就没有设置密码
  24. database: "chat", // 数据库名字
  25. connectionLimit: 1,//连接池
  26. };
  27. }

核心代码 处理数据和响应数据 socket.io 的处理私聊和群聊 和渲染列表

1. 主要用官网的emit 发送消息 和 on 事件监听消息 类似与一种订阅和发布的那种感觉 去看看官网文档 非常的详细 https://socket.io/docs/v4

  1. const { Server } = require("socket.io");
  2. // 引入jwt 模块
  3. const jwt = require("../uilt/JWT");
  4.  
  5. // 定义函数来处理数据
  6. function socketServer(httpServer) {
    //创建io 这个服务器它和koa用的端口是一个
  7. const io = new Server(httpServer);
  8. io.on("connection", async (socket) => {//当有设备连入的时候 会发生的事情
  9. // 拿到token值
  10. let token = socket.handshake.query.token;
  11. let payload = jwt.verify(token);
  12. if (payload) {
  13. // 如果 token 没过期就走这
  14. socket.user = payload.username;// 给每一个设备都打上一个标签挂上他自己的username 方便后面查找
  15. socket.emit(msgType.chatGroup, chatData(`欢迎${socket.user}`, "广播"));// 给每个进入的设备都发一个广播欢迎用户
  16. } else {
  17. // token 过期了就直接send error 给前端
  18. socket.emit(msgType.chatError, chatData("token过期"));
  19. }
  20. // 当获取列表传来的时候
  21. socket.on(msgType.chatList, (msg) => {
  22. // 就给前端返回列表
  23. sendAll(io);
  24. });
  25. // 当客户端发来群发
  26. socket.on(msgType.chatGroup, (msg) => {
  27. socket.broadcast.emit(msgType.chatGroup, chatData(msg.data, socket.user));//这个是利用官方给的方法忽略自己给所有人发 数据类型是群发
  28. });
  29. // 当客户端发来私聊 监听单聊发来的类型
  30. socket.on(msgType.singleChat, (msg) => {
      //这里用到前面在socke上打上的标记 数据类型是真的不好找而且嵌套太多开始参考了许多文献发现都没有太好的方法 官方给的方法是用嵌套字id 大家也可以试试上面有文档
  31. Array.from(io.sockets.sockets).forEach((item) => {
      // 其中item[1]是每一个的客户端 item[0]好像是id 大家可以自行打印一下
  32. if (item[1].user === msg.user) {
          //当item[1]找到给他打上的标记 等于了 客户端给我们传来的向哪个用户发的名字 就让这个客户端返回一个私聊的数据
  33. item[1].emit(msgType.singleChat, chatData(msg.data, socket.user));
  34. }
  35. });
  36. });
  37. // 当客户端关闭的时候会发生的 事情 这个也是官网给的 方法
  38. socket.on("disconnect", () => {
  39. sendAll(io);//重新渲染列表
  40. });
  41. });
  42. }
  43. // 把模块暴露出去
  44. module.exports = socketServer;
  45. // 定义回传的数据回传一样的数据可以方便前端渲染页面
  46. function chatData(data, user) {
  47. return {
  48. data,
  49. user,
  50. };
  51. }
  52. //定义回传类型 这个定义回传类型 语义化更加清晰 和前端的类型保持一致好判断不至于混乱
  53. const msgType = {
  54. chatError: 0,
  55. chatList: 1,
  56. chatGroup: 2,
  57. singleChat: 3,
  58. };
  59. // 发送列表
  60. function sendAll(io) {
  61. // 获取到列表;
    let arr = Array.from(io.sockets.sockets)// 先拿到 io.sockets.sockets这个是没一个的客户端 把它变为 数组
  62. .map((item) => item[1].user) // 利用数组的map方法来映射处理拿到每一个我们给它打的标记就是每个的用户名把数据在返回回去成为一个新的数组
  63. .filter((item) => item);//因为中间有好多undefined 必须得处理不然客户端会一直报错 把它筛选过滤利用数组的filter 只有真的留下了返回新的数组
  64. io.emit(msgType.chatList, Array.from(new Set(arr))); // 向客户端发送列表信息 但是这个列表信息如果多次刷新客户端会造成用户名多次出现在中列表所以用set结构来进行数组去重再变回数组
  65. }

前端代码login 登录页面代码只有js 代码 页面结构比较乱大家可以去 gitee 查看下载上面有地址

  1. 1 document.querySelector("button").addEventListener("click", function () {// 获取页面元素 添加点击事件
  2. 2 fetch("/login", {// fetch方法向login发post 请求携带获取到的input 的值
  3. 3 method: "post",
  4. 4 body: JSON.stringify({
  5. 5 username: username.value,
  6. 6 password: password.value,
  7. 7 }),
  8. 8 headers: {// 以josn 格式发送
  9. 9 "Content-Type": "application/json",
  10. 10 },
  11. 11 })
  12. 12 .then((res) => {//这个是没经过处理的函数想要获取到请求头中的token必须在第一个里面获取这个方法的请求头通过get方法获取到 大家可以依次打印
    13 localStorage.setItem("token", res.headers.get("authorization")); // 设置本地存储 存入token
  13. 14 return res.json();
  14. 15 })
  15. 16 .then((res) => {
  16. 17 if (res.OK) { // 判断后端传来的ok
  17. 18 localStorage.setItem("username", username.value);//设置username 方便后期渲染页面处理数据
  18. 19 location.href = "/";//跳转到/路径这里指向的是chat.html
  19. 20 } else {
  20. 21 text_err.innerHTML += "用户名密码错误"; //错误了就在页面上显示错误
  21. 22 }
  22. 23 });
  23. 24 });

前端chat 代码 处理数据和发送数据 先导入socket.io模块的客户端代码 我用的是4.5.2 版本的min.js

  1. https://github.com/socketio/socket.io/tree/main/client-dist
  2. 客户端和服务器端用的方法啥的都挺 一样的非常的不错
  1. // scoket服务器连接
    const socket = io(
  2. `ws://localhost:3000?token=${localStorage.getItem("token")}`//先连接服务器发送token验证token
  3. );
  4. //定义接受后端类型 和给服务端传的数据类型和服务器的一样
  5. const msgType = {
  6. chatError: 0,
  7. chatList: 1,
  8. chatGroup: 2,
  9. singleChat: 3,
  10. };
  11. // --------------------------------------发送消息
  12. // 进来直接去请求列表
  13. socket.emit(msgType.chatList);
  14. document
  15. .querySelector("button")
  16. .addEventListener("click", function () { // 给按钮添加点击事件
  17. // 如果为空return
  18. if (!ipt.value.trim()) return alert("不能为空");
  19. if (select.value === "all") {
  20. // 发送群聊 数据是输入框的数据
  21. socket.emit(msgType.chatGroup, chatData(ipt.value));
  22. // 因为群聊没有给自己发所以要自己渲染页面
  23. renderLi(chatData(ipt.value, localStorage.getItem("username")),true);
  24. } else {
  25. // 私聊 发送想要聊天的用户给客户端和数据
  26. socket.emit(msgType.singleChat, chatData(ipt.value, select.value));
  27. // 因为群聊没有给自己发所以要自己渲染页面
  28. renderLi(chatData(ipt.value, localStorage.getItem("username")),true);
  29. }
  30. //清空聊天框
  31. ipt.value = "";
  32. });
  33.  
  34. // ---------------------------------------接受消息
  35. // 接受群聊消息
  36. socket.on(msgType.chatGroup, (msg) => {
  37. renderLi(msg);//传到方法中渲染小li
  38. });
  39. // 接收列表信息
  40. socket.on(msgType.chatList, (msg) => {
  41. select.innerHTML =
  42. ` <option value="all">all</option>` + // 第一个一定是all 所以用到这种拼接的方法
  43. msg.map((item) => ` // 用到了map 加工处理每个数据
  44. <option value="${item}">${item}</option>
  45. `).join("");// 把数据转为字符串
  46. });
  47. // 接受错误消息
  48. socket.on(msgType.chatError, (msg) => {
  49. localStorage.removeItem("token"); //当有错误传来的时候先删除无用的token 在跳转页面
  50. location.href = "/login";
  51. });
  52. // 接受私聊消息
  53. socket.on(msgType.singleChat, (msg) => {
  54. renderLi(msg);// 渲染数据
  55. });
  56. // ------------------------------------------方法
  57. // 定义渲染ul中li
  58. function renderLi(data, flag) {
  59. // 创建一个小 li
  60. const li = document.createElement("li");
  61. // 给li 加class类 判断 flag 来决定左右
  62. li.className = flag ? "right" : "left";
  63. // 把数据放进去
  64. li.innerHTML = `<span>${data.user}</span>${data.data}`;
  65. ul.appendChild(li);//给ul 添加 li 在最后面
  66. // 定义的方法让聊天框到最低下
  67. scrollTo();
  68. }
  69. // 定义接受后端的数据
  70. function chatData(data, user) {
  71. return {
  72. data,
  73. user,
  74. };
  75. }
  76. // 聊天框滚动事件
  77. function scrollTo() {
  78. document.querySelector(".dialog").scrollTo(0, ul.offsetHeight);//让聊天框到最下面去
  79. }

请大佬多多指教

注释不易!!!

基于koa模块和socket.io模块搭建的node服务器实现通过jwt 验证来渲染列表、私聊、群聊功能的更多相关文章

  1. koa+mysql+vue+socket.io全栈开发之web api篇

    目标是建立一个 web QQ的项目,使用的技术栈如下: 后端是基于koa2 的 web api 服务层,提供curd操作的http接口,登录验证使用的是 json web token,跨域方案使用的是 ...

  2. koa+mysql+vue+socket.io全栈开发之数据访问篇

    后端搭起大体的框架后,接着涉及到的就是如何将数据持久化的问题,也就是对数据库进行 CURD 操作. 关于数据库方案, mongodb 和 mysql 都使用过,但我选用的是 mysql,原因: 目前为 ...

  3. 基于react+react-router+redux+socket.io+koa开发一个聊天室

    最近练手开发了一个项目,是一个聊天室应用.项目虽不大,但是使用到了react, react-router, redux, socket.io,后端开发使用了koa,算是一个比较综合性的案例,很多概念和 ...

  4. 使用Node.js的socket.io模块开发实时web程序

    首发:个人博客,更新&纠错&回复 今天的思维漫游如下:从.net的windows程序开发,摸到nodejs的桌面程序开发,又熟悉了一下nodejs,对“异步”的理解有了上上周对操作系统 ...

  5. nodejs之socket.io模块——实现了websocket协议

    Nodejs实现websocket的4种方式:socket.io.WebSocket-Node.faye-websocket-node.node-websocket-server,主要使用的是sock ...

  6. 使用socket.io开发简单群聊功能

    1.新建package.json文件: { "name": "socket-chat-example", "version": " ...

  7. 27、通过visual s'tudio 验证 SOCKET编程:搭建一个TCP服务器

    本文就是在windows下进行socket编程,搭建一个TCP客户端. 在visual studio下编程,首先在windows下进行初始化(这点在linux下是不需要的): /* 初始化 Winso ...

  8. 手把手教你从购买vps到搭建一个node服务器

    要准备什么? 1.5刀 2.最好有FQ软件(可以用蓝灯) let's Go! 一.vps购买 vps可以选择digital ocean(do) 链接 ,由于是外国网站,响应比较慢,所以最好翻个墙. g ...

  9. 基于C语言的Socket网络编程搭建简易的Web服务器(socket实现的内部原理)

    首先编写我们服务器上需要的c文件WebServer.c 涉及到的函数API: int copy(FILE *read_f, FILE * write_f) ----- 文件内容复制的方法 int Do ...

随机推荐

  1. (数据库提权——Redis)Redis未授权访问漏洞总结

    一.介绍 1.Redis数据库 Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key- ...

  2. Tomcat深入浅出(一)

    一.Tomcat简介 我们下载好Tomcat后需要配置一下Java环境:如果打开出现闪退得情况,首先是jdk 同时配置JRE_HOME Tomcat的一些关键目录: /bin:存放用于启动及关闭的文件 ...

  3. Java编程作业

    1.编程题 设计一个用户类User,类中的变量有用户名.密码和记录用户数量的变量,定义3个构造方法:无参的.为用户名赋值的.为用户名和密码赋值的,还有获取和设置密码的方法和返回类信息的方法. pack ...

  4. idea 内置tomcat jersey 上传文件报403错误

    Request processing failed; nested exception is com.sun.jersey.api.client.UniformInterfaceException: ...

  5. 大数据--Hive的安装以及三种交互方式

    1.3 Hive的安装(前提是:mysql和hadoop必须已经成功启动了) 在之前博客中我有记录安装JDK和Hadoop和Mysql的过程,如果还没有安装,请先进行安装配置好,对应的随笔我也提供了百 ...

  6. 使用gulp助力前端自动化

    前言 随着前端诸如webpack,rollup,vite的发展,gulp感觉似乎好像被取代了.其实并没有,只不过它从台前退居到了幕后.我们仍然可以在很多项目中看到它的身影,比如elementplus. ...

  7. tcp协议传输中的粘包问题

    什么是粘包问题 tcp是流体协议. 其nagle算法会将数据量较小. 并且发送间隔时间较短的多个数据包合并为一个发送. 网络传输的时候是一段一段字节流的发送. 在接收方看来根本不知道字节流从何开始. ...

  8. Python3.7+jieba(结巴分词)配合Wordcloud2.js来构造网站标签云(关键词集合)

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_138 其实很早以前就想搞一套完备的标签云架构了,迫于没有时间(其实就是懒),一直就没有弄出来完整的代码,说到底标签对于网站来说还是 ...

  9. 利用Css3样式属性Cursor来更换自定义个性化鼠标指针(光标)

    现而今,我们纵向的回顾整个大前端的历史,不难发现,人们对前端的审美要求越来越高,越来越严苛,与此同时,人们对追求美的体验是也极致的,从理性到感性,从平面到几何,从现实到虚拟,所以从某种角度来说,作为前 ...

  10. PostGresql listen与notify命令

    LISTEN与NOTIFY命令 PostgreSQL提供了client端和其他client端通过服务器端进行消息通信的机制.这种机制 是通过LISTEN和NOTIFY命令来完成的. 1.LISTEN与 ...