客户端:Vue3,服务端:Node,基于Socket.IO实现单聊的功能

目录

1.介绍

2.环境搭建

3.本功能实现的主要逻辑

4.客户端和服务端的主要代码

5.效果展示

6.socket.io的运作原理


1.介绍

本篇主要讲讲基于Socket.IO实现单聊功能的主要实现,包括了客户端和服务端Node。

在这个即时通讯无处不在的时代,实时聊天功能已经成为我们日常生活和工作中不可或缺的一部分。无论是与朋友闲聊,还是与同事协作,单聊功能都是构建任何通讯平台的基石。今天,我们将一起探索如何利用前端界的新星——Vue 3,以及后端的强大工具——Node.js,结合实时通讯的利器Socket.IO,来打造一个高效、稳定的单聊应用。

在本篇文章中,我们将从零开始,一步步搭建起一个简单的单聊系统。我们会深入了解Vue 3的新特性,如Composition API,以及如何利用它们来构建响应式的用户界面。同时,我们也会探索Node.js的强大功能,以及如何通过Socket.IO实现实时数据的双向传输。

无论你是Vue的新手,还是希望将Socket.IO集成到现有Node.js应用中的开发者,本文都将为你提供实用的指导和深入的见解。我们将一起走过环境搭建、服务端设置、客户端实现、消息格式设计等关键步骤,最终实现一个完整的单聊功能。

2.环境搭建

前端可以使用Vue cli 或者使用 vite都可以,后端使用任何框架也可以,本案例中使用的是express框架。

当然,无论使用哪种方式,我们都得先下载socket.io

npm install socket.io
pnpm add socket.io
yarn add socket.io

或者是使用cdn:socket.io的cnd

然后我们需要搭建一些必要的代码框架,来展示单聊的功能。

在实际的聊天功能中的业务肯定比我这个案例要复杂的多,我这里就是只做一个demo,没有数据库的一些操作,就是实现单聊(单发信息)的功能。

客户端的设计:

  • 我们可以实时的看到所有在线的用户,(聊天肯定有一个username,或者id,我们的这个username主要通过url中的query参数获取,这样可以不用操作数据库)
  • 可以给单独某个用户发送信息,页面会渲染发送的信息详情。

服务端设计:

  • 可以给客户端提供连接socket
  • 可以实时的存储当前在线的用户并发送给客户端
  • 可以接收消息给单独某个用户发送

3.本功能实现的主要逻辑

单聊是这篇文章的主要功能,其实其他很多的连接,是否断连,推送消息,接收消息等等功能从socket.io的官网的基础教程中就可以找到,主要是这个单聊的逻辑。

我们可以在客户端建一个user的数组,只要一有人连接,就往当中存储必要的username和userid,(注意这里的id是socket.io连接时创建的,每次都不唯一),如果有重连的就替换成新的id,有断连的就从当中剔除,然后再接收数据时可以查找到对应的id,给其单独的推送消息即可。

let userList = [] 
//监听接收消息的对象
  socket.on('sendMessage', (data) => {
    const targetSocket = io.sockets.sockets.get(data.id)
    const targetUser = userList.find((user) => user.id === data.id)
    if (targetSocket) {
      targetSocket.emit('receiveMessage', {
        sendForm: data.sendForm,
        toUser: targetUser.username,
        message: data.message,
        date: new Date().getTime()
      })
    }
  })

我们主要的还是通过id去进行单独推送信息。

然后客户端可以监听此信息,去接受服务端发来的数据

//监听接收信息
  socket.on("receiveMessage", (data) => {
    messageList.value.push(data)
  })

4.客户端和服务端的主要代码

客户端:

<template>
  <div>
    <!-- <router-view></router-view> -->
    <button @click="createWebSocketConnection">建立Websocket连接</button>
    <button @click="close">主动断开连接</button>
    <ul>
      <li v-for="i in userList" :key="i.id" @click="chooseUser = i.username, chooseid = i.id"
        :class="{ active: i.username === chooseUser }">{{ i.username }}</li>
    </ul>
    <div class="text">
      <input type="text" v-model="message" @focus="handleFocus" @blur="handleBlur">
      <button @click="sendMessage">发送信息</button>
    </div>
    <h1>{{ typing }}</h1>

    <!-- 聊天内容 -->
    <div class="chat">
      <ul>
        <li v-for="i in messageList" :key="i.id">
          <p>发送者:{{ i.sendForm }} ,接收者:{{ i.toUser }},内容:{{ i.message }}</p>
        </li>
      </ul>
    </div>
  </div>
</template>

<script setup>
import { io } from "socket.io-client";
import { useRoute } from "vue-router";
import { onUnmounted, ref } from 'vue'
const $route = useRoute()
const userList = ref([])
const message = ref('')
const messageList = ref([])
//选择当前需要发送信息的对象
const chooseUser = ref('')
const chooseid = ref('')
//谁正在输入中
const typing = ref('')
let socket = null
const createWebSocketConnection = () => {
  //随机生成一个username

  socket = io("http://localhost:5000", {
    query: { username: $route.query.username },
  });
  socket.on('connect', () => {
    console.log('连接成功')
    console.log(socket.id);
  })
  socket.on("disconnect", () => {
    console.log('断开连接'); // undefined
  });

  socket.on("userList", (data) => {
    userList.value = data
  })
  //监听接收信息
  socket.on("receiveMessage", (data) => {
    messageList.value.push(data)
  })
  socket.on('receiveTyping', (data) => {
    console.log(data);
    typing.value = data.message
  })
  socket.on('receiveStopTyping', (id) => {
    typing.value = ''
  })
}
const closeWebSocket = () => {
  socket.close()
}
const sendMessage = () => {
  //发送信息
  const messageData = {
    //发送对象
    sendForm: $route.query.username,
    //接受对象
    receiveForm: chooseUser.value,
    //时间
    time: new Date(),
    id: chooseid.value,
    message: message.value
  }
  socket.emit("sendMessage", messageData)
}
//监听用户正在输入中
const handleFocus = () => {
  const data = {
    username: $route.query.username,
    tousername: chooseUser.value,
    id: chooseid.value,
    message: `${$route.query.username} 正在输入中...`
  }
  socket.emit('typing', data)
}
//监听用户停止输入
const handleBlur = () => {
  socket.emit('stopTyping', chooseid.value)
}
const close = () => {
  socket.emit('close', $route.query.username)
  closeWebSocket()
}
onUnmounted(() => {
  close()
})


</script>

<style lang="css" scoped>
.active {
  color: red;
}
</style>

服务端:

const express = require('express')

const app = express()
const { Server } = require('socket.io')
app.get('/', (req, res) => {
  res.send('Hello World!')
})

//创建websocket服务器
const io = new Server(5000, {
  cors: {
    origin: '*' //配置跨域
  }
})
let userList = []

//监听事件
io.on('connection', (socket) => {
  console.log('a user connected')
  const username = socket.handshake.query.username
  if (!username) {
    return
  }

  const userinfo = userList.find((user) => user.username === username)

  if (userinfo) {
    //存在
    userinfo.id = socket.id
  } else {
    userList.push({
      id: socket.id,
      username
    })
  }
  console.log(userList)
  socket.on('disconnect', () => {
    console.log('user disconnected')
  })

  //发送事件
  io.emit('userList', userList)

  //监听接收消息的对象
  socket.on('sendMessage', (data) => {
    const targetSocket = io.sockets.sockets.get(data.id)
    const targetUser = userList.find((user) => user.id === data.id)
    if (targetSocket) {
      targetSocket.emit('receiveMessage', {
        sendForm: data.sendForm,
        toUser: targetUser.username,
        message: data.message,
        date: new Date().getTime()
      })
    }
  })
  //监听断开连接的信息
  socket.on('close', (username) => {
    userList = userList.filter((user) => user.username !== username)
    console.log(userList)
    io.emit('userList', userList)
  })
  //监听谁正在输入的事件
  socket.on('typing', (data) => {
    const targetSocket = io.sockets.sockets.get(data.id)
    if (targetSocket) {
      targetSocket.emit('receiveTyping', data)
    }
  })
  //监听停止输入的事件
  socket.on('stopTyping', (id) => {
    const targetSocket = io.sockets.sockets.get(id)
    if (targetSocket) {
      targetSocket.emit('receiveStopTyping')
    }
  })
})

app.listen(3000)

5.效果展示

效果是显而易见的,整体实现了单聊的效果。

6.socket.io的运作原理

Socket.IO 的运作原理涉及到几个关键的技术和概念,以实现实时的双向通信。以下是 Socket.IO 的基本运作原理:

1. 基于 WebSocket 的通信

  • WebSocket 是一种提供全双工通信通道的协议,允许服务器和客户端之间进行实时数据交换。Socket.IO 在底层默认使用 WebSocket 作为传输层,因为它提供了最快和最直接的通信方式。

2. 自动传输升级

  • 当客户端尝试与服务器建立连接时,Socket.IO 会首先尝试使用 WebSocket。如果浏览器或网络环境不支持 WebSocket,Socket.IO 会自动降级到其他可用的传输方式,如长轮询(Long-Polling)。

3. 事件驱动的接口

  • Socket.IO 提供了一个事件驱动的接口,允许开发者在客户端和服务器端监听和发射(emit)事件。这意味着除了简单的数据传输,Socket.IO 还支持复杂的交互模式,如实时游戏、协作编辑等。

4. 心跳包

  • 为了维持连接的活跃状态并检测连接是否仍然有效,Socket.IO 定期在客户端和服务器之间发送心跳包。如果在一定时间内没有收到响应,Socket.IO 会认为连接已断开,并尝试重新连接。

5. 房间和命名空间

  • Socket.IO 允许创建房间(rooms)和命名空间(namespaces),以便对通信进行分组和隔离。这样,不同的用户可以被添加到不同的房间,或者在不同的命名空间中通信,而不会互相干扰。

6. 广播和去中心化通信

  • 服务器可以向所有连接的客户端广播消息,也可以向特定的客户端或房间发送消息。此外,Socket.IO 支持去中心化通信,客户端可以直接向其他客户端发送消息,而无需通过服务器中转。

7. 断开重连

  • 如果客户端与服务器的连接断开,Socket.IO 会尝试自动重连。它会尝试使用不同的传输方式,直到重新建立连接。这个过程对用户来说是透明的,不会影响应用的实时性。

8. 状态管理

  • Socket.IO 管理每个连接的状态,包括连接、断开、重连等。服务器和客户端都可以监听这些状态变化,并根据状态执行相应的操作。

通过这些机制,Socket.IO 能够在各种网络环境下提供稳定的实时通信能力,使其成为构建实时应用的强大工具。开发者可以专注于业务逻辑的实现,而不必过多担心底层的通信细节。

具体的可以查看官方文档:socket.io的运作原理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/463022.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

波奇学Linux:线程安全和自选锁和读写锁

STL不是线程安全的 单例模式的线程安全 自选锁&#xff1a;当线程申请锁失败时&#xff0c;不是挂起&#xff0c;而是一直申请 挂起等待锁 &#xff1a;当线程申请锁失败时&#xff0c;把锁挂起 一般临界区时间短的适合自选锁&#xff0c;长的适合挂起等待锁

如何在“Microsoft Visual Studio”中使用OpenCV编译应用程序

返回目录&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 前一篇&#xff1a;OpenCV4.9.0在windows系统下的安装 后一篇&#xff1a; 警告&#xff1a; 本教程可以包含过时的信息。 我在这里描述的所有内容都将适用于 OpenCV 的C\C接口。我首先假…

wsl ubuntu 安装的正确方式

目录 wsl ubuntu 安装的正确方式&#xff1a; 将wsl2设置为默认版本&#xff1a; 1、打开powershell 2、设置wsl的版本为2 ​编辑 3、更新wsl程序 4、强制关闭子系统 5、查看wsl支持的列表 6、安装指定版本的系统 wsl ubuntu 安装的正确方式&#xff1a; 此时&#xff0c…

Leetcode31. 删除无效的括号

心路历程&#xff1a; 一开始看到有点懵&#xff0c;后来发现有点像按照一定规则穷举所有可能情况&#xff0c;想到了排列组合问题&#xff0c;再结合问题长度不固定&#xff0c;无法用已知个for循环表示&#xff0c;从而想到了回溯。这个题相当于需要在一定规则下枚举。 按照…

刚刚离乳的幼猫该如何选择猫粮品牌?

亲爱的猫友们&#xff0c;当你家的幼猫刚刚离乳&#xff0c;准备踏入猫粮的世界时&#xff0c;如何选择一款合适的猫粮品牌确实是个让人头疼的问题。&#x1f43e; 别担心&#xff0c;今天我就来为大家推荐一款值得信赖的幼猫粮——福派斯幼猫粮。 1️⃣ 考虑幼猫的营养需求 幼…

SQLiteC/C++接口详细介绍之sqlite3类(十三)

返回目录&#xff1a;SQLite—免费开源数据库系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍之sqlite3类&#xff08;十二&#xff09; 下一篇&#xff1a;SQLiteC/C接口详细介绍之sqlite3类&#xff08;十四&#xff09;&#xff08;未发表&#xff09; 40.sqlite3…

如何在webapp中于动发布一个应用

目录 第一步&#xff1a;在webapp文件夹内自定义文件夹第二步&#xff1a;生成一个文本&#xff0c;并把后缀改为 .html第三步&#xff1a;进入bin文件夹打开服务第四步&#xff1a;打开方式选择java第六步&#xff1a;输入你想输出的东西第七步&#xff1a;双击运行即可 第一步…

网络爬虫丨基于scrapy+mysql爬取博客信息

文章目录 写在前面实验描述实验框架实验需求 实验内容1.安装依赖库2.创建Scrapy项目3.配置系统设置4.配置管道文件5.连接数据库6.分析要爬取的内容7.编写爬虫文件 运行结果写在后面 写在前面 本期内容&#xff1a;基于scrapymysql爬取博客信息并保存到数据库中 实验需求 ana…

线程有哪几种状态(附图)以及线程状态的变化

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 线程的几种状态 线程的状态包括新建状态(New)、就绪状态(Runnable)、运行状态(Running)、阻塞状态(Blocked)、等待状态(Waiting)、超时等待状态…

1、FreeRTOS之任务管理

void vTask1( void *pvParameters ) { const char *pcTaskName "Task 1 is running\r\n"; volatile unsigned long ul; /* 和大多数任务一样&#xff0c;该任务处于一个死循环中。 */ for( ;; ) { /* Print out the name of this task. */ vPrintString( pcTaskNam…

腾讯云图形验证码的PHP示例

需要准备的 1.API密钥 SecretId 及 SecretKey 两部分&#xff0c; SecretId 用于标识 API 调用者的身份&#xff0c; SecretKey 用于加密签名字符串和服务器端验证签名字符串的密钥。 前往API密钥管理页面&#xff0c;即可进行获取 https://console.cloud.tencent.com/cam/ca…

切面条-蓝桥杯?-Lua 中文代码解题第1题

切面条-蓝桥杯&#xff1f;-Lua 中文代码解题第1题 一根高筋拉面&#xff0c;中间切一刀&#xff0c;可以得到2根面条。 如果先对折1次&#xff0c;中间切一刀&#xff0c;可以得到3根面条。 如果连续对折2次&#xff0c;中间切一刀&#xff0c;可以得到5根面条。 那么&#xf…

【二】【单片机】有关独立按键的实验

自定义延时函数Delay 分别用Delay.c文件存储Delay函数。用Delay.h声明Delay函数。每次将这两个文件复制到工程中&#xff0c;直接使用。 //Delay.c void Delay(unsigned int xms) //11.0592MHz {while(xms--){unsigned char i, j;i 2;j 199;do{while (--j);}…

web高可用集群(nginx负载均衡+keepalived实现调度器HA)

web高可用集群(nginx负载均衡keepalived实现调度器HA&#xff09; 主机IP地址代理服务器192.168.88.66代理服务器192.168.88.38Real server192.168.88.10Real server192.168.88.20 配置俩台Real server [rootweb1 ~]# vim /etc/yum.repos.d/nginx.repo [rootweb1 ~]# cat /e…

图解缓存淘汰算法 LRU、LFU | 最近最少使用、最不经常使用算法 | go语言实现

写在前面 无论是什么系统&#xff0c;在研发的过程中不可避免的会使用到缓存&#xff0c;而缓存一般来说我们不会永久存储&#xff0c;但是缓存的内容是有限的&#xff0c;那么我们如何在有限的内存空间中&#xff0c;尽可能的保留有效的缓存信息呢&#xff1f; 那么我们就可以…

AI毕业论文降重GPTS,避免AI检测,高效完成论文

视频演示 AI毕业论文降重GPTS&#xff0c;避免AI检测&#xff0c;高效完成论文&#xff01; 开发目的 “毕业论文降重”GPTS应用&#xff0c;作用为&#xff1a;重新表述学术论文&#xff0c;降低相似性评分&#xff0c;避免AI检测。 使用地址 地址&#xff1a;毕业论文降重…

浏览器如何进行静态资源缓存?—— 强缓存 协商缓存

在平时使用浏览器排查问题的过程中&#xff0c;我们有时会看到浏览器网络请求中出现304状态码&#xff0c;那么是什么情况下出现304呢&#xff1f;下面是关于这一现象的解释&#xff1a; 浏览器如何进行静态资源缓存&#xff1f;—— 强缓存 & 协商缓存 状态码 304浏览器如…

springboot基于spring boot的在线答题微信小程序

摘 要 在线答题微信小程序是考试中重要的一环&#xff0c;在线答题是学生获取任务信息的主要渠道。为了方便学生能够在网站上查看任务信息、考试&#xff0c;于是开发了基于 springboot框架设计与实现了一款简洁、轻便的在线答题微信小程序。本微信小程序解决了在线答题事务中的…

linux源配置:ubuntu、centos

1、ubuntu源配置 1&#xff09;先查电脑版本型号: lsb_release -c2&#xff09;再编辑源更新&#xff0c;源要与上面型号对应 参考&#xff1a;https://midoq.github.io/2022/05/30/Ubuntu20-04%E6%9B%B4%E6%8D%A2%E5%9B%BD%E5%86%85%E9%95%9C%E5%83%8F%E6%BA%90/ /etc/apt/…

HTTPS(超文本传输安全协议)工作过程

一、简述HTTPS HTTPS超文本传输协议&#xff08;全称&#xff1a;Hypertext Transfer Protocol Secure &#xff09;&#xff0c;是以安全为目标的 HTTP 通道&#xff0c;在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性 。HTTPS 在HTTP 的基础下加入SSL&#x…