【问题标题】:Spring integration TCP server not receiving messagesSpring集成TCP服务器不接收消息
【发布时间】:2018-03-01 00:03:00
【问题描述】:

我正在尝试创建一个 TCP 服务器,该服务器在端口 5002 上接受来自外部程序的消息。但是,它没有从外部程序接收消息。

@Bean
public TcpReceivingChannelAdapter inbound(AbstractServerConnectionFactory cf) {
   TcpReceivingChannelAdapter adapter = new TcpReceivingChannelAdapter();
   adapter.setConnectionFactory(cf);
   adapter.setOutputChannel(tcpIn());
   return adapter;
 }

@Bean
public MessageChannel tcpIn() {
    return new DirectChannel();
}

@Bean
@Transformer(inputChannel = "tcpIn", outputChannel = "serviceChannel")
public ObjectToStringTransformer transformer() {
    return new ObjectToStringTransformer();
}

@ServiceActivator(inputChannel = "serviceChannel")
public void messageToService(String in) {
    // Message received
}

@Bean
public AbstractServerConnectionFactory serverConnectionFactory() {
    TcpNetServerConnectionFactory tcpNetServerConnectionFactory = new TcpNetServerConnectionFactory(5002);
    tcpNetServerConnectionFactory.setSoTimeout(5000);
    tcpNetServerConnectionFactory.setMapper(new TimeoutMapper());
    return tcpNetServerConnectionFactory;
}

为了验证我的 TCP 服务器是否正常工作,我像这样使用 telnet,程序确实收到了文本“hello”。

telnet 192.168.1.2 5002
Trying 192.168.1.2...
Connected to 192.168.1.2.
Escape character is '^]'.
hello

设置wireshark我可以看到计算机正在5002端口接收来自外部程序(我期待的)的消息。为什么我的程序无法接收这些消息?

最终解决方案更新:

由于有效载荷没有停止线,我必须按照@Artem Bilan 的描述实现我自己的反序列化器。我使用“~”字符来表示客户端的行尾。

@Bean
public AbstractServerConnectionFactory serverConnectionFactory() {
    TcpNetServerConnectionFactory tcpNetServerConnectionFactory = new TcpNetServerConnectionFactory(tcpPort);
    tcpNetServerConnectionFactory.setSoTimeout(0);
  tcpNetServerConnectionFactory.setDeserializer(endOfLineSerializer());
    tcpNetServerConnectionFactory.setSerializer(endOfLineSerializer());
    tcpNetServerConnectionFactory.setMapper(new TimeoutMapper());
    return tcpNetServerConnectionFactory;
}

我实现的示例序列化程序:

public class EndOfLineSerializer extends AbstractPooledBufferByteArraySerializer {

private static final char MANUAL_STOP_LINE = '~';
private static final char AUTO_STOP_LINE = '\t';
private static final byte[] CRLF = "\r\n".getBytes();

/**
 * Reads the data in the inputStream to a byte[]. Data must be terminated
 * by a single byte. Throws a {@link SoftEndOfStreamException} if the stream
 * is closed immediately after the terminator (i.e. no data is in the process of
 * being read).
 */
@Override
protected byte[] doDeserialize(InputStream inputStream, byte[] buffer) throws IOException {
    int n = 0;
    int bite;

    try {
        while (true) {

            try {
                bite = inputStream.read();
            } catch (SocketTimeoutException e) {
                bite = -1;
            }

            if (bite < 0) {
                // Payload complete
                break;
            }

            if ((n > 0 && bite == '\n' && buffer[n - 1] == '\r') || bite == this.MANUAL_STOP_LINE || bite == this.AUTO_STOP_LINE) {
                break;
            }

            buffer[n++] = (byte) bite;
            if (n >= this.maxMessageSize) {
                throw new IOException("Terminator not found before max message length: " + this.maxMessageSize);
            }
        }
        return copyToSizedArray(buffer, n);
    } catch (IOException e) {
        publishEvent(e, buffer, n);
        throw e;
    } catch (RuntimeException e) {
        publishEvent(e, buffer, n);
        throw e;
    }
}

/**
 * Writes the byte[] to the stream and appends the CRLF.
 */
@Override
public void serialize(byte[] bytes, OutputStream outputStream) throws IOException {
    outputStream.write(bytes);
    outputStream.write(this.CRLF);
    }
}

【问题讨论】:

    标签: spring spring-boot tcp spring-integration


    【解决方案1】:

    TcpNetServerConnectionFactory 默认使用ByteArrayCrLfSerializer,这就是消息分隔符:

    private static final byte[] CRLF = "\r\n".getBytes();
    

    因此,您应该确保您的客户最后发送带有正确符号的消息。

    有许多开箱即用的序列化程序供您选择:

    https://docs.spring.io/spring-integration/docs/5.0.3.RELEASE/reference/html/ip.html#tcp-connection-factories

    或者您可以实现自己的 Deserializer 并注入到 serverConnectionFactory bean 定义中。

    【讨论】:

    • 愚蠢的问题,但是当我使用wireshark时,它怎么知道消息分隔符是什么?假设我有 5 个不同的程序都使用不同的分隔符类型,那么由于它们需要不同的序列化程序,我是否需要为每个程序设置不同的 TCP 端口?
    • 我认为wireshark只是不做任何分隔,它只是流式传输字节。您对不同序列化程序的看法听起来不像您的责任。您编写了一个服务器端,因此您决定了您希望如何期望数据的规则。在客户端拦截传出流量并添加或删除额外字节以满足您的服务器要求要容易得多。
    • 我从中接收数据的一个程序的文档很差。开箱即用的序列化程序都不能处理它们发送数据的方式。我更新了上面的图像以显示它如何连接、发送数据,然后不关闭连接。我在初始标题上看到了 STX (0x02) 标志。
    • 如何从这个 Wireshark 数据中确定正确的消息分隔符?我可以看到 ByteArraySingleTerminatorSerializer 与正确的终止字符一起使用
    【解决方案2】:

    the documentation;向下滚动到

    TCP 是一种流协议;这意味着必须为通过 TCP 传输的数据提供某种结构,以便接收方可以将数据划分为离散的消息。连接工厂配置为使用(反)序列化程序在消息有效负载和通过 TCP 发送的位之间进行转换。这是通过分别为入站和出站消息提供反序列化器和序列化器来实现的。提供了许多标准(反)序列化程序。

    并阅读有关标准反序列化程序的信息。根据您的配置,标准解串器正在等待终止 \r\n (CRLF)。

    Telnet 附加 CRLF,这就是它起作用的原因。

    【讨论】:

    • 糟糕!同样的答案又在同一时间。 :-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-03
    • 1970-01-01
    • 2016-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-06
    相关资源
    最近更新 更多