简体   繁体   English

如何在 spring boot 中创建一个有效的 TCP Server 套接字以及如何处理传入的消息?

[英]How to create a working TCP Server socket in spring boot and how to handle the incoming message?

I have tried to implement a TCP server socket with spring integration in an allready existing spring boot application, but I am facing a problem and this problem drives me crazy... The client is sending a message (a byte array) to the server and timesout.我试图在一个现有的 spring boot 应用程序中实现一个带有 spring 集成的 TCP 服务器套接字,但我面临一个问题,这个问题让我发疯......客户端正在向服务器发送一条消息(一个字节数组),然后超时。 That's it.就是这样。 I am not receiving any exceptions from the server.我没有收到来自服务器的任何异常。 It seems I have provided the wrong port or somthing but after checking the port, I am sure it is the right one.似乎我提供了错误的端口或某些东西,但在检查端口后,我确定它是正确的。

This is my annotation based configuration class:这是我基于注释的配置类:

import home.brew.server.socket.ServerSocketHandler;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.ip.dsl.Tcp;

@Log4j2
@Configuration
@EnableIntegration
public class TcpServerSocketConfiguration {

    @Value("${socket.port}")
    private int serverSocketPort;

    @Bean
    public IntegrationFlow server(ServerSocketHandler serverSocketHandler) {
        TcpServerConnectionFactorySpec connectionFactory = 
            Tcp.netServer(socketPort) 
              .deserializer(new CustomSerializerDeserializer())
              .serializer(new CustomSerializerDeserializer())
              .soTcpNoDelay(true);

        TcpInboundGatewaySpec inboundGateway = 
           Tcp.inboundGateway(connectionFactory);

        return IntegrationFlows
         .from(inboundGateway)
         .handle(serverSocketHandler::handleMessage)
         .get();
    }

    @Bean
    public ServerSocketHandler serverSocketHandler() {
        return new ServerSocketHandler();
    }
}

I wanted to make the receive functionality work before I try to send an answer, so that's why have a minimal configuration.我想让接收功能在我尝试发送答案之前工作,所以这就是为什么有一个最小的配置。

And the following class should process the received message from the server socket下面的类应该处理从服务器套接字接收到的消息

import lombok.extern.log4j.Log4j2;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.MessagingException;

@Log4j2
public class ServerSocketHandler {

    public String handleMessage(Message<?> message, MessageHeaders messageHeaders) {
        log.info(message.getPayload());
        // TODO implement something useful to process the incoming message here...
        return message.getPayload().toString();
    } 
}

The handler method from above was never invoked even once!上面的处理程序方法甚至从未被调用过! I have googled for some example implementations or tutorials but I haven't found anyhing what worked for me.我在谷歌上搜索了一些示例实现或教程,但我没有找到任何对我有用的东西。 I allready tried the implementations of these sites:我已经尝试过这些网站的实现:

  1. https://vispud.blogspot.com/2019/03/how-to-implement-simple-echo-socket.html https://vispud.blogspot.com/2019/03/how-to-implement-simple-echo-socket.html
  2. https://docs.spring.io/spring-integration/docs/current/reference/html/ip.html#note-nio https://docs.spring.io/spring-integration/docs/current/reference/html/ip.html#note-nio
  3. Spring Boot TCP Client Spring Boot TCP 客户端

and a bunch of sites more... but nothing helped me :-(还有一堆网站......但没有任何帮助我:-(

UPDATE 1更新 1

I have implemented a custom serializer/deserializer:我已经实现了一个自定义的序列化器/反序列化器:

import lombok.Data;
import lombok.extern.log4j.Log4j2;
import org.springframework.core.serializer.Deserializer;
import org.springframework.core.serializer.Serializer;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

@Log4j2
@Data
public class CustomSerializerDeserializer implements Serializer<byte[]>, 
Deserializer<byte[]> {


@Override
public byte[] deserialize(InputStream inputStream) throws IOException {
    return inputStream.readAllBytes();
}

@Override
public void serialize(byte[] object, OutputStream outputStream) throws IOException {
    outputStream.write(object);
}
}

After the client have sent a message, the custom serializer is invoked but the content ist always empty.客户端发送消息后,会调用自定义序列化程序,但内容始终为空。 I have no idea why.... The serializer needs a lot of time to read all bytes from the stream and in the end it is empty.我不知道为什么.... 序列化器需要大量时间从流中读取所有字节,最后它是空的。 The procedure is repeating all the time, so I think I have build an infinty loop by accident...这个过程一直在重复,所以我想我偶然建立了一个无限循环......

UPDATE 2更新 2

I have captured the communication between Client and server socket: It looks like I am stuck in the handshake and therefore there is no payload...我已经捕获了客户端和服务器套接字之间的通信:看起来我陷入了握手中,因此没有有效负载......

捕获客户端和服务器套接字之间的通信

So if anybody could help me out with this, I would be very thankful and if you need some more information, just let me know.因此,如果有人能帮我解决这个问题,我将非常感激,如果您需要更多信息,请告诉我。

Thanks in advance!提前致谢!

How are you communicating with this server?您如何与此服务器通信? By default the connection factory is configured to require the input to be terminated by CRLF (eg Telnet).默认情况下,连接工厂配置为要求输入由 CRLF(例如 Telnet)终止。 You have to configure a different deserializer if your client uses something else to indicate a message end.如果您的客户端使用其他东西来指示消息结束,您必须配置不同的解串器。

Also, your method signature is incorrect;另外,您的方法签名不正确; it should be:它应该是:

public String handleMessage(byte[] message, MessageHeaders messageHeaders) {
    String string = new String(message);
    System.out.println(string);
    return string.toUpperCase();
}

This works fine for me with Telnet:这对我来说很好用 Telnet:

$ telnet localhost 1234
Trying ::1...
Connected to localhost.
Escape character is '^]'.
foo
FOO
^]
telnet> quit
Connection closed.

And here is a version that works with just LF (eg netcat):这是一个仅适用于 LF(例如 netcat)的版本:

@Bean
public IntegrationFlow server(ServerSocketHandler serverSocketHandler) {
    return IntegrationFlows.from(Tcp.inboundGateway(
            Tcp.netServer(1234)
                .deserializer(TcpCodecs.lf())
                .serializer(TcpCodecs.lf())))
            .handle(serverSocketHandler::handleMessage)
            .get();
}
$ nc localhost 1234
foo
FOO
^C

Well, after a few days of analysing and coding, I found the best solution for me to handle TCP socket communications using spring integration.好吧,经过几天的分析和编码,我找到了使用 spring 集成处理 TCP 套接字通信的最佳解决方案。 For other developers who are struggling with the same problems.对于正在努力解决相同问题的其他开发人员。 Here is what I've done so far.这是我到目前为止所做的。

This class contains a - for me working - annotation based TCP socket connection configuration此类包含一个 - 对我来说工作 - 基于注释的 TCP 套接字连接配置

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.ip.IpHeaders;
import org.springframework.integration.ip.tcp.TcpInboundGateway;
import org.springframework.integration.ip.tcp.TcpOutboundGateway;
import org.springframework.integration.ip.tcp.connection.AbstractClientConnectionFactory;
import org.springframework.integration.ip.tcp.connection.AbstractServerConnectionFactory;
import org.springframework.integration.ip.tcp.connection.TcpNetClientConnectionFactory;
import org.springframework.integration.ip.tcp.connection.TcpNetServerConnectionFactory;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.web.context.request.RequestContextListener;

/**
 * Spring annotation based configuration
 */
@Configuration
@EnableIntegration
@IntegrationComponentScan
public class TcpServerSocketConfiguration {

    public static final CustomSerializerDeserializer SERIALIZER = new CustomSerializerDeserializer();
    @Value("${socket.port}")
    private int socketPort;

    /**
     * Reply messages are routed to the connection only if the reply contains the ip_connectionId header
     * that was inserted into the original message by the connection factory.
     */
    @MessagingGateway(defaultRequestChannel = "toTcp")
    public interface Gateway {
        void send(String message, @Header(IpHeaders.CONNECTION_ID) String connectionId);
    }

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

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

    @Bean
    public AbstractServerConnectionFactory serverCF() {
        TcpNetServerConnectionFactory serverCf = new TcpNetServerConnectionFactory(socketPort);
        serverCf.setSerializer(SERIALIZER);
        serverCf.setDeserializer(SERIALIZER);
        serverCf.setSoTcpNoDelay(true);
        serverCf.setSoKeepAlive(true);
        // serverCf.setSingleUse(true);
        // final int soTimeout = 5000;
        // serverCf.setSoTimeout(soTimeout);
        return serverCf;
    }

    @Bean
    public AbstractClientConnectionFactory clientCF() {

        TcpNetClientConnectionFactory clientCf = new TcpNetClientConnectionFactory("localhost", socketPort);
        clientCf.setSerializer(SERIALIZER);
        clientCf.setDeserializer(SERIALIZER);
        clientCf.setSoTcpNoDelay(true);
        clientCf.setSoKeepAlive(true);
        // clientCf.setSingleUse(true);
        // final int soTimeout = 5000;
        // clientCf.setSoTimeout(soTimeout);
        return clientCf;
    }

    @Bean
    public TcpInboundGateway tcpInGate() {
        TcpInboundGateway inGate = new TcpInboundGateway();
        inGate.setConnectionFactory(serverCF());
        inGate.setRequestChannel(fromTcp());
        inGate.setReplyChannel(toTcp());
        return inGate;
    }

    @Bean
    public TcpOutboundGateway tcpOutGate() {
        TcpOutboundGateway outGate = new TcpOutboundGateway();
        outGate.setConnectionFactory(clientCF());
        outGate.setReplyChannel(toTcp());
        return outGate;
    }

This class contains a custom serializer and deserialiser此类包含自定义序列化器和反序列化器

import lombok.extern.log4j.Log4j2;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.serializer.Deserializer;
import org.springframework.core.serializer.Serializer;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.nio.charset.StandardCharsets;

/**
* A custom serializer for incoming and/or outcoming messages.
*/
@Log4j2
public class CustomSerializerDeserializer implements Serializer<byte[]>, Deserializer<byte[]> {

    @NotNull
    @Override
    public byte[] deserialize(InputStream inputStream) throws IOException {
        byte[] message = new byte[0];
        if (inputStream.available() > 0) {
            message = inputStream.readAllBytes();
        }
        log.debug("Deserialized message {}", new String(message, StandardCharsets.UTF_8));
        return message;
    }

    @Override
    public void serialize(@NotNull byte[] message, OutputStream outputStream) throws IOException {
        log.info("Serializing {}", new String(message, StandardCharsets.UTF_8));
        outputStream.write(message);
        outputStream.flush();
    }
}

In the following classes you can implement some buisness logic to process incoming ...在以下类中,您可以实现一些业务逻辑来处理传入的...

import lombok.extern.log4j.Log4j2;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.stereotype.Component;

@Log4j2
@Component
@MessageEndpoint
public class ClientSocketHandler {

    @ServiceActivator(inputChannel = "toTcp")
    public byte[] handleMessage(byte[] msg) {
        // TODO implement some buisiness logic here
        return msg;
    }
}

and outgoing messages.和传出的消息。

import lombok.extern.log4j.Log4j2;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.stereotype.Component;

@Log4j2
@Component
@MessageEndpoint
public class ClientSocketHandler {

    @ServiceActivator(inputChannel = "toTcp")
    public byte[] handleMessage(byte[] msg) {
        // implement some business logic here
        return msg;
    }
}

Hope it helps.希望能帮助到你。 ;-) ;-)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM