Требуется ли разбивать данные перед их отправкой через SignalR?

Как правильно разделить и отправить большие данные через SignalR, чтобы избежать ошибок и обеспечить корректную обработку клиентом и сервером?

В моем клиент-серверном решении, использующем SignalR, у меня есть клиенты, сервер и админ. Обычные клиенты отправляют данные серверу, который затем перенаправляет их админу(ам). Однако, при отправке больших данных клиентом серверу (более 32kb) происходит ошибка. Я решил эту проблему, добавив на сервере следующий код:

services.AddSignalR(o =>
{
    o.MaximumReceiveMessageSize = null;
});
Все работает нормально сейчас, но я хотел убедиться, что это правильное решение. Я хотел бы узнать, спасает ли данная опция даже в случае отправки, например, гигабайта данных?

Я думаю, что возможным решением будет разбивка отправки данных по частям, но у меня есть определенные сложности. Данные, которые я отправляю, представляют собой коллекцию классов (например, IReadOnlyCollection details), внутри которой также есть коллекции разных размеров.

public class ChangeSet
{
    // свойства
}

public class Details
{
    // свойства

    public List<ChangeSet> Added { get; set; }

    public List<ChangeSet> Removed { get; set; }
}

В такой ситуации я думаю, что нужно делить данные по байтам. Изначально клиент должен получить от сервера максимальный размер отправляемых данных после аутентификации. Затем перед каждой отправкой данных клиент должен отправить их не как IReadOnlyCollection details, а как byte[] chunk, int current, int total. При получении данных сервер должен сохранять их и, если current = total, попытаться преобразовать byte[] chunks в IReadOnlyCollection details. Если происходит ошибка, то все полученные данные от данного клиента удаляются из списка. Если преобразование данных происходит успешно, данные могут быть отправлены админу в том же порядке, и админ может их корректно обработать. При отключении клиента все полученные данные по ConnectionId также должны быть удалены.
  • 27 октября 2023 г. 15:59
Ответы на вопрос 2
Разбивка данных перед их отправкой через SignalR зависит от размера данных и ограничений, установленных на сервере и клиенте. SignalR имеет ограничение на максимальный размер сообщения, которое по умолчанию составляет 32 КБ. Ограничение может быть изменено на сервере, как вы сделали, установив o.MaximumReceiveMessageSize = null в методе ConfigureServices() при конфигурировании SignalR.

Однако, следует помнить, что SignalR сообщение будет загружаться в память целиком, поэтому отправка очень больших данных может привести к исчерпанию памяти на сервере или клиенте. Если вы планируете отправлять гигабайты данных, рекомендуется разбить эти данные на меньшие куски или использовать другие методы передачи данных, например, передачу файлов.

В вашем случае, если данные, которые вы отправляете, могут быть разделены на куски, то вы можете разделить данные на пакеты и отправить их по частям. Клиент может отправлять эти пакеты, указывая текущую позицию и общее количество пакетов. Сервер может принимать эти пакеты и объединять их при получении последнего пакета.

Вы правильно сделали предположение о том, что данные нужно отправлять как byte[] chunk, int current, int total. При получении пакетов на сервере, он может сохранять их и, когда будет получен последний пакет (current = total), объединять эти пакеты в один и преобразовывать их в нужный формат данных.

При отключении клиента или окончании передачи данных, вам также нужно удалить сохраненные данные, чтобы избежать накопления ненужных данных.

Но важно помнить, что при передаче больших объемов данных посредством SignalR или любой другой технологии, следует оценить возможные ограничения, связанные с размером сообщений, потреблением памяти и производительностью, и решить соответствующие проблемы в вашем приложении.
На ASP.NET Core есть ограничение на размер запроса в 50 Мб. Но мы увеличили лимит, чтобы работало для наших нужд. Однако возникает проблема с аллокацией памяти. Когда отправляются большие чанки данных, объекты аллоцируются в LOH (куча больших объектов). И они могут оставаться там долгое время и не выгружаться.

Для решения этой проблемы предлагаю разбить отправку на части. Если отправляемые данные представлены коллекцией классов, можно сначала отправлять данные о добавленных элементах, а затем данные об удаленных элементах. Если это не решает проблему, то можно стримить каждый элемент по отдельности. Например, сначала отправлять количество элементов, а потом по одному элементу за раз.

Главная идея заключается в том, чтобы не сериализовать и отправлять один большой объект целиком. 

Если у вас есть файл размером 1 Гб, лучше отправлять его чанками и сохранять на диск. При получении данных с сервера, также читайте файл по частям, а не загружайте его полностью в память.
Похожие вопросы