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

huangapple go评论74阅读模式
英文:

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

问题

这是我的注解配置类:

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;
import org.springframework.integration.ip.dsl.TcpInboundGatewaySpec;
import org.springframework.integration.ip.tcp.connection.TcpServerConnectionFactorySpec;

@Log4j2
@Configuration
@EnableIntegration
public class TcpServerSocketConfiguration {

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

    @Bean
    public IntegrationFlow server(ServerSocketHandler serverSocketHandler) {
        TcpServerConnectionFactorySpec connectionFactory = 
            Tcp.netServer(serverSocketPort) 
              .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();
    }
}

处理从服务器套接字接收的消息的类:

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());
        // 在这里实现处理传入消息的有用操作...
        return message.getPayload().toString();
    } 
}

更新 1

我实现了自定义的序列化/反序列化:

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);
    }
}

在客户端发送消息后,会调用自定义的序列化器,但内容始终为空。我不知道为什么... 序列化器需要很长时间从流中读取所有字节,在最后它是空的。该过程一直在重复,所以我认为我无意中构建了一个无限循环...

更新 2

我捕获了客户端和服务器套接字之间的通信:
看起来我卡在握手阶段,因此没有有效载荷...

(图片已省略)

如果有人能够帮助我解决这个问题,我将非常感谢。如果您需要更多信息,请随时让我知道。

提前致谢!

英文:

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. 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(&quot;${socket.port}&quot;)
    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&lt;?&gt; 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
  2. https://docs.spring.io/spring-integration/docs/current/reference/html/ip.html#note-nio
  3. https://stackoverflow.com/questions/54057281/spring-boot-tcp-client

and a bunch of sites more... but nothing helped me How to create a working TCP Server socket in spring boot and how to handle the incoming message?

UPDATE 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&lt;byte[]&gt;, 
Deserializer&lt;byte[]&gt; {


@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

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...

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

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!

答案1

得分: 5

好的,以下是已翻译的内容:


好的,经过几天的分析和编码,我找到了我处理使用Spring Integration进行TCP套接字通信的最佳解决方案。对于其他正在与相同问题苦苦挣扎的开发者,以下是我迄今为止所做的。

这个类包含了一个基于注解的我认为有效的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;

/**
 * 基于注解的Spring配置
 */
@Configuration
@EnableIntegration
@IntegrationComponentScan
public class TcpServerSocketConfiguration {

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

    /**
     * 只有当回复包含由连接工厂插入到原始消息中的ip_connectionId头时,才将回复消息路由到连接。
     */
    @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方法 ...

    @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;
    }
}

这个类包含了一个自定义的序列化器和反序列化器。

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;

/**
 * 用于传入和/或传出消息的自定义序列化器。
 */
@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("反序列化消息 {}", new String(message, StandardCharsets.UTF_8));
        return message;
    }

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

在以下类中,您可以实现一些业务逻辑来处理传入的...

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 在这里实现一些业务逻辑
        return msg;
    }
}

...和传出的消息。

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) {
        // 在这里实现一些业务逻辑
        return msg;
    }
}

希望能对您有所帮助。;-)

英文:

Well, after a few days of analysing and coding, I found the best solution for me to handle TCP socket communications using spring integration. 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

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(&quot;${socket.port}&quot;)
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 = &quot;toTcp&quot;)
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(&quot;localhost&quot;, 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&lt;byte[]&gt;, Deserializer&lt;byte[]&gt; {
@NotNull
@Override
public byte[] deserialize(InputStream inputStream) throws IOException {
byte[] message = new byte[0];
if (inputStream.available() &gt; 0) {
message = inputStream.readAllBytes();
}
log.debug(&quot;Deserialized message {}&quot;, new String(message, StandardCharsets.UTF_8));
return message;
}
@Override
public void serialize(@NotNull byte[] message, OutputStream outputStream) throws IOException {
log.info(&quot;Serializing {}&quot;, 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 = &quot;toTcp&quot;)
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 = &quot;toTcp&quot;)
public byte[] handleMessage(byte[] msg) {
// implement some business logic here
return msg;
}
}

Hope it helps. How to create a working TCP Server socket in spring boot and how to handle the incoming message?

答案2

得分: 3

以下是翻译好的部分:

你是如何与这个服务器进行通信的?默认情况下,连接工厂被配置为需要以CRLF(例如Telnet)结束输入。如果客户端使用其他方式来表示消息结尾,你必须配置一个不同的反序列化器。

另外,你的方法签名是不正确的;它应该是:

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

这对我来说在使用Telnet时效果很好:

$ telnet localhost 1234
尝试 ::1...
已连接到 localhost。
退出字符为 '^]'。
foo
FOO
^]
telnet> 退出
连接已关闭。

这是一个适用于只使用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
英文:

How are you communicating with this server? By default the connection factory is configured to require the input to be terminated by CRLF (e.g. 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 localhost 1234
Trying ::1...
Connected to localhost.
Escape character is &#39;^]&#39;.
foo
FOO
^]
telnet&gt; quit
Connection closed.

And here is a version that works with just LF (e.g. 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

huangapple
  • 本文由 发表于 2020年10月17日 02:08:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/64394292.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定