实现一个简单的websocket交互

在现代 web 开发中,实时聊天功能是非常常见的应用场景之一。它广泛应用于社交平台、客服系统、在线协作工具等。实现这种功能的核心技术之一是 WebSocket,它允许在客户端和服务器之间建立持久的、全双工的连接,支持实时数据的双向传输。

在这篇文章中,我们将介绍如何使用 WebSocket 实现一个简单的实时聊天功能。我们将从基础的 WebSocket 连接开始,逐步完善聊天功能,包括消息的发送与接收、用户连接管理以及一些进阶功能(例如自动滚动、显示消息时间等)。

一、WebSocket 简介

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它最初由 HTML5 提出,并且得到了各大浏览器的广泛支持。

与传统的 HTTP 协议不同,WebSocket 在建立连接后不会再进行请求和响应,它允许客户端和服务器之间随时发送和接收数据。这样可以避免频繁的请求和响应,从而提高实时性的体验。

二、如何使用 WebSocket 实现聊天功能

front

bash 复制代码
npm i socket.io-client
js 复制代码
  "dependencies": {
    "ant-design-vue": "^1.7.8",
    "socket.io-client": "^4.8.1",
    "vue": "2.7.16",
    "vue-router": "3.0.1",
    "vue-template-compiler": "^2.7.16"
  }
js 复制代码
//main.js
import io from 'socket.io-client'
const socket = io('http://localhost:3000') // 连接服务器域名
// 连接websocket服务器
Vue.prototype.$socket = socket;
html 复制代码
<template>
    <div class="chat-container">
        <h1>基于websocket实时聊天</h1>
        <div class="container">
            <div class="chat-inner" ref='scrollBottom'>
                <transition-group tag="div">
                    <div :class="'messages' + ` ${item.role === 'client' ? 'user' : 'service'}`"
                    v-for="(item, index) in messageList"
                    :key="index"
                    > 
                        <span v-if="item.role === 'service'" class="typing">{{ item.content }}</span>
                        <span v-else>{{ item.content }}</span>
                    </div>   
                </transition-group>
            </div>
            <div class="chat-win">
                <a-input-search 
                    placeholder="请输入内容" 
                    v-model="userInput"
                    size="large" 
                    @search="onSend">
                    <a-button 
                        :loading="loading"
                        type="primary" 
                        slot="enterButton">
                        发送
                    </a-button>
                </a-input-search>
            </div>
        </div>
    </div>
</template>

<script>
export default {
    name: 'chatRoom',
    data () {
        return {
            userInput: '',
            loading: false,
            messageList: [
                {role: 'client', content: 'hello'},
                {role: 'service', content: 'hello, user'}
            ],
            typingMessage: null // 打字机效果
        }
    },
    methods: {
        onSend(){
            if (!this.userInput) return false;
            this.loading = true;
            const msg = {
                role: 'client',
                content: this.userInput,
            };
            this.$socket.emit('chat message', msg); // 发送消息到服务端
            this.messageList.push(msg); // 显示客户端消息
            this.userInput = ''; // 清空输入框
            this.saveMessages(); // 保存消息
            this.scrollBottom(); // 滚动到最新消息
            this.loading = false;
        },

        async typeBotMessage(content) {
            this.typingMessage = { content: "", role: "service" };
            this.messageList.push(this.typingMessage); // 在聊天框中添加服务端消息

            await new Promise((resolve) => {
                const chunkSize = 1; // 每次插入的字符数
                let i = 0;
                const renderChunk = () => {
                    this.typingMessage.content += content.slice(i, i + chunkSize); // 模拟打字机效果
                    this.saveMessages(); // 保存消息
                    this.scrollBottom(); // 滚动到最新消息
                    i += chunkSize;
                    if (i < content.length) {
                        setTimeout(renderChunk, 50); // 控制打字速度
                    } else {
                        this.typingMessage = null; // 清除打字消息
                        resolve();
                    }
                };
                renderChunk();
            });
        },

        saveMessages() {
            localStorage.setItem('chatMessages', JSON.stringify(this.messageList));
        },

        scrollBottom() {
            this.$nextTick(() => {
                const container = this.$refs.scrollBottom;
                if (container) {
                    container.scrollTop = container.scrollHeight;
                }   
            });
        }
    },

    mounted() {
        this.$socket.on('connect', () => {
             console.log('connected to the server');
        });
        this.$socket.on('response', async (msg) => {
            console.log('Server response:', msg);
            await this.typeBotMessage(msg); // 模拟服务端的打字效果
            this.scrollBottom(); // 滚动到最新消息
        });
    }
};
</script>

<style lang="less" scoped>
    /* 进入动画 */
    .v-enter-active, .v-leave-active {
        transition: all 0.5s ease;
    }
    .v-enter {
        opacity: 0;
        transform: translateY(20px); /* 初始状态和离开时稍微向下偏移 */
    }
    .v-leave-to {
        opacity: 1;
        transform: translateY(0);
    }
    .chat-container {
        text-align: center;
        height: 440px;

        .container {
            position: relative;
            display: flex;
            justify-content: center;
            width: 100%;
            height: 100%;
            
            .chat-inner {
                position: absolute;
                left: 50%;
                transform: translateX(-50%);
                border-radius: 2%;
                border: 1px solid #ccc;
                width: 60%;
                height: 400px;
                overflow-y: auto;
                background-color: rgba(112, 112, 112, .1);

                .messages {
                    margin: 10px auto;
                    width: 95%;
                    line-height: 35px;
                    background-color: white;
                }

                .user {
                    text-align: right;
                    padding-right: 10px;
                }

                .service {
                    background-color: aquamarine;
                    text-align: left;
                    padding-left: 10px;
                }
            }

            .chat-win {
                width: 60%;
                position: absolute;
                bottom: 0;
            }
        }
    }
</style>

service

js 复制代码
"dependencies"{
    "cors": "^2.8.5",
    "express": "^4.21.1",
    "socket.io": "^4.8.1"
}
js 复制代码
const express = require('express')
const cors = require('cors')
const socketIo = require('socket.io')
const http = require('http')
const app = express();
app.use(cors())
const server = http.createServer(app)
const io = socketIo(server, {
    cors: {
        allowOrigin: '*'
    }
})
io.on('connection', (socket) =>{
    console.log('user connected')
    socket.on('chat message', (msg) =>{
        console.log('msg=',msg)
        io.emit('response', '我已经收到,我是服务端')
    })
    socket.on('disconnect', () =>{
        console.log('user disconnected')
    })
})
server.listen(3000, () => {
    console.log(`服务启动在${3000}端口`)
})
相关推荐
GDAL6 分钟前
vue3入门教程:ref能否完全替代reactive?
前端·javascript·vue.js
六卿6 分钟前
react防止页面崩溃
前端·react.js·前端框架
z千鑫32 分钟前
【前端】详解前端三大主流框架:React、Vue与Angular的比较与选择
前端·vue.js·react.js
大梦百万秋1 小时前
Spring Boot实战:构建一个简单的RESTful API
spring boot·后端·restful
m0_748256141 小时前
前端 MYTED单篇TED词汇学习功能优化
前端·学习
斌斌_____1 小时前
Spring Boot 配置文件的加载顺序
java·spring boot·后端
路在脚下@2 小时前
Spring如何处理循环依赖
java·后端·spring
小白学前端6662 小时前
React Router 深入指南:从入门到进阶
前端·react.js·react
海绵波波1072 小时前
flask后端开发(1):第一个Flask项目
后端·python·flask
web130933203982 小时前
前端下载后端文件流,文件可以下载,但是打不开,显示“文件已损坏”的问题分析与解决方案
前端