Socket.io 是什么,如何使用,与 WebSocket 的关系?

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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值