Какое отношение handle_call()
имеет к gen_tcp
?
gen_tcp
можно настроить для чтения из сокета и send()
сообщений в любой процесс, называемый :gen_tcp.accept()
, так называемый управляющий процесс , который может быть gen_server;но gen_server
обрабатывает сообщения, отправленные в его почтовый ящик с handle_info()
- не handle_call()
.handle_call()
обрабатывает сообщения, отправленные :gen_server.call()
.
Вот пример :
defmodule TcpServer do
use GenServer
require Logger
def start_link() do
ip = Application.get_env :gen_tcp, :ip, {127,0,0,1}
port = Application.get_env :gen_tcp, :port, 6666
IO.puts "gen_tcp is listening on port: #{port}"
GenServer.start_link(__MODULE__, {ip, port},[])
end
def init({ip, port}) do
{:ok, listen_socket}= :gen_tcp.listen(
port,
[:binary, {:packet,0}, {:active,true}, {:ip,ip}]
)
{:ok, socket } = :gen_tcp.accept listen_socket
{:ok, %{ip: ip, port: port, socket: socket} }
end
def handle_call({:tcp, _socket, packet}, state) do
Logger.info("handle_call(): Received packet: #{inspect packet}")
{:reply, {:ok, packet}, state}
end
def handle_info({:tcp,socket,packet},state) do
Logger.info "handle_info(:tcp, ...): incoming packet: #{inspect packet}"
:gen_tcp.send(socket, "****#{packet}*****")
{:noreply,state}
end
def handle_info({:tcp_closed, _socket}, state) do
Logger.info("handle_info({:tcp_closed, ...): Client closed socket.")
{:noreply, state}
end
def handle_info({:tcp_error, socket, reason}, state) do
Logger.info("Connection closed due to #{reason}: #{socket}")
{:noreply,state}
end
end
Для запуска сервера:
~/elixir_programs/tcp_server$ iex -S mix
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.8.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> TcpServer.start_link()
gen_tcp is listening on port: 6666
В другом окне терминала:
~/python_programs$ cat 5.py
import socket
port = 6666
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", port))
s.send("\x02Test\x03".encode())
print(s.recv(1024))
~/python_programs$ p36 5.py
b'****\x02Test\x03*****'
~/python_programs$
Как видите, не было дублирования символов \x02
и \x03
.
Вернитесь в окно сервера:
{:ok, #PID<0.123.0>}
iex(2)>
21:54:18.363 [info] handle_info(:tcp, ...): incoming packet: <<2, 84, 101, 115, 116, 3>>
21:54:18.369 [info] handle_info({:tcp_closed, ...): Client closed socket.
У вас есть другой процесс, который контролирует процесс и вызывает :gen_server.call({:tcp, socket, packet})
?
Кстати, в этом коде:
def handle_info({:tcp,socket,packet},state) do
packet
может быть целым пакетом, 1/2 пакета или 1/10 пакета.Так работают сокеты.Опция конфигурации {packet, 0}
сообщает gen_tcp
, что на передней части пакета нет заголовка длины (0 байт), а {packet, 1|2|4}
сообщает gen_tcp
, что длина пакета содержится в первом байте, первом2 байта или первые 4 байта соответственно.Таким образом, gen_tcp
может прочитать первые 1|2|4
байтов, чтобы получить длину пакета, скажем, L, затем продолжить чтение из сокета, пока он не получит L байтов.Затем gen_tcp упаковывает части в одно сообщение и отправляет все сообщение в gen_server
.С другой стороны, когда вы указываете {packet, 0}
, вы говорите gen_tcp
, что пакет не имеет заголовка длины;и поскольку сокет разделяет один пакет на неопределенное количество порций, gen_tcp
не знает, где находится конец пакета, поэтому единственная опция gen_tcp
- это чтение порции из сокета и отправка порциисерверу gen_server;затем прочитайте другой кусок и отправьте его на gen_server и т. д., и т. д. Это означает, что gen_server должен выяснить, где находится конец пакета.
Следовательно, ваш сервер и ваш клиент должны договориться опротокол для оповещения об окончании пакета;и handle_info(:tcp, ...)
придется хранить фрагменты пакета в state
(или в дБ) до тех пор, пока он не прочитает все фрагменты, составляющие пакет.
Один протокол, который вы можете использовать дляСигналом окончания пакета является: клиент закрывает сокет.В этом случае будет вызываться
def handle_info({:tcp_closed, _socket}, state)
, и внутри этого функционального предложения вы можете собрать фрагменты, хранящиеся в state
(или в db), в законченное сообщение, а затем сделать все, что необходимо, например,отправьте сообщение обратно клиенту.
Если вы используете STX
и ETX
в качестве начального сообщения, протокола конечного сообщения, тогда handle_info(:tcp, ...)
все равно придется искать символ STX
для сигналачто он должен начать хранить чанки в state
, а когда handle_info(:tcp, ...)
находит чанк с символом ETX
, вам нужно собрать все сообщение.