【你的第一个socket应用】Vue3+Node实现一个WebSocket即时通讯聊天室

简介: 这篇文章主要是用WebSocket技术实现一个即时通讯聊天室。从0到1一步一步的编写所有代码,上手容易
Hi~,我是 一碗周,一个在舒适区垂死挣扎的前端,如果写的文章有幸可以得到你的青睐,万分有幸~

🍐 写在前面

这篇文章主要是用WebSocket技术实现一个即时通讯聊天室,首先先要了解为什么使用WebSocket而不是普通的HTTP协议,如果使用HTTP协议它是下面这种情况:

  • 我发送一条消息,发送一个发送消息的请求;
  • 一直轮询接收别人发送的消息,不管有没有发送都要定时去调用接口。

这里明显有资源的浪费,因为我们不管有没有数据都要定时的去调用接收消息的接口。

这个就可以通过WebSocket去解决,本篇文章包括的内容如下

  • 简单说一下WebSocket
  • 使用daisyUI+Vue3搭建页面
  • socket通信demo
  • 实现即时通讯聊天室

最终实现效果如下:

socketdemo_e8JER6UWGn.gif

🍍 技术栈

项目的源代码在Github中,项目采用pnpm+Monorepo的方式搭建,如何搭建一个Monorepo项目可以参考从0开始使用pnpm构建一个Monorepo方式管理的demo

文本所用到的技术如下:

服务端:

  • socket.io@4.15.1
  • nodemon@2

客户端

🍏 什么是WebSocket

WebSocket是另一种网络协议,但没有完全脱离HTTP,握手阶段采用的就是HTTP协议,这么做的好处就是不易被屏蔽,能通过各种HTTP代理服务器

WebSocket最大的特点就是服务器可以主动向客户端推送消息,当然,客户端也可以主动的向服务器发送消息。而普通的HTTP协议只能由客户端向服务器发送,服务器根据内容进行返回。

通信过程如下图:

websocket通信_7tcKrs3ZfG.png

🍎 搭建页面

首先我们使用Vue+daisyUI搭建一下静态页面。

这里静态页面用啥都能写,我图省事选择了daisyUI,想要了解可以通过我 上一篇文章,简单的介绍。

🥭 组件的编写

这里我将聊天部分主要拆分3个组件,如下图所示:

image_FESRXF0wOk.png

这里对这几个组件的思路进行讲解,源代码可以去GitHub中获取。

🍌 NavHeader组件

这个组件比较简单,没有什么复杂的,将群聊名称和群聊人数通过父级传递过去就好,Props定义如下:

interface Props {
  groupName: string
  personNumber: number
}

组件的代码比较简单,这里就不列出占篇幅了,如果需要可以从Github中获取。

🍋 ChatItem组件

这里为了省事,把聊天的消息和进群退群的通知封装到一个组件,通过不同的type进行划分,类型定义如下:

export interface ChatDataItem {
  type: 'your' | 'me' | 'tips'
  id: string // 这条消息唯一的id
  name?: string // 用户名称
  content: string // 聊天内容 || 提示内容
  avatar?: string // 头像
  userId?: string // 用户的id
}
interface Props {
  chatData: ChatDataItem[]
}

type属性值如果为me表示自己发送的消息,如果为your则表示对面发送的消息,要是为tips则表示进群退群的提示。

🍊 InputBox组件

这个组件就更简单了,就是一个输入框,一个发送按钮,有个细节就是在输入框中按下回车可以触发与按下按钮相同的事件,这个在Vue中特别简单,就是通过keyup事件的enter修饰符即可,写法如下:

<input
  type="text"
  v-model="value"
  @keyup.enter="handleSend"
/>

🍉 JoinModal组件

这个组件是加入弹框组件,我使用的是Vue,如果你用的是小程序或者H5的话这个组件做成一个页面会更好一些。

这里我就做了最简单的一版,头像是随机的,然后关闭弹框后将头像以及名称通过事件的方式进行返回,其中<script>代码如下:

import { ref } from 'vue'
import avatarList from './../assets/avatar'
export interface JoinEvent {
  name: string
  avatar: string
}
const aList = [...avatarList]
const emits = defineEmits({
  // 校验 join 事件
  join: (e: JoinEvent) => {
    const { name, avatar } = e
    if (name && avatar) {
      return true
    } else {
      console.warn('未输入名字~')
      return false
    }
  },
})

const name = ref('')
const isOpen = ref(true)
const handleJoin = () => {
  // 随机头像
  const randomIndex = Math.floor(Math.random() * aList.length)
  const avatar = aList[randomIndex]

  emits('join', { name: name.value, avatar })
  isOpen.value = false
}

🍈 组件的使用

前面我们编写了很多组件,这里我们将组件组合起来进行展示,示例代码如下:

<script setup lang="ts">
import { reactive, ref } from 'vue'
import MainContainer from './components/MainContainer.vue'
import NavHeader from './components/NavHeader.vue'
import ChatItem, { ChatDataItem } from './components/ChatItem.vue'
import InputBox from './components/InputBox.vue'
import JoinModal, { JoinEvent } from './components/JoinModal.vue'

// 聊天数据
const chatData = ref<ChatDataItem[]>([])
// 当前用户
const curUser = reactive({ name: '', avatar: '', id: '', })
// 用户列表
const userList = ref(new Map())
const message = ref('')

const handleSend = (v: string) => {
  console.log(v) // v 即要发送的数据
  message.value = ''
}
const handleJoin = (e: JoinEvent) => {
  console.log(e) // 要加入的用户
}
</script>

<template>
  <!-- 外层容器 -->
  <MainContainer>
    <!-- 顶部栏 -->
    <NavHeader :group-name="'甜粥铺'" :person-number="userList.size" />
    <!-- 内容区域 -->
    <div class="px-4">
      <ChatItem :chat-data="chatData" />
    </div>
    <InputBox v-model="message" @send="handleSend" />
  </MainContainer>
  <JoinModal @join="handleJoin" />
</template>

<style scoped></style>

运行效果如下图:

image_eNcJ4rOgGq.png

🍇 实现即时通讯聊天室

🍓 socket通信例子

这里我使用的是socket.iosocket.io-client,这个操作的话会更简便一些,代码更简单一些,首先看一个小demo:

服务端代码如下:

import { Server } from 'socket.io'
// 开启cors跨域 https://socket.io/docs/v4/handling-cors/
const io = new Server(5432, { cors: true })

io.on('connection', socket => {
  console.log('连接成功')

  // receive a message from the client
  socket.on('send', e => {
    console.log(e)
    socket.emit('back', '服务器返回的消息')
  })

  socket.on('disconnecting', () => {
    console.log('用户离开,连接断开')
  })
})
首先需要保证已经安装了 socket.io这个依赖

这里我们监听connection这个事件,如果连接成功会触发这个回调函数,回调函数中有个socket实例,其中包含很多属性和方法,其中有一个id,用于表示这个连接唯一的标识。

服务端监听send,当客户端有消息进来则发出一个back事件,在客户端那边进行监听;

如果关闭这个连接,在客户端会发出一个disconnecting事件,服务器监听并作出响应。

客户端代码如下:

<script setup lang="ts">
import { io } from 'socket.io-client'
// 创建 socket 实例
const socket = io('ws://localhost:5432')
const send = () => {
  socket.emit('send', '来自客户端的消息')
}
socket.on('back', e => {
  console.log(e)
})
</script>

<template>
  <button class="btn btn-success" @click="send">发送</button>
</template>

<style scoped></style>

创建io示例的过程中进行与服务端的socket进行连接,整个过程如下所示:

  • 当与服务端连接成功后,服务端输出**;
  • 在客户端点击【发送按钮】,服务器输出**;
  • 客户端触发send后,发出back事件,客户端输出**
  • 最后关闭标签页断开连接,服务器输出**。

🥝 实现用户登入,保存状态

首先我们先实现服务端,服务端相对来说比较简单,实现代码如下:

import { Server } from 'socket.io'
const io = new Server(5432, { cors: true })

let userList = new Map()
io.on('connection', socket => {
  // 监听加入用户加入
  socket.on('join', e => {
    userList.set(socket.id, e)
    // 加入成功后返回加入成功的事件
    socket.emit('joined', Object.assign({}, e, { id: socket.id }))
  })
})

这里监听join事件,加入后将数据存储前面定义的map中,然后发出一个joined事件表示用户已经成功加入。

客户端的话需要在弹框关闭后发送join事件给服务端,然后监听joined事件并进行存储加入的用户的数据,实现代码如下:

import { io } from 'socket.io-client'
// 创建 socket 实例
const socket = io('ws://192.168.0.103:5432')
const curUser = reactive({
  name: '',
  avatar: '',
  id: '',
})
// 发送加入事件
const handleJoin = (e: JoinEvent) => {
  socket.emit('join', Object.assign({}, e))
}
// 监听加入成功的事件
socket.on('joined', (e: typeof curUser) => {
  curUser.avatar = e.avatar
  curUser.id = e.id
  curUser.name = e.name
})

编写完成进行测试,我们加入以后可以发现在加入成功后curUser的数据发送了变化。

🍐 实现用户加入欢迎

用户加入后欢迎实现非常简单,在加入成功后去发出事件,然后在客户端监听这个事件就好,实现代码如下:

服务端

import { Server } from 'socket.io'
const io = new Server(5432, { cors: true })

let userList = new Map()
io.on('connection', socket => {
  // 监听加入用户加入
  socket.on('join', e => {
    userList.set(socket.id, e)
    // 加入成功后返回加入成功的事件
    socket.emit('joined', Object.assign({}, e, { id: socket.id }))
    
    const uList = [...userList.entries()]
    // 触发广播
    socket.broadcast.emit('welcome', {
      ...e,
      uList,
    })
    // 自己展示加入的信息
    socket.emit('welcome', {
      ...e,
      uList,
    })
  })
})

这里发出了两次welcome事件,这是因为第一次是广播,发送给除自己外的所有人,第二次是仅仅发送给自己。

客户端实现只需要往chatDatapush数据即可,代码如下:

// 监听 welcome
socket.on('welcome', ({ name, uList }) => {
  // 将当前群聊中的成员保存到uList中
  uList.forEach((item: any[]) => {
    const [id, value] = item
    userList.value.set(id, value)
  })
  // 在消息卡片中展示欢迎信息
  chatData.value.push({
    type: 'tips',
    id: Math.random().toString().split('.')[1].slice(0, 10),
    content: '欢迎' + name + '加入群聊~',
  })
})

此时加入一个成员即可展示对应的信息。

🫐 实现消息的发送与展示

这里我们实现一下消息的发送以及接受展示,服务端只需将收到的消息广播出去即可,服务端代码如下:

// 监听消息发送
socket.on('send', e => {
  // 接受到消息给他广播出去
  socket.broadcast.emit('message', e)
})

客户端代码如下:

// 点击发送按钮或者在输入框中键入回车
const handleSend = (v: string) => {
  const obj = {
    id: Math.random().toString().split('.')[1].slice(0, 10),
    name: curUser.name,
    avatar: curUser.avatar,
    content: v,
    userId: curUser.id,
  }
  // 在 chatData 中新增一条数据,表示自己发送的
  const type: 'me' = 'me'
  chatData.value.push(Object.assign({}, { type }, obj))
  // 清空 input box 中的内容
  message.value = ''
  // 发出send事件,将消息发送出去
  socket.emit('send', obj)
}
// 监听消息的广播
socket.on('message', (e: any) => {
  const msg = Object.assign({}, e, { type: 'your' }) as ChatDataItem
  chatData.value.push(msg)
})

这里的发送消息其实就是如何往数组中push数据。

🍏 实现用户退出播报

最后我们来实现一下用户退出的播报功能,首先我们在服务端监听disconnecting事件的触发,如果触发则将用户在用户列表中删除并发出一个quit事件,在客户端进行展示。

服务端代码如下:

// 用户离开
socket.on('disconnecting', () => {
  const bool = userList.delete(socket.id)
  // 如果有用户离开,在进行广播(因为只打开页面不进入关闭页面也会触发这个事件)
  bool && socket.broadcast.emit('quit', socket.id)
})

客户端代码如下:

// 监听退出
socket.on('quit', (id: string) => {
  const user = userList.value.get(id)
  userList.value.delete(id)
  chatData.value.push({
    type: 'tips',
    id: Math.random().toString().split('.')[1].slice(0, 10),
    content: user.name + '退出群聊~',
  })
})

到这为止我们就把所有的代码全部写完了,现在就与开头的动图中实现的效果是一致的。

完整代码

🍎 写在最后

文章结束了,这里就简单的做了一个socket的demo,如果文中哪有问题欢迎指点\~

最后我想说一句话:

文章看完又点赞,年薪迟早过百万。

目录
相关文章
|
3月前
|
监控 JavaScript 算法
如何使用内存监控工具来定位和解决Node.js应用中的性能问题?
总之,利用内存监控工具结合代码分析和业务理解,能够逐步定位和解决 Node.js 应用中的性能问题,提高应用的运行效率和稳定性。需要耐心和细致地进行排查和优化,不断提升应用的性能表现。
220 77
|
3月前
|
存储 缓存 JavaScript
如何优化Node.js应用的内存使用以提高性能?
通过以上多种方法的综合运用,可以有效地优化 Node.js 应用的内存使用,提高性能,提升用户体验。同时,不断关注内存管理的最新技术和最佳实践,持续改进应用的性能表现。
171 62
|
2月前
|
监控 算法 JavaScript
基于 Node.js Socket 算法搭建局域网屏幕监控系统
在数字化办公环境中,局域网屏幕监控系统至关重要。基于Node.js的Socket算法实现高效、稳定的实时屏幕数据传输,助力企业保障信息安全、监督工作状态和远程技术支持。通过Socket建立监控端与被监控端的数据桥梁,确保实时画面呈现。实际部署需合理分配带宽并加密传输,确保信息安全。企业在使用时应权衡利弊,遵循法规,保障员工权益。
51 7
|
3月前
|
存储 缓存 监控
如何使用内存监控工具来优化 Node.js 应用的性能
需要注意的是,不同的内存监控工具可能具有不同的功能和特点,在使用时需要根据具体工具的要求和操作指南进行正确使用和分析。
91 31
|
3月前
|
JavaScript 前端开发 API
深入理解Node.js事件循环及其在后端开发中的应用
本文旨在揭示Node.js的核心特性之一——事件循环,并探讨其对后端开发实践的深远影响。通过剖析事件循环的工作原理和关键组件,我们不仅能够更好地理解Node.js的非阻塞I/O模型,还能学会如何优化我们的后端应用以提高性能和响应能力。文章将结合实例分析事件循环在处理大量并发请求时的优势,以及如何避免常见的编程陷阱,从而为读者提供从理论到实践的全面指导。
|
3月前
|
JavaScript
如何使用内存快照分析工具来分析Node.js应用的内存问题?
需要注意的是,不同的内存快照分析工具可能具有不同的功能和操作方式,在使用时需要根据具体工具的说明和特点进行灵活运用。
75 3
|
3月前
|
Web App开发 JSON JavaScript
Node.js 中的中间件机制与 Express 应用
Node.js 中的中间件机制与 Express 应用
|
3月前
|
缓存 监控 前端开发
Go 语言中如何集成 WebSocket 与 Socket.IO,实现高效、灵活的实时通信
本文探讨了在 Go 语言中如何集成 WebSocket 与 Socket.IO,实现高效、灵活的实时通信。首先介绍了 WebSocket 和 Socket.IO 的基本概念及其优势,接着详细讲解了 Go 语言中 WebSocket 的实现方法,以及二者集成的重要意义和具体步骤。文章还讨论了集成过程中需要注意的问题,如协议兼容性、消息格式、并发处理等,并提供了实时聊天、数据监控和在线协作工具等应用案例,最后提出了性能优化策略,包括数据压缩、缓存策略和连接管理优化。旨在帮助开发者更好地理解并应用这些技术。
157 3
|
4月前
|
网络协议 测试技术 网络安全
Python编程-Socket网络编程
Python编程-Socket网络编程
45 0
|
7月前
|
网络协议 开发者 Python
深度探索Python Socket编程:从理论到实践,进阶篇带你领略网络编程的魅力!
【7月更文挑战第25天】在网络编程中, Python Socket编程因灵活性强而广受青睐。本文采用问答形式深入探讨其进阶技巧。**问题一**: Socket编程基于TCP/IP,通过创建Socket对象实现通信,支持客户端和服务器间的数据交换。**问题二**: 提升并发处理能力的方法包括多线程(适用于I/O密集型任务)、多进程(绕过GIL限制)和异步IO(asyncio)。**问题三**: 提供了一个使用asyncio库实现的异步Socket服务器示例,展示如何接收及响应客户端消息。通过这些内容,希望能激发读者对网络编程的兴趣并引导进一步探索。
87 4

热门文章

最新文章