socket.io实现简易聊天室功能
本文简单介绍使用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实现简易聊天室功能的更多相关文章
- Express+Socket.IO 实现简易聊天室
代码地址如下:http://www.demodashi.com/demo/12477.html 闲暇之余研究了一下 Socket.io,搭建了一个简易版的聊天室,如有不对之处还望指正,先上效果图: 首 ...
- node+express+socket.io制作一个聊天室功能
首先是下载包: npm install express npm install socket.io 建立文件: 服务器端代码:server.js var http=require("http ...
- 示例:Socket应用之简易聊天室
在实际应用中,Server总是在指定的端口上监听是否有Client请求,一旦监听到Client请求,Server就会启动一个线程来响应该请求,而Server本身在启动完线程之后马上又进入监听状态. 示 ...
- 使用socket.io打造公共聊天室
最近的计算机网络课上老师开始讲socket,tcp相关的知识,当时脑袋里就蹦出一个想法,那就是打造一个聊天室.实现方式也挺多的,常见的可以用C++或者Java进行socket编程来构建这么一个聊天室. ...
- AngularJS+Node.js+socket.io 开发在线聊天室
所有文章搬运自我的个人主页:sheilasun.me 不得不说,上手AngularJS比我想象得难多了,把官网提供的PhoneCat例子看完,又跑到慕课网把大漠穷秋的AngularJS实战系列看了一遍 ...
- 利用socket.io构建一个聊天室
利用socket.io来构建一个聊天室,输入自己的id和消息,所有的访问用户都可以看到,类似于群聊. socket.io 这里只用来做一个简单的聊天室,官网也有例子,很容易就做出来了.其实主要用的东西 ...
- Socket.io文字直播聊天室的简单代码
直接上代码吧,被注释掉的主要是调试代码,和技术选型的测试代码 var app = require('express')(); var server = require('http').Server(a ...
- socket.io 实现简易聊天
客户端: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF- ...
- Socket实现简易聊天室,Client,Server
package seday08; import java.io.BufferedWriter;import java.io.OutputStream;import java.io.OutputStre ...
- [Python] socket发送UDP广播实现聊天室功能
一.说明 本文主要使用socket.socket发送UDP广播来实现聊天室功能. 重点难点:理解UDP通讯流程.多线程.UDP广播收发等. 测试环境:Win10\Python3.5. 程序基本流程:创 ...
随机推荐
- vivo浏览器的神奇操作
关闭 root 权限也就罢了,你还搞这种操作 看到那个源文件了吗? 只有点击源文件下载的才是 官方提供的安装包, 而你首先看到的下载,点击后会下载vivo 应用商店的安装包. 那么这两种安装包有什么区 ...
- SpringCloud Alibaba(二) - Sentinel,整合OpenFeign,GateWay服务网关
1.环境准备 1.1Nacos 单机启动:startup.cmd -m standalone 1.2 Sentinel 启动命令:java -Dserver.port=8858 -Dcsp.senti ...
- 防御式编程之断言assert的使用
防御式编程的重点就是需要防御一些程序未曾预料的错误,这是一种提高软件质量的辅助性方法,断言assert就用于防御式编程,编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设.使用断言是 ...
- JAVA里Map的一些常用方法
Map的常用方法 案例1 场景:一张建行用户体验金信息大表(百万级别),里面存在一个字段对多条数据,需要统计某个字段的多条数据累加值以供于别的服务调用. 优化前解决:直接查出来一个大list给到另一个 ...
- 记开源项目:DotNetCore.CAP.MySql问题分析:only mysqlparameter objects may be stored
1. 简介 最近在学习分布式事务及解决方案,最终找到了开源项目DotNetCore.CAP ,因为自己用的MySql数据库比较多.于是也使用MySQL+EFCore+RabbitMQ+CAP实现事务 ...
- 分布式日志:Exceptionless的安装与部署
安装步骤 首先exceptionless依赖elasticsearch,而elasticsearch需要java环境,所以先安装jdk jdk下载地址:https://www.oracle.com/t ...
- vue移动端封装底部导航
<template> <div class="myfooter flex-betweenCenter"> <router-link tag=" ...
- linux挖矿处置
挖矿的类型 主动挖矿:用户在个人电脑或服务器使用挖矿程序进行CPU,GPU计算,获取虚拟货币. 被动挖矿:挖矿病毒通过系统漏洞,恶意程序,弱口令等方式入侵服务器,设备感染挖矿病毒后会开始挖掘虚拟货币. ...
- 解决RockyLinux和Centos Stream 9中firefox无法播放HTML视频问题
如题在测试两种centos后续系统时,发现firefox无法播放HTML视频问题.经过一番折腾找到了解决的办法,具体解决如下: 首先下载VLC $sudo yum install vlc 而后重启浏览 ...
- 国产paozhu c++ web framework 正式版发布
经过大半个月测试修改 paozhu c++ web framework 正式版发布, 1.0.5 release 官方第一次发布正式版,可以用于生产环境. 易用性 超越国外各种 c++ web fra ...