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. 程序基本流程:创 ...
随机推荐
- LayuI 动态下拉框和动态设置选中
动态下拉框 //下拉框异步加载 function asyncSelect(thisId, grade, selectNodeName) { $("#" + selectNodeNa ...
- 10 STL-list
重新系统学习c++语言,并将学习过程中的知识在这里抄录.总结.沉淀.同时希望对刷到的朋友有所帮助,一起加油哦! 生命就像一朵花,要拼尽全力绽放!死磕自个儿,身心愉悦! 写在前面,本篇章主要介绍S ...
- from 表单非空验证以及多表单提交
开发中我们常用到$('#formid').serialize()方法进行表单序列化提交,但也相应催生了表单的非空严重以及多表单提交. form html: <form id="form ...
- 在Cloudreve网盘系统中集成kkFileView在线预览(暂时)
服务器:WindowsServer 2016 Cloudreve 需求方想整一个在小团队内部使用的网盘系统,最终在千挑万选之下选中了Cloudreve. Github地址:https://github ...
- vue3 + element plus 使用字节跳动图标
使用场景: 提一下vue2 用法>> 下面回到正题 vue3 用法 1 安装包: npm install @icon-park/vue-next --save 2 字节跳动图标库取图地 ...
- Blazor和Vue对比学习(进阶.路由导航一):基本使用
Blazor和Vue都是单文件组件SPA,路由的实现逻辑非常相似,页面路径的改变都是组件的切换,但因为各自语言的特性,在实现方式上有较大差异. 一.安装 1.Vue:Router是Vue的一个插件.如 ...
- Spring Boot实现任意位置的properties及yml文件内容配置与获取
〇.参考资料 1.Spring Boot 中文乱码问题解决方案汇总 https://blog.51cto.com/u_15236724/5372824 2.spring boot读取自定义配置prop ...
- 连表操作join 子查询 SQL补充 数据库软件navicat pymysql模块
目录 多表查询的两种方法 方式1:连表操作 方式2:子查询 SQL补充知识点 1.分组之前字段拼接 concat concat_ws 2.SQL执行判断条件 exists 3.表相关SQL补充 修改表 ...
- Java程序员除了做增删改查还能干嘛?
就以Java后端开发为例,说说不同级别程序员干的事情. 1 初级开发,大概是有3年Java开发经验. 22年底,上海,这批程序员如果学历是本科,薪资一般是8k到2w,当然如果能进好公司或互联网大厂,薪 ...
- 异常处理语法结构、yield生成器及其表达式
今日内容回顾 目录 今日内容回顾 异常处理语法结构 异常处理实战应用 生成器对象 自定义range功能 yield冷门用法 yield与return对比 生成器表达式 笔试题 异常处理语法结构 异常处 ...