本文简单介绍使用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. 【Java EE】Day09 JavaScript基础、ECMAScript语法、Java对象

    一.简介 1.概念 客户端脚本语言 脚本语言:无需编译,直接被解析执行 运行在:客户端浏览器,每个浏览器都有解析引擎 功能: 用户与页面交互 控制html元素 使页面产生动态效果 2.发展史 1992 ...

  2. 【每日一题】【DFS】【BFS】【队列】2021年12月5日-199. 二叉树的右视图

    解答: /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * ...

  3. JavaScript入门⑧-事件总结大全

    JavaScript入门系列目录 JavaScript入门①-基础知识筑基 JavaScript入门②-函数(1)基础{浅出} JavaScript入门③-函数(2)原理{深入}执行上下文 JavaS ...

  4. python多线程批量操作交换机

    import time import socket import threading def device_info(): ip_list = [] name_list = [] user_list ...

  5. Crane如何做到利用率提升3倍稳定性还不受损?

    作为云平台用户,我们都希望购买的服务器物尽其用,能够达到最大利用率.然而要达到理论上的节点负载目标是很的,计算节点总是存在一些装箱碎片和低负载导致的闲置资源.下图展示了某个生产系统的CPU资源现状,从 ...

  6. 《HTTP权威指南》– 16.重定向与负载均衡

    重定向 重定向 的目标是尽快地将HTTP报文发送到可用的Web服务器上去.在穿过因特网的路径上,HTTP报文传输的方向会受到HTTP应用程序和报文经由的路由设备的影响: 配置创建客户端报文的浏览器应用 ...

  7. Java多线程详解(通俗易懂)

    一.线程简介 1. 什么是进程? 电脑中会有很多单独运行的程序,每个程序有一个独立的进程,而进程之间是相互独立存在的.例如图中的微信.酷狗音乐.电脑管家等等. 2. 什么是线程? 进程想要执行任务就需 ...

  8. 《深度探索C++对象模型》第六章 执行期语意学

    new运算符和delete运算符 运算符new看似是一个简单的运算,比如: int *pi=new int(5); 但是它实际由两个步骤完成: 1.通过适当的new运算符函数实体,配置所需的内存: / ...

  9. 无旋树堆(FHQ-Treap)学习笔记

    简介 无旋树堆(一般统称 \(\text{FHQ-Treap}\)),是一种平衡树.可以用很少的代码达到很优秀的复杂度. 前置知识: 二叉搜索树 \(\text{BST}\) \(\text{Trea ...

  10. epoll分布式通讯

    参考http://www.xmailserver.org/linux-patches/nio-improve.html  epoll通讯 参考https://blog.csdn.net/yangqua ...