Как я могу определить полный ответ при получении большого количества пакетов в одновременных запросах TCP

У меня есть одно TCP-соединение с сервером, но, возможно, у меня есть несколько запросов одновременно. Большую часть времени ответ будет настолько большим, что я буду постоянно получать много фрагментов данных. Я могу проверить длину данных, чтобы определить, что это КОНЕЦ ПОТОКА. Но при множественных запросах иногда пакеты "смешиваются" с другими запросами, что вызывает много сбоев.

Например,

для обычного запроса:

  • -> запрос №1 1/3
  • -> запрос №1 2/3
  • -> запрос №1 3/3
  • -> запрос №1 выполнен

в реальной жизни:

  • -> запрос №1 1/3
  • -> запрос №1 2/3
  • -> запрос № 2 1/3
  • -> запрос № 2 2/3
  • -> отказ в какой-то момент

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

Как я могу решить эту проблему с TCP в целом?

Я показываю часть кода ниже (на случай, если кто-то знает erlang и elixir)

# Create TCP connection
{:ok, socket} =
      :gen_tcp.connect(host_charlist, port, [:binary, active: true, keepalive: true])

# Send request
def handle_call({:send, msg}, from, state) do
  :ok = :gen_tcp.send(state.socket, msg)
  new_state = %{state | from: from, msg: ""}

  {:noreply, new_state}
end

# When it receive packet
def handle_info({:tcp, _socket, msg}, state) do
  new_state = %{state | msg: state.msg <> msg}
  current_msg_size = byte_size(new_state.msg)
  defined_msg_size = Response.get_body_size(new_state.msg) # this msg size can read from the first packet's header

  cond do
    current_msg_size == defined_msg_size ->
      GenServer.reply(new_state.from, {:ok, new_state.msg})
      {:noreply, %{new_state | msg: ""}}

    current_msg_size > defined_msg_size ->
      GenServer.reply(new_state.from, {:error, "Message size exceed."})
      {:noreply, %{new_state | msg: ""}}

    true ->
      {:noreply, new_state}
  end
end

Я предполагаю, что вы не контролируете ни сервер, ни протокол, вы просто хотите написать клиент. Если это так, вам необходимо изучить спецификацию этого протокола. У каждого протокола, который может иметь несколько запросов по одному и тому же TCP-соединению (DNS, HTTP/2), есть свой способ обработки.

bortzmeyer 09.05.2022 18:36

Это просто TCP в двоичном виде. Да, я не контролирую серверы, для меня это как черный ящик.

CW LOK 10.05.2022 06:17

«TCP в двоичном виде» на самом деле ничего не значит. Я хочу сказать: если вы не контролируете сервер, у вас есть хотя бы спецификация/документация протокола? В противном случае это означает реверс-инжиниринг, который часто бывает долгим и болезненным. (Комментарий Чен Ю «Это зависит от ваших подробных спецификаций протоколов» также хорошо читается).

bortzmeyer 10.05.2022 09:27
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
59
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

На уровне TCP в соединении request и response не существуют, это одна трубка, передающая байты с одной стороны на другую по порядку.

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

Возможные решения включают в себя:

  1. Сериализация
  2. Кадрирование: вы формируете ответы и каким-то образом гарантируете, что кадры отправляются полностью без чередования на сервере, затем ваш получатель может проверять каждый кадр (возможно, охватывающий несколько receive) и назначать его соответствующему запросу.
  3. Одно соединение для каждого запроса: пусть ОС обрабатывает чередование в сети за счет сокета и рукопожатия каждый раз.

Не могли бы вы объяснить больше о кадрировании?

CW LOK 10.05.2022 06:17

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

bortzmeyer 10.05.2022 09:30

Идентификатора нет. Заголовок идет только с первым пакетом, остальное — это только фрагменты тела (без заголовка).

CW LOK 10.05.2022 11:06

Хорошо, позвольте мне попробовать другой способ: как в этом протоколе клиент должен демультиплексировать ответы, соответствующие различным запросам? Как видно из ответа Хосе М., существует несколько способов, но мы не знаем, как это делается в этом конкретном (неуказанном?) протоколе.

bortzmeyer 10.05.2022 17:37

Вы можете изменить параметр TCP с active = true на active = false или на active = once

:gen_tcp.connect(host_charlist, port, [:binary, active: true, keepalive: true])

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

1、получить звонок от верхнего уровня, обработать ваш клиентский запрос, установить active = false, дождаться ответа сервера.

2. Старайтесь не получать только связанные сообщения, игнорируйте их в очереди сообщений процесса или сохраняйте их в буфере.

3、получив ответ сервера, обработайте его, затем установите active = once.

4、если тайм-аут, установите active = once.

https://www.erlang.org/doc/man/gen_tcp.html#controlling_process-2

controlling_process(Socket, Pid) -> ok | {error, Reason} Types Socket = socket() Pid = pid() Reason = closed | not_owner | badarg | inet:posix() Assigns a new controlling process Pid to Socket. The controlling process is the process that receives messages from the socket. If called by any other process than the current controlling process, {error, not_owner} is returned. If the process identified by Pid is not an existing local pid, {error, badarg} is returned. {error, badarg} may also be returned in some cases when Socket is closed during the execution of this function.

If the socket is set in active mode, this function will transfer any messages in the mailbox of the caller to the new controlling process. If any other process is interacting with the socket while the transfer is happening, the transfer may not work correctly and messages may remain in the caller's mailbox. For instance changing the sockets active mode before the transfer is complete may cause this.

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

José M 09.05.2022 17:06

Когда в active = false, сообщение tcp будет заблокировано, пока вы не вызовете gen_tcp:receive для получения сообщения. Так что управлять программой легко. когда вы получаете несвязанное сообщение (чередующееся сообщение), игнорируйте его или позволяйте другому процессу сделать это, просто сосредоточьтесь на текущем вызове. Это делает вещь легкой в ​​обращении. Это один из способов решения проблемы, а не только этот.

Chen Yu 10.05.2022 00:48

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

José M 10.05.2022 02:58

Я думаю, что одна из проблем заключается в том, что трудно определить, к какому запросу относится предстоящий пакет, я не могу игнорировать большинство пакетов, особенно я обрабатываю параллельные запросы.

CW LOK 10.05.2022 06:12

Это связано с самим протоколом, сообщение должно иметь заголовок и часть тела, при получении сообщения проанализируйте заголовок и найдите подсказку, используйте dict сохранить подсказку и запустить таймер для более позднего связанного сообщения. Если тайм-аут или связанное сообщение завершено, удалите форму подсказки dict и удалите связанное timer.

Chen Yu 10.05.2022 06:18

Хорошо, но как я узнаю, какая подсказка относится к какому заголовку?

CW LOK 10.05.2022 08:25

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

Chen Yu 10.05.2022 08:31

Другие вопросы по теме