跳至主要内容

私信 - 第二部分

本指南分为四个不同的部分

这是我们在 第一部分 结束时的位置

Chat

交换私信目前基于 socket.id 属性,它工作良好,但这里存在问题,因为此 ID 仅对当前 Socket.IO 会话有效,并且每次客户端和服务器之间的低级连接断开时都会更改。

因此,每次用户重新连接时,都会创建一个新用户

Duplicate users

这... 不太好。让我们解决这个问题!

安装

让我们检出第二部分的分支

git checkout examples/private-messaging-part-2

这是您在当前目录中应该看到的内容

├── babel.config.js
├── package.json
├── public
│ ├── favicon.ico
│ ├── fonts
│ │ └── Lato-Regular.ttf
│ └── index.html
├── README.md
├── server
│ ├── index.js (updated)
│ ├── package.json
│ └── sessionStore.js (created)
└── src
├── App.vue (updated)
├── components
│ ├── Chat.vue (updated)
│ ├── MessagePanel.vue
│ ├── SelectUsername.vue
│ ├── StatusIcon.vue
│ └── User.vue
├── main.js
└── socket.js

完整的差异可以在 这里 找到。

工作原理

持久会话 ID

在服务器端 (server/index.js),我们创建两个随机值

  • 一个会话 ID,私有,它将用于在重新连接时验证用户
  • 一个用户 ID,公开,它将用作交换消息的标识符
io.use((socket, next) => {
const sessionID = socket.handshake.auth.sessionID;
if (sessionID) {
// find existing session
const session = sessionStore.findSession(sessionID);
if (session) {
socket.sessionID = sessionID;
socket.userID = session.userID;
socket.username = session.username;
return next();
}
}
const username = socket.handshake.auth.username;
if (!username) {
return next(new Error("invalid username"));
}
// create new session
socket.sessionID = randomId();
socket.userID = randomId();
socket.username = username;
next();
});

然后将会话详细信息发送给用户

io.on("connection", (socket) => {
// ...
socket.emit("session", {
sessionID: socket.sessionID,
userID: socket.userID,
});
// ...
});

在客户端 (src/App.vue),我们将会话 ID 存储在 localStorage

socket.on("session", ({ sessionID, userID }) => {
// attach the session ID to the next reconnection attempts
socket.auth = { sessionID };
// store it in the localStorage
localStorage.setItem("sessionID", sessionID);
// save the ID of the user
socket.userID = userID;
});

实际上,有几种可能的实现

  • 根本不存储:重新连接将保留会话,但刷新页面将丢失它
  • sessionStorage: 重新连接和刷新页面将保留会话
  • localStorage: 重新连接和刷新页面将保留会话 + 此会话将在浏览器选项卡之间共享

在这里,我们选择了 localStorage 选项,因此所有选项卡都将链接到同一个会话 ID,这意味着

  • 您可以与自己聊天(太棒了!)
  • 您现在需要使用另一个浏览器(或浏览器的私密模式)来创建另一个对等方

最后,我们在应用程序启动时获取会话 ID

created() {
const sessionID = localStorage.getItem("sessionID");

if (sessionID) {
this.usernameAlreadySelected = true;
socket.auth = { sessionID };
socket.connect();
}
// ...
}

您现在应该能够刷新选项卡而不会丢失会话

Persistent sessions

在服务器端,会话保存在内存存储 (server/sessionStore.js) 中

class InMemorySessionStore extends SessionStore {
constructor() {
super();
this.sessions = new Map();
}

findSession(id) {
return this.sessions.get(id);
}

saveSession(id, session) {
this.sessions.set(id, session);
}

findAllSessions() {
return [...this.sessions.values()];
}
}

同样,这仅适用于单个 Socket.IO 服务器,我们将在本指南的第四部分中回到这一点。

私信(更新)

私信现在基于在服务器端生成的 userID,因此我们需要做两件事

  • 使 Socket 实例加入关联的房间
io.on("connection", (socket) => {
// ...
socket.join(socket.userID);
// ...
});
  • 更新转发处理程序
io.on("connection", (socket) => {
// ...
socket.on("private message", ({ content, to }) => {
socket.to(to).to(socket.userID).emit("private message", {
content,
from: socket.userID,
to,
});
});
// ...
});

以下是发生的情况

Private messaging

使用 socket.to(to).to(socket.userID).emit(...),我们在接收方和发送方(不包括给定的 Socket 实例)的 房间 中广播。

所以现在我们有

Chat (v2)

断开连接处理程序

在服务器端,Socket 实例发出两个特殊事件:disconnectingdisconnect

我们需要更新我们的“断开连接”处理程序,因为会话现在可以在选项卡之间共享

io.on("connection", (socket) => {
// ...
socket.on("disconnect", async () => {
const matchingSockets = await io.in(socket.userID).allSockets();
const isDisconnected = matchingSockets.size === 0;
if (isDisconnected) {
// notify other users
socket.broadcast.emit("user disconnected", socket.userID);
// update the connection status of the session
sessionStore.saveSession(socket.sessionID, {
userID: socket.userID,
username: socket.username,
connected: false,
});
}
});
});

allSockets() 方法返回一个 Set,其中包含在给定房间中的所有 Socket 实例的 ID。

注意:我们也可以使用 io.of("/").sockets 对象,就像在第一部分中一样,但 allSockets() 方法也适用于多个 Socket.IO 服务器,这在扩展时将很有用。

文档:allSockets() 方法

回顾

好的,所以... 我们现在拥有的更好,但还有一个问题:消息实际上并没有在服务器上持久化。因此,当用户重新加载页面时,它会丢失所有现有的对话。

这可以通过例如将消息保存在浏览器的 localStorage 中来解决,但还有一个更令人讨厌的后果

  • 当发送方断开连接时,它发送的所有数据包都会 缓冲,直到重新连接(在大多数情况下,这很好)
Chat with sender that gets disconnected
  • 但当接收方断开连接时,数据包会丢失,因为给定房间中没有监听的 Socket 实例
Chat with recipient that gets disconnected

我们将在本指南的 第三部分 中尝试解决这个问题。

感谢您的阅读!