1

Some important maven dependencies

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
      <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.43.Final</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
     </dependencies>

netty server:

@Component
@Order(value = 0)
@Async
public class UdpServer implements CommandLineRunner {

    private Logger logger = LoggerFactory.getLogger(UdpServer.class);

    @Value("${udp.port:8896}")
    private int udpPort;

    @Autowired
    UdpServerHandler udpServerHandler;

    private EventLoopGroup eventLoopGroup;

    @Override
    public void run(String... args) {
         eventLoopGroup = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup)
                    .channel(NioDatagramChannel.class)
                    .option(ChannelOption.SO_BROADCAST, true)
                    .option(ChannelOption.SO_REUSEADDR,true)
                    .handler(udpServerHandler);
            logger.info("udp server start udp port: ",udpPort);
            ChannelFuture cf2 = bootstrap.bind(udpPort).sync();
            cf2.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    @PreDestroy
    public void closeUdp(){
        logger.info("udp server closed");
        eventLoopGroup.shutdownGracefully();
    }
}

UdpServerHandler:

@Component
@SuppressWarnings("all")
public class UdpServerHandler extends SimpleChannelInboundHandler<DatagramPacket> {

    private Logger logger = LoggerFactory.getLogger(UdpServerHandler.class);

    @Autowired
    UdpService udpService;

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        try {
            Channel channel = ctx.channel();
            SocketAddress socketAddress = channel.remoteAddress();
            if (socketAddress != null){
                String channelKey = socketAddress.toString();
                logger.info("channel :{} connected", channelKey);
            }

        }catch (Exception e){
            e.printStackTrace();
        }

        super.channelActive(ctx);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket datagramPacket) {

        try {
            String sender = datagramPacket.sender().toString();
            logger.info(sender);

        }catch (Exception e){
            e.printStackTrace();
        }

        try {
            udpService.solveData(ctx, datagramPacket);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        String channelKey = ctx.channel().remoteAddress().toString();
        logger.info("channel :{} closed", channelKey);
        super.channelInactive(ctx);
    }
}

Rabbitmq config:

@Configuration
public class RabbitConfig {

    @Value("${spring.rabbitmq.host}")
    private String host;
    @Value("${spring.rabbitmq.port}")
    private int port;
    @Value("${spring.rabbitmq.username}")
    private String username;
    @Value("${spring.rabbitmq.password}")
    private String password;
    @Value("${spring.rabbitmq.virtual-host}")
    private String virtualHost;

    @Autowired
    DownlinkUdpConfig downlinkUdpConfig;

    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory(host, port);
        connectionFactory.setUsername(username);
        connectionFactory.setPassword(password);
        connectionFactory.setVirtualHost(virtualHost);
        connectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
        return connectionFactory;
    }

    @Bean
    @Primary
    @Scope("prototype")
    Encoder multipartFormEncoder() {
        return new SpringFormEncoder();
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public RabbitTemplate rabbitTemplate() {
        return new RabbitTemplate(connectionFactory());
    }

    @Bean
    public Queue DownlinkQueue() {
        Map<String, Object> arguments=new HashMap<>(3);
        arguments.put("x-dead-letter-exchange", downlinkUdpConfig.getDeadExchange());
        arguments.put("x-dead-letter-routing-key", downlinkUdpConfig.getDeadRoutingKey());
        arguments.put("x-message-ttl",downlinkUdpConfig.getxMessageTtl());
        return new Queue(downlinkUdpConfig.getQueue(), true,false,false,arguments);
    }

    @Bean
    public DirectExchange DownlinkExchange() {
        return new DirectExchange(downlinkUdpConfig.getExchange());
    }

    @Bean
    public Binding bindingDownlinkQueue() {
        return BindingBuilder.bind(DownlinkQueue()).to(DownlinkExchange()).with(downlinkUdpConfig.getRoutingKey());
    }

    @Bean
    public Queue DownlinkDeadQueue(){
        return new Queue(downlinkUdpConfig.getDeadQueue(),true);
    }

    @Bean
    public DirectExchange DownlinkDeadExchange(){
        return new DirectExchange(downlinkUdpConfig.getDeadExchange(),true,false);
    }

    @Bean
    public Binding bindingDownlinkDead(){
        return BindingBuilder.bind(DownlinkDeadQueue()).to(DownlinkDeadExchange()).with(downlinkUdpConfig.getDeadRoutingKey());
    }
}

DownlinkUdpListener:

@Component
public class DownlinkUdpListener {

    Logger logger = LoggerFactory.getLogger(this.getClass());

    @RabbitListener(queues = "downlink_udp_queue")
    public void process(Channel channel, Message message) {
        //1.get message from rabbitMq
        String rabbitMessage = new String(message.getBody());
        logger.info("get result from rabbitMq is:{}", rabbitMessage);

        //1.1 ack get message
        try {
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (IOException e) {
            logger.info("ack error, message is:{}", rabbitMessage);
        }
    }
}

The problem now is that I have no consumers in the downlink_udp_queue queue on the rabbitmq management interface. I suspect that the SimpleChannelInboundHandler of UdpServerHandler causes @RabbitListener to not take effect.

enter image description here

Aniruddh Parihar
  • 3,072
  • 3
  • 21
  • 39
jialing xv
  • 11
  • 2
  • It's not obvious how adding netty could break `@RabbitListener` (sounds dubious). DEBUG logging might help you to figure it out. If not, post an [MCRE](https://stackoverflow.com/help/minimal-reproducible-example) someplace. – Gary Russell Jul 27 '22 at 13:30

1 Answers1

0

I had the same problem and what worked for me was to wait until the Spring Boot server was fully started.

So I implemented a SpringApplicationRunListener. Then in the running method added the startup code for the Netty Server.

For the EventLoop, create a component and refer to it through the context

@Component
public class NioEventLoopGroupPojo {
    NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
    
    public NioEventLoopGroup getEventLoopGroup() {
        return eventLoopGroup;
    }
    
    @PreDestroy
    public void closeUdp() {
        eventLoopGroup.shutdownGracefully();
    }
}

Then the SpringApplicationRunListener starts the Netty server

public class SpringBootContextListener implements SpringApplicationRunListener
{
    private SpringApplication app;
    private String[] args;

    public SpringBootContextListener(SpringApplication app, String[] args){
        super();
        this.app = app;
        this.args = args;
    }

    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        // Does Nothing
    }

    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        // Does Nothing
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        // Does Nothing
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        // Does Nothing
    }

    @Override
    public void started(ConfigurableApplicationContext context) {
        // Does Nothing
    }

    @Override
    public void running(ConfigurableApplicationContext context) {
        int udpPort = context.getEnvironment().getProperty("udp.port");
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(context.getBean(NioEventLoopGroupPojo.class).getEventLoopGroup())
                    .channel(NioDatagramChannel.class)
                    .option(ChannelOption.SO_BROADCAST, true)
                    .option(ChannelOption.SO_REUSEADDR,true)
                    .handler(context.getBean(UdpServerHandler));
            ChannelFuture cf2 = bootstrap.bind(udpPort).sync();
            cf2.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        // Does Nothing
    }
}

Then do not forget to create the spring.factories file under resources/META-INF with your listener:

org.springframework.boot.SpringApplicationRunListener=your-package.SpringBootContextListener