sockets - Elixir 套接字重复 STX 和 ETX 作为响应
问题描述
我有使用这种方法的套接字服务器:
@impl true
def handle_call({:tcp, socket, packet}, state) do
Logger.info("Received packet: \x02#{packet}\x03 and send response")
{:reply, {:ok, packet}, state}
end
我在 python 中编写了将“\x02Test\x03”发送到套接字的脚本:
import socket
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打印的响应是 b'\x02\x02Test\x03\x03'
解决方案
有什么handle_call()
关系gen_tcp
?
gen_tcp
可以配置为从套接字和send()
消息读取到任何称为的进程,即:gen_tcp.accept()
所谓的控制进程,可以是 gen_server;但是 a使用--notgen_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
如您所见, and\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 数据包。这就是套接字的工作方式。config选项{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
(或数据库中)的块组装成完整的消息,然后执行任何必要的操作,例如将消息发送回客户端。
如果你使用STX
andETX
作为你的开始消息,结束消息协议,那么handle_info(:tcp, ...)
仍然需要寻找STX
字符来表示它应该开始在 中存储块state
,并且当handle_info(:tcp, ...)
找到一个包含ETX
字符的块时,你必须组装整个信息。
推荐阅读
- migration - 如何使用注册模型将 MlFlow 实验从一个 Databricks 工作区迁移到另一个工作区?
- reactjs - 扩展一个 React 样式的组件
- reactjs - 使用 bundleResourceIO 为 React Native 加载自定义 TensorFlow.js 模型失败
- python - 在 macOS 应用程序安装程序期间安装 Python 和 openCV
- android - React-Native 中 android 的 Face Id 身份验证
- r - R - 用 plotly 绘制反应子集
- javascript - 如何在动态添加的 js 文件中引用代码?
- python - 如何在 Python 中为 Postgres 处理空闲事务?
- google-cloud-platform - 如何删除导入的sql程序
- yocto - Yocto(Zeus) Perf 构建失败