本文简单介绍使用websocket实现一个简单的聊天室功能,我这里是用vite初始化的vue3项目。

在线体验地址:http://chat.lb0125.com/chat

需要安装的库:

socket.io、socket.io-client等

1、代码

整体代码目录结构如下,分为客户端和服务端代码:

1.1、服务端代码chat_server

a、首先使用 npm init  初始化一个node工程

b、然后npm install socket.io

c、新建一个app.js文件,代码如下:

const { createServer } = require("http");
const { Server } = require("socket.io"); const httpServer = createServer();
const io = new Server(httpServer, {
cors: { //解决跨域问题
origin: "*",
methods: ["GET", "POST"]
}
}); io.on("connection", (socket, data) => {
// 接受消息
socket.on('sendMsg', (data) => {
// io表示广播出去,发送给全部的连接
io.emit('sendMsged', data)
}); // 接受登录事件
socket.on('login', data => {
io.emit('logined', data)
}) // 接受登出事件
socket.on('logout', data => {
io.emit('logouted', data)
}) // 监听客户端与服务端断开连接
socket.on('disconnecting', () => {
console.log('客户端断开了连接')
})
}); httpServer.listen(3011, function () {
console.log('http://localhost:3011')
});

1.2、客户端代码 chat_client

由于我这里还使用安装了vue-router4、element-plus、less-loader moment等库,当然您可以根据自己需要决定是否安装

第一步:初始化vue3+vite项目

  npm create vite@latest 后 根据提示输入项目名称,选择vue版本进行后续操作

第二步:npm install socket.io-client以及其他需要使用到的库

第三步:添加环境配置文件,修改vite.config.js配置以及编写代码

a、vite.config.js:

import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
//引入gzip静态资源压缩
import viteCompression from 'vite-plugin-compression'
const path = require('path');
const { resolve } = require('path'); export default ({ mode,command }) => {
console.log('当前环境=========' + mode)
const plugins = [
vue(),
];
// 如果是非开发环境,配置按需导入element-plus组件
if (mode !== 'development') {
plugins.push(
AutoImport({
resolvers: [ElementPlusResolver()],
})
);
plugins.push(
Components({
resolvers: [ElementPlusResolver()],
})
);
} return defineConfig({
base: './',
plugins,
hmr: { overlay: false }, // 配置前端服务地址和端口(可注释掉,默认是localhost:3000)
server: {
host: '0.0.0.0',
port: 9000,
// 是否开启 https
https: false,
// 本地跨域代理
proxy: {
'/***': {
target:'http://****',
changeOrigin: true,
},
}
}, // 起个别名,在引用资源时,可以用‘@/资源路径’直接访问
resolve: {
alias: {
"@": resolve(__dirname, "src"),
},
}, css: {
preprocessorOptions: {
less: {
modifyVars: {
hack: `true; @import (reference) "${path.resolve("src/assets/css/base.less")}";`,
},
javascriptEnabled: true,
},
},
}, // 打包配置
build: {
minify: 'terser',
terserOptions: {
compress: {
drop_console: true, // 清除console
drop_debugger: true, // 清除debugger
},
},
chunkSizeWarningLimit: 1500, // 大文件报警阈值设置
rollupOptions: {
output: {
chunkFileNames: 'static/js/[name]-[hash].js',
entryFileNames: 'static/js/[name]-[hash].js',
assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
// 超大静态资源拆分
manualChunks(id) {
if (id.includes('node_modules')) {
let str = id.toString().split('node_modules/')[1].split('/')[0].toString();
return str.substring(str.lastIndexOf('@') + 1);
}
}
}
}
}
})
}

b、表情图片放置在chat_client/public/imgs/face目录下:

 c、在src/common目录下新建constant.js文件:

/**
* 常量
*/ // 是否是开发环境
export const isDev = import.meta.env.VITE_APP_ENV == "development" ? true : false; // 当前所属环境
export const env = import.meta.env.VITE_APP_ENV; // api地址
export const baseUrl = import.meta.env.VITE_APP_BASEURL;
export const wsUrl = import.meta.env.VITE_WS_URL; // 表情
export const faceImgs = [
{ img: 'weixiao.png', name: '[微笑]' },
{ img: 'yukuai.png', name: '[愉快]' },
{ img: 'aoman.png', name: '[傲慢]' },
{ img: 'baiyan.png', name: '[白眼]' },
{ img: 'ciya.png', name: '[呲牙]' },
{ img: 'daku.png', name: '[大哭]' },
{ img: 'deyi.png', name: '[得意]' },
{ img: 'fadai.png', name: '[发呆]' },
{ img: 'fanu.png', name: '[发怒]' },
{ img: 'ganga.png', name: '[尴尬]' },
{ img: 'haixiu.png', name: '[害羞]' },
{ img: 'jie.png', name: '[饥饿]' },
{ img: 'jingkong.png', name: '[惊恐]' },
{ img: 'jingya.png', name: '[惊讶]' },
{ img: 'lenghan.png', name: '[冷汗]' },
{ img: 'liulei.png', name: '[流泪]' },
{ img: 'nanguo.png', name: '[难过]' },
{ img: 'piezui.png', name: '[撇嘴]' },
{ img: 'se.png', name: '[色]' },
{ img: 'shui.png', name: '[睡]' },
{ img: 'tiaopi.png', name: '[调皮]' },
{ img: 'touxiao.png', name: '[偷笑]' },
{ img: 'zhuakuang.png', name: '[抓狂]' },
]

e、在src/views目录下新建chat文件夹,并在chat文件夹下新建Chat.vue文件

<template>
<div class="container">
<div class="header"
:style="{ 'border-bottom': isMobile ? '1px solid #eee' : '0 none', padding: isMobile ? '0 12px' : '0px' }">
<el-button type="primary" @click="onConnect" v-if="userName == ''">进入会话</el-button>
<span v-else style="flex:1;">您的姓名:{{ userName }}</span>
<el-button @click="onDisConnect" v-show="userName !== ''">退出会话</el-button>
</div>
<div class="content" id="content" @click="() => { isShowFace = false }"
:style="{ border: isMobile ? 'none' : '1px solid #eee' }">
<ul>
<li v-for="(item, i) in msgList" :key="i">
<p v-if="item.type == 0" class="item content-login">{{ item.date }} {{
item.userId == userId ? '您' :
item.userName
}}{{ item.msg }}</p>
<p v-if="item.type == -1" class="item content-logout">{{ item.date }} {{
item.userId == userId ? '您' :
item.userName
}}{{ item.msg }}</p>
<div v-if="item.type == 1" class="item" :class="{ 'text-right': userId == item.userId }">
<!-- 自己发送的消息 -->
<div v-if="item.userId == userId" class="clearfix">
<div class="content-name" style="text-align:right;">
<span style="margin-right:10px;">{{ item.date }}</span>
<span>{{ item.userName }}</span>
</div>
<div class="content-msg right" style="text-align: right;">
<p style="display:inline-block;text-align:left;border-top-right-radius: 0px;background: #73e273;"
v-html="item.msg"></p>
</div>
</div>
<!-- 别人发送的消息 -->
<div v-else>
<div class="content-name">
<span>{{ item.userName }}</span>
<span style="margin-left:10px;">{{ item.date }}</span>
</div>
<div class="content-msg" style="text-align: left;">
<p style="display:inline-block;text-align:left;border-top-left-radius: 0px;" v-html="item.msg"></p>
</div>
</div>
</div>
</li>
</ul>
</div>
<div class="footer" :style="{ padding: isMobile ? '10px 12px' : '10px 0px' }">
<el-input class="content-input" type="textarea" :autosize="{ minRows: 1, maxRows: 5 }" resize="none" v-model="msg"
ref="inputRef" placeholder="请输入发送内容" @focus="onInputFocus" @keyup.enter="onSend" />
<div class="face-icon">
<img src="@/assets/img/face.png" alt="表情" @click="onToggleFace">
</div>
<div class="send-dv">
<el-button type="primary" class="send-btn" @click="onSend" :disabled="userName == ''">发 送</el-button>
</div>
</div>
<div class="face-dv" v-show="isShowFace" :class="{ 'face-dv-pc': !isMobile }">
<div class="arrow" v-show="!isMobile"></div>
<div class="face">
<div class="face-item" v-for="item in faceImgs" :key="item.img" @click="onInputFace(item.img, item.name)">
<img :src="`./imgs/face/${item.img}`" />
</div>
</div>
</div>
</div>
</template> <script>
export default {
name: 'chat'
}
</script>
<script setup>
import { onMounted, ref, reactive, nextTick } from "vue"
import moment from 'moment'
import { ElMessage, ElMessageBox } from 'element-plus'
import { io } from 'socket.io-client';
import { wsUrl, faceImgs } from '@/common/constant';
import { isPC, guid } from '@/utils/utils'; const isMobile = ref(!isPC());
const isShowFace = ref(false);
const inputRef = ref(null);
const msg = ref('')
const userId = ref(''); // 用户id
const userName = ref(''); // 用户姓名
let socket = null; const abc = ref('<span>asdf</span>')
const msgList = reactive(
[
// { id: 0, type: 1, userName: '张安', date: '2012-12-12 12:12:12', msg: '哈哈哈1' },
// { id: 1, type: 0, userName: '张安1', date: '2012-12-12 12:12:12', msg: '张安进入会话' },
// { id: 2, type: 1, userName: '张安2', date: '2012-12-12 12:12:12', msg: '哈哈哈3' },
]
) // 表情框显示隐藏切换
const onToggleFace = () => {
if (userName.value == '') {
ElMessage.warning('请先点击进入会话');
return;
}
if (isShowFace.value) {
isShowFace.value = false;
} else {
isShowFace.value = true;
} } // 输入框获取焦点事件
const onInputFocus = (e) => {
isShowFace.value = false;
if (userName.value == '') {
ElMessage.warning('请先点击进入会话');
}
} // 点击表情
const onInputFace = (img, name) => {
console.log(img, name)
msg.value += name;
isShowFace.value = false;
} // 点击发送消息
const onSend = () => {
if (msg.value.trim() == '') {
ElMessage.warning('不可以发送空白消息')
return
}
let str = msg.value;
faceImgs.forEach(item => {
if (str.indexOf(item.name) > -1) {
str = str.replaceAll(item.name, `<img src="./imgs/face/${item.img}" style="width:20px;vertical-align:top;" />`);
}
})
socket.emit('sendMsg', {
type: 1,
userId: userId.value,
userName: userName.value,
date: moment(new Date()).format('HH:mm:ss'),
msg: str
})
msg.value = '';
isShowFace.value = false;
} const onConnect = () => {
console.log('onConnect')
ElMessageBox.prompt('请输入您的姓名', '提示', {
confirmButtonText: '确 认',
cancelButtonText: '取 消',
inputPattern: /^[\s\S]*.*[^\s][\s\S]*$/,
inputErrorMessage: '你的姓名',
}).then(({ value }) => {
userId.value = guid();
userName.value = value;
socket = io.connect(wsUrl);
setTimeout(() => {
socket.emit('login', {
id: Math.random() * 100000000,
type: 0,
date: moment(new Date()).format('HH:mm:ss'),
userId: userId.value,
userName: userName.value
})
}, 200) socket.on('connect', () => {
console.log('客户端建立连接'); // true
}); // 监听登录事件
socket.on('logined', data => {
msgList.push({
id: Math.random() * 100000000,
type: data.type,
userId: data.userId,
userName: data.userName,
date: data.date,
msg: ' 进入会话'
})
}) // 监听登录出事件
socket.on('logouted', data => {
msgList.push({
id: Math.random() * 100000000,
type: data.type,
userId: data.userId,
userName: data.userName,
date: data.date,
msg: '离开会话'
})
}) // 监听发送消息事件
socket.on('sendMsged', data => {
msgList.push({
id: Math.random() * 100000000,
type: data.type,
userId: data.userId,
userName: data.userName,
date: data.date,
msg: data.msg
})
nextTick(() => {
let contentNode = document.getElementById('content')
contentNode.scrollTop = contentNode.scrollHeight
})
})
}).catch(() => { })
} const onDisConnect = () => {
console.log('onDisConnect')
socket.emit('logout', {
id: Math.random() * 100000000,
type: -1,
date: moment(new Date()).format('HH:mm:ss'),
userId: userId.value,
userName: userName.value
})
setTimeout(() => {
socket.disconnect()
userId.value = '';
userName.value = '';
}, 200)
} </script> <style lang="less" scoped>
.container {
position: relative;
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
max-width: 522px;
margin: 0px auto; .header {
height: 50px;
display: flex;
align-items: center;
border-bottom: 1px solid #eee;
} .content {
flex: 1;
padding: 12px;
overflow-y: auto; .item {
margin-bottom: 20px; &.content-login {
font-size: 12px;
color: #666;
text-align: center;
} &.content-logout {
font-size: 12px;
color: #666;
text-align: center;
}
} .content-name {
font-size: 12px;
color: #666;
margin-bottom: 5px;
} .content-msg {
width: 90%;
word-break: break-word;
font-size: 16px; p {
padding: 8px 10px;
background: #eee;
border-radius: 8px;
color: #232323;
}
} } .footer {
display: flex;
align-items: end; .content-input {
flex: 1
} .face-icon {
position: relative;
width: 56px;
height: 100%; img {
position: absolute;
width: 26px;
height: 26px;
left: 17px;
bottom: 4px;
}
} .send-dv {
position: relative;
width: 60px;
height: 100%; .send-btn {
position: absolute;
width: 100%;
height: 34px;
left: 0;
bottom: 0;
}
} } .face-dv {
height: 150px;
background: #eee; &.face-dv-pc {
position: absolute;
width: 100%;
left: 0px;
bottom: 53px;
} .arrow {
position: absolute;
bottom: -20px;
right: 75px;
width: 0;
height: 0;
border: 10px solid;
border-color: #eee transparent transparent transparent;
} .face {
width: 100%;
height: 100%;
padding: 4px;
overflow: auto; .face-item {
display: inline-flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
margin: 6px;
border-radius: 4px;
&:hover {
background: #ced8d5;
} img {
width: 27px;
height: 27px;
}
}
}
}
} ::-webkit-scrollbar {
/*滚动条整体样式*/
width: 4px;
/*高宽分别对应横竖滚动条的尺寸*/
height: 4px;
} ::-webkit-scrollbar-thumb {
/*滚动条里面小方块*/
border-radius: 3px;
background: #1c1e2038;
} :deep(.el-textarea__inner) {
min-height: 34px !important;
} :deep(.el-textarea__inner) {
font-size: 16px;
// 隐藏滚动条
scrollbar-width: none;
/* firefox */
-ms-overflow-style: none; /* IE 10+ */
&::-webkit-scrollbar {
display: none;
/* Chrome Safari */
}
}</style>

注意:上面Chat.vue文件里引入了utils/utils文件里的isPC和guid方法,这两个方法分别是用来判断当前是否是pc端和生成uuid的。

2、效果图

3、部署代码到服务器

最后分别把客户端代码和服务端代码部署到服务器上就可以玩耍了

需要购买阿里云产品和服务的,点击此链接可以领取优惠券红包,优惠购买哦,领取后一个月内有效: https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=fp9ccf07

socket.io实现简易聊天室功能的更多相关文章

  1. Express+Socket.IO 实现简易聊天室

    代码地址如下:http://www.demodashi.com/demo/12477.html 闲暇之余研究了一下 Socket.io,搭建了一个简易版的聊天室,如有不对之处还望指正,先上效果图: 首 ...

  2. node+express+socket.io制作一个聊天室功能

    首先是下载包: npm install express npm install socket.io 建立文件: 服务器端代码:server.js var http=require("http ...

  3. 示例:Socket应用之简易聊天室

    在实际应用中,Server总是在指定的端口上监听是否有Client请求,一旦监听到Client请求,Server就会启动一个线程来响应该请求,而Server本身在启动完线程之后马上又进入监听状态. 示 ...

  4. 使用socket.io打造公共聊天室

    最近的计算机网络课上老师开始讲socket,tcp相关的知识,当时脑袋里就蹦出一个想法,那就是打造一个聊天室.实现方式也挺多的,常见的可以用C++或者Java进行socket编程来构建这么一个聊天室. ...

  5. AngularJS+Node.js+socket.io 开发在线聊天室

    所有文章搬运自我的个人主页:sheilasun.me 不得不说,上手AngularJS比我想象得难多了,把官网提供的PhoneCat例子看完,又跑到慕课网把大漠穷秋的AngularJS实战系列看了一遍 ...

  6. 利用socket.io构建一个聊天室

    利用socket.io来构建一个聊天室,输入自己的id和消息,所有的访问用户都可以看到,类似于群聊. socket.io 这里只用来做一个简单的聊天室,官网也有例子,很容易就做出来了.其实主要用的东西 ...

  7. Socket.io文字直播聊天室的简单代码

    直接上代码吧,被注释掉的主要是调试代码,和技术选型的测试代码 var app = require('express')(); var server = require('http').Server(a ...

  8. socket.io 实现简易聊天

    客户端: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF- ...

  9. Socket实现简易聊天室,Client,Server

    package seday08; import java.io.BufferedWriter;import java.io.OutputStream;import java.io.OutputStre ...

  10. [Python] socket发送UDP广播实现聊天室功能

    一.说明 本文主要使用socket.socket发送UDP广播来实现聊天室功能. 重点难点:理解UDP通讯流程.多线程.UDP广播收发等. 测试环境:Win10\Python3.5. 程序基本流程:创 ...

随机推荐

  1. c++题目:吃西瓜

    吃西瓜 [问题描述] 老胡买了是长方体形的西瓜来犒劳大家.... 这块西瓜长m厘米,宽n厘米,高h厘米.他发现如果把这块西瓜平均地分成m*n*h块1立方厘米的小正方体,那么每一小块都会有一个营养值(可 ...

  2. Oracle 一对多将多行数据整理为一行

    在开发中一对多的关系经常用到,昨天遇到一个客户问题,要求将所有反馈任务的信息整理为一行展示并导出excel. 我数据库对应关系:通任务task对应feedback表多个反馈,也就是说每个任务都有很多条 ...

  3. 我开发的开源项目,让.NET7中的EFCore更轻松地使用强类型Id

    在领域驱动设计(DDD)中,有一个非常重要的概念:"强类型Id".使用强类型Id来做标识属性的类型会比用int.Guid等通用类型能带来更多的好处.比如有一个根据根据Id删除用户的 ...

  4. ArcEngine 释放对象

    释放对象 例如IFeatureCursor,IFeatureClass等 ESRI.ArcGIS.ADF.ComReleaser.ReleaseCOMObject(myFeatureCursor); ...

  5. MYSQL快速安装整理

    参考教程:https://www.cnblogs.com/brad93/p/16650780.html [检查是否已安装过] find / -name mysql [快速安装开始] groupadd  ...

  6. 【每日一题】2022年2月10日-NC160 二分查找-I

    描述请实现无重复数字的升序数组的二分查找 给定一个 元素升序的.无重复数字的整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标(下标 ...

  7. Semaphore信号量源码解析(基于jdk11)

    目录 1.Semaphore信号量源码解析(基于jdk11) 1.1 Semaphore概述 1.2 Semaphore的原理 1.2.1 基本结构(jdk11) 1.2.2 可中断获取信号量 1.2 ...

  8. 微服务开发平台 Spring Cloud Blade 部署实践

    本文介绍使用 Rainbond 快速部署 Spring Cloud Blade 微服务平台.Spring Cloud Blade 是一个由商业级项目升级优化而来的微服务架构,采用Spring Boot ...

  9. UOJ33 [UR#2] 树上 GCD

    UOJ33 [UR#2] 树上 GCD 简要题意: 给定一棵有根树,对于每个 \(i \in [1,n)\),求出下式的值: \[Ans[i] = \sum_{u<v} \gcd({\rm{di ...

  10. 目标检测+双目测距——基于yolov5

    效果 测试例子1 输入: 左图 右图 输出: 测试例子2 输入: 左图 右图 输出: 测试例子3 输入: 左图 右图 输出: 核心代码 基于yolov5-6.1版本和双目测距+点云--使用Middle ...