Spring Integration TCP入站/出站适配器与非Spring客户端

时间:2017-01-10 12:23:30

标签: java spring tcp spring-integration

我正在设计一个使用Spring MVC Web应用程序运行的系统。它将用于向现有(非Spring)应用程序发送和接收TCP命令,该应用程序用于控制某些网络数据过滤器。我只是在玩Spring Integration TCP的东西(我是SI和Spring的新手)试图理解它,但我很难找到一个基本的例子。

我需要通信是异步的,因为服务器和客户端可以随时发送数据,它可能需要也可能不需要回复。所以我相信我需要使用的是协作通道适配器而不是网关。

我的演示程序应该等待客户端连接,然后接收一系列String消息,并回复它。用户还可以键入要从服务器端发送的内容。

它基于示例中的tcp-client-server示例。我想通过Java配置而不是XML来完成所有工作。

我希望下面的演示要做的是将传入的数据回送给客户端。

这是服务器配置类:

@Configuration()
@EnableIntegration
@IntegrationComponentScan
public class ServerConfiguration implements ApplicationListener<TcpConnectionEvent> {

private final int port = SocketUtils.findAvailableServerSocket(5000);

@MessagingGateway(defaultRequestChannel="toTcp")
public interface Gateway {
    String send(String in);
}

@Bean
public AbstractServerConnectionFactory serverFactory() {
    System.out.println("serverFactory");
    AbstractServerConnectionFactory connectionFactory = new TcpNetServerConnectionFactory(port);
    return connectionFactory;
}

@Bean MessageChannel toTcp() {
    System.out.println("creating toTcp DirectChannel");
    DirectChannel dc = new DirectChannel();
    dc.setBeanName("toTcp");

    return dc;
}

@Bean
public MessageChannel fromTcp() {
    System.out.println("creating fromTcp DirectChannel");
    DirectChannel dc = new DirectChannel();
    dc.setBeanName("fromTcp");

    return dc;
}

// Inbound channel adapter. This receives the data from the client
@Bean
public TcpReceivingChannelAdapter inboundAdapter(AbstractServerConnectionFactory connectionFactory) {
    System.out.println("Creating inbound adapter");
    TcpReceivingChannelAdapter inbound = new TcpReceivingChannelAdapter();

    inbound.setConnectionFactory(connectionFactory);
    inbound.setOutputChannel("fromTcp");

    return inbound;
}

// Outbound channel adapter. This sends the data to the client
@Bean
@ServiceActivator(inputChannel="toTcp")
public TcpSendingMessageHandler outboundAdapter(AbstractServerConnectionFactory connectionFactory) {
    System.out.println("Creating outbound adapter");
    TcpSendingMessageHandler outbound = new TcpSendingMessageHandler();
    outbound.setConnectionFactory(connectionFactory);
    return outbound;
}

// Endpoint example 
@MessageEndpoint
public static class Echo {

    // Server
    @Transformer(inputChannel="fromTcp", outputChannel="toEcho")
    public String convert(byte[] bytes) {
        System.out.println("convert: " + new String(bytes));
        return new String(bytes);
    }

    // Server
    @ServiceActivator(inputChannel="toEcho", outputChannel="toTcp")
    public String upCase(String in) {
        System.out.println("upCase: " + in.toUpperCase());
        return in.toUpperCase();
    }
}

@Override
public void onApplicationEvent(TcpConnectionEvent event) {
    System.out.println("Got TcpConnectionEvent: source=" + event.getSource() + 
            ", id=" + event.getConnectionId()); 
}   
}

这是主要课程:

@SpringBootApplication
@IntegrationComponentScan
@EnableMessageHistory
public class SpringIntegrationTcpTest {

    @Autowired
    private ServerConfiguration.Gateway gateway;

    public String send(String data) {
        return gateway.send(data);
    }


public static void main(String[] args) throws IOException {

    ConfigurableApplicationContext context = SpringApplication.run(SpringIntegrationTcpTest.class, args);

    SpringIntegrationTcpTest si = context.getBean(SpringIntegrationTcpTest.class);

    final AbstractServerConnectionFactory crLfServer = context.getBean(AbstractServerConnectionFactory.class);

    final Scanner scanner = new Scanner(System.in);
    System.out.print("Waiting for server to accept connections on port " + crLfServer.getPort());
    TestingUtilities.waitListening(crLfServer, 100000L);
    System.out.println("running.\n\n");

    System.out.println("Please enter some text and press <enter>: ");
    System.out.println("\tNote:");
    System.out.println("\t- Entering FAIL will create an exception");
    System.out.println("\t- Entering q will quit the application");
    System.out.print("\n");

    while (true) {

        final String input = scanner.nextLine();

        if("q".equals(input.trim())) {
            break;
        }
        else {
            final String result = si.send(input);
            System.out.println(result);
        }
    }

    scanner.close();
    context.close();
}
}

这是虚拟客户端类:

public class TcpClient {

    public TcpClient() {
    }

    private void connect(String host, int port) throws InterruptedException {
        Socket socket = null;
        Writer out = null;
        BufferedReader in = null;

        try {
            System.out.print("Connecting to " + host + " on port " + port + " ... ");
            socket = new Socket(host, port);
            System.out.println("connected.");

            System.out.println("sending 100 messages");

            out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            for (int i = 1; i < 100; ++i) {
                String msg =  "hello" + i;

                out.write(msg+"\r\n");
                out.flush();
                //System.out.print(msg+"\r\n");

                System.out.println("Waiting for message ...");

                StringBuffer str = new StringBuffer();
                int c;
                while ((c = in.read()) != -1) {
                    str.append((char) c);
                }       

                String response = str.toString();
                System.out.println("got message: " + response);

                Thread.sleep(1000);
            }


        } catch (IOException e) {

            System.err.println("Test ended with an exception: " + port + ", " + e.getMessage());

        } finally {
            try {
                socket.close();
                out.close();
                //in.close();

            } catch (Exception e) {
                // swallow exception
            }

        }       

    }

    public static void main(String[] args) throws InterruptedException {

        String host = args[0];
        int port = Integer.parseInt(args[1]);
        new TcpClient().connect(host, port);
    }

}

我花了很多时间玩网关等,并使用telnet进行工作,并使用网关从客户端接收消息。我不能做的是使用通道适配器使其正常工作。

当客户端启动时,它将发送由服务器接收并打印到控制台的字符串。似乎没有发回任何内容,因为客户端只是坐在“等待消息...”。从服务器端发送内容时,我得到以下异常:

Please enter some text and press <enter>:
        Note:
        - Entering FAIL will create an exception
        - Entering q will quit the application

Got TcpConnectionEvent: source=org.springframework.integration.ip.tcp.connection.TcpNetConnection@67162888, id=127.0.0.1:50940:5000:052bf55b-526a-4ea9-bfe3-8ecc573239a3
convert: hello1
upCase: HELLO1
qwe
2017-01-10 12:09:13.995 ERROR 7296 --- [           main] o.s.i.ip.tcp.TcpSendingMessageHandler    : Unable to find outbound socket for GenericMessage [payload=qwe, headers={replyChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel@6b5894c8, history=serverConfiguration$Gateway,toTcp,serverConfiguration.outboundAdapter.serviceActivator.handler,outboundAdapter, errorChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel@6b5894c8, id=a4ea72f2-6b12-379b-1b15-f75b821f0b7f, timestamp=1484050153995}]
Exception in thread "main" org.springframework.messaging.MessageHandlingException: Unable to find outbound socket
        at org.springframework.integration.ip.tcp.TcpSendingMessageHandler.handleMessageInternal(TcpSendingMessageHandler.java:123)
        at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:127)

所以问题在于没有出站套接字。那么定义了出站套接字在哪里?还有什么我做错了?

2 个答案:

答案 0 :(得分:2)

  1. 您不需要在频道上调用setBeanName - 框架会自动为您执行此操作。

  2. 您的网关正在等待回复,而toTcp频道已连接到未返回回复的频道适配器 - 在此方案中使用void返回类型

  3.   

    o.s.i.ip.tcp.TcpSendingMessageHandler:无法找到出站套接字

    要向连接的客户端发送任意消息,您需要通过设置ip_connectionId标头告知适配器将客户端发送给哪个客户端(其中IpHeaders.CONNECTION_ID为<{1}}。

    您需要设置该标题 - 您可以通过TcpConnectionOpenEvent捕获它并通过网关将其添加到标题中...

    void send(@Payload String data, @Header(IpHeaders.CONNECTION_ID) String connectionId);
    

答案 1 :(得分:0)

感谢您的回复。我已经在您描述并在连接事件中存储客户端ID时将更改添加到网关。它几乎可以工作,但在我退出服务器程序之前,回复没有显示在客户端控制台上。

这是服务器端输出:

Please enter some text and press <enter>:
        Note:
        - Entering FAIL will create an exception
        - Entering q will quit the application

client id is 127.0.0.1:58209:5000:ed6b6d48-5de7-4470-ac59-924788cf2957
convert: hello1
upCase: HELLO1
abc
def
ghi
q

这是客户端输出:

Connecting to localhost on port 5000 ... connected.
sending 1000 messages
Waiting for message ...
got message: HELLO1 <-- appears after server quit
abc 
def 
ghi 

最后四行仅在服务器程序退出后显示。

在主类中,我通过以下方式存储客户端ID:

@Override
public void onApplicationEvent(TcpConnectionEvent event) {
    clientId = event.getConnectionId(); 
    System.out.println("client id is " + clientId);
}

然后通过

发送消息
send(data, clientId)

网关现在定义为

@MessagingGateway(defaultRequestChannel="toTcp")
public interface Gateway {
    //void send(String in);
    void send(@Payload String data, @Header(IpHeaders.CONNECTION_ID) String connectionId);
}

客户端程序与上一篇文章相同。