Миграция с 3.x на 4.0
Выпуск 4.0.0 добавляет довольно много новых функций, которые подробно описаны ниже, но также содержит несколько изменений API, нарушающих совместимость (отсюда и значительная переработка).
Обратите внимание, что эти изменения, нарушающие совместимость, влияют только на API на стороне сервера. Протокол Socket.IO сам по себе не обновлялся, поэтому клиент v3 сможет подключиться к серверу v4 и наоборот. Кроме того, режим совместимости (allowEIO3: true) по-прежнему доступен между клиентом Socket.IO v2 и сервером Socket.IO v4.
Вот полный список изменений:
Изменения, нарушающие совместимость
io.to() теперь неизменяемый
Ранее трансляция в заданную комнату (вызовом io.to()) изменяла экземпляр io, что могло привести к неожиданному поведению, например:
io.to("room1");
io.to("room2").emit(/* ... */); // also sent to room1
// or with async/await
io.to("room3").emit("details", await fetchDetails()); // random behavior: maybe in room3, maybe to all clients
Вызов io.to() (или любого другого модификатора трансляции) теперь возвращает неизменяемый экземпляр.
Примеры:
const operator1 = io.to("room1");
const operator2 = operator1.to("room2");
const operator3 = socket.broadcast;
const operator4 = socket.to("room3").to("room4");
operator1.emit(/* ... */); // only to clients in "room1"
operator2.emit(/* ... */); // to clients in "room1" or in "room2"
operator3.emit(/* ... */); // to all clients but the sender
operator4.emit(/* ... */); // to clients in "room3" or in "room4" but the sender
wsEngine параметр
Формат параметра wsEngine был обновлен для устранения следующей ошибки:
Critical dependency: the request of a dependency is an expression
при сборке сервера с помощью webpack.
Ранее:
const io = require("socket.io")(httpServer, {
wsEngine: "eiows"
});
После:
const io = require("socket.io")(httpServer, {
wsEngine: require("eiows").Server
});
Конфигурация
Обеспечение совместимости с клиентами Swift v15
До версии 16.0.0 клиент Swift не включал параметр запроса EIO, и сервер Socket.IO v3 по умолчанию выводил EIO=4.
Поэтому клиент Swift v15 не мог подключиться к серверу, даже при включенном режиме совместимости (allowEIO3: true), если явно не был указан параметр запроса:
let manager = SocketManager(socketURL: URL(string: "http://localhost:8080")!, config: [ .log(true), .connectParams(["EIO": "3"]) ]) let socket = manager.defaultSocket
Сервер Socket.IO v4 теперь будет выводить EIO=3, если параметр запроса EIO не указан.
Значение по умолчанию параметра pingTimeout было увеличено
Значение по умолчанию для pingTimeout (используемое в механизме определения отключения) было обновлено с 60000 до 5000 в socket.io@2.1.0 (март 2018 г.).
Тогда это объяснялось так:
Некоторые пользователи сталкивались с большими задержками между отключением на стороне сервера и на стороне клиента. Событие "disconnect" долгое время не срабатывало в браузере, вероятно, из-за задержки таймера. Отсюда и изменения.
Однако текущее значение (5 с) вызывало неожиданные отключения при отправке большого объема данных по медленному сетевому соединению, так как оно предотвращает обмен пакетами ping-pong между клиентом и сервером. Это также может произойти, когда синхронная задача блокирует сервер более чем на 5 секунд.
Поэтому новое значение (20 с) кажется хорошим балансом между быстрым обнаружением отключения и толерантностью к различным задержкам.
Новые функции
Возможность исключения определенных комнат при трансляции
Благодаря отличной работе Sebastiaan Marynissen, теперь вы можете исключить конкретную комнату при трансляции:
io.except("room1").emit(/* ... */); // to all clients except the ones in "room1"
io.to("room2").except("room3").emit(/* ... */); // to all clients in "room2" except the ones in "room3"
socket.broadcast.except("room1").emit(/* ... */); // to all clients except the ones in "room1" and the sender
socket.except("room1").emit(/* ... */); // same as above
socket.to("room4").except("room5").emit(/* ... */); // to all clients in "room4" except the ones in "room5" and the sender
Возможность передачи массива в io.to()
Метод to() теперь принимает массив комнат.
Ранее:
const rooms = ["room1", "room2", "room3"];
for (const room of rooms) {
io.to(room);
}
// broadcast to clients in "room1", "room2" or "room3"
// WARNING !!! this does not work anymore in v4, see the breaking change above
io.emit(/* ... */);
После:
io.to(["room1", "room2", "room3"]).emit(/* ... */); socket.to(["room1", "room2", "room3"]).emit(/* ... */);
Дополнительные служебные методы
Были добавлены некоторые (долгожданные) методы:
-
socketsJoin: заставляет соответствующие экземпляры сокетов присоединиться к указанным комнатам
// make all Socket instances join the "room1" room
io.socketsJoin("room1");
// make all Socket instances of the "admin" namespace in the "room1" room join the "room2" room
io.of("/admin").in("room1").socketsJoin("room2");
-
socketsLeave: заставляет соответствующие экземпляры сокетов покинуть указанные комнаты
// make all Socket instances leave the "room1" room
io.socketsLeave("room1");
// make all Socket instances of the "admin" namespace in the "room1" room leave the "room2" room
io.of("/admin").in("room1").socketsLeave("room2");
-
disconnectSockets: заставляет соответствующие экземпляры сокетов отключиться
// make all Socket instances disconnect
io.disconnectSockets();
// make all Socket instances of the "admin" namespace in the "room1" room disconnect
io.of("/admin").in("room1").disconnectSockets();
// this also works with a single socket ID
io.of("/admin").in(theSocketId).disconnectSockets();
-
fetchSockets: возвращает соответствующие экземпляры сокетов
// return all Socket instances of the main namespace
const sockets = await io.fetchSockets();
// return all Socket instances of the "admin" namespace in the "room1" room
const sockets = await io.of("/admin").in("room1").fetchSockets();
// this also works with a single socket ID
const sockets = await io.in(theSocketId).fetchSockets();
Переменная sockets в примере выше представляет собой массив объектов, содержащих подмножество обычных методов класса Socket:
for (const socket of sockets) {
console.log(socket.id);
console.log(socket.handshake);
console.log(socket.rooms);
socket.emit(/* ... */);
socket.join(/* ... */);
socket.leave(/* ... */);
socket.disconnect(/* ... */);
}
Эти методы имеют ту же семантику, что и трансляция, и к ним применяются те же фильтры:
io.of("/admin").in("room1").except("room2").local.disconnectSockets();
Что приводит к отключению всех экземпляров Socket с пространством имен "admin"
- в комнате "room1" (
in("room1")илиto("room1")) - кроме тех, что в комнате "room2" (
except("room2")) - и только на текущем сервере Socket.IO (
local)
отключения.
Типизированные события
Благодаря отличной работе Maxime Kjaer, пользователи TypeScript теперь могут типизировать события, отправляемые между клиентом и сервером.
Сначала вы объявляете сигнатуру каждого события:
interface ClientToServerEvents {
noArg: () => void;
basicEmit: (a: number, b: string, c: number[]) => void;
}
interface ServerToClientEvents {
withAck: (d: string, cb: (e: number) => void) => void;
}
И теперь вы можете использовать их на стороне клиента:
import { io, Socket } from "socket.io-client";
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io();
socket.emit("noArg");
socket.emit("basicEmit", 1, "2", [3]);
socket.on("withAck", (d, cb) => {
cb(4);
});
Теперь ваша IDE должна правильно определять тип каждого аргумента:
Аналогично на стороне сервера (ServerToClientEvents и ClientToServerEvents обратны):
import { Server } from "socket.io";
const io = new Server<ClientToServerEvents, ServerToClientEvents>(3000);
io.on("connect", (socket) => {
socket.on("noArg", () => {
// ...
});
socket.on("basicEmit", (a, b, c) => {
// ...
});
socket.emit("withAck", "42", (e) => {
console.log(e);
});
});
По умолчанию события не типизированы, и аргументы будут определены как any.
autoUnref параметр
И наконец, благодаря отличной работе KC Erb, был добавлен параметр autoUnref.
При установке autoUnref в значение true (по умолчанию false) клиент Socket.IO позволит программе завершиться, если в системе событий нет других активных таймеров/TCP-сокетов (даже если клиент подключен):
const socket = io({
autoUnref: true
});
Примечание: этот параметр применим только к клиентам Node.js.
Известные проблемы миграции
cannot get emit of undefined
Следующее выражение:
socket.to("room1").broadcast.emit(/* ... */);
работало в Socket.IO v3, но теперь считается недопустимым, так как флаг broadcast бесполезен, поскольку метод to("room1") уже переводит экземпляр Socket в режим трансляции.
// VALID
socket.broadcast.emit(/* ... */); // to all clients but the sender
socket.to("room1").emit(/* ... */); // to clients in "room1" but the sender
// VALID (but useless 'broadcast' flag)
socket.broadcast.to("room1").emit(/* ... */);
// INVALID
socket.to("room1").broadcast.emit(/* ... */);
© 2014–2021 Automattic
Licensed under the MIT License.
https://socket.io/docs/v4/migrating-from-3-x-to-4-0