Как я могу определить полный ответ при получении большого количества пакетов в одновременных запросах 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

🤔 А знаете ли вы, что...
Erlang - функциональный язык программирования, созданный в 1986 году в компании Ericsson.


59
2

Ответы:

Решено

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

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

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

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

Вы можете изменить параметр 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.