Socket.io 是什么,如何使用,与 WebSocket 的关系?
介绍
上篇我们讲到了 WebSocket 的使用,不太了解的小伙伴,建议先到我上篇文章了解下再来学习。
Socket.io是一个建立在 WebSocket 协议之上的库,他提供了低延迟、双向通信、基于事件(可以自定义双方约定好的事件)的功能,保证了使用中的稳定性和兼容性,比如用户使用的浏览器版本不支持,会使用 HTTP长轮询 实现:断线自动重连的功能。
与 WebSocket 区别
- Socket.io 虽然建立在 WebSocket 协议之上,但不是 WebSocket 的实现。
- 目前仍有些许浏览器无法建立WebSocket连接,使用 Socket.io 可以做兼容性处理,转为 HTTP长轮询。
- 服务器和客户端之间的WebSocket连接可能会中断,双方都不知道连接的中断状态。Socket.io 自带一个心跳机制,它可以定期检查连接的状态,WebSocket 需要手动实现。
- 虽然 Socket.io 确实是在客户端支持的情况下使用 WebSocket 进行传输,但是它为每个数据包添加了额外的 元数据。这就是 WebSocket 客户端无法成功连接到 Socket.io 服务端,Socket.io 客户端也无法连接到普通的WebSocket服务器的原因。所以双方要同时规定好使用 websocket 还是 Socket.io,不能混用。
- Socket.io 提供了支持派发事件和监听时间的便捷功能,以下是官网案例:
socket.emit("hello", "world", (response) => {
console.log(response); // "got it"
});
socket.on("hello", (arg, callback) => {
console.log(arg); // "world"
callback("got it");
});
上面代码中的"hello"就是双方约定好的事件,平常项目开发中一般会在前面加上 $,表示自定义事件。
- Socket.io 还提供了方便快捷的广播方式
// socket.io
io.on("connection", (socket) => {
socket.broadcast.emit("hello", "world");
});
// 原生 websocket
socket.on('message', (message) => {
ws.clients.forEach((client) => {
client.send({
type: "hello",
message
});
})
})
使用 Socket.io 实现一个简易聊天室
- 我这里前端使用 vue3+TS,后端 node(这是肯定的,只支持node)
- 先看成品展示,简单做了一下,样式请不要在乎。。。
- 以下是代码部分
- 前端
- html
<template>
<div class="container">
<div class="users-area">
<!-- 待开发,感兴趣自己可以试着做做 -->
<input type="text" placeholder="搜索">
<ul class="users-list">
<li v-for="u in users" :key="u">{{ u }}</li>
</ul>
</div>
<div class="msg-area">
<div class="message-area">
<div class="item" :class="{ self: self === item.name }" v-for="(item, ind) in msgList" :key="ind">
<span class="name">{{ item.name }}</span>
<p class="message">{{ item.message }}</p>
</div>
</div>
<div class="input-area">
<textarea v-model="message" @keydown.enter="sendMsg"></textarea>
</div>
</div>
</div>
</template>
- TS
<script setup lang="ts">
import { defineProps, watch, ref, defineEmits } from 'vue';
const message = ref('');
const emits = defineEmits(['chat'])
type Props = {
users?: any[];
msgList?: { name: string, message: string}[];
self: string
}
const props = withDefaults(defineProps<Props>(), {
users: () => [],
msgList: () => []
})
const sendMsg = () => {
const val = message.value.trim();
if (val) {
message.value = '';
emits('chat', { name: props.self, message: val });
}
}
</script>
- CSS
<style scoped>
.container {
display: flex;
width: 550px;
height: 500px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
background-color: rgb(235, 235, 235);
overflow: hidden;
}
.users-area {
width: 150px;
border-right: 1px solid #ccc;
line-height: 30px;
overflow: auto;
flex: 0 0 auto;
}
.users-area input {
margin-top: 10px;
width: 80%;
border: none
}
.users-area input:focus {
outline: 1px solid #aaa
}
.users-area p {
text-align: center;
border-bottom: 1px solid #ccc;
}
.users-area .users-list {
padding: 0;
margin: 10px 0;
list-style: none;
border-top: 1px solid #ccc;
}
.users-list li {
padding: 0 10px;
margin: 10px 0;
font-size: 12px;
border-bottom: 1px solid #ccc;
background: rgb(220, 220, 220);
}
.msg-area {
display: flex;
flex-direction: column;
flex: 1;
background-color: rgb(245, 245, 245);
}
.message-area {
height: 75%;
padding: 1em;
font-size: 14px;
line-height: 1.3;
overflow-y: scroll;
}
.item {
float: left;
max-width: 70%;
clear: both;
margin-bottom: 1em;
}
.name {
font-size: 12px;
color: #333;
}
.message {
border-radius: 6px;
padding: 10px;
margin: 4px 0;
background-color: #fff
}
.item.self {
float: right;
}
.self .message {
background-color: rgb(137, 217, 97);
}
.self .name {
text-align: right;
}
.input-area {
flex: 1;
border-top: 1px solid #ccc;
}
.input-area textarea {
width: 100%;
height: 100%;
padding: 10px 20px;
border: none;
outline: none;
}
</style>
- 上面是子组件(聊天框),以下是父组件逻辑
<template>
<WeChat :self="self" :msgList="msgList" :users="users" @chat="handleChat" />
</template>
<script setup lang='ts'>
import WeChat from '@/components/WeChat.vue';
import { io, Socket } from 'socket.io-client';
import { ref, onMounted, onUnmounted } from 'vue';
interface IChatContent {
name: string;
message: string
}
const msgList = ref<IChatContent[]>([]);
const users = ref<string[]>([]);
const self = ref('');
const socket = ref<Socket | null>(null);
onMounted(() => {
socket.value = io('http://localhost:3000');
// 接受消息
socket.value.on('$messages', (data: IChatContent) => {
msgList.value.push(data);
});
// 监听加入聊天室的用户
socket.value.on('$users', (data: string[]) => {
users.value = data;
});
// 监听其他人发送的消息
socket.value.on('$msgList', (data: IChatContent[]) => {
msgList.value = data;
});
})
const handleChat = (data: IChatContent) => {
msgList.value.push(data);
// 将当前聊天内容发送给服务器,通知其他人
(socket.value as Socket).emit('$messages', data.message);
}
/**
* 组件卸载时断开连接
*/
onUnmounted(() => {
(socket.value as Socket).disconnect();
})
</script>
- node 端
import { Server } from "socket.io";
const io = new Server({
path: '/',
cors: '*'
});
const usersList = []; // 用户集合
const chatContentList = []; // 消息体集合
let ind = 0; // 记录当前是第几位成员(做简单区分)
io.on("connection", (socket) => {
const userName = '成员' + ++ind;
usersList.push(userName);
// 加入新成员并告知客户端(这里不能广播,因为广播不包括自己)
socket.emit('$users', usersList);
socket.on('$messages', (message) => {
const content = {
name: userName,
message
}
chatContentList.push(content);
// 给所有用户发送当前用户发来的消息
socket.broadcast.emit('$messages', content);
})
// 监听到连接关闭
socket.on('disconnect', () => {
// 清除用户
usersList.splice(usersList.indexOf(userName), 1);
// 广播通知所有用户
socket.broadcast.emit('$users', usersList);
});
});
io.listen(3000);
- 更多细节与功能请参考官网:https://socket.io/docs/v4/tutorial/introduction
- 欢迎各位小伙伴指出意见,觉得不错的话不要忘记留下个赞哦,你的鼓励是我更新的动力,谢谢。