为何本地Telnet可连接但外部客户端无法建立Spring Integration TCP连接?
我正在开发一个Spring Boot应用,作为serviceA与serviceB两个外部客户端之间的中间件,全程采用TCP/IP Socket通信。serviceA发起通信,应用接收消息后进行处理、数据库日志记录等操作,再将消息转发至serviceB;serviceB返回响应,应用处理后再回传给serviceA。
用telnet localhost <port>测试时,能正常发起通信,消息可处理至serviceC并收到响应,但实际外部客户端serviceA尝试连接应用时,应用无任何响应。
疑问:为何应用无任何响应?注:tcpInboundFlow接收的消息为字符串格式,是否与TcpInboundGateway使用的序列化/反序列化器有关?
我采用Spring Integration TCP框架,结合Java DSL与注解配置实现功能:
- 使用TcpInboundGateway配合serverConnectionFactory接收字符串格式消息
- 通过路由器根据业务逻辑确定目标通道
- 使用转换器进行后续处理
- 通过TcpOutboundGateway将消息推送至外部客户端
- 使用serviceActivator处理外部客户端的响应
以下是部分组件代码片段(所有通道均为directChannel实现,未展示):
@Bean public IntegrationFlow tcpInboundFlow() { return IntegrationFlow.from(tcpInboundGateway()) .handle("messageProcessingService", "processMessage") .route(mtiRouter()) .get(); } @Bean @Router(inputChannel = "handlerOutputChannel") public AbstractMessageRouter mtiRouter() { return new AbstractMessageRouter() { @Override protected Collection<MessageChannel> determineTargetChannels(Message<?> message) { ISOMsg isoMsg = (ISOMsg) message.getPayload(); try { String mti = isoMsg.getMTI(); log.info("reached the router"); return switch (mti) { case "0800" -> Collections.singleton(networkManagementChannel()); case "0420" -> Collections.singleton(reversalChannel()); case "0200" -> Collections.singleton(financialRequestChannel()); default -> throw new TmsIsoException("Unsupported MTI: " + mti); }; } catch (ISOException e) { log.info("failed to reach the router"); throw new TmsIsoException("Failed to get mti from iso msg"); } } }; } @Bean public IntegrationFlow financialRequestFlow() { return IntegrationFlow.from(financialRequestChannel()) .transform("financialRequestProcessingService", "processFinancialRequestMessage") .transform("transformationService", "keyMessage") .handle(tcpOutboundGateway()) .transform("responseService", "processResponseMessageForPurchase") .get(); } @Bean public TcpNetServerConnectionFactory serverConnectionFactory() { TcpNetServerConnectionFactory factory = new TcpNetServerConnectionFactory(30002); factory.setSerializer(new ByteArrayCrLfSerializer()); factory.setDeserializer(new ByteArrayCrLfSerializer()); // factory.setSoTimeout(60000); return factory; } @Bean public TcpNetClientConnectionFactory clientConnectionFactory() { TcpNetClientConnectionFactory factory = new TcpNetClientConnectionFactory("196.46.20.30", 5334); factory.setSerializer(customIsoMessageSerializer); factory.setDeserializer(customIsoMessageDeserializer); return factory; } @Bean public TcpInboundGateway tcpInboundGateway() { TcpInboundGateway gateway = new TcpInboundGateway(); gateway.setConnectionFactory(serverConnectionFactory()); gateway.setRequestChannel(requestChannel()); gateway.setReplyChannel(replyChannel()); // gateway.setErrorChannel(errorChannel()); gateway.setReplyTimeout(60000); gateway.setBeanName("tcpIn"); return gateway; } @Bean public TcpOutboundGateway tcpOutboundGateway() { TcpOutboundGateway gateway = new TcpOutboundGateway(); gateway.setConnectionFactory(clientConnectionFactory()); gateway.setRemoteTimeout(60000); gateway.setReplyChannelName("replyChannel"); return gateway; }
从现象和代码来看,核心问题大概率出在序列化/反序列化器不匹配以及消息流转的一致性上,具体拆解如下:
1. 序列化器不匹配是核心嫌疑
你用telnet测试正常,是因为telnet发送的消息默认会以CRLF(回车换行)作为结束符,正好匹配serverConnectionFactory里配置的ByteArrayCrLfSerializer。但外部客户端serviceA发送的消息,很可能没有遵循CRLF的结束规则,或者使用了其他序列化方式,导致服务端的ByteArrayCrLfSerializer无法正确识别消息边界,一直处于等待消息完整的状态,所以应用没有任何响应。
另外注意到,你的messageProcessingService.processMessage方法会将字符串消息转换成ISOMsg对象,但如果serviceA发送的原始消息格式(比如ISO 8583的二进制格式)和ByteArrayCrLfSerializer的字符串解析逻辑不兼容,也会导致后续路由逻辑无法触发,进而没有响应。
2. 消息流转的通道配置存在矛盾
- 你的
tcpInboundGateway指定了requestChannel和replyChannel,但tcpInboundFlow却是直接从tcpInboundGateway开始构建的。这会导致消息同时流向两个地方:一个是requestChannel,另一个是tcpInboundFlow的处理链,可能引发消息丢失或流转混乱。 - 路由器
mtiRouter的inputChannel是handlerOutputChannel,但tcpInboundFlow里的handle方法之后直接调用route(mtiRouter()),这里需要确认messageProcessingService.processMessage的输出是否正确发送到了handlerOutputChannel,否则路由器无法接收到消息,后续流程无法执行。
3. 具体修复建议
(1)统一序列化/反序列化逻辑
- 确认serviceA的消息格式:如果是ISO 8583二进制消息,需要替换
serverConnectionFactory的序列化器为你自定义的customIsoMessageSerializer和customIsoMessageDeserializer,和客户端连接工厂保持一致,而不是使用ByteArrayCrLfSerializer。 - 如果serviceA确实发送的是带CRLF的字符串,需要在客户端侧确认消息结尾是否正确添加了
\r\n,或者在服务端调整序列化器的结束符配置。
(2)修正消息流转通道配置
- 移除
tcpInboundGateway的requestChannel和replyChannel配置,因为tcpInboundFlow已经通过from(tcpInboundGateway())接管了消息处理,重复配置会导致消息分流。 - 确保
messageProcessingService.processMessage的输出通道是handlerOutputChannel,或者直接在tcpInboundFlow里明确指定输出通道,比如:
.handle("messageProcessingService", "processMessage") .channel("handlerOutputChannel") .route(mtiRouter())
(3)添加错误日志与调试
- 启用
tcpInboundGateway的errorChannel,并编写对应的错误处理逻辑,打印详细的异常信息,方便定位消息处理过程中的问题。 - 在
messageProcessingService.processMessage方法中添加日志,确认是否接收到了serviceA的消息,以及消息内容是否符合预期。
(4)检查网络与防火墙配置
- 确认serviceA所在网络能正常访问应用的30002端口,没有防火墙或安全组拦截。
- 用
netstat -an命令确认应用的30002端口处于监听状态。
内容的提问来源于stack exchange,提问作者serethewind

