Hot questions for Using Transmission Control Protocol in spring boot

Question:

I'm looking for an example to connect TCP through sping boot without xml(spring-integration).

I got the following snippet from How to create a Tcp Connection in spring boot to accept connections? URL.

in this example, just main method alone enough to connect tcp. why other beans and the transformer are declared here?

Is it wrong? Instead of using simple Java socket client to accept the response, I would like to integrate with Spring. But no suitable examples available using Java DSL.

Could you please help?

package com.example;

import java.net.Socket;

import javax.net.SocketFactory;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.annotation.Transformer;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.ip.tcp.TcpReceivingChannelAdapter;
import org.springframework.integration.ip.tcp.connection.AbstractServerConnectionFactory;
import org.springframework.integration.ip.tcp.connection.TcpNetServerConnectionFactory;
import org.springframework.integration.transformer.ObjectToStringTransformer;
import org.springframework.messaging.MessageChannel;

@SpringBootApplication
public class So39290834Application {

    public static void main(String[] args) throws Exception {
        ConfigurableApplicationContext context = SpringApplication.run(So39290834Application.class, args);
        Socket socket = SocketFactory.getDefault().createSocket("localhost", 9999);
        socket.getOutputStream().write("foo\r\n".getBytes());
        socket.close();
        Thread.sleep(1000);
        context.close();
    }

    @Bean
    public TcpNetServerConnectionFactory cf() {
        return new TcpNetServerConnectionFactory(9999);
    }

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

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

    @ServiceActivator(inputChannel = "serviceChannel")
    public void service(String in) {
        System.out.println(in);
    }

}

Answer:

This application is both the client and the server.

That question was specifically about how to write the server side (accept the connection), using Spring Integration.

The main() method is simply a test that connects to the server side. It uses standard Java sockets APIs; it could also have been written to use Spring Integration components on the client side.

BTW, you don't have to use XML to write a Spring Integration application, you can configure it with annotations, or use the Java DSL. Read the documentation.

EDIT

Client/Server Example using Java DSL

@SpringBootApplication
public class So54057281Application {

    public static void main(String[] args) {
        SpringApplication.run(So54057281Application.class, args);
    }

    @Bean
    public IntegrationFlow server() {
        return IntegrationFlows.from(Tcp.inboundGateway(
                    Tcp.netServer(1234)
                        .serializer(codec()) // default is CRLF
                        .deserializer(codec()))) // default is CRLF
                .transform(Transformers.objectToString()) // byte[] -> String
                .<String, String>transform(p -> p.toUpperCase())
                .get();
    }

    @Bean
    public IntegrationFlow client() {
        return IntegrationFlows.from(MyGateway.class)
                .handle(Tcp.outboundGateway(
                    Tcp.netClient("localhost", 1234)
                        .serializer(codec()) // default is CRLF
                        .deserializer(codec()))) // default is CRLF
                .transform(Transformers.objectToString()) // byte[] -> String
                .get();
    }

    @Bean
    public AbstractByteArraySerializer codec() {
        return TcpCodecs.lf();
    }

    @Bean
    @DependsOn("client")
    ApplicationRunner runner(MyGateway gateway) {
        return args -> {
            System.out.println(gateway.exchange("foo"));
            System.out.println(gateway.exchange("bar"));
        };
    }

    public interface MyGateway {

        String exchange(String out);

    }

}

result

FOO
BAR

Question:

I have noticed that whenever I send a request on a TcpOutboundGateway configured to connect to a Host/Port that does not exist/is unavailable, the processing of the request will hang for 1 minute and 15 seconds before throwing the following exception…

java.net.ConnectException: Operation timed out (Connection timed out)

I am hoping to reduce that 1 minute and 15 second wait time but have not been able to find the correct way to do so. Up to this point I have tried setting the remoteTimeout, requestTimeout and sendTimeout on the TcpOutboundGateway and none of those seem to do the trick.

Is it possible to configure the TcpOutboundGateway in way that will reduce the amount of time it waits before throwing that exception? If possible, how so?

Note: In regards to "a Host/Port that does not exist", an example of this would be running my application locally with the TcpOutboundGateway configured to send to 127.0.0.3:2000


Answer:

The connection problem is not a gateway responsibility. It is really about a ConnectionFactory.

See AbstractClientConnectionFactory:

/**
 * Set the connection timeout in seconds. Defaults to 60.
 * @param connectTimeout the timeout.
 * @since 5.2
 */
public void setConnectTimeout(int connectTimeout) {

Althoug, I see that this one might not be available for you since we are going yet to release 5.2 only the next week.

For the current 5.1.x version you need to extend a TcpNetClientConnectionFactory and its createSocket() to provide an appropriate connection timeout:

public class MyTcpNetClientConnectionFactory extends TcpNetClientConnectionFactory {

    protected Socket createSocket(String host, int port) throws IOException {
        Socket socket = getTcpSocketFactorySupport().getSocketFactory().createSocket();
        socket.connect(new InetSocketAddress(host, port), 1000);
        return socket;
    }
}

}

Question:

How to accept TCP protocol into a Spring Boot application? HTTP works over TCP but I need to accept lower-level protocol.

Can anyone give me a route or hint on how to implement it?

Is it the same as for HTTP connection with Controller, Service, Repository architecture?


Answer:

You will need to write your program using Socket programming by implementing CommandLineRunner interface of Spring Boot. Socket part of the program connect to a server using TCP and start accepting the request, then transform the binary message as per your required format and do the rest of the processing.

And if you want to create a server, then use ServerSocket in your program that will accept connections from client.