У меня есть одно TCP-соединение с сервером, но, возможно, у меня есть несколько запросов одновременно. Большую часть времени ответ будет настолько большим, что я буду постоянно получать много фрагментов данных. Я могу проверить длину данных, чтобы определить, что это КОНЕЦ ПОТОКА. Но при множественных запросах иногда пакеты "смешиваются" с другими запросами, что вызывает много сбоев.
Например,
для обычного запроса:
в реальной жизни:
На данный момент моя единственная мысль состоит в том, чтобы сделать это последовательно, выполняя запрос один за другим. Однако это не может полностью решить мою проблему, потому что иногда я получаю подписанные события, которые не контролируются.
Как я могу решить эту проблему с 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 в двоичном виде. Да, я не контролирую серверы, для меня это как черный ящик.
«TCP в двоичном виде» на самом деле ничего не значит. Я хочу сказать: если вы не контролируете сервер, у вас есть хотя бы спецификация/документация протокола? В противном случае это означает реверс-инжиниринг, который часто бывает долгим и болезненным. (Комментарий Чен Ю «Это зависит от ваших подробных спецификаций протоколов» также хорошо читается).





На уровне TCP в соединении request и response не существуют, это одна трубка, передающая байты с одной стороны на другую по порядку.
Чтобы обрабатывать чередование по одному соединению, вы должны обрабатывать его на один уровень вверх по стеку.
Возможные решения включают в себя:
receive) и назначать его соответствующему запросу.Не могли бы вы объяснить больше о кадрировании?
Во многих сетевых протоколах сообщения «обрамлены», каждое сообщение структурировано по сети с такими полями, как длина сообщения, заголовок сообщения с определенным идентификатором сообщения и т. д. Предполагается, что письменная спецификация описывает это кадрирование. Опять же, если вы ничего не знаете о формате сообщений, ваша задача будет сложной, потому что вам нужно будет реконструировать (найти спецификацию по битам), что является сложной задачей.
Идентификатора нет. Заголовок идет только с первым пакетом, остальное — это только фрагменты тела (без заголовка).
Хорошо, позвольте мне попробовать другой способ: как в этом протоколе клиент должен демультиплексировать ответы, соответствующие различным запросам? Как видно из ответа Хосе М., существует несколько способов, но мы не знаем, как это делается в этом конкретном (неуказанном?) протоколе.
Вы можете изменить параметр 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 сообщения поступают в процесс в том же порядке, в котором они поступают в сокет, поэтому использование вместо этого пассивного режима не решает проблему чередования.
Когда в active = false, сообщение tcp будет заблокировано, пока вы не вызовете gen_tcp:receive для получения сообщения. Так что управлять программой легко. когда вы получаете несвязанное сообщение (чередующееся сообщение), игнорируйте его или позволяйте другому процессу сделать это, просто сосредоточьтесь на текущем вызове. Это делает вещь легкой в обращении. Это один из способов решения проблемы, а не только этот.
Проблема, как я понимаю из фрагмента, заключается в том, что байты в соединении чередуются из-за одновременных запросов. Используемый активный или пассивный режим не влияет на порядок байтов в сокете.
Я думаю, что одна из проблем заключается в том, что трудно определить, к какому запросу относится предстоящий пакет, я не могу игнорировать большинство пакетов, особенно я обрабатываю параллельные запросы.
Это связано с самим протоколом, сообщение должно иметь заголовок и часть тела, при получении сообщения проанализируйте заголовок и найдите подсказку, используйте dict сохранить подсказку и запустить таймер для более позднего связанного сообщения. Если тайм-аут или связанное сообщение завершено, удалите форму подсказки dict и удалите связанное timer.
Хорошо, но как я узнаю, какая подсказка относится к какому заголовку?
Это зависит от вашей подробной спецификации протоколов, если протокол предназначен для многоканальной связи, он должен включать такие «идентификатор», «замечание», «сеанс», «код» и т. д., такая информация может использоваться для различения различных запросов. Если протокол предназначен для одноканальной связи, это будет проблемой, вам следует изменить протокол, чтобы добавить такую информацию.
Я предполагаю, что вы не контролируете ни сервер, ни протокол, вы просто хотите написать клиент. Если это так, вам необходимо изучить спецификацию этого протокола. У каждого протокола, который может иметь несколько запросов по одному и тому же TCP-соединению (DNS, HTTP/2), есть свой способ обработки.