socket.io一对一实时聊天

本文介绍了Socket.IO,一个用于实现实时双向通信的JavaScript库,如何在Vue3前端和Express后端中配合使用,实现连接、断开连接以及事件驱动的通信机制。重点讲解了关键APIemit和on的应用以及其在聊天应用中的部署和优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

socket.io是什么?

Socket.IO 是一个库,可以在客户端和服务器之间实现 低延迟双向 和 基于事件的 通信

Socket.IO 是一个开源的 JavaScript 库,可以实现实时通信,适用于构建实时应用程序,如消息通讯和多人在线游戏等等。能用于实时通讯的库有很多,其中包括:WebSocket、SignalR Pusher等等,但是socket.io对于消息通讯支持得比较好,所以我们选用soket.io。

官网链接地址:介绍 | Socket.IO

效果展示

用到的技术栈

前端:vue3+socket.io-client

后端:express+socket.io

connection --- 建立连接
disconnect --- 断开连接

// server-side
io.on("connection", (socket) => {
  console.log(socket.id); // x8WIv7-mJelg7on_ALbx
});

// client-side
socket.on("connect", () => {
  console.log(socket.id); // x8WIv7-mJelg7on_ALbx
});

socket.on("disconnect", () => {
  console.log(socket.id); // undefined
});

服务端

客户端

优点及特点介绍

跨平台兼容性:socket.io 支持在多种平台和设备上使用,并且可以在客户端和服务器之间建立可靠的连接,包括浏览器、服务器和移动设备。

双向通信:socket.io 提供了一个基于事件的双向通信机制,使得服务器和客户端可以实时地发送和接收数据。这种实时通信模型非常适用于聊天应用程序、实时协作工具等需要即时更新的场景。

异地分布支持:socket.io 具有对异地分布的支持,即使服务器和客户端在不同的服务器上,也可以通过 socket.io 建立稳定的连接并进行实时通信。

自动回退:socket.io 使用了一种自动回退机制,如果浏览器或设备不支持 WebSocket 协议,它将自动降级到使用长轮询或其他传输方式,以确保与服务器的连接仍然可靠。

极简的 API:socket.io 提供了简单易用的 API,使得开发者可以轻松地建立和管理基于事件的通信。它提供了事件绑定、消息发送、房间管理等功能,使得开发者可以专注于业务逻辑的实现。

可靠性和容错性:socket.io 具备可靠性和容错性,在网络不稳定或断开连接的情况下,可以自动重新连接,并提供一些机制来处理丢失的数据包,保证通信的顺利进行。

在服务器端,我们可以使用 on 方法来监听自定义事件

socket.on('event', (data) => {
  console.log('Received data from client:', data);
});

在客户端,我们可以使用 emit 方法来触发自定义事件,并传递数据给服务器 

socket.emit('event', data);

socket.io 实现实时通信的流程

  1. 客户端发送连接请求。
  2. 服务器响应请求,建立连接。
  3. 客户端和服务器之间进行双向数据传输。
  4. 在需要时,客户端或服务器可以断开连接。

emit 和 on

emit 和 on 是最重要的两个api,分别对应 发送 和 监听 事件。

  • socket.emit(eventName[, ...args]):发射(触发)一个事件
  • socket.on(eventName, callback):监听一个 emit 发射的事件

安装命令

npm install socket.io-client

首页进入聊天页面

<template>
  <div class="home">
    <h1 style="color: red;">欢迎来到银熊联盟聊天部</h1>
    <button style="width: 200px; height: 40px;" @click="router.push('/about?username=q1')">用户01</button><br />
    <hr />
    <button style="width: 200px; height: 40px;" @click="router.push('/about?username=q2')">用户02</button><br />
    <hr />
    <button style="width: 200px; height: 40px;" @click="router.push('/about?username=q3')">用户03</button><br />
  </div>
</template>

<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()

</script>

<style>
.home {
  text-align: center;
  margin: auto;
}
</style>

进入到聊天页面

<template>
  <div>
    <h1 style="color: red;">欢迎来到银熊联盟聊天部</h1>
    <h2 style="color: fuchsia;">有朋自远方来,不亦说乎!</h2>
    <div class="box-liao">
      <h3>以下是用户,点击和某个用户聊天,一对一私密聊天,不会被第三者极道</h3>
      <ul>
        <template v-for="userInfo of state.userList" :key="userInfo.id">
          <li v-if="userInfo.username === state.username">
            {{ userInfo.username }}
          </li>
          <li v-else>
            <a href="javascript:;" @click="selectUser(userInfo)">{{ userInfo.username }}</a>
          </li>
        </template>
      </ul>
    </div>
    <div v-if="state.targetUser">
      <h3>你将要和{{ state.targetUser.username }}用户进行交流,请准备...</h3>
      <input type="text" placeholder="说点什么吧 ..." v-model="state.msgText" @keyup.enter="sendMessage">
      <button @click="sendMessage">send</button>
    </div>
    <div class="box-xx" ref="messageContainer">
      <span style="color: blueviolet;font-size: 22px;">你是{{ state.username }}用户,请保持不变,感谢</span>
      <ul>
        <li v-for="(item, index) of messageList" :key="index"
          :class="[item.fromUsername === state.username ? 'message-right' : 'message-left']">
          <p><span style="color: red;font-size: 20px;;">{{ item.fromUsername }}</span> : {{
            dayjs(item.dateTime).format('YYYY-MM-DD HH:mm:ss') }}</p>
          <p>{{ item.msg }}</p>
        </li>
      </ul>
    </div>
  </div>
</template>

<script setup>
import { io } from "socket.io-client";
import { useRouter } from "vue-router";
import { reactive, computed, ref, nextTick } from "vue";
import dayjs from 'dayjs'

const router = useRouter()
console.log(router);
const state = reactive({
  username: router.currentRoute.value.query.username,
  userList: [],
  targetUser: null,
  msgText: '',
  messageBox: {}
})

const messageContainer = ref(null);

const messageList = computed(() => {
  return (state.messageBox[state.username] && state.targetUser) ?
    state.messageBox[state.username].filter(item => {
      return item.fromUsername === state.targetUser.username ||
        item.toUsername === state.targetUser.username
    }) : []
})

const socket = io("http://localhost:6789", { // 后端接口
  autoConnect: true; // 自动连接
  query: {
    username: state.username
  }
})

const selectUser = (userInfo) => {
  // console.log(userInfo);
  state.targetUser = userInfo
  console.log('userInfo', userInfo);
}

const sendMessage = () => {
  // 如果msgText中没有数据就不会触发此事件机制
  if (!state.msgText.length) return;
  // 清除两端的空格
  const message = state.msgText.trim();
  // 判断两端是否有空格,并处理
  if (message) {
    appendMessage({
      fromUsername: state.username,
      toUsername: state.targetUser.username,
      msg: state.msgText,
      dateTime: new Date().getTime()
    })

    // 用户的个人信息
    socket.emit('send', {
      fromUsername: state.username,
      targetId: state.targetUser.id,
      msg: state.msgText
    })
    // 清空聊天框的信息
    state.msgText = ''

    // 滚动到底部
    nextTick(() => {
      scrollToBottom();
    });
  }
}

// 置底
const scrollToBottom = () => {
  if (messageContainer.value) {
    messageContainer.value.scrollTop = messageContainer.value.scrollHeight;
  }
};

socket.on('online', (data) => {
  console.log('online', data);
  // 聊条信息赋值给一个数组
  state.userList = data.userList
})

socket.on('receive', (data) => {
  console.log('receive', data);
  appendMessage(data)
  // 滚动到底部
  nextTick(() => {
    scrollToBottom();
  });
})

socket.on('duankai', (data) => {
  console.log('duankai', data);
  alert(data.msg)
  // appendMessage(data)
})

socket.on('tuichu', (data) => {
  console.log('tuichu', data);
  alert(data.msg)
  // appendMessage(data)
})

socket.on('error', (err) => {
  // 报错时触发
  console.log(err);
})

socket.on('disconnect', (data) => {
  // 用户断开连接时执行的操作
  socket.emit('disconnect'); // 向服务器发送断开连接的消息
});

function appendMessage(data) {
  !state.messageBox[state.username] && (state.messageBox[state.username] = [])
  state.messageBox[state.username].push(data)
}
</script>

<style scoped>
.message-left {
  text-align: left;
  background-color: #eaeaea;
  padding: 5px;
  margin-bottom: 10px;
}

.message-right {
  text-align: right;
  background-color: #eaeaea;
  padding: 5px;
  margin-bottom: 10px;
}

.box-xx {
  height: 500px;
  /* 设置容器高度 */
  overflow-y: auto;
  /* 启用垂直滚动 */
}

.box-liao ul {
  list-style: none;
  display: flex;
  justify-content: flex-start;
}

.box-liao ul li {
  padding: 10px;
  vertical-align: middle;
  display: flex;
  justify-content: center;
  align-items: center;
}

.box-liao ul li a {
  text-decoration: none;
  color: red;
  font-size: 22px;
  height: 30px;
  line-height: 30px;
}
</style>

后端接口搭建

安装插件

npm i socket.io

安装跨域插件

npm i cors
const express = require("express");
const app = express();

// 跨域插件
const cors=require('cors')

// 创建http服务器
const httpServer=require("http").createServer(app)

const { Server } = require("socket.io");

app.use(cors()) // 暴露插件

// 创建 socket.io 服务器
const io = new Server(httpServer, {
  cors: {
    origin: ["http://localhost:8080"], // 客户端路径及端口
    methods: ["GET","POST"]
  },
});

const userList = []; // 私聊数据表

io.on("connection", (socket) => {
  console.log("开始了");
  // 私人聊天版本
  // console.log('connection',socket.id);
  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,
    });

    // 监听用户断开连接事件,将用户从用户列表中移除
    socket.on("disconnect", () => {
      const index = userList.findIndex((user) => user.id === socket.id);
      if (index !== -1) {
        userList.splice(index, 1);
      }
    });
  }
  
  // console.log(userList)
  io.emit("online", {
    userList,
  });

  socket.on("send", ({ fromUsername, targetId, msg }) => {
    // console.log(fromUsername,targetId,msg);
    // console.log(io.sockets.sockets.get(targetId));
    const targetSocket = io.sockets.sockets.get(targetId);
    const toUser = userList.find((user) => user.id === targetId);
    if (targetSocket) {
      targetSocket.emit("receive", {
        fromUsername,
        toUsername: toUser.username,
        msg,
        dateTime: new Date().getTime(),
      });
    } else {
      console.log("找不到目标用户的 socket");
      io.emit("duankai", {
        msg: "可能你或(对方)刷新了页面找不到目标用户,请重新选择目标用户,在进行交谈",
      });
    }
  });
});

httpServer.listen(6789, (err) => {
  console.log("服务器启动成功");
});

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值