From e815164a383416b694564fdd20923c3fda704ae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=AA=E5=85=89=E5=90=AF?= Date: Sun, 29 Sep 2024 14:50:21 +0800 Subject: [PATCH 1/2] Translate tutorial documentation into Chinese. --- .../current/tutorial/01-introduction.md | 49 ++++--- .../tutorial/02-project-initialization.md | 38 ++--- .../current/tutorial/03-serving-html.md | 20 +-- .../tutorial/04-integrating-socket-io.md | 50 +++---- .../current/tutorial/05-emitting-events.md | 20 +-- .../current/tutorial/06-broadcasting.md | 26 ++-- .../current/tutorial/07-api-overview.md | 130 +++++++++--------- .../tutorial/08-handling-disconnections.md | 24 ++-- .../tutorial/09-connection-state-recovery.md | 60 ++++---- .../current/tutorial/10-server-delivery.md | 60 ++++---- .../current/tutorial/11-client-delivery.md | 86 ++++++------ .../tutorial/12-scaling-horizontally.md | 78 +++++------ .../current/tutorial/13-ending-notes.md | 46 +++---- 13 files changed, 343 insertions(+), 344 deletions(-) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/01-introduction.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/01-introduction.md index 4d6797cf..d12ecffa 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/01-introduction.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/01-introduction.md @@ -1,65 +1,65 @@ --- -title: Tutorial - Introduction -sidebar_label: Introduction +title: 教程 - 介绍 +sidebar_label: 介绍 slug: introduction --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Getting started +# 入门指南 -Welcome to the Socket.IO tutorial! +欢迎来到 Socket.IO 教程! -In this tutorial we'll create a basic chat application. It requires almost no basic prior knowledge of Node.JS or Socket.IO, so it’s ideal for users of all knowledge levels. +在本教程中,我们将创建一个基本的聊天应用程序。几乎不需要任何 Node.JS 或 Socket.IO 的基础知识,因此适合所有知识水平的用户。 -## Introduction {#introduction} +## 介绍 -Writing a chat application with popular web applications stacks like LAMP (PHP) has normally been very hard. It involves polling the server for changes, keeping track of timestamps, and it’s a lot slower than it should be. +使用像 LAMP(PHP)这样的流行 Web 应用程序栈编写聊天应用程序通常非常困难。它涉及轮询服务器以获取更改、跟踪时间戳,并且速度比预期的要慢得多。 -Sockets have traditionally been the solution around which most real-time chat systems are architected, providing a bi-directional communication channel between a client and a server. +套接字传统上是大多数实时聊天系统构建的解决方案,提供客户端和服务器之间的双向通信通道。 -This means that the server can *push* messages to clients. Whenever you write a chat message, the idea is that the server will get it and push it to all other connected clients. +这意味着服务器可以*推送*消息给客户端。每当您编写聊天消息时,服务器会获取它并将其推送给所有其他已连接的客户端。 -## How to use this tutorial {#how-to-use-this-tutorial} +## 如何使用本教程 -### Tooling {#tooling} +### 工具 -Any text editor (from a basic text editor to a complete IDE such as [VS Code](https://code.visualstudio.com/)) should be sufficient to complete this tutorial. +任何文本编辑器(从基本文本编辑器到完整的 IDE,如 [VS Code](https://code.visualstudio.com/))都足以完成本教程。 -Additionally, at the end of each step you will find a link to some online platforms ([CodeSandbox](https://codesandbox.io) and [StackBlitz](https://stackblitz.com), namely), allowing you to run the code directly from your browser: +此外,在每个步骤的末尾,您会找到一些在线平台的链接(如 [CodeSandbox](https://codesandbox.io) 和 [StackBlitz](https://stackblitz.com)),允许您直接从浏览器运行代码: -![Screenshot of the CodeSandbox platform](/images/codesandbox.png) +![CodeSandbox 平台截图](/images/codesandbox.png) -### Syntax settings {#syntax-settings} +### 语法设置 -In the Node.js world, there are two ways to import modules: +在 Node.js 世界中,有两种方式导入模块: -- the standard way: ECMAScript modules (or ESM) +- 标准方式:ECMAScript 模块(或 ESM) ```js import { Server } from "socket.io"; ``` -Reference: https://nodejs.org/api/esm.html +参考:https://nodejs.org/api/esm.html -- the legacy way: CommonJS +- 传统方式:CommonJS ```js const { Server } = require("socket.io"); ``` -Reference: https://nodejs.org/api/modules.html +参考:https://nodejs.org/api/modules.html -Socket.IO supports both syntax. +Socket.IO 支持这两种语法。 :::tip -We recommend using the ESM syntax in your project, though this might not always be feasible due to some packages not supporting this syntax. +我们建议在您的项目中使用 ESM 语法,但由于某些包不支持此语法,这可能并不总是可行。 ::: -For your convenience, throughout the tutorial, each code block allows you to select your preferred syntax: +为了方便您,在整个教程中,每个代码块都允许您选择首选的语法: @@ -78,5 +78,4 @@ import { Server } from "socket.io"; - -Ready? Click "Next" to get started. +准备好了吗?点击“下一步”开始吧。 \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/02-project-initialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/02-project-initialization.md index 0208a303..00e050c0 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/02-project-initialization.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/02-project-initialization.md @@ -1,17 +1,17 @@ --- -title: "Tutorial step #1 - Project initialization" -sidebar_label: "Step #1: Project initialization" +title: "教程步骤 #1 - 项目初始化" +sidebar_label: "步骤 #1: 项目初始化" slug: step-1 --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Project initialization +# 项目初始化 -The first goal is to set up a simple HTML webpage that serves out a form and a list of messages. We’re going to use the Node.JS web framework `express` to this end. Make sure [Node.JS](https://nodejs.org) is installed. +我们的首要目标是设置一个简单的 HTML 网页,用于提供一个表单和消息列表。我们将使用 Node.JS 的 Web 框架 `express` 来实现这一目标。请确保已安装 [Node.JS](https://nodejs.org)。 -First let’s create a `package.json` manifest file that describes our project. I recommend you place it in a dedicated empty directory (I’ll call mine `socket-chat-example`). +首先,让我们创建一个 `package.json` 清单文件来描述我们的项目。建议将其放在一个专用的空目录中(我将其命名为 `socket-chat-example`)。 @@ -44,17 +44,17 @@ First let’s create a `package.json` manifest file that describes our project. :::caution -The "name" property must be unique, you cannot use a value like "socket.io" or "express", because npm will complain when installing the dependency. +"name" 属性必须是唯一的,不能使用类似 "socket.io" 或 "express" 的值,因为在安装依赖时 npm 会报错。 ::: -Now, in order to easily populate the `dependencies` property with the things we need, we’ll use `npm install`: +现在,为了方便地填充我们需要的 `dependencies` 属性,我们将使用 `npm install`: ``` npm install express@4 ``` -Once it's installed we can create an `index.js` file that will set up our application. +安装完成后,我们可以创建一个 `index.js` 文件来设置我们的应用程序。 @@ -97,28 +97,28 @@ server.listen(3000, () => { -This means that: +这意味着: -- Express initializes `app` to be a function handler that you can supply to an HTTP server (as seen in line 5). -- We define a route handler `/` that gets called when we hit our website home. -- We make the http server listen on port 3000. +- Express 初始化 `app` 为一个函数处理器,可以提供给 HTTP 服务器(如第 5 行所示)。 +- 我们定义了一个路由处理器 `/`,当访问我们的网站主页时会被调用。 +- 我们让 HTTP 服务器监听 3000 端口。 -If you run `node index.js` you should see the following: +如果运行 `node index.js`,你应该会看到以下内容: -A console saying that the server has started listening on port 3000 +控制台显示服务器已开始监听 3000 端口 -And if you point your browser to `http://localhost:3000`: +如果将浏览器指向 `http://localhost:3000`: -A browser displaying a big 'Hello World' +浏览器显示一个大的 'Hello World' -So far, so good! +到目前为止,一切顺利! :::info -You can run this example directly in your browser on: +你可以在浏览器中直接运行此示例: - [CodeSandbox](https://codesandbox.io/p/sandbox/github/socketio/chat-example/tree/cjs/step1?file=index.js) - [StackBlitz](https://stackblitz.com/github/socketio/chat-example/tree/cjs/step1?file=index.js) @@ -127,7 +127,7 @@ You can run this example directly in your browser on: -You can run this example directly in your browser on: +你可以在浏览器中直接运行此示例: - [CodeSandbox](https://codesandbox.io/p/sandbox/github/socketio/chat-example/tree/esm/step1?file=index.js) - [StackBlitz](https://stackblitz.com/github/socketio/chat-example/tree/esm/step1?file=index.js) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/03-serving-html.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/03-serving-html.md index 22b9fac0..12963094 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/03-serving-html.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/03-serving-html.md @@ -1,17 +1,17 @@ --- -title: "Tutorial step #2 - Serving HTML" -sidebar_label: "Step #2: Serving HTML" +title: "教程步骤 #2 - 提供 HTML 服务" +sidebar_label: "步骤 #2: 提供 HTML 服务" slug: step-2 --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Serving HTML +# 提供 HTML -So far in `index.js` we’re calling `res.send` and passing it a string of HTML. Our code would look very confusing if we just placed our entire application’s HTML there, so instead we're going to create a `index.html` file and serve that instead. +到目前为止,在 `index.js` 中我们使用 `res.send` 并传递一个 HTML 字符串。如果将整个应用程序的 HTML 都放在这里,代码会显得非常混乱,因此我们将创建一个 `index.html` 文件并提供该文件。 -Let’s refactor our route handler to use `sendFile` instead. +让我们重构路由处理器以使用 `sendFile`。 @@ -67,7 +67,7 @@ server.listen(3000, () => { -Put the following in your `index.html` file: +在你的 `index.html` 文件中放入以下内容: ```html @@ -97,16 +97,16 @@ Put the following in your `index.html` file: ``` -If you restart the process (by hitting Control+C and running `node index.js` again) and refresh the page it should look like this: +如果你重启进程(按下 Control+C 然后再次运行 `node index.js`)并刷新页面,它应该看起来像这样: -A browser displaying an input and a 'Send' button +浏览器显示一个输入框和一个 'Send' 按钮 :::info -You can run this example directly in your browser on: +你可以在浏览器中直接运行此示例: - [CodeSandbox](https://codesandbox.io/p/sandbox/github/socketio/chat-example/tree/cjs/step2?file=index.js) - [StackBlitz](https://stackblitz.com/github/socketio/chat-example/tree/cjs/step2?file=index.js) @@ -115,7 +115,7 @@ You can run this example directly in your browser on: -You can run this example directly in your browser on: +你可以在浏览器中直接运行此示例: - [CodeSandbox](https://codesandbox.io/p/sandbox/github/socketio/chat-example/tree/esm/step2?file=index.js) - [StackBlitz](https://stackblitz.com/github/socketio/chat-example/tree/esm/step2?file=index.js) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/04-integrating-socket-io.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/04-integrating-socket-io.md index 3ce91a72..62ccab45 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/04-integrating-socket-io.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/04-integrating-socket-io.md @@ -1,26 +1,26 @@ --- -title: "Tutorial step #3 - Integrating Socket.IO" -sidebar_label: "Step #3: Integrating Socket.IO" +title: "教程步骤 #3 - 集成 Socket.IO" +sidebar_label: "步骤 #3: 集成 Socket.IO" slug: step-3 --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Integrating Socket.IO +# 集成 Socket.IO -Socket.IO is composed of two parts: +Socket.IO 由两部分组成: -- A server that integrates with (or mounts on) the Node.JS HTTP Server (the [`socket.io`](https://www.npmjs.com/package/socket.io) package) -- A client library that loads on the browser side (the [`socket.io-client`](https://www.npmjs.com/package/socket.io-client) package) +- 一个与 Node.JS HTTP 服务器集成(或挂载)的服务器([`socket.io`](https://www.npmjs.com/package/socket.io) 包) +- 一个在浏览器端加载的客户端库([`socket.io-client`](https://www.npmjs.com/package/socket.io-client) 包) -During development, `socket.io` serves the client automatically for us, as we’ll see, so for now we only have to install one module: +在开发过程中,`socket.io` 会自动为我们提供客户端服务,如我们将看到的,因此现在我们只需安装一个模块: ``` npm install socket.io ``` -That will install the module and add the dependency to `package.json`. Now let’s edit `index.js` to add it: +这将安装模块并将依赖项添加到 `package.json`。现在让我们编辑 `index.js` 来添加它: @@ -92,10 +92,10 @@ server.listen(3000, () => { -Notice that I initialize a new instance of `socket.io` by passing the `server` (the HTTP server) object. Then I listen on the `connection` event for incoming sockets and log it to the console. +注意,我通过传递 `server`(HTTP 服务器)对象来初始化一个新的 `socket.io` 实例。然后我监听 `connection` 事件以接收传入的套接字并将其记录到控制台。 -Now in index.html add the following snippet before the `` (end body tag): +现在在 `index.html` 中,在 ``(结束 body 标签)之前添加以下代码片段: @@ -120,33 +120,33 @@ Now in index.html add the following snippet before the `` (end body tag): -That’s all it takes to load the `socket.io-client`, which exposes an `io` global (and the endpoint `GET /socket.io/socket.io.js`), and then connect. +这就是加载 `socket.io-client` 所需的全部内容,它会暴露一个全局的 `io`(以及端点 `GET /socket.io/socket.io.js`),然后进行连接。 -If you would like to use the local version of the client-side JS file, you can find it at `node_modules/socket.io/client-dist/socket.io.js`. +如果你想使用客户端 JS 文件的本地版本,可以在 `node_modules/socket.io/client-dist/socket.io.js` 找到它。 :::tip -You can also use a CDN instead of the local files (e.g. ``). +你也可以使用 CDN 而不是本地文件(例如 ``)。 ::: -Notice that I’m not specifying any URL when I call `io()`, since it defaults to trying to connect to the host that serves the page. +注意,当我调用 `io()` 时,我没有指定任何 URL,因为它默认尝试连接到提供页面的主机。 :::note -If you're behind a reverse proxy such as apache or nginx please take a look at [the documentation for it](/docs/v4/reverse-proxy/). +如果你在使用反向代理(如 apache 或 nginx),请查看[相关文档](/docs/v4/reverse-proxy/)。 -If you're hosting your app in a folder that is *not* the root of your website (e.g., `https://example.com/chatapp`) then you also need to specify the [path](/docs/v4/server-options/#path) in both the server and the client. +如果你的应用托管在网站的非根目录下(例如 `https://example.com/chatapp`),那么你还需要在服务器和客户端中指定[path](/docs/v4/server-options/#path)。 ::: -If you now restart the process (by hitting Control+C and running `node index.js` again) and then refresh the webpage you should see the console print “a user connected”. +如果你现在重新启动进程(按 Control+C 然后再次运行 `node index.js`)并刷新网页,你应该会看到控制台打印“a user connected”。 -Try opening several tabs, and you’ll see several messages. +尝试打开多个标签页,你会看到多条消息。 -A console displaying several messages, indicating that some users have connected +控制台显示多条消息,表明有用户已连接 -Each socket also fires a special `disconnect` event: +每个套接字还会触发一个特殊的 `disconnect` 事件: ```js io.on('connection', (socket) => { @@ -157,16 +157,16 @@ io.on('connection', (socket) => { }); ``` -Then if you refresh a tab several times you can see it in action. +然后,如果你多次刷新一个标签页,你可以看到它的效果。 -A console displaying several messages, indicating that some users have connected and disconnected +控制台显示多条消息,表明有用户已连接和断开连接 :::info -You can run this example directly in your browser on: +你可以在浏览器中直接运行此示例: - [CodeSandbox](https://codesandbox.io/p/sandbox/github/socketio/chat-example/tree/cjs/step3?file=index.js) - [StackBlitz](https://stackblitz.com/github/socketio/chat-example/tree/cjs/step3?file=index.js) @@ -175,7 +175,7 @@ You can run this example directly in your browser on: -You can run this example directly in your browser on: +你可以在浏览器中直接运行此示例: - [CodeSandbox](https://codesandbox.io/p/sandbox/github/socketio/chat-example/tree/esm/step3?file=index.js) - [StackBlitz](https://stackblitz.com/github/socketio/chat-example/tree/esm/step3?file=index.js) @@ -184,4 +184,4 @@ You can run this example directly in your browser on: -::: +::: \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/05-emitting-events.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/05-emitting-events.md index da1bfd8d..a422a0a0 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/05-emitting-events.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/05-emitting-events.md @@ -1,17 +1,17 @@ --- -title: "Tutorial step #4 - Emitting events" -sidebar_label: "Step #4: Emitting events" +title: "教程步骤 #4 - 触发事件" +sidebar_label: "步骤 #4: 触发事件" slug: step-4 --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Emitting events +# 触发事件 -The main idea behind Socket.IO is that you can send and receive any events you want, with any data you want. Any objects that can be encoded as JSON will do, and [binary data](/blog/introducing-socket-io-1-0/#binary) is supported too. +Socket.IO 的核心理念是你可以发送和接收任何你想要的事件,并附带任何数据。任何可以编码为 JSON 的对象都可以使用,[二进制数据](/blog/introducing-socket-io-1-0/#binary) 也支持。 -Let’s make it so that when the user types in a message, the server gets it as a `chat message` event. The `script` section in `index.html` should now look as follows: +让我们实现一个功能,当用户输入消息时,服务器将其作为 `chat message` 事件接收。`index.html` 中的 `script` 部分应如下所示: @@ -58,7 +58,7 @@ Let’s make it so that when the user types in a message, the server gets it as -And in `index.js` we print out the `chat message` event: +在 `index.js` 中,我们打印出 `chat message` 事件: ```js io.on('connection', (socket) => { @@ -68,7 +68,7 @@ io.on('connection', (socket) => { }); ``` -The result should be like the following video: +结果应如下视频所示: @@ -77,7 +77,7 @@ The result should be like the following video: -You can run this example directly in your browser on: +你可以在浏览器中直接运行此示例: - [CodeSandbox](https://codesandbox.io/p/sandbox/github/socketio/chat-example/tree/cjs/step4?file=index.js) - [StackBlitz](https://stackblitz.com/github/socketio/chat-example/tree/cjs/step4?file=index.js) @@ -86,7 +86,7 @@ You can run this example directly in your browser on: -You can run this example directly in your browser on: +你可以在浏览器中直接运行此示例: - [CodeSandbox](https://codesandbox.io/p/sandbox/github/socketio/chat-example/tree/esm/step4?file=index.js) - [StackBlitz](https://stackblitz.com/github/socketio/chat-example/tree/esm/step4?file=index.js) @@ -95,4 +95,4 @@ You can run this example directly in your browser on: -::: +::: \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/06-broadcasting.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/06-broadcasting.md index a24d9f34..511e439f 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/06-broadcasting.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/06-broadcasting.md @@ -1,24 +1,24 @@ --- -title: "Tutorial step #5 - Broadcasting" -sidebar_label: "Step #5: Broadcasting" +title: "教程步骤 #5 - 广播" +sidebar_label: "步骤 #5: 广播" slug: step-5 --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Broadcasting +# 广播 -The next goal is for us to emit the event from the server to the rest of the users. +接下来的目标是从服务器向其他用户发送事件。 -In order to send an event to everyone, Socket.IO gives us the `io.emit()` method. +为了向所有人发送事件,Socket.IO 提供了 `io.emit()` 方法。 ```js -// this will emit the event to all connected sockets +// 这将向所有连接的套接字发送事件 io.emit('hello', 'world'); ``` -If you want to send a message to everyone except for a certain emitting socket, we have the `broadcast` flag for emitting from that socket: +如果你想向除某个特定发送套接字之外的所有人发送消息,可以使用 `broadcast` 标志从该套接字发送: ```js io.on('connection', (socket) => { @@ -26,7 +26,7 @@ io.on('connection', (socket) => { }); ``` -In this case, for the sake of simplicity we’ll send the message to everyone, including the sender. +在这个例子中,为了简单起见,我们将向所有人发送消息,包括发送者。 ```js io.on('connection', (socket) => { @@ -36,7 +36,7 @@ io.on('connection', (socket) => { }); ``` -And on the client side when we capture a `chat message` event we’ll include it in the page. +在客户端,当我们捕获到 `chat message` 事件时,会将其显示在页面中。 @@ -107,7 +107,7 @@ And on the client side when we capture a `chat message` event we’ll include it -Let's see it in action: +让我们看看实际效果: @@ -116,7 +116,7 @@ Let's see it in action: -You can run this example directly in your browser on: +你可以在浏览器中直接运行这个示例: - [CodeSandbox](https://codesandbox.io/p/sandbox/github/socketio/chat-example/tree/cjs/step5?file=index.js) - [StackBlitz](https://stackblitz.com/github/socketio/chat-example/tree/cjs/step5?file=index.js) @@ -125,7 +125,7 @@ You can run this example directly in your browser on: -You can run this example directly in your browser on: +你可以在浏览器中直接运行这个示例: - [CodeSandbox](https://codesandbox.io/p/sandbox/github/socketio/chat-example/tree/esm/step5?file=index.js) - [StackBlitz](https://stackblitz.com/github/socketio/chat-example/tree/esm/step5?file=index.js) @@ -134,4 +134,4 @@ You can run this example directly in your browser on: -::: +::: \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/07-api-overview.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/07-api-overview.md index 58f742b7..7ada45bc 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/07-api-overview.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/07-api-overview.md @@ -1,6 +1,6 @@ --- -title: "Tutorial - Overview of the API" -sidebar_label: "Overview of the API" +title: "教程 - API 概览" +sidebar_label: "API 概览" slug: api-overview toc_max_heading_level: 4 --- @@ -10,28 +10,28 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Overview of the API +# API 概览 -Before we go any further, let's take a quick tour of the API provided by Socket.IO: +在深入了解之前,让我们快速浏览一下 Socket.IO 提供的 API: -## Common API {#common-api} +## 通用 API -The following methods are available for both the client and the server. +以下方法可用于客户端和服务器。 -### Basic emit {#basic-emit} +### 基本的 emit -As we have seen in [step #4](05-emitting-events.md), you can send any data to the other side with `socket.emit()`: +如我们在[步骤 #4](05-emitting-events.md)中所见,可以使用 `socket.emit()` 向另一端发送任何数据: - + -*Client* +*客户端* ```js socket.emit('hello', 'world'); ``` -*Server* +*服务器* ```js io.on('connection', (socket) => { @@ -42,9 +42,9 @@ io.on('connection', (socket) => { ``` - + -*Server* +*服务器* ```js io.on('connection', (socket) => { @@ -52,7 +52,7 @@ io.on('connection', (socket) => { }); ``` -*Client* +*客户端* ```js socket.on('hello', (arg) => { @@ -63,18 +63,18 @@ socket.on('hello', (arg) => { -You can send any number of arguments, and all serializable data structures are supported, including binary objects like [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer), [TypedArray](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) or [Buffer](https://nodejs.org/docs/latest/api/buffer.html#buffer_buffer) (Node.js only): +你可以发送任意数量的参数,并且支持所有可序列化的数据结构,包括二进制对象,如 [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)、[TypedArray](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) 或 [Buffer](https://nodejs.org/docs/latest/api/buffer.html#buffer_buffer)(仅限 Node.js): - + -*Client* +*客户端* ```js socket.emit('hello', 1, '2', { 3: '4', 5: Uint8Array.from([6]) }); ``` -*Server* +*服务器* ```js io.on('connection', (socket) => { @@ -87,9 +87,9 @@ io.on('connection', (socket) => { ``` - + -*Server* +*服务器* ```js io.on('connection', (socket) => { @@ -97,7 +97,7 @@ io.on('connection', (socket) => { }); ``` -*Client* +*客户端* ```js socket.on('hello', (arg1, arg2, arg3) => { @@ -112,44 +112,44 @@ socket.on('hello', (arg1, arg2, arg3) => { :::tip -Calling `JSON.stringify()` on objects is not needed: +不需要对对象调用 `JSON.stringify()`: ```js -// BAD +// 错误示例 socket.emit('hello', JSON.stringify({ name: 'John' })); -// GOOD +// 正确示例 socket.emit('hello', { name: 'John' }); ``` ::: -### Acknowledgements {#acknowledgements} +### 确认机制 -Events are great, but in some cases you may want a more classic request-response API. In Socket.IO, this feature is named "acknowledgements". +事件非常有用,但在某些情况下,你可能需要更经典的请求-响应 API。在 Socket.IO 中,这个功能被称为“确认机制”。 -It comes in two flavors: +它有两种形式: -#### With a callback function {#with-a-callback-function} +#### 使用回调函数 -You can add a callback as the last argument of the `emit()`, and this callback will be called once the other side has acknowledged the event: +你可以在 `emit()` 的最后一个参数中添加一个回调函数,当另一端确认事件后,该回调将被调用: - + -*Client* +*客户端* ```js socket.timeout(5000).emit('request', { foo: 'bar' }, 'baz', (err, response) => { if (err) { - // the server did not acknowledge the event in the given delay + // 服务器未在给定时间内确认事件 } else { console.log(response.status); // 'ok' } }); ``` -*Server* +*服务器* ```js io.on('connection', (socket) => { @@ -164,15 +164,15 @@ io.on('connection', (socket) => { ``` - + -*Server* +*服务器* ```js io.on('connection', (socket) => { socket.timeout(5000).emit('request', { foo: 'bar' }, 'baz', (err, response) => { if (err) { - // the client did not acknowledge the event in the given delay + // 客户端未在给定时间内确认事件 } else { console.log(response.status); // 'ok' } @@ -180,7 +180,7 @@ io.on('connection', (socket) => { }); ``` -*Client* +*客户端* ```js socket.on('request', (arg1, arg2, callback) => { @@ -195,25 +195,25 @@ socket.on('request', (arg1, arg2, callback) => { -#### With a Promise {#with-a-promise} +#### 使用 Promise -The `emitWithAck()` method provides the same functionality, but returns a Promise which will resolve once the other side acknowledges the event: +`emitWithAck()` 方法提供相同的功能,但返回一个 Promise,一旦另一端确认事件,该 Promise 将被解析: - + -*Client* +*客户端* ```js try { const response = await socket.timeout(5000).emitWithAck('request', { foo: 'bar' }, 'baz'); console.log(response.status); // 'ok' } catch (e) { - // the server did not acknowledge the event in the given delay + // 服务器未在给定时间内确认事件 } ``` -*Server* +*服务器* ```js io.on('connection', (socket) => { @@ -228,9 +228,9 @@ io.on('connection', (socket) => { ``` - + -*Server* +*服务器* ```js io.on('connection', async (socket) => { @@ -238,12 +238,12 @@ io.on('connection', async (socket) => { const response = await socket.timeout(5000).emitWithAck('request', { foo: 'bar' }, 'baz'); console.log(response.status); // 'ok' } catch (e) { - // the client did not acknowledge the event in the given delay + // 客户端未在给定时间内确认事件 } }); ``` -*Client* +*客户端* ```js socket.on('request', (arg1, arg2, callback) => { @@ -260,21 +260,21 @@ socket.on('request', (arg1, arg2, callback) => { :::caution -Environments that [do not support Promises](https://caniuse.com/promises) (such as Internet Explorer) will need to add a polyfill or use a compiler like [babel](https://babeljs.io/) in order to use this feature (but this is out of the scope of this tutorial). +不支持 [Promises](https://caniuse.com/promises) 的环境(如 Internet Explorer)需要添加 polyfill 或使用类似 [babel](https://babeljs.io/) 的编译器才能使用此功能(但这超出了本教程的范围)。 ::: -### Catch-all listeners {#catch-all-listeners} +### 全局监听器 -A catch-all listeners is a listener that will be called for any incoming event. This is useful for debugging your application: +全局监听器是一个会在任何传入事件时被调用的监听器。这对于调试应用程序非常有用: -*Sender* +*发送者* ```js socket.emit('hello', 1, '2', { 3: '4', 5: Uint8Array.from([6]) }); ``` -*Receiver* +*接收者* ```js socket.onAny((eventName, ...args) => { @@ -283,7 +283,7 @@ socket.onAny((eventName, ...args) => { }); ``` -Similarly, for outgoing packets: +类似地,对于传出数据包: ```js socket.onAnyOutgoing((eventName, ...args) => { @@ -292,50 +292,50 @@ socket.onAnyOutgoing((eventName, ...args) => { }); ``` -## Server API {#server-api} +## 服务器 API -### Broadcasting {#broadcasting} +### 广播 -As we have seen in [step #5](06-broadcasting.md), you can broadcast an event to all connected clients with `io.emit()`: +如我们在[步骤 #5](06-broadcasting.md)中所见,可以使用 `io.emit()` 向所有连接的客户端广播事件: ```js io.emit('hello', 'world'); ``` -### Rooms {#rooms} +### 房间 -In Socket.IO jargon, a *room* is an arbitrary channel that sockets can join and leave. It can be used to broadcast events to a subset of connected clients: +在 Socket.IO 术语中,*房间* 是一个可以让 socket 加入和离开的任意通道。它可以用于向一部分连接的客户端广播事件: ```js io.on('connection', (socket) => { - // join the room named 'some room' + // 加入名为 'some room' 的房间 socket.join('some room'); - // broadcast to all connected clients in the room + // 向房间内所有连接的客户端广播 io.to('some room').emit('hello', 'world'); - // broadcast to all connected clients except those in the room + // 向除房间内的客户端外的所有连接客户端广播 io.except('some room').emit('hello', 'world'); - // leave the room + // 离开房间 socket.leave('some room'); }); ``` -That's basically it! For future reference, the whole API can be found [here](../server-api.md) (server) and [here](../client-api.md) (client). +基本就是这样!如需参考,完整的 API 可以在[这里](../server-api.md)(服务器)和[这里](../client-api.md)(客户端)找到。 \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/08-handling-disconnections.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/08-handling-disconnections.md index 42c4556e..72910f34 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/08-handling-disconnections.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/08-handling-disconnections.md @@ -1,6 +1,6 @@ --- -title: Tutorial - Handling disconnections -sidebar_label: Handling disconnections +title: 教程 - 处理断开连接 +sidebar_label: 处理断开连接 slug: handling-disconnections --- @@ -9,35 +9,35 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Handling disconnections +# 处理断开连接 -Now, let's highlight two really important properties of Socket.IO: +现在,让我们强调 Socket.IO 的两个非常重要的特性: -1. a Socket.IO client is not always connected -2. a Socket.IO server does not store any event +1. Socket.IO 客户端并不总是连接的 +2. Socket.IO 服务器不存储任何事件 :::caution -Even over a stable network, it is not possible to maintain a connection alive forever. +即使在稳定的网络上,也无法永远保持连接。 ::: -Which means that your application needs to be able to synchronize the local state of the client with the global state on the server after a temporary disconnection. +这意味着您的应用程序需要能够在临时断开连接后,将客户端的本地状态与服务器上的全局状态同步。 :::note -The Socket.IO client will automatically try to reconnect after a small delay. However, any missed event during the disconnection period will effectively be lost for this client. +Socket.IO 客户端会在短暂延迟后自动尝试重新连接。然而,断开连接期间错过的任何事件对于该客户端来说将会丢失。 ::: -In the context of our chat application, this implies that a disconnected client might miss some messages: +在我们的聊天应用程序中,这意味着断开连接的客户端可能会错过一些消息: -We will see in the next steps how we can improve this. +我们将在接下来的步骤中看到如何改进这一点。 \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/09-connection-state-recovery.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/09-connection-state-recovery.md index e1f21c8a..62023a23 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/09-connection-state-recovery.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/09-connection-state-recovery.md @@ -1,22 +1,22 @@ --- -title: "Tutorial step #6 - Connection state recovery" -sidebar_label: "Step #6: Connection state recovery" +title: "教程步骤 #6 - 连接状态恢复" +sidebar_label: "步骤 #6: 连接状态恢复" slug: step-6 --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Connection state recovery +# 连接状态恢复 -First, let's handle disconnections by pretending that there was no disconnection: this feature is called "Connection state recovery". +首先,让我们通过假装没有断开连接来处理断开连接的情况:这个功能称为“连接状态恢复”。 -This feature will **temporarily** store all the events that are sent by the server and will try to restore the state of a client when it reconnects: +此功能将**临时**存储服务器发送的所有事件,并在客户端重新连接时尝试恢复其状态: -- restore its rooms -- send any missed events +- 恢复其房间 +- 发送任何错过的事件 -It must be enabled on the server side: +必须在服务器端启用此功能: ```js title="index.js" const io = new Server(server, { @@ -26,27 +26,27 @@ const io = new Server(server, { }); ``` -Let's see it in action: +让我们看看它的实际效果: -As you can see in the video above, the "realtime" message is eventually delivered when the connection is reestablished. +如上面视频所示,当连接重新建立时,“实时”消息最终会被传递。 :::note -The "Disconnect" button was added for demonstration purposes. +“断开连接”按钮是为了演示目的而添加的。
- Code + 代码 ```html
- + // highlight-start - + // highlight-end
@@ -57,10 +57,10 @@ The "Disconnect" button was added for demonstration purposes. toggleButton.addEventListener('click', (e) => { e.preventDefault(); if (socket.connected) { - toggleButton.innerText = 'Connect'; + toggleButton.innerText = '连接'; socket.disconnect(); } else { - toggleButton.innerText = 'Disconnect'; + toggleButton.innerText = '断开连接'; socket.connect(); } }); @@ -73,9 +73,9 @@ The "Disconnect" button was added for demonstration purposes. ```html
- + // highlight-start - + // highlight-end
@@ -86,10 +86,10 @@ The "Disconnect" button was added for demonstration purposes. toggleButton.addEventListener('click', function(e) { e.preventDefault(); if (socket.connected) { - toggleButton.innerText = 'Connect'; + toggleButton.innerText = '连接'; socket.disconnect(); } else { - toggleButton.innerText = 'Disconnect'; + toggleButton.innerText = '断开连接'; socket.connect(); } }); @@ -103,29 +103,29 @@ The "Disconnect" button was added for demonstration purposes. ::: -Great! Now, you may ask: +很好!你可能会问: -> But this is an awesome feature, why isn't this enabled by default? +> 这是一个很棒的功能,为什么默认没有启用呢? -There are several reasons for this: +原因有几个: -- it doesn't always work, for example if the server abruptly crashes or gets restarted, then the client state might not be saved -- it is not always possible to enable this feature when scaling up +- 它并不总是有效,例如,如果服务器突然崩溃或重启,客户端状态可能无法保存 +- 在扩展时并不总是可以启用此功能 :::tip -That being said, it is indeed a great feature since you don't have to synchronize the state of the client after a temporary disconnection (for example, when the user switches from WiFi to 4G). +尽管如此,这确实是一个很棒的功能,因为在临时断开连接后(例如,当用户从 WiFi 切换到 4G 时),你不必同步客户端的状态。 ::: -We will explore a more general solution in the next step. +我们将在下一步探索一个更通用的解决方案。 :::info -You can run this example directly in your browser on: +你可以在浏览器中直接运行此示例: - [CodeSandbox](https://codesandbox.io/p/sandbox/github/socketio/chat-example/tree/cjs/step6?file=index.js) - [StackBlitz](https://stackblitz.com/github/socketio/chat-example/tree/cjs/step6?file=index.js) @@ -134,7 +134,7 @@ You can run this example directly in your browser on: -You can run this example directly in your browser on: +你可以在浏览器中直接运行此示例: - [CodeSandbox](https://codesandbox.io/p/sandbox/github/socketio/chat-example/tree/esm/step6?file=index.js) - [StackBlitz](https://stackblitz.com/github/socketio/chat-example/tree/esm/step6?file=index.js) @@ -143,4 +143,4 @@ You can run this example directly in your browser on: -::: +::: \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/10-server-delivery.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/10-server-delivery.md index b7d27856..006eaa63 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/10-server-delivery.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/10-server-delivery.md @@ -1,30 +1,30 @@ --- -title: "Tutorial step #7 - Server delivery" -sidebar_label: "Step #7: Server delivery" +title: "教程步骤 #7 - 服务器传递" +sidebar_label: "步骤 #7: 服务器传递" slug: step-7 --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Server delivery +# 服务器传递 -There are two common ways to synchronize the state of the client upon reconnection: +在重新连接时同步客户端状态有两种常见方法: -- either the server sends the whole state -- or the client keeps track of the last event it has processed and the server sends the missing pieces +- 服务器发送整个状态 +- 客户端跟踪其处理的最后一个事件,服务器发送缺失的部分 -Both are totally valid solutions and choosing one will depend on your use case. In this tutorial, we will go with the latter. +这两种方案都是完全有效的,选择哪种取决于你的使用场景。在本教程中,我们将选择后者。 -First, let's persist the messages of our chat application. Today there are plenty of great options, we will use with [SQLite](https://www.sqlite.org/) here. +首先,让我们持久化聊天应用程序的消息。如今有很多不错的选择,这里我们使用 [SQLite](https://www.sqlite.org/)。 :::tip -If you are not familiar with SQLite, there are plenty of tutorials available online, like [this one](https://www.sqlitetutorial.net/). +如果你不熟悉 SQLite,可以在网上找到很多教程,比如 [这个](https://www.sqlitetutorial.net/)。 ::: -Let's install the necessary packages: +安装必要的包: @@ -50,7 +50,7 @@ pnpm add sqlite sqlite3 -We will simply store each message in a SQL table: +我们将简单地将每条消息存储在一个 SQL 表中: @@ -67,13 +67,13 @@ const { open } = require('sqlite'); async function main() { // highlight-start - // open the database file + // 打开数据库文件 const db = await open({ filename: 'chat.db', driver: sqlite3.Database }); - // create our 'messages' table (you can ignore the 'client_offset' column for now) + // 创建我们的 'messages' 表(可以暂时忽略 'client_offset' 列) await db.exec(` CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -98,13 +98,13 @@ async function main() { // highlight-start let result; try { - // store the message in the database + // 将消息存储在数据库中 result = await db.run('INSERT INTO messages (content) VALUES (?)', msg); } catch (e) { - // TODO handle the failure + // TODO 处理失败 return; } - // include the offset with the message + // 包含偏移量与消息一起发送 io.emit('chat message', msg, result.lastID); // highlight-end }); @@ -131,13 +131,13 @@ import { Server } from 'socket.io'; import sqlite3 from 'sqlite3'; import { open } from 'sqlite'; -// open the database file +// 打开数据库文件 const db = await open({ filename: 'chat.db', driver: sqlite3.Database }); -// create our 'messages' table (you can ignore the 'client_offset' column for now) +// 创建我们的 'messages' 表(可以暂时忽略 'client_offset' 列) await db.exec(` CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -164,13 +164,13 @@ io.on('connection', (socket) => { // highlight-start let result; try { - // store the message in the database + // 将消息存储在数据库中 result = await db.run('INSERT INTO messages (content) VALUES (?)', msg); } catch (e) { - // TODO handle the failure + // TODO 处理失败 return; } - // include the offset with the message + // 包含偏移量与消息一起发送 io.emit('chat message', msg, result.lastID); // highlight-end }); @@ -184,7 +184,7 @@ server.listen(3000, () => { -The client will then keep track of the offset: +客户端将跟踪偏移量: @@ -263,7 +263,7 @@ The client will then keep track of the offset: -And finally the server will send the missing messages upon (re)connection: +最后,服务器将在(重新)连接时发送缺失的消息: ```js title="index.js" // [...] @@ -274,7 +274,7 @@ io.on('connection', async (socket) => { try { result = await db.run('INSERT INTO messages (content) VALUES (?)', msg); } catch (e) { - // TODO handle the failure + // TODO 处理失败 return; } io.emit('chat message', msg, result.lastID); @@ -282,7 +282,7 @@ io.on('connection', async (socket) => { // highlight-start if (!socket.recovered) { - // if the connection state recovery was not successful + // 如果连接状态恢复不成功 try { await db.each('SELECT id, content FROM messages WHERE id > ?', [socket.handshake.auth.serverOffset || 0], @@ -291,7 +291,7 @@ io.on('connection', async (socket) => { } ) } catch (e) { - // something went wrong + // 出现错误 } } // highlight-end @@ -300,16 +300,16 @@ io.on('connection', async (socket) => { // [...] ``` -Let's see it in action: +让我们看看实际效果: -As you can see in the video above, it works both after a temporary disconnection and a full page refresh. +如上面视频所示,它在临时断开连接和完全刷新页面后都能正常工作。 :::tip -The difference with the "Connection state recovery" feature is that a successful recovery might not need to hit your main database (it might fetch the messages from a Redis stream for example). +与“连接状态恢复”功能的区别在于,成功的恢复可能不需要访问主数据库(例如,它可能从 Redis 流中获取消息)。 ::: -OK, now let's talk about the client delivery. +好了,现在让我们讨论客户端传递。 \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/11-client-delivery.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/11-client-delivery.md index 0cd1b615..022ffc9e 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/11-client-delivery.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/11-client-delivery.md @@ -1,47 +1,47 @@ --- -title: "Tutorial step #8 - Client delivery" -sidebar_label: "Step #8: Client delivery" +title: "教程步骤 #8 - 客户端消息传递" +sidebar_label: "步骤 #8: 客户端消息传递" slug: step-8 --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Client delivery +# 客户端消息传递 -Let's see how we can make sure that the server always receives the messages sent by the clients. +让我们看看如何确保服务器始终接收到客户端发送的消息。 :::info -By default, Socket.IO provides an "at most once" guarantee of delivery (also known as "fire and forget"), which means that there will be no retry in case the message does not reach the server. +默认情况下,Socket.IO 提供“最多一次”的传递保证(也称为“发送即忘”),这意味着如果消息未到达服务器,将不会重试。 ::: -## Buffered events {#buffered-events} +## 缓冲事件 -When a client gets disconnected, any call to `socket.emit()` is buffered until reconnection: +当客户端断开连接时,任何对 `socket.emit()` 的调用都会被缓冲,直到重新连接: -In the video above, the "realtime" message is buffered until the connection is reestablished. +在上面的视频中,“实时”消息被缓冲,直到连接重新建立。 -This behavior might be totally sufficient for your application. However, there are a few cases where a message could be lost: +这种行为可能完全满足您的应用需求。然而,在以下几种情况下,消息可能会丢失: -- the connection is severed while the event is being sent -- the server crashes or get restarted while processing the event -- the database is temporarily not available +- 在事件发送过程中连接中断 +- 服务器在处理事件时崩溃或重启 +- 数据库暂时不可用 -## At least once {#at-least-once} +## 至少一次 -We can implement an "at least once" guarantee: +我们可以实现“至少一次”的传递保证: -- manually with an acknowledgement: +- 手动使用确认机制: ```js function emit(socket, event, arg) { socket.timeout(5000).emit(event, arg, (err) => { if (err) { - // no ack from the server, let's retry + // 服务器没有确认,重试 emit(socket, event, arg); } }); @@ -50,7 +50,7 @@ function emit(socket, event, arg) { emit(socket, 'hello', 'world'); ``` -- or with the `retries` option: +- 或使用 `retries` 选项: ```js const socket = io({ @@ -61,12 +61,12 @@ const socket = io({ socket.emit('hello', 'world'); ``` -In both cases, the client will retry to send the message until it gets an acknowledgement from the server: +在这两种情况下,客户端将重试发送消息,直到收到服务器的确认: ```js io.on('connection', (socket) => { socket.on('hello', (value, callback) => { - // once the event is successfully handled + // 一旦事件成功处理 callback(); }); }) @@ -74,17 +74,17 @@ io.on('connection', (socket) => { :::tip -With the `retries` option, the order of the messages is guaranteed, as the messages are queued and sent one by one. This is not the case with the first option. +使用 `retries` 选项时,消息的顺序是有保证的,因为消息是逐个排队发送的。第一种方法则不保证顺序。 ::: -## Exactly once {#exactly-once} +## 精确一次 -The problem with retries is that the server might now receive the same message multiple times, so it needs a way to uniquely identify each message, and only store it once in the database. +重试的问题在于服务器可能会多次接收到相同的消息,因此需要一种方法来唯一标识每条消息,并且只在数据库中存储一次。 -Let's see how we can implement an "exactly once" guarantee in our chat application. +让我们看看如何在聊天应用中实现“精确一次”的传递保证。 -We will start by assigning a unique identifier to each message on the client side: +我们将从客户端为每条消息分配一个唯一标识符开始: @@ -99,7 +99,7 @@ We will start by assigning a unique identifier to each message on the client sid serverOffset: 0 }, // highlight-start - // enable retries + // 启用重试 ackTimeout: 10000, retries: 3, // highlight-end @@ -113,7 +113,7 @@ We will start by assigning a unique identifier to each message on the client sid e.preventDefault(); if (input.value) { // highlight-start - // compute a unique offset + // 计算唯一偏移量 const clientOffset = `${socket.id}-${counter++}`; socket.emit('chat message', input.value, clientOffset); // highlight-end @@ -144,7 +144,7 @@ We will start by assigning a unique identifier to each message on the client sid serverOffset: 0 }, // highlight-start - // enable retries + // 启用重试 ackTimeout: 10000, retries: 3, // highlight-end @@ -158,7 +158,7 @@ We will start by assigning a unique identifier to each message on the client sid e.preventDefault(); if (input.value) { // highlight-start - // compute a unique offset + // 计算唯一偏移量 var clientOffset = `${socket.id}-${counter++}`; socket.emit('chat message', input.value, clientOffset); // highlight-end @@ -181,13 +181,13 @@ We will start by assigning a unique identifier to each message on the client sid :::note -The `socket.id` attribute is a random 20-characters identifier which is assigned to each connection. +`socket.id` 属性是分配给每个连接的随机20字符标识符。 -We could also have used [`getRandomValues()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues) to generate a unique offset. +我们也可以使用 [`getRandomValues()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues) 来生成唯一偏移量。 ::: -And then we store this offset alongside the message on the server side: +然后我们在服务器端将此偏移量与消息一起存储: ```js title="index.js" // [...] @@ -202,17 +202,17 @@ io.on('connection', async (socket) => { } catch (e) { // highlight-start if (e.errno === 19 /* SQLITE_CONSTRAINT */ ) { - // the message was already inserted, so we notify the client + // 消息已插入,因此通知客户端 callback(); } else { - // nothing to do, just let the client retry + // 无需操作,让客户端重试 } return; // highlight-end } io.emit('chat message', msg, result.lastID); // highlight-start - // acknowledge the event + // 确认事件 callback(); // highlight-end }); @@ -226,7 +226,7 @@ io.on('connection', async (socket) => { } ) } catch (e) { - // something went wrong + // 出现错误 } } }); @@ -234,15 +234,15 @@ io.on('connection', async (socket) => { // [...] ``` -This way, the UNIQUE constraint on the `client_offset` column prevents the duplication of the message. +这样,`client_offset` 列上的 UNIQUE 约束可以防止消息重复。 :::caution -Do not forget to acknowledge the event, or else the client will keep retrying (up to `retries` times). +不要忘记确认事件,否则客户端将继续重试(最多 `retries` 次)。 ```js socket.on('chat message', async (msg, clientOffset, callback) => { - // ... and finally + // ... 最后 callback(); }); ``` @@ -251,18 +251,18 @@ socket.on('chat message', async (msg, clientOffset, callback) => { :::info -Again, the default guarantee ("at most once") might be sufficient for your application, but now you know how it can be made more reliable. +同样,默认的“最多一次”保证可能足以满足您的应用需求,但现在您知道如何提高其可靠性。 ::: -In the next step, we will see how we can scale our application horizontally. +在下一步中,我们将了解如何横向扩展我们的应用。 :::info -You can run this example directly in your browser on: +您可以在浏览器中直接运行此示例: - [CodeSandbox](https://codesandbox.io/p/sandbox/github/socketio/chat-example/tree/cjs/step8?file=index.js) - [StackBlitz](https://stackblitz.com/github/socketio/chat-example/tree/cjs/step8?file=index.js) @@ -271,7 +271,7 @@ You can run this example directly in your browser on: -You can run this example directly in your browser on: +您可以在浏览器中直接运行此示例: - [CodeSandbox](https://codesandbox.io/p/sandbox/github/socketio/chat-example/tree/esm/step8?file=index.js) - [StackBlitz](https://stackblitz.com/github/socketio/chat-example/tree/esm/step8?file=index.js) @@ -280,4 +280,4 @@ You can run this example directly in your browser on: -::: +::: \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/12-scaling-horizontally.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/12-scaling-horizontally.md index 5269225b..45e52386 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/12-scaling-horizontally.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/12-scaling-horizontally.md @@ -1,6 +1,6 @@ --- -title: "Tutorial step #9 - Scaling horizontally" -sidebar_label: "Step #9: Scaling horizontally" +title: "教程步骤 #9 - 水平扩展" +sidebar_label: "步骤 #9: 水平扩展" slug: step-9 --- @@ -9,30 +9,30 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Scaling horizontally +# 水平扩展 -Now that our application is resilient to temporary network interruptions, let's see how we can horizontally scale it in order to be able to support thousands of concurrent clients. +现在我们的应用程序已经能够应对临时的网络中断,让我们看看如何通过水平扩展来支持数千个并发客户端。 :::note -- Horizontal scaling (also known as "scaling out") means adding new servers to your infrastructure to cope with new demands -- Vertical scaling (also known as "scaling up") means adding more resources (processing power, memory, storage, ...) to your existing infrastructure +- 水平扩展(也称为“扩展出”)是指向基础设施中添加新的服务器以应对新的需求 +- 垂直扩展(也称为“扩展上”)是指为现有基础设施添加更多资源(处理能力、内存、存储等) ::: -First step: let's use all the available cores of the host. By default, Node.js runs your Javascript code in a single thread, which means that even with a 32-core CPU, only one core will be used. Fortunately, the Node.js [`cluster` module](https://nodejs.org/api/cluster.html#cluster) provides a convenient way to create one worker thread per core. +第一步:利用主机的所有可用核心。默认情况下,Node.js 在单线程中运行 JavaScript 代码,这意味着即使有 32 核 CPU,也只会使用一个核心。幸运的是,Node.js 的 [`cluster` 模块](https://nodejs.org/api/cluster.html#cluster) 提供了一种方便的方法来为每个核心创建一个工作线程。 -We will also need a way to forward events between the Socket.IO servers. We call this component an "Adapter". +我们还需要一种方法在 Socket.IO 服务器之间转发事件。我们称这个组件为“适配器”。 -So let's install the cluster adapter: +现在安装集群适配器: @@ -58,7 +58,7 @@ pnpm add @socket.io/cluster-adapter -Now we plug it in: +现在我们将其接入: @@ -79,14 +79,14 @@ const { createAdapter, setupPrimary } = require('@socket.io/cluster-adapter'); if (cluster.isPrimary) { // highlight-start const numCPUs = availableParallelism(); - // create one worker per available core + // 为每个可用核心创建一个工作线程 for (let i = 0; i < numCPUs; i++) { cluster.fork({ PORT: 3000 + i }); } - // set up the adapter on the primary thread + // 在主线程上设置适配器 return setupPrimary(); // highlight-end } @@ -97,7 +97,7 @@ async function main() { const io = new Server(server, { connectionStateRecovery: {}, // highlight-start - // set up the adapter on each worker thread + // 在每个工作线程上设置适配器 adapter: createAdapter() // highlight-end }); @@ -105,7 +105,7 @@ async function main() { // [...] // highlight-start - // each worker will listen on a distinct port + // 每个工作线程将监听不同的端口 const port = process.env.PORT; server.listen(port, () => { @@ -135,14 +135,14 @@ import { createAdapter, setupPrimary } from '@socket.io/cluster-adapter'; if (cluster.isPrimary) { // highlight-start const numCPUs = availableParallelism(); - // create one worker per available core + // 为每个可用核心创建一个工作线程 for (let i = 0; i < numCPUs; i++) { cluster.fork({ PORT: 3000 + i }); } - // set up the adapter on the primary thread + // 在主线程上设置适配器 setupPrimary(); // highlight-end } else { @@ -151,7 +151,7 @@ if (cluster.isPrimary) { const io = new Server(server, { connectionStateRecovery: {}, // highlight-start - // set up the adapter on each worker thread + // 在每个工作线程上设置适配器 adapter: createAdapter() // highlight-end }); @@ -159,7 +159,7 @@ if (cluster.isPrimary) { // [...] // highlight-start - // each worker will listen on a distinct port + // 每个工作线程将监听不同的端口 const port = process.env.PORT; server.listen(port, () => { @@ -172,49 +172,49 @@ if (cluster.isPrimary) { -That's it! This will spawn one worker thread per CPU available on your machine. Let's see it in action: +完成了!这将在您的机器上为每个可用的 CPU 生成一个工作线程。让我们看看它的实际效果: -As you can see in the address bar, each browser tab is connected to a different Socket.IO server, and the adapter is simply forwarding the `chat message` events between them. +如您在地址栏中所见,每个浏览器标签页连接到不同的 Socket.IO 服务器,适配器只是简单地在它们之间转发 `chat message` 事件。 :::tip -There are currently 5 official adapter implementations: +目前有 5 种官方适配器实现: -- the [Redis adapter](../categories/05-Adapters/adapter-redis.md) -- the [Redis Streams adapter](../categories/05-Adapters/adapter-redis-streams.md) -- the [MongoDB adapter](../categories/05-Adapters/adapter-mongo.md) -- the [Postgres adapter](../categories/05-Adapters/adapter-postgres.md) -- the [Cluster adapter](../categories/05-Adapters/adapter-cluster.md) +- [Redis 适配器](../categories/05-Adapters/adapter-redis.md) +- [Redis Streams 适配器](../categories/05-Adapters/adapter-redis-streams.md) +- [MongoDB 适配器](../categories/05-Adapters/adapter-mongo.md) +- [Postgres 适配器](../categories/05-Adapters/adapter-postgres.md) +- [集群适配器](../categories/05-Adapters/adapter-cluster.md) -So you can choose the one that best suits your needs. However, please note that some implementations do not support the Connection state recovery feature, you can find the compatibility matrix [here](../categories/01-Documentation/connection-state-recovery.md#compatibility-with-existing-adapters). +您可以选择最适合您需求的那个。不过,请注意某些实现不支持连接状态恢复功能,您可以在[这里](../categories/01-Documentation/connection-state-recovery.md#compatibility-with-existing-adapters)找到兼容性矩阵。 ::: :::note -In most cases, you would also need to ensure that all the HTTP requests of a Socket.IO session reach the same server (also known as "sticky session"). This is not needed here though, as each Socket.IO server has its own port. +在大多数情况下,您还需要确保 Socket.IO 会话的所有 HTTP 请求都到达同一服务器(也称为“粘性会话”)。不过这里不需要这样,因为每个 Socket.IO 服务器都有自己的端口。 -More information [here](../categories/02-Server/using-multiple-nodes.md). +更多信息请查看[这里](../categories/02-Server/using-multiple-nodes.md)。 ::: -And that finally completes our chat application! In this tutorial, we have seen how to: +这就完成了我们的聊天应用程序!在本教程中,我们学习了如何: -- send an event between the client and the server -- broadcast an event to all or a subset of connected clients -- handle temporary disconnections -- scale up +- 在客户端和服务器之间发送事件 +- 向所有或部分连接的客户端广播事件 +- 处理临时断线 +- 扩展 -You should now have a better overview of the features provided by Socket.IO. Now it's your time to build your own realtime application! +您现在应该对 Socket.IO 提供的功能有更好的了解。现在是您构建自己的实时应用程序的时候了! :::info -You can run this example directly in your browser on: +您可以在浏览器中直接运行此示例: - [CodeSandbox](https://codesandbox.io/p/sandbox/github/socketio/chat-example/tree/cjs/step9?file=index.js) - [StackBlitz](https://stackblitz.com/github/socketio/chat-example/tree/cjs/step9?file=index.js) @@ -223,7 +223,7 @@ You can run this example directly in your browser on: -You can run this example directly in your browser on: +您可以在浏览器中直接运行此示例: - [CodeSandbox](https://codesandbox.io/p/sandbox/github/socketio/chat-example/tree/esm/step9?file=index.js) - [StackBlitz](https://stackblitz.com/github/socketio/chat-example/tree/esm/step9?file=index.js) @@ -233,4 +233,4 @@ You can run this example directly in your browser on: -::: +::: \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/13-ending-notes.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/13-ending-notes.md index 9ed74da6..fd98977c 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/13-ending-notes.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/13-ending-notes.md @@ -1,14 +1,14 @@ --- -title: Ending notes +title: 结束说明 slug: ending-notes --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Ending notes +# 结束说明 -## Final server code {#final-server-code} +## 最终服务器代码 @@ -194,7 +194,7 @@ if (cluster.isPrimary) { -## Final client code {#final-client-code} +## 最终客户端代码 @@ -327,33 +327,33 @@ if (cluster.isPrimary) { -## Homework {#homework} +## 作业 -Here are some ideas to improve the application: +以下是一些改进应用程序的想法: -- Broadcast a message to connected users when someone connects or disconnects. -- Add support for nicknames. -- Don’t send the same message to the user that sent it. Instead, append the message directly as soon as they press enter. -- Add “{user} is typing” functionality. -- Show who’s online. -- Add private messaging. -- Share your improvements! +- 当有人连接或断开连接时,向已连接用户广播消息。 +- 添加昵称支持。 +- 不要将相同的消息发送给发送它的用户,而是在他们按下回车时直接附加消息。 +- 添加“{用户} 正在输入”功能。 +- 显示在线用户。 +- 添加私人消息功能。 +- 分享你的改进! -## Getting this example {#getting-this-example} +## 获取此示例 -You can find it on GitHub [here](https://github.com/socketio/chat-example). +你可以在 GitHub 上找到它 [这里](https://github.com/socketio/chat-example)。 ``` git clone https://github.com/socketio/chat-example.git ``` -## Next steps {#next-steps} +## 下一步 -Please check out: +请查看: -- [our other examples](/get-started/) -- [our Troubleshooting guide](../categories/01-Documentation/troubleshooting.md) -- [the Emit cheatsheet](../emit-cheatsheet.md) -- [the complete Server API](../server-api.md) -- [the complete Client API](../client-api.md) -- the different sections of [our guide](../categories/01-Documentation/index.md) +- [我们的其他示例](/get-started/) +- [我们的故障排除指南](../categories/01-Documentation/troubleshooting.md) +- [Emit 速查表](../emit-cheatsheet.md) +- [完整的服务器 API](../server-api.md) +- [完整的客户端 API](../client-api.md) +- [我们的指南](../categories/01-Documentation/index.md) 的不同部分 \ No newline at end of file From 7a80bcf371392ab9d17852bb161824f9e011f152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=AA=E5=85=89=E5=90=AF?= Date: Mon, 9 Dec 2024 14:44:19 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=9C=AC=E5=9C=B0ai?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E7=BF=BB=E8=AF=91=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ignored/diff_file.py | 18 + .vscode/settings.json | 20 + .../08-Miscellaneous/eio-protocol.md | 370 +++++---- docusaurus.config.js | 76 +- i18n/zh-CN/code.json | 180 ++--- .../categories/01-Documentation/index.md | 86 +- .../05-Adapters/adapter-redis-streams.md | 56 +- .../08-Miscellaneous/eio-protocol.md | 374 +++++---- .../08-Miscellaneous/sio-protocol-1.md | 21 + .../08-Miscellaneous/sio-protocol-1.md.raw | 21 + .../08-Miscellaneous/sio-protocol.md.raw | 734 ++++++++++++++++++ .../docusaurus-theme-classic/footer.json | 38 +- .../docusaurus-theme-classic/navbar.json | 52 +- 13 files changed, 1418 insertions(+), 628 deletions(-) create mode 100644 .ignored/diff_file.py create mode 100644 .vscode/settings.json create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/08-Miscellaneous/sio-protocol-1.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/08-Miscellaneous/sio-protocol-1.md.raw create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/08-Miscellaneous/sio-protocol.md.raw diff --git a/.ignored/diff_file.py b/.ignored/diff_file.py new file mode 100644 index 00000000..b1481b87 --- /dev/null +++ b/.ignored/diff_file.py @@ -0,0 +1,18 @@ +# docs/tutorial/01-introduction.md +# i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/01-introduction.md +import glob +import hashlib +from os import chdir +from os.path import dirname + +chdir(dirname(__file__) + "/..") + +src_files = glob.glob('docs/tutorial/*.md') +dst_files = glob.glob('i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/*.md') + +for src_file in src_files: + dst_file = 'i18n/zh-CN/docusaurus-plugin-content-docs/current/tutorial/' + src_file.split('/')[-1] + src_md5 = hashlib.md5(open(src_file, 'rb').read()).hexdigest() + dst_md5 = hashlib.md5(open(dst_file, 'rb').read()).hexdigest() + if src_md5 != dst_md5: + print(src_file, dst_file) diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..29819fc5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,20 @@ +{ + "MicroPython.executeButton": [ + { + "text": "▶", + "tooltip": "Run", + "alignment": "left", + "command": "extension.executeFile", + "priority": 3.5 + } + ], + "MicroPython.syncButton": [ + { + "text": "$(sync)", + "tooltip": "sync", + "alignment": "left", + "command": "extension.execute", + "priority": 4 + } + ] +} \ No newline at end of file diff --git a/docs/categories/08-Miscellaneous/eio-protocol.md b/docs/categories/08-Miscellaneous/eio-protocol.md index ff8f9f6f..ff951368 100644 --- a/docs/categories/08-Miscellaneous/eio-protocol.md +++ b/docs/categories/08-Miscellaneous/eio-protocol.md @@ -1,101 +1,99 @@ --- -title: The Engine.IO protocol +title: Engine.IO 协议 sidebar_position: 3 slug: /engine-io-protocol/ --- -This document describes the 4th version of the Engine.IO protocol. +本文档描述了 Engine.IO 协议的第 4 版。 -The source of this document can be found [here](https://github.com/socketio/engine.io-protocol). +本文档的源代码可以在[这里](https://github.com/socketio/engine.io-protocol)找到。 -**Table of content** +**目录** -- [Introduction](#introduction) -- [Transports](#transports) - - [HTTP long-polling](#http-long-polling) - - [Request path](#request-path) - - [Query parameters](#query-parameters) - - [Headers](#headers) - - [Sending and receiving data](#sending-and-receiving-data) - - [Sending data](#sending-data) - - [Receiving data](#receiving-data) +- [介绍](#introduction) +- [传输](#transports) + - [HTTP 长轮询](#http-long-polling) + - [请求路径](#request-path) + - [查询参数](#query-parameters) + - [头信息](#headers) + - [发送和接收数据](#sending-and-receiving-data) + - [发送数据](#sending-data) + - [接收数据](#receiving-data) - [WebSocket](#websocket) -- [Protocol](#protocol) - - [Handshake](#handshake) - - [Heartbeat](#heartbeat) - - [Upgrade](#upgrade) - - [Message](#message) -- [Packet encoding](#packet-encoding) - - [HTTP long-polling](#http-long-polling-1) +- [协议](#protocol) + - [握手](#handshake) + - [心跳](#heartbeat) + - [升级](#upgrade) + - [消息](#message) +- [数据包编码](#packet-encoding) + - [HTTP 长轮询](#http-long-polling-1) - [WebSocket](#websocket-1) -- [History](#history) - - [From v2 to v3](#from-v2-to-v3) - - [From v3 to v4](#from-v3-to-v4) -- [Test suite](#test-suite) +- [历史](#history) + - [从 v2 到 v3](#from-v2-to-v3) + - [从 v3 到 v4](#from-v3-to-v4) +- [测试套件](#test-suite) +## 介绍 +Engine.IO 协议实现了客户端和服务器之间的[全双工](https://en.wikipedia.org/wiki/Duplex_(telecommunications)#FULL-DUPLEX)和低开销通信。 -## Introduction +它基于[WebSocket 协议](https://en.wikipedia.org/wiki/WebSocket),并在无法建立 WebSocket 连接时使用[HTTP 长轮询](https://en.wikipedia.org/wiki/Push_technology#Long_polling)作为后备。 -The Engine.IO protocol enables [full-duplex](https://en.wikipedia.org/wiki/Duplex_(telecommunications)#FULL-DUPLEX) and low-overhead communication between a client and a server. +参考实现是用[TypeScript](https://www.typescriptlang.org/)编写的: -It is based on the [WebSocket protocol](https://en.wikipedia.org/wiki/WebSocket) and uses [HTTP long-polling](https://en.wikipedia.org/wiki/Push_technology#Long_polling) as fallback if the WebSocket connection can't be established. +- 服务器: https://github.com/socketio/engine.io +- 客户端: https://github.com/socketio/engine.io-client -The reference implementation is written in [TypeScript](https://www.typescriptlang.org/): +[Socket.IO 协议](https://github.com/socketio/socket.io-protocol)建立在这些基础之上,在 Engine.IO 协议提供的通信通道上增加了额外的功能。 -- server: https://github.com/socketio/engine.io -- client: https://github.com/socketio/engine.io-client +## 传输 -The [Socket.IO protocol](https://github.com/socketio/socket.io-protocol) is built on top of these foundations, bringing additional features over the communication channel provided by the Engine.IO protocol. +Engine.IO 客户端和 Engine.IO 服务器之间的连接可以通过以下方式建立: -## Transports - -The connection between an Engine.IO client and an Engine.IO server can be established with: - -- [HTTP long-polling](#http-long-polling) +- [HTTP 长轮询](#http-long-polling) - [WebSocket](#websocket) -### HTTP long-polling +### HTTP 长轮询 -The HTTP long-polling transport (also simply referred as "polling") consists of successive HTTP requests: +HTTP 长轮询传输(也简称为“轮询”)由连续的 HTTP 请求组成: -- long-running `GET` requests, for receiving data from the server -- short-running `POST` requests, for sending data to the server +- 长时间运行的 `GET` 请求,用于从服务器接收数据 +- 短时间运行的 `POST` 请求,用于向服务器发送数据 -#### Request path +#### 请求路径 -The path of the HTTP requests is `/engine.io/` by default. +HTTP 请求的路径默认是 `/engine.io/`。 -It might be updated by libraries built on top of the protocol (for example, the Socket.IO protocol uses `/socket.io/`). +它可能会被基于该协议构建的库更新(例如,Socket.IO 协议使用 `/socket.io/`)。 -#### Query parameters +#### 查询参数 -The following query parameters are used: +使用以下查询参数: -| Name | Value | Description | +| 名称 | 值 | 描述 | |-------------|-----------|--------------------------------------------------------------------| -| `EIO` | `4` | Mandatory, the version of the protocol. | -| `transport` | `polling` | Mandatory, the name of the transport. | -| `sid` | `` | Mandatory once the session is established, the session identifier. | +| `EIO` | `4` | 必须,协议的版本。 | +| `transport` | `polling` | 必须,传输的名称。 | +| `sid` | `` | 一旦会话建立,必须,表示会话标识符。 | -If a mandatory query parameter is missing, then the server MUST respond with an HTTP 400 error status. +如果缺少必需的查询参数,服务器必须响应 HTTP 400 错误状态。 -#### Headers +#### 头信息 -When sending binary data, the sender (client or server) MUST include a `Content-Type: application/octet-stream` header. +发送二进制数据时,发送方(客户端或服务器)必须包含 `Content-Type: application/octet-stream` 头。 -Without an explicit `Content-Type` header, the receiver SHOULD infer that the data is plaintext. +如果没有明确的 `Content-Type` 头,接收方应推断数据是纯文本。 -Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type +参考: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type -#### Sending and receiving data +#### 发送和接收数据 -##### Sending data +##### 发送数据 -To send some packets, a client MUST create an HTTP `POST` request with the packets encoded in the request body: +要发送一些数据包,客户端必须创建一个 HTTP `POST` 请求,并在请求体中编码数据包: ``` -CLIENT SERVER +客户端 服务器 │ │ │ POST /engine.io/?EIO=4&transport=polling&sid=... │ @@ -105,18 +103,18 @@ CLIENT SERVER │ │ ``` -The server MUST return an HTTP 400 response if the session ID (from the `sid` query parameter) is not known. +如果会话 ID(来自 `sid` 查询参数)未知,服务器必须返回 HTTP 400 响应。 -To indicate success, the server MUST return an HTTP 200 response, with the string `ok` in the response body. +为了表示成功,服务器必须返回 HTTP 200 响应,并在响应体中包含字符串 `ok`。 -To ensure packet ordering, a client MUST NOT have more than one active `POST` request. Should it happen, the server MUST return an HTTP 400 error status and close the session. +为了确保数据包的顺序,客户端不得有多个活动的 `POST` 请求。如果发生这种情况,服务器必须返回 HTTP 400 错误状态并关闭会话。 -##### Receiving data +##### 接收数据 -To receive some packets, a client MUST create an HTTP `GET` request: +要接收一些数据包,客户端必须创建一个 HTTP `GET` 请求: ``` -CLIENT SERVER +客户端 服务器 │ GET /engine.io/?EIO=4&transport=polling&sid=... │ │ ──────────────────────────────────────────────────► │ @@ -128,57 +126,57 @@ CLIENT SERVER │ HTTP 200 │ ``` -The server MUST return an HTTP 400 response if the session ID (from the `sid` query parameter) is not known. +如果会话 ID(来自 `sid` 查询参数)未知,服务器必须返回 HTTP 400 响应。 -The server MAY not respond right away if there are no packets buffered for the given session. Once there are some packets to be sent, the server SHOULD encode them (see [Packet encoding](#packet-encoding)) and send them in the response body of the HTTP request. +如果没有缓冲的包要发送,服务器可能不会立即响应。一旦有一些包要发送,服务器应对其进行编码(参见[数据包编码](#packet-encoding))并在 HTTP 请求的响应体中发送它们。 -To ensure packet ordering, a client MUST NOT have more than one active `GET` request. Should it happen, the server MUST return an HTTP 400 error status and close the session. +为了确保数据包的顺序,客户端不得有多个活动的 `GET` 请求。如果发生这种情况,服务器必须返回 HTTP 400 错误状态并关闭会话。 ### WebSocket -The WebSocket transport consists of a [WebSocket connection](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API), which provides a bidirectional and low-latency communication channel between the server and the client. +WebSocket 传输由一个[WebSocket 连接](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)组成,它在服务器和客户端之间提供了一个双向和低延迟的通信通道。 -The following query parameters are used: +使用以下查询参数: -| Name | Value | Description | +| 名称 | 值 | 描述 | |-------------|-------------|-------------------------------------------------------------------------------| -| `EIO` | `4` | Mandatory, the version of the protocol. | -| `transport` | `websocket` | Mandatory, the name of the transport. | -| `sid` | `` | Optional, depending on whether it's an upgrade from HTTP long-polling or not. | +| `EIO` | `4` | 必须,协议的版本。 | +| `transport` | `websocket` | 必须,传输的名称。 | +| `sid` | `` | 可选,取决于是否从 HTTP 长轮询升级。 | -If a mandatory query parameter is missing, then the server MUST close the WebSocket connection. +如果缺少必需的查询参数,服务器必须关闭 WebSocket 连接。 -Each packet (read or write) is sent its own [WebSocket frame](https://datatracker.ietf.org/doc/html/rfc6455#section-5). +每个数据包(读或写)都在自己的[WebSocket 帧](https://datatracker.ietf.org/doc/html/rfc6455#section-5)中发送。 -A client MUST NOT open more than one WebSocket connection per session. Should it happen, the server MUST close the WebSocket connection. +客户端不得为每个会话打开多个 WebSocket 连接。如果发生这种情况,服务器必须关闭 WebSocket 连接。 -## Protocol +## 协议 -An Engine.IO packet consists of: +Engine.IO 数据包由以下部分组成: -- a packet type -- an optional packet payload +- 数据包类型 +- 可选的数据包负载 -Here is the list of available packet types: +以下是可用的数据包类型列表: -| Type | ID | Usage | +| 类型 | ID | 用途 | |---------|-----|--------------------------------------------------| -| open | 0 | Used during the [handshake](#handshake). | -| close | 1 | Used to indicate that a transport can be closed. | -| ping | 2 | Used in the [heartbeat mechanism](#heartbeat). | -| pong | 3 | Used in the [heartbeat mechanism](#heartbeat). | -| message | 4 | Used to send a payload to the other side. | -| upgrade | 5 | Used during the [upgrade process](#upgrade). | -| noop | 6 | Used during the [upgrade process](#upgrade). | +| open | 0 | 用于[握手](#handshake)期间。 | +| close | 1 | 用于指示传输可以关闭。 | +| ping | 2 | 用于[心跳机制](#heartbeat)。 | +| pong | 3 | 用于[心跳机制](#heartbeat)。 | +| message | 4 | 用于向另一方发送负载。 | +| upgrade | 5 | 用于[升级过程](#upgrade)。 | +| noop | 6 | 用于[升级过程](#upgrade)。 | -### Handshake +### 握手 -To establish a connection, the client MUST send an HTTP `GET` request to the server: +要建立连接,客户端必须向服务器发送 HTTP `GET` 请求: -- HTTP long-polling first (by default) +- 首先是 HTTP 长轮询(默认) ``` -CLIENT SERVER +客户端 服务器 │ │ │ GET /engine.io/?EIO=4&transport=polling │ @@ -188,10 +186,10 @@ CLIENT SERVER │ │ ``` -- WebSocket-only session +- 仅 WebSocket 会话 ``` -CLIENT SERVER +客户端 服务器 │ │ │ GET /engine.io/?EIO=4&transport=websocket │ @@ -201,17 +199,17 @@ CLIENT SERVER │ │ ``` -If the server accepts the connection, then it MUST respond with an `open` packet with the following JSON-encoded payload: +如果服务器接受连接,则必须响应一个带有以下 JSON 编码负载的 `open` 数据包: -| Key | Type | Description | +| 键 | 类型 | 描述 | |----------------|------------|-------------------------------------------------------------------------------------------------------------------| -| `sid` | `string` | The session ID. | -| `upgrades` | `string[]` | The list of available [transport upgrades](#upgrade). | -| `pingInterval` | `number` | The ping interval, used in the [heartbeat mechanism](#heartbeat) (in milliseconds). | -| `pingTimeout` | `number` | The ping timeout, used in the [heartbeat mechanism](#heartbeat) (in milliseconds). | -| `maxPayload` | `number` | The maximum number of bytes per chunk, used by the client to aggregate packets into [payloads](#packet-encoding). | +| `sid` | `string` | 会话 ID。 | +| `upgrades` | `string[]` | 可用的[传输升级](#upgrade)列表。 | +| `pingInterval` | `number` | 用于[心跳机制](#heartbeat)的 ping 间隔(以毫秒为单位)。 | +| `pingTimeout` | `number` | 用于[心跳机制](#heartbeat)的 ping 超时(以毫秒为单位)。 | +| `maxPayload` | `number` | 每个块的最大字节数,客户端用于将数据包聚合到[负载](#packet-encoding)中。 | -Example: +示例: ```json { @@ -223,188 +221,184 @@ Example: } ``` -The client MUST send the `sid` value in the query parameters of all subsequent requests. +客户端必须在所有后续请求的查询参数中发送 `sid` 值。 -### Heartbeat +### 心跳 -Once the [handshake](#handshake) is completed, a heartbeat mechanism is started to check the liveness of the connection: +一旦[握手](#handshake)完成,启动一个心跳机制来检查连接的活跃性: ``` -CLIENT SERVER +客户端 服务器 - │ *** Handshake *** │ + │ *** 握手 *** │ │ │ │ ◄───────────────────────────────────────────────── │ - │ 2 │ (ping packet) + │ 2 │ (ping 数据包) │ ─────────────────────────────────────────────────► │ - │ 3 │ (pong packet) + │ 3 │ (pong 数据包) ``` -At a given interval (the `pingInterval` value sent in the handshake) the server sends a `ping` packet and the client has a few seconds (the `pingTimeout` value) to send a `pong` packet back. +在给定的间隔(握手中发送的 `pingInterval` 值)内,服务器发送一个 `ping` 数据包,客户端有几秒钟(`pingTimeout` 值)来发送一个 `pong` 数据包。 -If the server does not receive a `pong` packet back, then it SHOULD consider that the connection is closed. +如果服务器没有收到 `pong` 数据包,则应认为连接已关闭。 -Conversely, if the client does not receive a `ping` packet within `pingInterval + pingTimeout`, then it SHOULD consider that the connection is closed. +相反,如果客户端在 `pingInterval + pingTimeout` 内没有收到 `ping` 数据包,则应认为连接已关闭。 -### Upgrade +### 升级 -By default, the client SHOULD create an HTTP long-polling connection, and then upgrade to better transports if available. +默认情况下,客户端应创建一个 HTTP 长轮询连接,然后升级到更好的传输(如果可用)。 -To upgrade to WebSocket, the client MUST: +要升级到 WebSocket,客户端必须: -- pause the HTTP long-polling transport (no more HTTP request gets sent), to ensure that no packet gets lost -- open a WebSocket connection with the same session ID -- send a `ping` packet with the string `probe` in the payload +- 暂停 HTTP 长轮询传输(不再发送 HTTP 请求),以确保没有数据包丢失 +- 使用相同的会话 ID 打开一个 WebSocket 连接 +- 发送一个带有字符串 `probe` 的 `ping` 数据包 -The server MUST: +服务器必须: -- send a `noop` packet to any pending `GET` request (if applicable) to cleanly close HTTP long-polling transport -- respond with a `pong` packet with the string `probe` in the payload +- 向任何挂起的 `GET` 请求发送一个 `noop` 数据包(如果适用),以干净地关闭 HTTP 长轮询传输 +- 响应一个带有字符串 `probe` 的 `pong` 数据包 -Finally, the client MUST send a `upgrade` packet to complete the upgrade: +最后,客户端必须发送一个 `upgrade` 数据包以完成升级: ``` -CLIENT SERVER +客户端 服务器 │ │ │ GET /engine.io/?EIO=4&transport=websocket&sid=... │ │ ───────────────────────────────────────────────────► │ │ ◄─────────────────────────────────────────────────┘ │ - │ HTTP 101 (WebSocket handshake) │ + │ HTTP 101 (WebSocket 握手) │ │ │ - │ ----- WebSocket frames ----- │ + │ ----- WebSocket 帧 ----- │ │ ─────────────────────────────────────────────────► │ - │ 2probe │ (ping packet) + │ 2probe │ (ping 数据包) │ ◄───────────────────────────────────────────────── │ - │ 3probe │ (pong packet) + │ 3probe │ (pong 数据包) │ ─────────────────────────────────────────────────► │ - │ 5 │ (upgrade packet) + │ 5 │ (upgrade 数据包) │ │ ``` -### Message - -Once the [handshake](#handshake) is completed, the client and the server can exchange data by including it in a `message` packet. +### 消息 +一旦[握手](#handshake)完成,客户端和服务器可以通过在 `message` 数据包中包含数据来交换数据。 -## Packet encoding +## 数据包编码 -The serialization of an Engine.IO packet depends on the type of the payload (plaintext or binary) and on the transport. +Engine.IO 数据包的序列化取决于负载的类型(纯文本或二进制)和传输方式。 -### HTTP long-polling +### HTTP 长轮询 -Due to the nature of the HTTP long-polling transport, multiple packets might be concatenated in a single payload in order to increase throughput. +由于 HTTP 长轮询传输的性质,多个数据包可能会被连接到一个负载中以增加吞吐量。 -Format: +格式: ``` [][][][...] ``` -Example: +示例: ``` 4hello\x1e2\x1e4world -with: +其中: -4 => message packet type -hello => message payload -\x1e => separator -2 => ping packet type -\x1e => separator -4 => message packet type -world => message payload +4 => 消息数据包类型 +hello => 消息负载 +\x1e => 分隔符 +2 => ping 数据包类型 +\x1e => 分隔符 +4 => 消息数据包类型 +world => 消息负载 ``` -The packets are separated by the [record separator character](https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Field_separators): `\x1e` +数据包由[记录分隔符字符](https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Field_separators)分隔:`\x1e` -Binary payloads MUST be base64-encoded and prefixed with a `b` character: +二进制负载必须进行 base64 编码并以 `b` 字符为前缀: -Example: +示例: ``` 4hello\x1ebAQIDBA== -with: +其中: -4 => message packet type -hello => message payload -\x1e => separator -b => binary prefix -AQIDBA== => buffer <01 02 03 04> encoded as base64 +4 => 消息数据包类型 +hello => 消息负载 +\x1e => 分隔符 +b => 二进制前缀 +AQIDBA== => 缓冲区 <01 02 03 04> 编码为 base64 ``` -The client SHOULD use the `maxPayload` value sent during the [handshake](#handshake) to decide how many packets should be concatenated. +客户端应使用握手期间发送的 `maxPayload` 值来决定应连接多少数据包。 ### WebSocket -Each Engine.IO packet is sent in its own [WebSocket frame](https://datatracker.ietf.org/doc/html/rfc6455#section-5). +每个 Engine.IO 数据包都在自己的[WebSocket 帧](https://datatracker.ietf.org/doc/html/rfc6455#section-5)中发送。 -Format: +格式: ``` [] ``` -Example: +示例: ``` 4hello -with: +其中: -4 => message packet type -hello => message payload (UTF-8 encoded) +4 => 消息数据包类型 +hello => 消息负载(UTF-8 编码) ``` -Binary payloads are sent as is, without modification. +二进制负载按原样发送,不做修改。 -## History +## 历史 -### From v2 to v3 +### 从 v2 到 v3 -- add support for binary data +- 增加对二进制数据的支持 -The [2nd version](https://github.com/socketio/engine.io-protocol/tree/v2) of the protocol is used in Socket.IO `v0.9` and below. +协议的[第 2 版](https://github.com/socketio/engine.io-protocol/tree/v2)用于 Socket.IO `v0.9` 及以下版本。 -The [3rd version](https://github.com/socketio/engine.io-protocol/tree/v3) of the protocol is used in Socket.IO `v1` and `v2`. +协议的[第 3 版](https://github.com/socketio/engine.io-protocol/tree/v3)用于 Socket.IO `v1` 和 `v2`。 -### From v3 to v4 +### 从 v3 到 v4 -- reverse ping/pong mechanism +- 反转 ping/pong 机制 -The ping packets are now sent by the server, because the timers set in the browsers are not reliable enough. We -suspect that a lot of timeout problems came from timers being delayed on the client-side. +现在由服务器发送 ping 数据包,因为浏览器中设置的计时器不够可靠。我们怀疑很多超时问题是由于客户端的计时器被延迟引起的。 -- always use base64 when encoding a payload with binary data +- 在编码带有二进制数据的负载时始终使用 base64 -This change allows to treat all payloads (with or without binary) the same way, without having to take in account -whether the client or the current transport supports binary data or not. +此更改允许以相同的方式处理所有负载(无论是否带有二进制),而无需考虑客户端或当前传输是否支持二进制数据。 -Please note that this only applies to HTTP long-polling. Binary data is sent in WebSocket frames with no additional transformation. +请注意,这仅适用于 HTTP 长轮询。二进制数据在 WebSocket 帧中发送,无需额外转换。 -- use a record separator (`\x1e`) instead of counting of characters +- 使用记录分隔符(`\x1e`)而不是字符计数 -Counting characters prevented (or at least makes harder) to implement the protocol in other languages, which may not use -the UTF-16 encoding. +字符计数使得在其他语言中实现协议变得更加困难,或者至少更难,因为这些语言可能不使用 UTF-16 编码。 -For example, `€` was encoded to `2:4€`, though `Buffer.byteLength('€') === 3`. +例如,`€` 被编码为 `2:4€`,尽管 `Buffer.byteLength('€') === 3`。 -Note: this assumes the record separator is not used in the data. +注意:这假设记录分隔符不用于数据中。 -The 4th version (current) is included in Socket.IO `v3` and above. +第 4 版(当前)包含在 Socket.IO `v3` 及以上版本中。 -## Test suite +## 测试套件 -The test suite in the [`test-suite/`](https://github.com/socketio/engine.io-protocol/tree/main/test-suite) directory lets you check the compliance of a server implementation. +[`test-suite/`](https://github.com/socketio/engine.io-protocol/tree/main/test-suite)目录中的测试套件让您可以检查服务器实现的合规性。 -Usage: +用法: -- in Node.js: `npm ci && npm test` -- in a browser: simply open the `index.html` file in your browser +- 在 Node.js 中:`npm ci && npm test` +- 在浏览器中:只需在浏览器中打开 `index.html` 文件 -For reference, here is expected configuration for the JavaScript server to pass all tests: +作为参考,以下是 JavaScript 服务器通过所有测试的预期配置: ```js import { listen } from "engine.io"; diff --git a/docusaurus.config.js b/docusaurus.config.js index 66d6a955..4f2f2a08 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -3,9 +3,9 @@ const darkCodeTheme = require("prism-react-renderer/themes/dracula"); const helpItems = [ { - // using 'type: "doc"' makes the link active whenever the user is on a page from the "/docs" directory - // see: https://github.com/facebook/docusaurus/issues/8018 - label: "Troubleshooting", + // 使用 'type: "doc"' 使链接在用户位于 "/docs" 目录中的页面时处于活动状态 + // 参见: https://github.com/facebook/docusaurus/issues/8018 + label: "故障排除", to: "/docs/v4/troubleshooting-connection-issues/", }, { @@ -13,7 +13,7 @@ const helpItems = [ href: "https://stackoverflow.com/questions/tagged/socket.io", }, { - label: "GitHub Discussions", + label: "GitHub 讨论", href: "https://github.com/socketio/socket.io/discussions", }, { @@ -28,14 +28,14 @@ const toolsItems = [ href: "https://cdn.socket.io", }, { - label: "Admin UI", + label: "管理界面", href: "https://admin.socket.io", }, ]; const newsItems = [ { - label: "Blog", + label: "博客", to: "/blog", }, { @@ -46,19 +46,19 @@ const newsItems = [ const aboutItems = [ { - label: "FAQ", + label: "常见问题", to: "/docs/v4/faq/", }, { - label: "Changelog", + label: "更新日志", to: "/docs/v4/changelog/", }, { - label: "Roadmap", + label: "路线图", href: "https://github.com/orgs/socketio/projects/3", }, { - label: "Become a sponsor", + label: "成为赞助商", href: "https://opencollective.com/socketio", }, ]; @@ -66,7 +66,7 @@ const aboutItems = [ /** @type {import('@docusaurus/types').DocusaurusConfig} */ module.exports = { title: "Socket.IO", - tagline: "Dinosaurs are cool", + tagline: "恐龙很酷", url: "https://socket.io", baseUrl: "/", onBrokenLinks: "throw", @@ -79,33 +79,33 @@ module.exports = { title: "Socket.IO", hideOnScroll: true, logo: { - alt: "Socket.IO logo", + alt: "Socket.IO 标志", src: "images/logo.svg", srcDark: "images/logo-dark.svg", }, items: [ { type: "dropdown", - label: "Docs", + label: "文档", position: "left", items: [ { type: "doc", - label: "Guide", + label: "指南", docId: "categories/Documentation/index", }, { type: "doc", - label: "Tutorial", + label: "教程", docId: "tutorial/introduction", }, { - label: "Examples", + label: "示例", to: "/get-started/", }, { type: "doc", - label: "Emit cheatsheet", + label: "Emit 速查表", docId: "emit-cheatsheet" } ] @@ -114,23 +114,23 @@ module.exports = { type: "doc", docId: "server-api", position: "left", - label: "Server API", + label: "服务器 API", }, { type: "doc", docId: "client-api", position: "left", - label: "Client API", + label: "客户端 API", }, { type: "dropdown", - label: "Ecosystem", + label: "生态系统", position: "left", items: [ { type: "html", className: "dropdown-category", - value: "Help", + value: "帮助", }, ...helpItems, { @@ -140,7 +140,7 @@ module.exports = { { type: "html", className: "dropdown-category", - value: "News", + value: "新闻", }, ...newsItems, { @@ -150,14 +150,14 @@ module.exports = { { type: "html", className: "dropdown-category", - value: "Tools", + value: "工具", }, ...toolsItems, ], }, { type: "dropdown", - label: "About", + label: "关于", position: "left", items: aboutItems, }, @@ -170,7 +170,7 @@ module.exports = { value: '', }, { - label: "Changelog", + label: "更新日志", to: "/docs/v4/changelog/", activeBaseRegex: 'never', } @@ -191,48 +191,48 @@ module.exports = { style: "dark", links: [ { - title: "Documentation", + title: "文档", items: [ { - label: "Guide", + label: "指南", to: "/docs/v4/", }, { - label: "Tutorial", + label: "教程", to: "/docs/v4/tutorial/introduction", }, { - label: "Examples", + label: "示例", to: "/get-started/", }, { - label: "Server API", + label: "服务器 API", to: "/docs/v4/server-api/", }, { - label: "Client API", + label: "客户端 API", to: "/docs/v4/client-api/", }, ], }, { - title: "Help", + title: "帮助", items: helpItems, }, { - title: "News", + title: "新闻", items: newsItems, }, { - title: "Tools", + title: "工具", items: toolsItems, }, { - title: "About", + title: "关于", items: aboutItems, }, ], - copyright: `Copyright © ${new Date().getFullYear()} Socket.IO`, + copyright: `版权所有 © ${new Date().getFullYear()} Socket.IO`, }, colorMode: { defaultMode: 'light', @@ -250,7 +250,7 @@ module.exports = { }, announcementBar: { content: - 'Latest blog post (July 25, 2024): npm package provenance.', + '最新博客文章 (2024年7月25日): npm 包来源.', backgroundColor: "#25c2a0", isCloseable: true, }, @@ -303,7 +303,7 @@ module.exports = { ], ], i18n: { - defaultLocale: "en", + defaultLocale: "zh-CN", locales: ["en", "fr", "pt-br", "zh-CN"], } }; diff --git a/i18n/zh-CN/code.json b/i18n/zh-CN/code.json index d857f394..77e8333e 100644 --- a/i18n/zh-CN/code.json +++ b/i18n/zh-CN/code.json @@ -1,207 +1,207 @@ { "theme.NotFound.title": { "message": "找不到页面", - "description": "The title of the 404 page" + "description": "404页面的标题" }, "theme.NotFound.p1": { "message": "找不到内容", - "description": "The first paragraph of the 404 page" + "description": "404页面的第一段" }, "theme.NotFound.p2": { "message": "请与链接到原始URL的网站所有者联系,并让他们知道他们的链接已断开。", - "description": "The 2nd paragraph of the 404 page" + "description": "404页面的第二段" }, "theme.AnnouncementBar.closeButtonAriaLabel": { "message": "关闭", - "description": "The ARIA label for close button of announcement bar" + "description": "公告栏关闭按钮的ARIA标签" }, "theme.blog.archive.title": { "message": "存档", - "description": "The page & hero title of the blog archive page" + "description": "博客存档页面的标题" }, "theme.blog.archive.description": { "message": "存档", - "description": "The page & hero description of the blog archive page" + "description": "博客存档页面的描述" }, "theme.blog.paginator.navAriaLabel": { "message": "博客文章列表分页", - "description": "The ARIA label for the blog pagination" + "description": "博客分页的ARIA标签" }, "theme.blog.paginator.newerEntries": { "message": "上一页", - "description": "The label used to navigate to the newer blog posts page (previous page)" + "description": "导航到较新博客文章页面的标签(上一页)" }, "theme.blog.paginator.olderEntries": { "message": "下一页", - "description": "The label used to navigate to the older blog posts page (next page)" + "description": "导航到较旧博客文章页面的标签(下一页)" }, "theme.blog.post.readingTime.plurals": { - "message": "Une minute de lecture|{readingTime} minutes de lecture", - "description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + "message": "阅读时间|阅读时间{count}分钟", + "description": "“阅读时间”的复数形式标签" }, "theme.blog.post.readMore": { "message": "阅读更多", - "description": "The label used in blog post item excerpts to link to full blog posts" + "description": "阅读更多按钮的标签" }, "theme.blog.post.paginator.navAriaLabel": { "message": "博客文章分页", - "description": "The ARIA label for the blog posts pagination" + "description": "博客文章分页的ARIA标签" }, "theme.blog.post.paginator.newerPost": { - "message": "上一篇", - "description": "The blog post button label to navigate to the newer/previous post" + "message": "较新的文章", + "description": "导航到较新博客文章的标签" }, "theme.blog.post.paginator.olderPost": { - "message": "下一篇", - "description": "The blog post button label to navigate to the older/next post" + "message": "较旧的文章", + "description": "导航到较旧博客文章的标签" }, "theme.blog.sidebar.navAriaLabel": { - "message": "博客最近的帖子浏览", - "description": "The ARIA label for recent posts in the blog sidebar" + "message": "博客侧边栏", + "description": "博客侧边栏的ARIA标签" }, "theme.blog.post.plurals": { - "message": "1 篇文章|{count} 篇文章", - "description": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + "message": "文章|{count}篇文章", + "description": "“文章”的复数形式标签" }, "theme.blog.tagTitle": { - "message": "{nPosts} 标记为 « {tagName} »", - "description": "The title of the page for a blog tag" + "message": "标签:{tagName}", + "description": "博客标签页面的标题" }, "theme.tags.tagsPageLink": { "message": "查看所有标签", - "description": "The label of the link targeting the tag list page" + "description": "查看所有标签的链接" }, "theme.CodeBlock.copyButtonAriaLabel": { "message": "复制代码", - "description": "The ARIA label for copy code blocks button" + "description": "代码块复制按钮的ARIA标签" }, "theme.CodeBlock.copied": { - "message": "复制成功", - "description": "The copied button label on code blocks" + "message": "已复制", + "description": "代码块复制成功后的提示" }, "theme.CodeBlock.copy": { "message": "复制", - "description": "The copy button label on code blocks" + "description": "代码块复制按钮的标签" }, "theme.docs.sidebar.expandButtonTitle": { - "message": "展开侧菜单", - "description": "The ARIA label and title attribute for expand button of doc sidebar" + "message": "展开侧边栏", + "description": "展开文档侧边栏按钮的标题" }, "theme.docs.sidebar.expandButtonAriaLabel": { - "message": "展开侧菜单", - "description": "The ARIA label and title attribute for expand button of doc sidebar" + "message": "展开侧边栏", + "description": "展开文档侧边栏按钮的ARIA标签" }, "theme.docs.paginator.navAriaLabel": { - "message": "文件分页", - "description": "The ARIA label for the docs pagination" + "message": "文档分页", + "description": "文档分页的ARIA标签" }, "theme.docs.paginator.previous": { "message": "上一页", - "description": "The label used to navigate to the previous doc" + "description": "文档分页的上一页标签" }, "theme.docs.paginator.next": { "message": "下一页", - "description": "The label used to navigate to the next doc" + "description": "文档分页的下一页标签" }, "theme.docs.sidebar.collapseButtonTitle": { - "message": "关闭侧菜单", - "description": "The title attribute for collapse button of doc sidebar" + "message": "折叠侧边栏", + "description": "折叠文档侧边栏按钮的标题" }, "theme.docs.sidebar.collapseButtonAriaLabel": { - "message": "关闭侧菜单", - "description": "The title attribute for collapse button of doc sidebar" + "message": "折叠侧边栏", + "description": "折叠文档侧边栏按钮的ARIA标签" }, "theme.docs.tagDocListPageTitle.nDocsTagged": { - "message": "1 篇标记文档|{count} 篇标记文档", - "description": "Pluralized label for \"{count} docs tagged\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + "message": "{count}篇文档被标记为“{tagName}”", + "description": "显示被标记为特定标签的文档数量" }, "theme.docs.tagDocListPageTitle": { - "message": "{nDocsTagged} 和 \"{tagName}\"", - "description": "The title of the page for a docs tag" + "message": "标签:{tagName}", + "description": "文档标签页面的标题" }, "theme.common.editThisPage": { "message": "编辑此页", - "description": "The link label to edit the current page" + "description": "编辑当前页面的链接标签" }, "theme.docs.versions.unreleasedVersionLabel": { - "message": "这是下一版本的文档 {versionLabel} : {siteTitle}.", - "description": "The label used to tell the user that he's browsing an unreleased doc version" + "message": "这是{siteTitle}的{versionLabel}版本文档。", + "description": "告知用户正在浏览未发布的文档版本" }, "theme.docs.versions.unmaintainedVersionLabel": { - "message": "这是 {siteTitle} {versionLabel}, 不再积极维护。", - "description": "The label used to tell the user that he's browsing an unmaintained doc version" + "message": "这是{siteTitle}的{versionLabel}版本,不再积极维护。", + "description": "告知用户正在浏览不再维护的文档版本" }, "theme.docs.versions.latestVersionSuggestionLabel": { - "message": "有关最新文档,请参阅{latestVersionLink} ({versionLabel}).", - "description": "The label used to tell the user to check the latest version" + "message": "有关最新文档,请参阅{latestVersionLink}({versionLabel})。", + "description": "建议用户查看最新版本的文档" }, "theme.docs.versions.latestVersionLinkLabel": { "message": "最新版本", - "description": "The label used for the latest version suggestion link label" + "description": "最新版本建议链接的标签" }, "theme.common.headingLinkTitle": { - "message": "置顶到此处", - "description": "Title for link to heading" + "message": "链接到此标题", + "description": "链接到标题的标题" }, "theme.lastUpdated.atDate": { - "message": " 在 {date}", - "description": "The words used to describe on which date a page has been last updated" + "message": "于{date}", + "description": "描述页面最后更新时间的文字" }, "theme.lastUpdated.byUser": { - "message": " 执行人: {user}", - "description": "The words used to describe by who the page has been last updated" + "message": "由{user}更新", + "description": "描述页面最后更新者的文字" }, "theme.lastUpdated.lastUpdatedAtBy": { - "message": "上次更新时间 {atDate}{byUser}", - "description": "The sentence used to display when a page has been last updated, and by who" + "message": "最后更新于{atDate},由{byUser}", + "description": "显示页面最后更新时间和更新者的句子" }, "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": { "message": "← 返回主菜单", - "description": "The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)" + "description": "移动端导航栏侧边栏二级菜单返回主菜单按钮的标签" }, "theme.navbar.mobileVersionsDropdown.label": { "message": "版本", - "description": "The label for the navbar versions dropdown on mobile view" + "description": "移动端导航栏版本下拉菜单的标签" }, "theme.common.skipToMainContent": { - "message": "转到主要内容", - "description": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation" + "message": "跳到主要内容", + "description": "跳到内容的标签,用于无障碍访问,允许通过键盘快速导航到主要内容" }, "theme.TOCCollapsible.toggleButtonLabel": { "message": "当前页面", - "description": "The label used by the button on the collapsible TOC component" + "description": "可折叠目录组件按钮的标签" }, "theme.tags.tagsListLabel": { - "message": "标签 :", - "description": "The label alongside a tag list" + "message": "标签:", + "description": "标签列表的标签" }, "theme.SearchPage.documentsFound.plurals": { "message": "找到文档|找到{count}篇文档", - "description": "Pluralized label for \"{count} documents found\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + "description": "“找到{count}篇文档”的复数形式标签" }, "theme.SearchPage.existingResultsTitle": { - "message": "搜索结果 « {query} »", - "description": "The search page title for non-empty query" + "message": "搜索结果“{query}”", + "description": "非空查询的搜索页面标题" }, "theme.SearchPage.emptyResultsTitle": { "message": "在文档中搜索", - "description": "The search page title for empty query" + "description": "空查询的搜索页面标题" }, "theme.SearchPage.inputPlaceholder": { - "message": "在此处输入关键字", + "message": "输入关键字", "description": "搜索框的提示文字" }, "theme.SearchPage.inputLabel": { "message": "搜索", - "description": "搜索框文字" + "description": "搜索框的标签" }, "theme.SearchPage.algoliaLabel": { "message": "在Algolia搜索", - "description": "The ARIA label for Algolia mention" + "description": "Algolia提及的ARIA标签" }, "theme.SearchPage.noResultsText": { - "message": "搜索结果为空", - "description": "搜索不到时的提示" + "message": "没有找到结果", + "description": "搜索无结果时的提示" }, "theme.SearchPage.fetchingNewResults": { "message": "正在搜索...", @@ -209,7 +209,7 @@ }, "theme.SearchBar.label": { "message": "搜索", - "description": "搜索按钮文字" + "description": "搜索按钮的标签" }, "theme.tags.tagsPageTitle": { "message": "标签", @@ -219,25 +219,25 @@ "message": "高性能" }, ", providing a low-overhead communication channel between the server and the client.": { - "message": ", 提供服务器和客户端之间的低负载通信通道。" + "message": ",提供服务器和客户端之间的低负载通信通道。" }, "Reliable": { "message": "可靠的" }, "Rest assured! In case the WebSocket connection is not possible, it will fall back to HTTP long-polling. And if the connection is lost, the client will automatically try to reconnect.": { - "message": "连接安全!如果WebSocket连接不可能,它将返回到HTTP长轮询。如果连接丢失,客户端将自动尝试重新连接。" + "message": "放心!如果WebSocket连接不可用,它将回退到HTTP长轮询。如果连接丢失,客户端将自动尝试重新连接。" }, "Scalable": { "message": "可扩展" }, "Scale to multiple servers and send events to all connected clients with ease.": { - "message": "将应用程序部署到多个服务器,并轻松地向所有连接的客户端发送事件。" + "message": "扩展到多个服务器,并轻松地向所有连接的客户端发送事件。" }, "Bidirectional and low-latency communication for every platform": { - "message": "支持及时、双向与基于事件的交流。它可以在每个平台、每个浏览器和每个设备上工作,可靠性和速度同样稳定。" + "message": "每个平台的双向低延迟通信" }, "Get started": { - "message": "教程" + "message": "开始" }, "Documentation": { "message": "文档" @@ -246,20 +246,20 @@ "message": "我们的赞助商" }, "Become a sponsor": { - "message": "成为我们的赞助商" + "message": "成为赞助商" }, "In most cases, the connection will be established with WebSocket, providing a low-overhead communication channel between the server and the client.": { - "message": "在大多数情况下,将使用WebSocket建立连接,在服务器和客户端之间提供低负载通信通道。" + "message": "在大多数情况下,将使用WebSocket建立连接,提供服务器和客户端之间的低负载通信通道。" }, "Basic example": { "message": "基本示例" }, "Run this example on": { - "message": "本地运行" + "message": "在此运行示例" }, "theme.ErrorPageContent.title": { "message": "抱歉,页面崩溃了!", - "description": "当页面崩溃时显示的标题" + "description": "页面崩溃时显示的标题" }, "theme.ErrorPageContent.tryAgain": { "message": "再试一次", @@ -267,10 +267,10 @@ }, "theme.BackToTopButton.buttonAriaLabel": { "message": "返回顶部", - "description": "返回顶部按钮" + "description": "返回顶部按钮的ARIA标签" }, "theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": { - "message": "切换 '{label}'", - "description": "用于切换可折叠侧栏类别" + "message": "切换“{label}”", + "description": "用于切换可折叠侧栏类别的标签" } -} \ No newline at end of file +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/01-Documentation/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/01-Documentation/index.md index 7aeaa649..0783d40f 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/01-Documentation/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/01-Documentation/index.md @@ -9,68 +9,68 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; :::tip -If you are new to Socket.IO, we recommend checking out our [tutorial](../../tutorial/01-introduction.md). +如果您是 Socket.IO 的新手,我们建议您查看我们的[教程](../../tutorial/01-introduction.md)。 ::: ## 什么是 Socket.IO {#what-socketio-is} -Socket.IO 是一个库,可以在客户端和服务器之间实现 **低延迟**, **双向** 和 **基于事件的** 通信。 +Socket.IO 是一个库,可以在客户端和服务器之间实现 **低延迟**、**双向** 和 **基于事件的** 通信。 -The Socket.IO connection can be established with different low-level transports: +Socket.IO 连接可以通过不同的低级传输方式建立: -- HTTP long-polling +- HTTP 长轮询 - [WebSocket](https://developer.mozilla.org/zh-CN/docs/Web/API/WebSockets_API) - [WebTransport](https://developer.mozilla.org/zh-CN/docs/Web/API/WebTransport_API) -Socket.IO will automatically pick the best available option, depending on: +Socket.IO 会根据以下情况自动选择最佳可用选项: -- the capabilities of the browser (see [here](https://caniuse.com/websockets) and [here](https://caniuse.com/webtransport)) -- the network (some networks block WebSocket and/or WebTransport connections) +- 浏览器的能力(参见[这里](https://caniuse.com/websockets)和[这里](https://caniuse.com/webtransport)) +- 网络(某些网络会阻止 WebSocket 和/或 WebTransport 连接) -You can find more detail about that in the ["How it works" section](./how-it-works.md). +您可以在["工作原理"部分](./how-it-works.md)找到更多详细信息。 -### Server implementations {#server-implementations} +### 服务器实现 {#server-implementations} -| Language | Website | -|----------------------|-----------------------------------------------------------------------------------------------------------------------------------| +| 语言 | 网站 | +|----------------------|--------------------------------------------------------------------------------------------------------------------------------| | JavaScript (Node.js) | - [安装步骤](../02-Server/server-installation.md)
- [API](../../server-api.md)
- [源代码](https://github.com/socketio/socket.io) | -| JavaScript (Deno) | https://github.com/socketio/socket.io-deno | -| Java | https://github.com/mrniko/netty-socketio | -| Java | https://github.com/trinopoty/socket.io-server-java | -| Python | https://github.com/miguelgrinberg/python-socketio | -| Golang | https://github.com/googollee/go-socket.io | -| Rust | https://github.com/Totodore/socketioxide | - -### Client implementations {#client-implementations} - -| Language | Website | -|-----------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------| -| JavaScript (browser, Node.js or React Native) | - [安装步骤](../03-Client/client-installation.md)
- [API](../../client-api.md)
- [源代码](https://github.com/socketio/socket.io-client) | -| JavaScript (for WeChat Mini-Programs) | https://github.com/weapp-socketio/weapp.socket.io | -| Java | https://github.com/socketio/socket.io-client-java | -| C++ | https://github.com/socketio/socket.io-client-cpp | -| Swift | https://github.com/socketio/socket.io-client-swift | -| Dart | https://github.com/rikulo/socket.io-client-dart | -| Python | https://github.com/miguelgrinberg/python-socketio | -| .Net | https://github.com/doghappy/socket.io-client-csharp | -| Rust | https://github.com/1c3t3a/rust-socketio | -| Kotlin | https://github.com/icerockdev/moko-socket-io | -| PHP | https://github.com/ElephantIO/elephant.io | +| JavaScript (Deno) | https://github.com/socketio/socket.io-deno | +| Java | https://github.com/mrniko/netty-socketio | +| Java | https://github.com/trinopoty/socket.io-server-java | +| Python | https://github.com/miguelgrinberg/python-socketio | +| Golang | https://github.com/googollee/go-socket.io | +| Rust | https://github.com/Totodore/socketioxide | + +### 客户端实现 {#client-implementations} + +| 语言 | 网站 | +|---------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------| +| JavaScript (浏览器、Node.js 或 React Native) | - [安装步骤](../03-Client/client-installation.md)
- [API](../../client-api.md)
- [源代码](https://github.com/socketio/socket.io-client) | +| JavaScript (用于微信小程序) | https://github.com/weapp-socketio/weapp.socket.io | +| Java | https://github.com/socketio/socket.io-client-java | +| C++ | https://github.com/socketio/socket.io-client-cpp | +| Swift | https://github.com/socketio/socket.io-client-swift | +| Dart | https://github.com/rikulo/socket.io-client-dart | +| Python | https://github.com/miguelgrinberg/python-socketio | +| .Net | https://github.com/doghappy/socket.io-client-csharp | +| Rust | https://github.com/1c3t3a/rust-socketio | +| Kotlin | https://github.com/icerockdev/moko-socket-io | +| PHP | https://github.com/ElephantIO/elephant.io | ## Socket.IO 不是什么 {#what-socketio-is-not} :::caution -Socket.IO **不是** WebSocket实现。 +Socket.IO **不是** WebSocket 实现。 ::: @@ -81,7 +81,7 @@ Socket.IO **不是** WebSocket实现。 const socket = io("ws://echo.websocket.org"); ``` -如果您正在寻找一个普通的 WebSocket 服务器,请查看 [ws](https://github.com/websockets/ws) 或 [µWebSockets.js](https://github.com/uNetworking/uWebSockets.js). +如果您正在寻找一个普通的 WebSocket 服务器,请查看 [ws](https://github.com/websockets/ws) 或 [µWebSockets.js](https://github.com/uNetworking/uWebSockets.js)。 还有[关于](https://github.com/nodejs/node/issues/19308)在 Node.js 核心中包含 WebSocket 服务器的讨论。 @@ -119,7 +119,7 @@ Socket.IO 库保持与服务器的开放 TCP 连接,这可能会导致用户 当客户端断开连接时,数据包会自动缓冲,并在重新连接时发送。 -更多信息[在这里](../03-Client/client-offline-behavior.md#buffered-events). +更多信息[在这里](../03-Client/client-offline-behavior.md#buffered-events)。 ### 收到后的回调 {#acknowledgements} @@ -156,7 +156,7 @@ socket.timeout(5000).emit("hello", "world", (err, response) => { ### 广播 {#broadcasting} -[在服务器端,您可以向所有连接的客户端](../04-Events/broadcasting-events.md)或[客户端的子集](../04-Events/rooms.md )发送事件: +[在服务器端,您可以向所有连接的客户端](../04-Events/broadcasting-events.md)或[客户端的子集](../04-Events/rooms.md)发送事件: ```js // 到所有连接的客户端 @@ -168,7 +168,7 @@ io.to("news").emit("hello"); 这在[扩展到多个节点](../02-Server/using-multiple-nodes.md)时也有效。 -### 多路复用 {#multiplexing} +### 多路复用 {#multiplexing} 命名空间允许您在单个共享连接上拆分应用程序的逻辑。例如,如果您想创建一个只有授权用户才能加入的“管理员”频道,这可能很有用。 @@ -182,15 +182,15 @@ io.of("/admin").on("connection", (socket) => { }); ``` -详情点击[这里](../06-Advanced/namespaces.md). +详情点击[这里](../06-Advanced/namespaces.md)。 ## 常见问题 {#common-questions} ### 现在还需要 Socket.IO 吗? {#is-socketio-still-needed-today} -[这是一个很好的问题,因为现在几乎所有地方](https://caniuse.com/mdn-api_websocket) 都支持 WebSocket 。 +[这是一个很好的问题,因为现在几乎所有地方](https://caniuse.com/mdn-api_websocket)都支持 WebSocket。 -话虽如此,我们相信,如果您在应用程序中使用普通的 WebSocket,您最终将需要实现 Socket.IO 中已经包含(并经过实战测试)的大部分功能,例如[重新连接](#automatic-reconnection),[确认](#acknowledgements)或[广播](#broadcasting). +话虽如此,我们相信,如果您在应用程序中使用普通的 WebSocket,您最终将需要实现 Socket.IO 中已经包含(并经过实战测试)的大部分功能,例如[重新连接](#automatic-reconnection)、[确认](#acknowledgements)或[广播](#broadcasting)。 ### Socket.IO 协议的数据表大小? {#what-is-the-overhead-of-the-socketio-protocol} @@ -208,7 +208,7 @@ io.of("/admin").on("connection", (socket) => { ::: -You can find the details of the Socket.IO protocol [here](../08-Miscellaneous/sio-protocol.md). +您可以在[这里](../08-Miscellaneous/sio-protocol.md)找到 Socket.IO 协议的详细信息。 ### 有些东西不能正常工作,想要获取帮助? {#something-does-not-work-properly-please-help} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/05-Adapters/adapter-redis-streams.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/05-Adapters/adapter-redis-streams.md index fc4d4607..56b8006c 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/05-Adapters/adapter-redis-streams.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/05-Adapters/adapter-redis-streams.md @@ -1,31 +1,31 @@ --- -title: Redis Streams adapter +title: Redis Streams 适配器 sidebar_position: 3 slug: /redis-streams-adapter/ --- -## How it works {#how-it-works} +## 工作原理 {#how-it-works} -The adapter will use a [Redis stream](https://redis.io/docs/data-types/streams/) to forward packets between the Socket.IO servers. +该适配器将使用 [Redis 流](https://redis.io/docs/data-types/streams/) 在 Socket.IO 服务器之间转发数据包。 -The main difference with the existing Redis adapter (which use the [Redis Pub/Sub mechanism](https://redis.io/docs/manual/pubsub/)) is that this adapter will properly handle any temporary disconnection to the Redis server and resume the stream without losing any packets. +与现有的 Redis 适配器(使用 [Redis Pub/Sub 机制](https://redis.io/docs/manual/pubsub/))的主要区别在于,该适配器将正确处理与 Redis 服务器的任何临时断开连接,并在不丢失任何数据包的情况下恢复流。 -Notes: +注意事项: -- a single stream is used for all namespaces -- the `maxLen` option allows to limit the size of the stream -- unlike the adapter based on Redis PUB/SUB mechanism, this adapter will properly handle any temporary disconnection to the Redis server and resume the stream -- if [connection state recovery](../01-Documentation/connection-state-recovery.md) is enabled, the sessions will be stored in Redis as a classic key/value pair +- 所有命名空间使用单个流 +- `maxLen` 选项允许限制流的大小 +- 与基于 Redis PUB/SUB 机制的适配器不同,该适配器将正确处理与 Redis 服务器的任何临时断开连接,并恢复流 +- 如果启用了[连接状态恢复](../01-Documentation/connection-state-recovery.md),会话将作为经典的键/值对存储在 Redis 中 -Source code: https://github.com/socketio/socket.io-redis-streams-adapter +源代码: https://github.com/socketio/socket.io-redis-streams-adapter -## Installation {#installation} +## 安装 {#installation} ``` npm install @socket.io/redis-streams-adapter redis ``` -## Usage {#usage} +## 使用方法 {#usage} ```js import { createClient } from "redis"; @@ -43,28 +43,28 @@ const io = new Server({ io.listen(3000); ``` -## Options {#options} +## 选项 {#options} -| Name | Description | Default value | -|---------------------|--------------------------------------------------------------------|---------------| -| `streamName` | The name of the Redis stream. | `socket.io` | -| `maxLen` | The maximum size of the stream. Almost exact trimming (~) is used. | `10_000` | -| `readCount` | The number of elements to fetch per XREAD call. | `100` | -| `heartbeatInterval` | The number of ms between two heartbeats. | `5_000` | -| `heartbeatTimeout` | The number of ms without heartbeat before we consider a node down. | `10_000` | +| 名称 | 描述 | 默认值 | +|---------------------|-------------------------------------------------------------------|---------------| +| `streamName` | Redis 流的名称。 | `socket.io` | +| `maxLen` | 流的最大大小。使用近似修剪(~)。 | `10_000` | +| `readCount` | 每次 XREAD 调用要获取的元素数量。 | `100` | +| `heartbeatInterval` | 两次心跳之间的毫秒数。 | `5_000` | +| `heartbeatTimeout` | 在我们认为节点宕机之前没有心跳的毫秒数。 | `10_000` | -## Common questions {#common-questions} +## 常见问题 {#common-questions} -- Do I still need to enable sticky sessions when using the Redis Streams adapter? +- 使用 Redis Streams 适配器时,我仍然需要启用粘性会话吗? -Yes. Failing to do so will result in HTTP 400 responses (you are reaching a server that is not aware of the Socket.IO session). +是的。如果不这样做,将导致 HTTP 400 响应(您正在访问一个不了解 Socket.IO 会话的服务器)。 -More information can be found [here](../02-Server/using-multiple-nodes.md#why-is-sticky-session-required). +更多信息请参见[这里](../02-Server/using-multiple-nodes.md#why-is-sticky-session-required)。 -- What happens when the Redis server is down? +- 当 Redis 服务器宕机时会发生什么? -Unlike the classic [Redis adapter](./adapter-redis.md), this adapter will properly handle any temporary disconnection to the Redis server and resume the stream without losing any packets. +与经典的 [Redis 适配器](./adapter-redis.md)不同,该适配器将正确处理与 Redis 服务器的任何临时断开连接,并在不丢失任何数据包的情况下恢复流。 -## Latest releases {#latest-releases} +## 最新版本 {#latest-releases} -- [0.1.0](https://github.com/socketio/socket.io-redis-streams-adapter/releases/tag/0.1.0) (April 2023) +- [0.1.0](https://github.com/socketio/socket.io-redis-streams-adapter/releases/tag/0.1.0) (2023年4月) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/08-Miscellaneous/eio-protocol.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/08-Miscellaneous/eio-protocol.md index cdf1101a..ff951368 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/08-Miscellaneous/eio-protocol.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/08-Miscellaneous/eio-protocol.md @@ -1,101 +1,99 @@ --- -title: The Engine.IO protocol +title: Engine.IO 协议 sidebar_position: 3 slug: /engine-io-protocol/ --- -This document describes the 4th version of the Engine.IO protocol. +本文档描述了 Engine.IO 协议的第 4 版。 -The source of this document can be found [here](https://github.com/socketio/engine.io-protocol). +本文档的源代码可以在[这里](https://github.com/socketio/engine.io-protocol)找到。 -**Table of content** +**目录** -- [Introduction](#introduction) -- [Transports](#transports) - - [HTTP long-polling](#http-long-polling) - - [Request path](#request-path) - - [Query parameters](#query-parameters) - - [Headers](#headers) - - [Sending and receiving data](#sending-and-receiving-data) - - [Sending data](#sending-data) - - [Receiving data](#receiving-data) +- [介绍](#introduction) +- [传输](#transports) + - [HTTP 长轮询](#http-long-polling) + - [请求路径](#request-path) + - [查询参数](#query-parameters) + - [头信息](#headers) + - [发送和接收数据](#sending-and-receiving-data) + - [发送数据](#sending-data) + - [接收数据](#receiving-data) - [WebSocket](#websocket) -- [Protocol](#protocol) - - [Handshake](#handshake) - - [Heartbeat](#heartbeat) - - [Upgrade](#upgrade) - - [Message](#message) -- [Packet encoding](#packet-encoding) - - [HTTP long-polling](#http-long-polling-1) +- [协议](#protocol) + - [握手](#handshake) + - [心跳](#heartbeat) + - [升级](#upgrade) + - [消息](#message) +- [数据包编码](#packet-encoding) + - [HTTP 长轮询](#http-long-polling-1) - [WebSocket](#websocket-1) -- [History](#history) - - [From v2 to v3](#from-v2-to-v3) - - [From v3 to v4](#from-v3-to-v4) -- [Test suite](#test-suite) +- [历史](#history) + - [从 v2 到 v3](#from-v2-to-v3) + - [从 v3 到 v4](#from-v3-to-v4) +- [测试套件](#test-suite) +## 介绍 +Engine.IO 协议实现了客户端和服务器之间的[全双工](https://en.wikipedia.org/wiki/Duplex_(telecommunications)#FULL-DUPLEX)和低开销通信。 -## Introduction {#introduction} +它基于[WebSocket 协议](https://en.wikipedia.org/wiki/WebSocket),并在无法建立 WebSocket 连接时使用[HTTP 长轮询](https://en.wikipedia.org/wiki/Push_technology#Long_polling)作为后备。 -The Engine.IO protocol enables [full-duplex](https://en.wikipedia.org/wiki/Duplex_(telecommunications)#FULL-DUPLEX) and low-overhead communication between a client and a server. +参考实现是用[TypeScript](https://www.typescriptlang.org/)编写的: -It is based on the [WebSocket protocol](https://en.wikipedia.org/wiki/WebSocket) and uses [HTTP long-polling](https://en.wikipedia.org/wiki/Push_technology#Long_polling) as fallback if the WebSocket connection can't be established. +- 服务器: https://github.com/socketio/engine.io +- 客户端: https://github.com/socketio/engine.io-client -The reference implementation is written in [TypeScript](https://www.typescriptlang.org/): +[Socket.IO 协议](https://github.com/socketio/socket.io-protocol)建立在这些基础之上,在 Engine.IO 协议提供的通信通道上增加了额外的功能。 -- server: https://github.com/socketio/engine.io -- client: https://github.com/socketio/engine.io-client +## 传输 -The [Socket.IO protocol](https://github.com/socketio/socket.io-protocol) is built on top of these foundations, bringing additional features over the communication channel provided by the Engine.IO protocol. +Engine.IO 客户端和 Engine.IO 服务器之间的连接可以通过以下方式建立: -## Transports {#transports} - -The connection between an Engine.IO client and an Engine.IO server can be established with: - -- [HTTP long-polling](#http-long-polling) +- [HTTP 长轮询](#http-long-polling) - [WebSocket](#websocket) -### HTTP long-polling {#http-long-polling} +### HTTP 长轮询 -The HTTP long-polling transport (also simply referred as "polling") consists of successive HTTP requests: +HTTP 长轮询传输(也简称为“轮询”)由连续的 HTTP 请求组成: -- long-running `GET` requests, for receiving data from the server -- short-running `POST` requests, for sending data to the server +- 长时间运行的 `GET` 请求,用于从服务器接收数据 +- 短时间运行的 `POST` 请求,用于向服务器发送数据 -#### Request path {#request-path} +#### 请求路径 -The path of the HTTP requests is `/engine.io/` by default. +HTTP 请求的路径默认是 `/engine.io/`。 -It might be updated by libraries built on top of the protocol (for example, the Socket.IO protocol uses `/socket.io/`). +它可能会被基于该协议构建的库更新(例如,Socket.IO 协议使用 `/socket.io/`)。 -#### Query parameters {#query-parameters} +#### 查询参数 -The following query parameters are used: +使用以下查询参数: -| Name | Value | Description | +| 名称 | 值 | 描述 | |-------------|-----------|--------------------------------------------------------------------| -| `EIO` | `4` | Mandatory, the version of the protocol. | -| `transport` | `polling` | Mandatory, the name of the transport. | -| `sid` | `` | Mandatory once the session is established, the session identifier. | +| `EIO` | `4` | 必须,协议的版本。 | +| `transport` | `polling` | 必须,传输的名称。 | +| `sid` | `` | 一旦会话建立,必须,表示会话标识符。 | -If a mandatory query parameter is missing, then the server MUST respond with an HTTP 400 error status. +如果缺少必需的查询参数,服务器必须响应 HTTP 400 错误状态。 -#### Headers {#headers} +#### 头信息 -When sending binary data, the sender (client or server) MUST include a `Content-Type: application/octet-stream` header. +发送二进制数据时,发送方(客户端或服务器)必须包含 `Content-Type: application/octet-stream` 头。 -Without an explicit `Content-Type` header, the receiver SHOULD infer that the data is plaintext. +如果没有明确的 `Content-Type` 头,接收方应推断数据是纯文本。 -Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type +参考: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type -#### Sending and receiving data {#sending-and-receiving-data} +#### 发送和接收数据 -##### Sending data {#sending-data} +##### 发送数据 -To send some packets, a client MUST create an HTTP `POST` request with the packets encoded in the request body: +要发送一些数据包,客户端必须创建一个 HTTP `POST` 请求,并在请求体中编码数据包: ``` -CLIENT SERVER +客户端 服务器 │ │ │ POST /engine.io/?EIO=4&transport=polling&sid=... │ @@ -105,18 +103,18 @@ CLIENT SERVER │ │ ``` -The server MUST return an HTTP 400 response if the session ID (from the `sid` query parameter) is not known. +如果会话 ID(来自 `sid` 查询参数)未知,服务器必须返回 HTTP 400 响应。 -To indicate success, the server MUST return an HTTP 200 response, with the string `ok` in the response body. +为了表示成功,服务器必须返回 HTTP 200 响应,并在响应体中包含字符串 `ok`。 -To ensure packet ordering, a client MUST NOT have more than one active `POST` request. Should it happen, the server MUST return an HTTP 400 error status and close the session. +为了确保数据包的顺序,客户端不得有多个活动的 `POST` 请求。如果发生这种情况,服务器必须返回 HTTP 400 错误状态并关闭会话。 -##### Receiving data {#receiving-data} +##### 接收数据 -To receive some packets, a client MUST create an HTTP `GET` request: +要接收一些数据包,客户端必须创建一个 HTTP `GET` 请求: ``` -CLIENT SERVER +客户端 服务器 │ GET /engine.io/?EIO=4&transport=polling&sid=... │ │ ──────────────────────────────────────────────────► │ @@ -128,57 +126,57 @@ CLIENT SERVER │ HTTP 200 │ ``` -The server MUST return an HTTP 400 response if the session ID (from the `sid` query parameter) is not known. +如果会话 ID(来自 `sid` 查询参数)未知,服务器必须返回 HTTP 400 响应。 -The server MAY not respond right away if there are no packets buffered for the given session. Once there are some packets to be sent, the server SHOULD encode them (see [Packet encoding](#packet-encoding)) and send them in the response body of the HTTP request. +如果没有缓冲的包要发送,服务器可能不会立即响应。一旦有一些包要发送,服务器应对其进行编码(参见[数据包编码](#packet-encoding))并在 HTTP 请求的响应体中发送它们。 -To ensure packet ordering, a client MUST NOT have more than one active `GET` request. Should it happen, the server MUST return an HTTP 400 error status and close the session. +为了确保数据包的顺序,客户端不得有多个活动的 `GET` 请求。如果发生这种情况,服务器必须返回 HTTP 400 错误状态并关闭会话。 -### WebSocket {#websocket} +### WebSocket -The WebSocket transport consists of a [WebSocket connection](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API), which provides a bidirectional and low-latency communication channel between the server and the client. +WebSocket 传输由一个[WebSocket 连接](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)组成,它在服务器和客户端之间提供了一个双向和低延迟的通信通道。 -The following query parameters are used: +使用以下查询参数: -| Name | Value | Description | +| 名称 | 值 | 描述 | |-------------|-------------|-------------------------------------------------------------------------------| -| `EIO` | `4` | Mandatory, the version of the protocol. | -| `transport` | `websocket` | Mandatory, the name of the transport. | -| `sid` | `` | Optional, depending on whether it's an upgrade from HTTP long-polling or not. | +| `EIO` | `4` | 必须,协议的版本。 | +| `transport` | `websocket` | 必须,传输的名称。 | +| `sid` | `` | 可选,取决于是否从 HTTP 长轮询升级。 | -If a mandatory query parameter is missing, then the server MUST close the WebSocket connection. +如果缺少必需的查询参数,服务器必须关闭 WebSocket 连接。 -Each packet (read or write) is sent its own [WebSocket frame](https://datatracker.ietf.org/doc/html/rfc6455#section-5). +每个数据包(读或写)都在自己的[WebSocket 帧](https://datatracker.ietf.org/doc/html/rfc6455#section-5)中发送。 -A client MUST NOT open more than one WebSocket connection per session. Should it happen, the server MUST close the WebSocket connection. +客户端不得为每个会话打开多个 WebSocket 连接。如果发生这种情况,服务器必须关闭 WebSocket 连接。 -## Protocol {#protocol} +## 协议 -An Engine.IO packet consists of: +Engine.IO 数据包由以下部分组成: -- a packet type -- an optional packet payload +- 数据包类型 +- 可选的数据包负载 -Here is the list of available packet types: +以下是可用的数据包类型列表: -| Type | ID | Usage | +| 类型 | ID | 用途 | |---------|-----|--------------------------------------------------| -| open | 0 | Used during the [handshake](#handshake). | -| close | 1 | Used to indicate that a transport can be closed. | -| ping | 2 | Used in the [heartbeat mechanism](#heartbeat). | -| pong | 3 | Used in the [heartbeat mechanism](#heartbeat). | -| message | 4 | Used to send a payload to the other side. | -| upgrade | 5 | Used during the [upgrade process](#upgrade). | -| noop | 6 | Used during the [upgrade process](#upgrade). | +| open | 0 | 用于[握手](#handshake)期间。 | +| close | 1 | 用于指示传输可以关闭。 | +| ping | 2 | 用于[心跳机制](#heartbeat)。 | +| pong | 3 | 用于[心跳机制](#heartbeat)。 | +| message | 4 | 用于向另一方发送负载。 | +| upgrade | 5 | 用于[升级过程](#upgrade)。 | +| noop | 6 | 用于[升级过程](#upgrade)。 | -### Handshake {#handshake} +### 握手 -To establish a connection, the client MUST send an HTTP `GET` request to the server: +要建立连接,客户端必须向服务器发送 HTTP `GET` 请求: -- HTTP long-polling first (by default) +- 首先是 HTTP 长轮询(默认) ``` -CLIENT SERVER +客户端 服务器 │ │ │ GET /engine.io/?EIO=4&transport=polling │ @@ -188,10 +186,10 @@ CLIENT SERVER │ │ ``` -- WebSocket-only session +- 仅 WebSocket 会话 ``` -CLIENT SERVER +客户端 服务器 │ │ │ GET /engine.io/?EIO=4&transport=websocket │ @@ -201,17 +199,17 @@ CLIENT SERVER │ │ ``` -If the server accepts the connection, then it MUST respond with an `open` packet with the following JSON-encoded payload: +如果服务器接受连接,则必须响应一个带有以下 JSON 编码负载的 `open` 数据包: -| Key | Type | Description | +| 键 | 类型 | 描述 | |----------------|------------|-------------------------------------------------------------------------------------------------------------------| -| `sid` | `string` | The session ID. | -| `upgrades` | `string[]` | The list of available [transport upgrades](#upgrade). | -| `pingInterval` | `number` | The ping interval, used in the [heartbeat mechanism](#heartbeat) (in milliseconds). | -| `pingTimeout` | `number` | The ping timeout, used in the [heartbeat mechanism](#heartbeat) (in milliseconds). | -| `maxPayload` | `number` | The maximum number of bytes per chunk, used by the client to aggregate packets into [payloads](#packet-encoding). | +| `sid` | `string` | 会话 ID。 | +| `upgrades` | `string[]` | 可用的[传输升级](#upgrade)列表。 | +| `pingInterval` | `number` | 用于[心跳机制](#heartbeat)的 ping 间隔(以毫秒为单位)。 | +| `pingTimeout` | `number` | 用于[心跳机制](#heartbeat)的 ping 超时(以毫秒为单位)。 | +| `maxPayload` | `number` | 每个块的最大字节数,客户端用于将数据包聚合到[负载](#packet-encoding)中。 | -Example: +示例: ```json { @@ -223,188 +221,184 @@ Example: } ``` -The client MUST send the `sid` value in the query parameters of all subsequent requests. +客户端必须在所有后续请求的查询参数中发送 `sid` 值。 -### Heartbeat {#heartbeat} +### 心跳 -Once the [handshake](#handshake) is completed, a heartbeat mechanism is started to check the liveness of the connection: +一旦[握手](#handshake)完成,启动一个心跳机制来检查连接的活跃性: ``` -CLIENT SERVER +客户端 服务器 - │ *** Handshake *** │ + │ *** 握手 *** │ │ │ │ ◄───────────────────────────────────────────────── │ - │ 2 │ (ping packet) + │ 2 │ (ping 数据包) │ ─────────────────────────────────────────────────► │ - │ 3 │ (pong packet) + │ 3 │ (pong 数据包) ``` -At a given interval (the `pingInterval` value sent in the handshake) the server sends a `ping` packet and the client has a few seconds (the `pingTimeout` value) to send a `pong` packet back. +在给定的间隔(握手中发送的 `pingInterval` 值)内,服务器发送一个 `ping` 数据包,客户端有几秒钟(`pingTimeout` 值)来发送一个 `pong` 数据包。 -If the server does not receive a `pong` packet back, then it SHOULD consider that the connection is closed. +如果服务器没有收到 `pong` 数据包,则应认为连接已关闭。 -Conversely, if the client does not receive a `pong` packet within `pingInterval + pingTimeout`, then it SHOULD consider that the connection is closed. +相反,如果客户端在 `pingInterval + pingTimeout` 内没有收到 `ping` 数据包,则应认为连接已关闭。 -### Upgrade {#upgrade} +### 升级 -By default, the client SHOULD create an HTTP long-polling connection, and then upgrade to better transports if available. +默认情况下,客户端应创建一个 HTTP 长轮询连接,然后升级到更好的传输(如果可用)。 -To upgrade to WebSocket, the client MUST: +要升级到 WebSocket,客户端必须: -- pause the HTTP long-polling transport (no more HTTP request gets sent), to ensure that no packet gets lost -- open a WebSocket connection with the same session ID -- send a `ping` packet with the string `probe` in the payload +- 暂停 HTTP 长轮询传输(不再发送 HTTP 请求),以确保没有数据包丢失 +- 使用相同的会话 ID 打开一个 WebSocket 连接 +- 发送一个带有字符串 `probe` 的 `ping` 数据包 -The server MUST: +服务器必须: -- send a `noop` packet to any pending `GET` request (if applicable) to cleanly close HTTP long-polling transport -- respond with a `pong` packet with the string `probe` in the payload +- 向任何挂起的 `GET` 请求发送一个 `noop` 数据包(如果适用),以干净地关闭 HTTP 长轮询传输 +- 响应一个带有字符串 `probe` 的 `pong` 数据包 -Finally, the client MUST send a `upgrade` packet to complete the upgrade: +最后,客户端必须发送一个 `upgrade` 数据包以完成升级: ``` -CLIENT SERVER +客户端 服务器 │ │ │ GET /engine.io/?EIO=4&transport=websocket&sid=... │ │ ───────────────────────────────────────────────────► │ │ ◄─────────────────────────────────────────────────┘ │ - │ HTTP 101 (WebSocket handshake) │ + │ HTTP 101 (WebSocket 握手) │ │ │ - │ ----- WebSocket frames ----- │ + │ ----- WebSocket 帧 ----- │ │ ─────────────────────────────────────────────────► │ - │ 2probe │ (ping packet) + │ 2probe │ (ping 数据包) │ ◄───────────────────────────────────────────────── │ - │ 3probe │ (pong packet) + │ 3probe │ (pong 数据包) │ ─────────────────────────────────────────────────► │ - │ 5 │ (upgrade packet) + │ 5 │ (upgrade 数据包) │ │ ``` -### Message {#message} - -Once the [handshake](#handshake) is completed, the client and the server can exchange data by including it in a `message` packet. +### 消息 +一旦[握手](#handshake)完成,客户端和服务器可以通过在 `message` 数据包中包含数据来交换数据。 -## Packet encoding {#packet-encoding} +## 数据包编码 -The serialization of an Engine.IO packet depends on the type of the payload (plaintext or binary) and on the transport. +Engine.IO 数据包的序列化取决于负载的类型(纯文本或二进制)和传输方式。 -### HTTP long-polling {#http-long-polling-1} +### HTTP 长轮询 -Due to the nature of the HTTP long-polling transport, multiple packets might be concatenated in a single payload in order to increase throughput. +由于 HTTP 长轮询传输的性质,多个数据包可能会被连接到一个负载中以增加吞吐量。 -Format: +格式: ``` [][][][...] ``` -Example: +示例: ``` 4hello\x1e2\x1e4world -with: +其中: -4 => message packet type -hello => message payload -\x1e => separator -2 => ping packet type -\x1e => separator -4 => message packet type -world => message payload +4 => 消息数据包类型 +hello => 消息负载 +\x1e => 分隔符 +2 => ping 数据包类型 +\x1e => 分隔符 +4 => 消息数据包类型 +world => 消息负载 ``` -The packets are separated by the [record separator character](https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Field_separators): `\x1e` +数据包由[记录分隔符字符](https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Field_separators)分隔:`\x1e` -Binary payloads MUST be base64-encoded and prefixed with a `b` character: +二进制负载必须进行 base64 编码并以 `b` 字符为前缀: -Example: +示例: ``` 4hello\x1ebAQIDBA== -with: +其中: -4 => message packet type -hello => message payload -\x1e => separator -b => binary prefix -AQIDBA== => buffer <01 02 03 04> encoded as base64 +4 => 消息数据包类型 +hello => 消息负载 +\x1e => 分隔符 +b => 二进制前缀 +AQIDBA== => 缓冲区 <01 02 03 04> 编码为 base64 ``` -The client SHOULD use the `maxPayload` value sent during the [handshake](#handshake) to decide how many packets should be concatenated. +客户端应使用握手期间发送的 `maxPayload` 值来决定应连接多少数据包。 -### WebSocket {#websocket-1} +### WebSocket -Each Engine.IO packet is sent in its own [WebSocket frame](https://datatracker.ietf.org/doc/html/rfc6455#section-5). +每个 Engine.IO 数据包都在自己的[WebSocket 帧](https://datatracker.ietf.org/doc/html/rfc6455#section-5)中发送。 -Format: +格式: ``` [] ``` -Example: +示例: ``` 4hello -with: +其中: -4 => message packet type -hello => message payload (UTF-8 encoded) +4 => 消息数据包类型 +hello => 消息负载(UTF-8 编码) ``` -Binary payloads are sent as is, without modification. +二进制负载按原样发送,不做修改。 -## History {#history} +## 历史 -### From v2 to v3 {#from-v2-to-v3} +### 从 v2 到 v3 -- add support for binary data +- 增加对二进制数据的支持 -The [2nd version](https://github.com/socketio/engine.io-protocol/tree/v2) of the protocol is used in Socket.IO `v0.9` and below. +协议的[第 2 版](https://github.com/socketio/engine.io-protocol/tree/v2)用于 Socket.IO `v0.9` 及以下版本。 -The [3rd version](https://github.com/socketio/engine.io-protocol/tree/v3) of the protocol is used in Socket.IO `v1` and `v2`. +协议的[第 3 版](https://github.com/socketio/engine.io-protocol/tree/v3)用于 Socket.IO `v1` 和 `v2`。 -### From v3 to v4 {#from-v3-to-v4} +### 从 v3 到 v4 -- reverse ping/pong mechanism +- 反转 ping/pong 机制 -The ping packets are now sent by the server, because the timers set in the browsers are not reliable enough. We -suspect that a lot of timeout problems came from timers being delayed on the client-side. +现在由服务器发送 ping 数据包,因为浏览器中设置的计时器不够可靠。我们怀疑很多超时问题是由于客户端的计时器被延迟引起的。 -- always use base64 when encoding a payload with binary data +- 在编码带有二进制数据的负载时始终使用 base64 -This change allows to treat all payloads (with or without binary) the same way, without having to take in account -whether the client or the current transport supports binary data or not. +此更改允许以相同的方式处理所有负载(无论是否带有二进制),而无需考虑客户端或当前传输是否支持二进制数据。 -Please note that this only applies to HTTP long-polling. Binary data is sent in WebSocket frames with no additional transformation. +请注意,这仅适用于 HTTP 长轮询。二进制数据在 WebSocket 帧中发送,无需额外转换。 -- use a record separator (`\x1e`) instead of counting of characters +- 使用记录分隔符(`\x1e`)而不是字符计数 -Counting characters prevented (or at least makes harder) to implement the protocol in other languages, which may not use -the UTF-16 encoding. +字符计数使得在其他语言中实现协议变得更加困难,或者至少更难,因为这些语言可能不使用 UTF-16 编码。 -For example, `€` was encoded to `2:4€`, though `Buffer.byteLength('€') === 3`. +例如,`€` 被编码为 `2:4€`,尽管 `Buffer.byteLength('€') === 3`。 -Note: this assumes the record separator is not used in the data. +注意:这假设记录分隔符不用于数据中。 -The 4th version (current) is included in Socket.IO `v3` and above. +第 4 版(当前)包含在 Socket.IO `v3` 及以上版本中。 -## Test suite {#test-suite} +## 测试套件 -The test suite in the [`test-suite/`](https://github.com/socketio/engine.io-protocol/tree/main/test-suite) directory lets you check the compliance of a server implementation. +[`test-suite/`](https://github.com/socketio/engine.io-protocol/tree/main/test-suite)目录中的测试套件让您可以检查服务器实现的合规性。 -Usage: +用法: -- in Node.js: `npm ci && npm test` -- in a browser: simply open the `index.html` file in your browser +- 在 Node.js 中:`npm ci && npm test` +- 在浏览器中:只需在浏览器中打开 `index.html` 文件 -For reference, here is expected configuration for the JavaScript server to pass all tests: +作为参考,以下是 JavaScript 服务器通过所有测试的预期配置: ```js import { listen } from "engine.io"; diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/08-Miscellaneous/sio-protocol-1.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/08-Miscellaneous/sio-protocol-1.md new file mode 100644 index 00000000..78c4d5cb --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/08-Miscellaneous/sio-protocol-1.md @@ -0,0 +1,21 @@ +--- +title: The Socket.IO protocol +sidebar_position: 4 +slug: /socket-io-protocol/ +--- + +This document describes the 5th version of the Socket.IO protocol. + +The source of this document can be found [here](https://github.com/socketio/socket.io-protocol). + +## Introduction {#introduction} + +The Socket.IO protocol enables [full-duplex](https://en.wikipedia.org/wiki/Duplex_(telecommunications)#FULL-DUPLEX) and low-overhead communication between a client and a server. + +It is built on top of [the Engine.IO protocol](https://github.com/socketio/engine.io-protocol), which handles the low-level plumbing with WebSocket and HTTP long-polling. + +The Socket.IO protocol adds the following features: + +- multiplexing (referred as ["namespace"](https://socket.io/docs/v4/namespaces) in the Socket.IO jargon) + +Example with the JavaScript API: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/08-Miscellaneous/sio-protocol-1.md.raw b/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/08-Miscellaneous/sio-protocol-1.md.raw new file mode 100644 index 00000000..78c4d5cb --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/08-Miscellaneous/sio-protocol-1.md.raw @@ -0,0 +1,21 @@ +--- +title: The Socket.IO protocol +sidebar_position: 4 +slug: /socket-io-protocol/ +--- + +This document describes the 5th version of the Socket.IO protocol. + +The source of this document can be found [here](https://github.com/socketio/socket.io-protocol). + +## Introduction {#introduction} + +The Socket.IO protocol enables [full-duplex](https://en.wikipedia.org/wiki/Duplex_(telecommunications)#FULL-DUPLEX) and low-overhead communication between a client and a server. + +It is built on top of [the Engine.IO protocol](https://github.com/socketio/engine.io-protocol), which handles the low-level plumbing with WebSocket and HTTP long-polling. + +The Socket.IO protocol adds the following features: + +- multiplexing (referred as ["namespace"](https://socket.io/docs/v4/namespaces) in the Socket.IO jargon) + +Example with the JavaScript API: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/08-Miscellaneous/sio-protocol.md.raw b/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/08-Miscellaneous/sio-protocol.md.raw new file mode 100644 index 00000000..724dd029 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/categories/08-Miscellaneous/sio-protocol.md.raw @@ -0,0 +1,734 @@ +--- +title: The Socket.IO protocol +sidebar_position: 4 +slug: /socket-io-protocol/ +--- + +This document describes the 5th version of the Socket.IO protocol. + +The source of this document can be found [here](https://github.com/socketio/socket.io-protocol). + +**Table of content** + +- [Introduction](#introduction) +- [Exchange protocol](#exchange-protocol) + - [Connection to a namespace](#connection-to-a-namespace) + - [Sending and receiving data](#sending-and-receiving-data) + - [Acknowledgement](#acknowledgement) + - [Disconnection from a namespace](#disconnection-from-a-namespace) +- [Packet encoding](#packet-encoding) + - [Format](#format) + - [Examples](#examples) + - [Connection to a namespace](#connection-to-a-namespace-1) + - [Sending and receiving data](#sending-and-receiving-data-1) + - [Acknowledgement](#acknowledgement-1) + - [Disconnection from a namespace](#disconnection-from-a-namespace-1) +- [Sample session](#sample-session) +- [History](#history) + - [Difference between v5 and v4](#difference-between-v5-and-v4) + - [Difference between v4 and v3](#difference-between-v4-and-v3) + - [Difference between v3 and v2](#difference-between-v3-and-v2) + - [Difference between v2 and v1](#difference-between-v2-and-v1) + - [Initial revision](#initial-revision) +- [Test suite](#test-suite) + + +## Introduction {#introduction} + +The Socket.IO protocol enables [full-duplex](https://en.wikipedia.org/wiki/Duplex_(telecommunications)#FULL-DUPLEX) and low-overhead communication between a client and a server. + +It is built on top of [the Engine.IO protocol](https://github.com/socketio/engine.io-protocol), which handles the low-level plumbing with WebSocket and HTTP long-polling. + +The Socket.IO protocol adds the following features: + +- multiplexing (referred as ["namespace"](https://socket.io/docs/v4/namespaces) in the Socket.IO jargon) + +Example with the JavaScript API: + +*Server* + +```js +// declare the namespace +const namespace = io.of("/admin"); +// handle the connection to the namespace +namespace.on("connection", (socket) => { + // ... +}); +``` + +*Client* + +```js +// reach the main namespace +const socket1 = io(); +// reach the "/admin" namespace (with the same underlying WebSocket connection) +const socket2 = io("/admin"); +// handle the connection to the namespace +socket2.on("connect", () => { + // ... +}); +``` + +- acknowledgement of packets + +Example with the JavaScript API: + +```js +// on one side +socket.emit("hello", "foo", (arg) => { + console.log("received", arg); +}); + +// on the other side +socket.on("hello", (arg, ack) => { + ack("bar"); +}); +``` + +The reference implementation is written in [TypeScript](https://www.typescriptlang.org/): + +- server: https://github.com/socketio/socket.io +- client: https://github.com/socketio/socket.io-client + + +## Exchange protocol {#exchange-protocol} + +A Socket.IO packet contains the following fields: + +- a packet type (integer) +- a namespace (string) +- optionally, a payload (Object | Array) +- optionally, an acknowledgment id (integer) + +Here is the list of available packet types: + +| Type | ID | Usage | +|---------------|-----|---------------------------------------------------------------------------------------| +| CONNECT | 0 | Used during the [connection to a namespace](#connection-to-a-namespace). | +| DISCONNECT | 1 | Used when [disconnecting from a namespace](#disconnection-from-a-namespace). | +| EVENT | 2 | Used to [send data](#sending-and-receiving-data) to the other side. | +| ACK | 3 | Used to [acknowledge](#acknowledgement) an event. | +| CONNECT_ERROR | 4 | Used during the [connection to a namespace](#connection-to-a-namespace). | +| BINARY_EVENT | 5 | Used to [send binary data](#sending-and-receiving-data) to the other side. | +| BINARY_ACK | 6 | Used to [acknowledge](#acknowledgement) an event (the response includes binary data). | + + +### Connection to a namespace {#connection-to-a-namespace} + +At the beginning of a Socket.IO session, the client MUST send a `CONNECT` packet: + +The server MUST respond with either: + +- a `CONNECT` packet if the connection is successful, with the session ID in the payload +- or a `CONNECT_ERROR` packet if the connection is not allowed + +``` +CLIENT SERVER + + │ ───────────────────────────────────────────────────────► │ + │ { type: CONNECT, namespace: "/" } │ + │ ◄─────────────────────────────────────────────────────── │ + │ { type: CONNECT, namespace: "/", data: { sid: "..." } } │ +``` + +If the server does not receive a `CONNECT` packet first, then it MUST close the connection immediately. + +A client MAY be connected to multiple namespaces at the same time, with the same underlying WebSocket connection. + +Examples: + +- with the main namespace (named `"/"`) + +``` +Client > { type: CONNECT, namespace: "/" } +Server > { type: CONNECT, namespace: "/", data: { sid: "wZX3oN0bSVIhsaknAAAI" } } +``` + +- with a custom namespace + +``` +Client > { type: CONNECT, namespace: "/admin" } +Server > { type: CONNECT, namespace: "/admin", data: { sid: "oSO0OpakMV_3jnilAAAA" } } +``` + +- with an additional payload + +``` +Client > { type: CONNECT, namespace: "/admin", data: { "token": "123" } } +Server > { type: CONNECT, namespace: "/admin", data: { sid: "iLnRaVGHY4B75TeVAAAB" } } +``` + +- in case the connection is refused + +``` +Client > { type: CONNECT, namespace: "/" } +Server > { type: CONNECT_ERROR, namespace: "/", data: { message: "Not authorized" } } +``` + +### Sending and receiving data {#sending-and-receiving-data} + +Once the [connection to a namespace](#connection-to-a-namespace) is established, the client and the server can begin exchanging data: + +``` +CLIENT SERVER + + │ ───────────────────────────────────────────────────────► │ + │ { type: EVENT, namespace: "/", data: ["foo"] } │ + │ │ + │ ◄─────────────────────────────────────────────────────── │ + │ { type: EVENT, namespace: "/", data: ["bar"] } │ +``` + +The payload is mandatory and MUST be a non-empty array. If that's not the case, then the receiver MUST close the connection. + +Examples: + +- with the main namespace + +``` +Client > { type: EVENT, namespace: "/", data: ["foo"] } +``` + +- with a custom namespace + +``` +Server > { type: EVENT, namespace: "/admin", data: ["bar"] } +``` + +- with binary data + +``` +Client > { type: BINARY_EVENT, namespace: "/", data: ["baz", > ] } +``` + +### Acknowledgement {#acknowledgement} + +The sender MAY include an event ID in order to request an acknowledgement from the receiver: + +``` +CLIENT SERVER + + │ ───────────────────────────────────────────────────────► │ + │ { type: EVENT, namespace: "/", data: ["foo"], id: 12 } │ + │ ◄─────────────────────────────────────────────────────── │ + │ { type: ACK, namespace: "/", data: ["bar"], id: 12 } │ +``` + +The receiver MUST respond with an `ACK` packet with the same event ID. + +The payload is mandatory and MUST be an array (possibly empty). + +Examples: + +- with the main namespace + +``` +Client > { type: EVENT, namespace: "/", data: ["foo"], id: 12 } +Server > { type: ACK, namespace: "/", data: [], id: 12 } +``` + +- with a custom namespace + +``` +Server > { type: EVENT, namespace: "/admin", data: ["foo"], id: 13 } +Client > { type: ACK, namespace: "/admin", data: ["bar"], id: 13 } +``` + +- with binary data + +``` +Client > { type: BINARY_EVENT, namespace: "/", data: ["foo", ], id: 14 } +Server > { type: ACK, namespace: "/", data: ["bar"], id: 14 } + +or + +Server > { type: EVENT, namespace: "/", data: ["foo" ], id: 15 } +Client > { type: BINARY_ACK, namespace: "/", data: ["bar", ], id: 15 } +``` + +### Disconnection from a namespace {#disconnection-from-a-namespace} + +At any time, one side can end the connection to a namespace by sending a `DISCONNECT` packet: + +``` +CLIENT SERVER + + │ ───────────────────────────────────────────────────────► │ + │ { type: DISCONNECT, namespace: "/" } │ +``` + +No response is expected from the other side. The low-level connection MAY be kept alive if the client is connected to another namespace. + + +## Packet encoding {#packet-encoding} + +This section details the encoding used by the default parser which is included in Socket.IO server and client, and +whose source can be found [here](https://github.com/socketio/socket.io-parser). + +The JavaScript server and client implementations also supports custom parsers, which have different tradeoffs and may benefit to +certain kind of applications. Please see [socket.io-json-parser](https://github.com/socketio/socket.io-json-parser) +or [socket.io-msgpack-parser](https://github.com/socketio/socket.io-msgpack-parser) for example. + +Please also note that each Socket.IO packet is sent as a Engine.IO `message` packet (more information [here](https://github.com/socketio/engine.io-protocol)), +so the encoded result will be prefixed by the character `"4"` when sent over the wire (in the request/response body with HTTP +long-polling, or in the WebSocket frame). + +### Format {#format} + +``` +[<# of binary attachments>-][,][][JSON-stringified payload without binary] + ++ binary attachments extracted +``` + +Note: the namespace is only included if it is different from the main namespace (`/`) + +### Examples {#examples} + +#### Connection to a namespace {#connection-to-a-namespace-1} + +- with the main namespace + +*Packet* + +``` +{ type: CONNECT, namespace: "/" } +``` + +*Encoded* + +``` +0 +``` + +- with a custom namespace + +*Packet* + +``` +{ type: CONNECT, namespace: "/admin", data: { sid: "oSO0OpakMV_3jnilAAAA" } } +``` + +*Encoded* + +``` +0/admin,{"sid":"oSO0OpakMV_3jnilAAAA"} +``` + +- in case the connection is refused + +*Packet* + +``` +{ type: CONNECT_ERROR, namespace: "/", data: { message: "Not authorized" } } +``` + +*Encoded* + +``` +4{"message":"Not authorized"} +``` + +#### Sending and receiving data {#sending-and-receiving-data-1} + +- with the main namespace + +*Packet* + +``` +{ type: EVENT, namespace: "/", data: ["foo"] } +``` + +*Encoded* + +``` +2["foo"] +``` + +- with a custom namespace + +*Packet* + +``` +{ type: EVENT, namespace: "/admin", data: ["bar"] } +``` + +*Encoded* + +``` +2/admin,["bar"] +``` + +- with binary data + +*Packet* + +``` +{ type: BINARY_EVENT, namespace: "/", data: ["baz", > ] } +``` + +*Encoded* + +``` +51-["baz",{"_placeholder":true,"num":0}] + ++ > +``` + +- with multiple attachments + +*Packet* + +``` +{ type: BINARY_EVENT, namespace: "/admin", data: ["baz", >, > ] } +``` + +*Encoded* + +``` +52-/admin,["baz",{"_placeholder":true,"num":0},{"_placeholder":true,"num":1}] + ++ > ++ > +``` + +Please remember that each Socket.IO packet is wrapped in a Engine.IO `message` packet, so they will be prefixed by the character `"4"` when sent over the wire. + +Example: `{ type: EVENT, namespace: "/", data: ["foo"] }` will be sent as `42["foo"]` + +#### Acknowledgement {#acknowledgement-1} + +- with the main namespace + +*Packet* + +``` +{ type: EVENT, namespace: "/", data: ["foo"], id: 12 } +``` + +*Encoded* + +``` +212["foo"] +``` + +- with a custom namespace + +*Packet* + +``` +{ type: ACK, namespace: "/admin", data: ["bar"], id: 13 } +``` + +*Encoded* + +``` +3/admin,13["bar"]` +``` + +- with binary data + +*Packet* + +``` +{ type: BINARY_ACK, namespace: "/", data: ["bar", >], id: 15 } +``` + +*Encoded* + +``` +61-15["bar",{"_placeholder":true,"num":0}] + ++ > +``` + +#### Disconnection from a namespace {#disconnection-from-a-namespace-1} + +- with the main namespace + +*Packet* + +``` +{ type: DISCONNECT, namespace: "/" } +``` + +*Encoded* + +``` +1 +``` + +- with a custom namespace + +``` +{ type: DISCONNECT, namespace: "/admin" } +``` + +*Encoded* + +``` +1/admin, +``` + + +## Sample session {#sample-session} + +Here is an example of what is sent over the wire when combining both the Engine.IO and the Socket.IO protocols. + +- Request n°1 (open packet) + +``` +GET /socket.io/?EIO=4&transport=polling&t=N8hyd6w +< HTTP/1.1 200 OK +< Content-Type: text/plain; charset=UTF-8 +0{"sid":"lv_VI97HAXpY6yYWAAAC","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":5000,"maxPayload":1000000} +``` + +Details: + +``` +0 => Engine.IO "open" packet type +{"sid":... => the Engine.IO handshake data +``` + +Note: the `t` query param is used to ensure that the request is not cached by the browser. + +- Request n°2 (namespace connection request): + +``` +POST /socket.io/?EIO=4&transport=polling&t=N8hyd7H&sid=lv_VI97HAXpY6yYWAAAC +< HTTP/1.1 200 OK +< Content-Type: text/plain; charset=UTF-8 +40 +``` + +Details: + +``` +4 => Engine.IO "message" packet type +0 => Socket.IO "CONNECT" packet type +``` + +- Request n°3 (namespace connection approval) + +``` +GET /socket.io/?EIO=4&transport=polling&t=N8hyd7H&sid=lv_VI97HAXpY6yYWAAAC +< HTTP/1.1 200 OK +< Content-Type: text/plain; charset=UTF-8 +40{"sid":"wZX3oN0bSVIhsaknAAAI"} +``` + +- Request n°4 + +`socket.emit('hey', 'Jude')` is executed on the server: + +``` +GET /socket.io/?EIO=4&transport=polling&t=N8hyd7H&sid=lv_VI97HAXpY6yYWAAAC +< HTTP/1.1 200 OK +< Content-Type: text/plain; charset=UTF-8 +42["hey","Jude"] +``` + +Details: + +``` +4 => Engine.IO "message" packet type +2 => Socket.IO "EVENT" packet type +[...] => content +``` + +- Request n°5 (message out) + +`socket.emit('hello'); socket.emit('world');` is executed on the client: + +``` +POST /socket.io/?EIO=4&transport=polling&t=N8hzxke&sid=lv_VI97HAXpY6yYWAAAC +> Content-Type: text/plain; charset=UTF-8 +42["hello"]\x1e42["world"] +< HTTP/1.1 200 OK +< Content-Type: text/plain; charset=UTF-8 +ok +``` + +Details: + +``` +4 => Engine.IO "message" packet type +2 => Socket.IO "EVENT" packet type +["hello"] => the 1st content +\x1e => separator +4 => Engine.IO "message" packet type +2 => Socket.IO "EVENT" packet type +["world"] => the 2nd content +``` + +- Request n°6 (WebSocket upgrade) + +``` +GET /socket.io/?EIO=4&transport=websocket&sid=lv_VI97HAXpY6yYWAAAC +< HTTP/1.1 101 Switching Protocols +``` + +WebSocket frames: + +``` +< 2probe => Engine.IO probe request +> 3probe => Engine.IO probe response +> 5 => Engine.IO "upgrade" packet type +> 42["hello"] +> 42["world"] +> 40/admin, => request access to the admin namespace (Socket.IO "CONNECT" packet) +< 40/admin,{"sid":"-G5j-67EZFp-q59rADQM"} => grant access to the admin namespace +> 42/admin,1["tellme"] => Socket.IO "EVENT" packet with acknowledgement +< 461-/admin,1[{"_placeholder":true,"num":0}] => Socket.IO "BINARY_ACK" packet with a placeholder +< => the binary attachment (sent in the following frame) +... after a while without message +> 2 => Engine.IO "ping" packet type +< 3 => Engine.IO "pong" packet type +> 1 => Engine.IO "close" packet type +``` + +## History {#history} + +### Difference between v5 and v4 {#difference-between-v5-and-v4} + +The 5th revision (current) of the Socket.IO protocol is used in Socket.IO v3 and above (`v3.0.0` was released in November 2020). + +It is built on top of the 4th revision of [the Engine.IO protocol](https://github.com/socketio/engine.io-protocol) (hence the `EIO=4` query parameter). + +List of changes: + +- remove the implicit connection to the default namespace + +In previous versions, a client was always connected to the default namespace, even if it requested access to another namespace. + +This is not the case anymore, the client must send a `CONNECT` packet in any case. + +Commits: [09b6f23](https://github.com/socketio/socket.io/commit/09b6f2333950b8afc8c1400b504b01ad757876bd) (server) and [249e0be](https://github.com/socketio/socket.io-client/commit/249e0bef9071e7afd785485961c4eef0094254e8) (client) + + +- rename `ERROR` to `CONNECT_ERROR` + +The meaning and the code number (4) are not modified: this packet type is still used by the server when the connection to a namespace is refused. But we feel the name is more self-descriptive. + +Commits: [d16c035](https://github.com/socketio/socket.io/commit/d16c035d258b8deb138f71801cb5aeedcdb3f002) (server) and [13e1db7c](https://github.com/socketio/socket.io-client/commit/13e1db7c94291c583d843beaa9e06ee041ae4f26) (client). + + +- the `CONNECT` packet now can contain a payload + +The client can send a payload for authentication/authorization purposes. Example: + +```json +{ + "type": 0, + "nsp": "/admin", + "data": { + "token": "123" + } +} +``` + +In case of success, the server responds with a payload contain the ID of the Socket. Example: + +```json +{ + "type": 0, + "nsp": "/admin", + "data": { + "sid": "CjdVH4TQvovi1VvgAC5Z" + } +} +``` + +This change means that the ID of the Socket.IO connection will now be different from the ID of the underlying Engine.IO connection (the one that is found in the query parameters of the HTTP requests). + +Commits: [2875d2c](https://github.com/socketio/socket.io/commit/2875d2cfdfa463e64cb520099749f543bbc4eb15) (server) and [bbe94ad](https://github.com/socketio/socket.io-client/commit/bbe94adb822a306c6272e977d394e3e203cae25d) (client) + + +- the payload `CONNECT_ERROR` packet is now an object instead of a plain string + +Commits: [54bf4a4](https://github.com/socketio/socket.io/commit/54bf4a44e9e896dfb64764ee7bd4e8823eb7dc7b) (server) and [0939395](https://github.com/socketio/socket.io-client/commit/09393952e3397a0c71f239ea983f8ec1623b7c21) (client) + + +### Difference between v4 and v3 {#difference-between-v4-and-v3} + +The 4th revision of the Socket.IO protocol is used in Socket.IO v1 (`v1.0.3` was released in June 2014) and v2 (`v2.0.0` was released in May 2017). + +The details of the revision can be found here: https://github.com/socketio/socket.io-protocol/tree/v4 + +It is built on top of the 3rd revision of [the Engine.IO protocol](https://github.com/socketio/engine.io-protocol) (hence the `EIO=3` query parameter). + +List of changes: + +- add a `BINARY_ACK` packet type + +Previously, an `ACK` packet was always treated as if it may contain binary objects, with recursive search for such +objects, which could hurt performance. + +Reference: https://github.com/socketio/socket.io-parser/commit/ca4f42a922ba7078e840b1bc09fe3ad618acc065 + +### Difference between v3 and v2 {#difference-between-v3-and-v2} + +The 3rd revision of the Socket.IO protocol is used in early Socket.IO v1 versions (`socket.io@1.0.0...1.0.2`) (released in May 2014). + +The details of the revision can be found here: https://github.com/socketio/socket.io-protocol/tree/v3 + +List of changes: + +- remove the usage of msgpack to encode packets containing binary objects (see also [299849b](https://github.com/socketio/socket.io-parser/commit/299849b00294c3bc95817572441f3aca8ffb1f65)) + +### Difference between v2 and v1 {#difference-between-v2-and-v1} + +List of changes: + +- add a `BINARY_EVENT` packet type + +This was added during the work towards Socket.IO 1.0, in order to add support for binary objects. The `BINARY_EVENT` +packets were encoded with [msgpack](https://msgpack.org/). + +### Initial revision {#initial-revision} + +This first revision was the result of the split between the Engine.IO protocol (low-level plumbing with WebSocket / HTTP +long-polling, heartbeat) and the Socket.IO protocol. It was never included in a Socket.IO release, but paved the way for +the next iterations. + +## Test suite {#test-suite} + +The test suite in the [`test-suite/`](https://github.com/socketio/socket.io-protocol/tree/main/test-suite) directory lets you check the compliance of a server implementation. + +Usage: + +- in Node.js: `npm ci && npm test` +- in a browser: simply open the `index.html` file in your browser + +For reference, here is expected configuration for the JavaScript server to pass all tests: + +```js +import { Server } from "socket.io"; + +const io = new Server(3000, { + pingInterval: 300, + pingTimeout: 200, + maxPayload: 1000000, + cors: { + origin: "*" + } +}); + +io.on("connection", (socket) => { + socket.emit("auth", socket.handshake.auth); + + socket.on("message", (...args) => { + socket.emit.apply(socket, ["message-back", ...args]); + }); + + socket.on("message-with-ack", (...args) => { + const ack = args.pop(); + ack(...args); + }) +}); + +io.of("/custom").on("connection", (socket) => { + socket.emit("auth", socket.handshake.auth); +}); +``` diff --git a/i18n/zh-CN/docusaurus-theme-classic/footer.json b/i18n/zh-CN/docusaurus-theme-classic/footer.json index d919c4ad..6f0166e4 100644 --- a/i18n/zh-CN/docusaurus-theme-classic/footer.json +++ b/i18n/zh-CN/docusaurus-theme-classic/footer.json @@ -1,70 +1,70 @@ { "link.title.Documentation": { "message": "文档", - "description": "The title of the footer links column with title=Documentation in the footer" + "description": "页脚中标题为 Documentation 的链接列的标题" }, "link.title.Community": { "message": "社区", - "description": "The title of the footer links column with title=Community in the footer" + "description": "页脚中标题为 Community 的链接列的标题" }, "link.title.More": { "message": "更多", - "description": "The title of the footer links column with title=More in the footer" + "description": "页脚中标题为 More 的链接列的标题" }, "link.item.label.Get started": { "message": "教程", - "description": "The label of footer link with label=Get started linking to /get-started/chat" + "description": "页脚中标签为 Get started 的链接,链接到 /get-started/chat" }, "link.item.label.Documentation": { "message": "文档", - "description": "The label of footer link with label=Documentation linking to /docs/v4/" + "description": "页脚中标签为 Documentation 的链接,链接到 /docs/v4/" }, "link.item.label.Examples": { "message": "示例", - "description": "The label of footer link with label=Examples linking to /get-started/" + "description": "页脚中标签为 Examples 的链接,链接到 /get-started/" }, "link.item.label.Server API": { "message": "服务器端API", - "description": "The label of footer link with label=Server API linking to /docs/v4/server-api/" + "description": "页脚中标签为 Server API 的链接,链接到 /docs/v4/server-api/" }, "link.item.label.Client API": { "message": "客户端API", - "description": "The label of footer link with label=Client API linking to /docs/v4/client-api/" + "description": "页脚中标签为 Client API 的链接,链接到 /docs/v4/client-api/" }, "link.item.label.Blog": { "message": "博客", - "description": "The label of footer link with label=Blog linking to /blog" + "description": "页脚中标签为 Blog 的链接,链接到 /blog" }, "link.item.label.GitHub": { "message": "GitHub", - "description": "The label of footer link with label=GitHub linking to https://github.com/socketio/socket.io" + "description": "页脚中标签为 GitHub 的链接,链接到 https://github.com/socketio/socket.io" }, "link.item.label.Slack": { "message": "Slack", - "description": "The label of footer link with label=Slack linking to https://socketio-slackin.herokuapp.com/" + "description": "页脚中标签为 Slack 的链接,链接到 https://socketio-slackin.herokuapp.com/" }, "link.item.label.Stack Overflow": { "message": "Stack Overflow", - "description": "The label of footer link with label=Stack Overflow linking to https://stackoverflow.com/questions/tagged/socket.io" + "description": "页脚中标签为 Stack Overflow 的链接,链接到 https://stackoverflow.com/questions/tagged/socket.io" }, "link.item.label.Twitter": { "message": "Twitter", - "description": "The label of footer link with label=Twitter linking to https://twitter.com/SocketIO" + "description": "页脚中标签为 Twitter 的链接,链接到 https://twitter.com/SocketIO" }, "link.item.label.Become a sponsor": { "message": "成为赞助商", - "description": "The label of footer link with label=Become a sponsor linking to https://opencollective.com/socketio" + "description": "页脚中标签为 Become a sponsor 的链接,链接到 https://opencollective.com/socketio" }, "link.item.label.CDN": { "message": "CDN", - "description": "The label of footer link with label=CDN linking to https://cdn.socket.io" + "description": "页脚中标签为 CDN 的链接,链接到 https://cdn.socket.io" }, "link.item.label.Admin UI": { "message": "Admin UI", - "description": "The label of footer link with label=Admin UI linking to https://admin.socket.io" + "description": "页脚中标签为 Admin UI 的链接,链接到 https://admin.socket.io" }, "copyright": { - "message": "Copyright © 2022 Socket.IO", - "description": "The footer copyright" + "message": "版权所有 © 2022 Socket.IO", + "description": "页脚版权信息" } -} +} \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-theme-classic/navbar.json b/i18n/zh-CN/docusaurus-theme-classic/navbar.json index 2bfa1679..1de93892 100644 --- a/i18n/zh-CN/docusaurus-theme-classic/navbar.json +++ b/i18n/zh-CN/docusaurus-theme-classic/navbar.json @@ -1,62 +1,50 @@ { "title": { - "message": "Socket.IO", - "description": "The title in the navbar" + "message": "Socket.IO" }, - "item.label.Documentation": { - "message": "文档", - "description": "Navbar item with label Documentation" + "item.label.Docs": { + "message": "文档" + }, + "item.label.Guide": { + "message": "指南" }, "item.label.Examples": { - "message": "示例", - "description": "Navbar item with label Examples" + "message": "示例" }, "item.label.Server API": { - "message": "服务器端API", - "description": "Navbar item with label Server API" + "message": "服务器端API" }, "item.label.Client API": { - "message": "客户端API", - "description": "Navbar item with label Client API" + "message": "客户端API" }, "item.label.Community": { - "message": "社区", - "description": "Navbar item with label Community" + "message": "社区" }, "item.label.Resources": { - "message": "更多", - "description": "Navbar item with label Resources" + "message": "更多" }, "item.label.Blog": { - "message": "博客", - "description": "Navbar item with label Blog" + "message": "博客" }, "item.label.GitHub": { - "message": "GitHub", - "description": "Navbar item with label GitHub" + "message": "GitHub" }, "item.label.Slack": { - "message": "Slack", - "description": "Navbar item with label Slack" + "message": "Slack" }, "item.label.Stack Overflow": { - "message": "Stack Overflow", - "description": "Navbar item with label Stack Overflow" + "message": "Stack Overflow" }, "item.label.Twitter": { - "message": "Twitter", - "description": "Navbar item with label Twitter" + "message": "Twitter" }, "item.label.Become a sponsor": { - "message": "成为赞助商", - "description": "Navbar item with label Become a sponsor" + "message": "成为赞助商" }, "item.label.CDN": { - "message": "CDN", - "description": "Navbar item with label CDN" + "message": "CDN" }, "item.label.Admin UI": { - "message": "Admin UI", - "description": "Navbar item with label Admin UI" + "message": "Admin UI" } -} +} \ No newline at end of file