【问题标题】:Elixir socket duplicate STX and ETX in responseElixir 套接字重复 STX 和 ETX 作为响应
【发布时间】:2019-06-27 00:41:31
【问题描述】:

我有使用这种方法的套接字服务器:

@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'

【问题讨论】:

  • 你用什么来设置套接字? :gen_tcp? :ranch?还有什么?
  • 我正在使用 :gen_tcp
  • 你能展示你的 Elixir 代码的其余部分吗?
  • 发送 \x02Test\x03,然后你打印whatever你收到环绕 another我>\x02...\x03。这里没有问题要解决。
  • @user207421,该操作声称 response 具有重复项,而不是日志输出。响应应该在与日志输出不同的窗口中,但我最初也认为操作也在查看错误的窗口。真正的问题是:为什么有人会在日志输出周围加上数据包中的相同字符?

标签: sockets elixir


【解决方案1】:

handle_call()gen_tcp 有什么关系?

gen_tcp 可以配置为从套接字和send() 消息读取到任何称为:gen_tcp.accept() 的进程,即所谓的控制进程,它可以是gen_server;但是gen_server 使用handle_info() 处理发送到其邮箱的消息——而不是handle_call()handle_call() 处理:gen_server.call() 发送的消息。

这里是an example

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(或数据库中)中的块组装成完整的消息,然后执行任何必要的操作,例如将消息发送回客户端。

如果您使用STXETX 作为您的开始消息、结束消息协议,那么handle_info(:tcp, ...) 仍然需要寻找STX 字符来表示它应该开始在state 中存储块,当handle_info(:tcp, ...) 找到一个包含ETX 字符的块时,您必须组装整个消息。

【讨论】:

  • '因此,您的服务器和您的客户端必须就一个协议来表示数据包的结束':例如 STX/ETX?他已经在使用了吗?
猜你喜欢
  • 2015-03-26
  • 2021-01-13
  • 2012-10-14
  • 2013-11-24
  • 1970-01-01
  • 1970-01-01
  • 2016-06-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多