简体   繁体   中英

Using Tomcat HTTP and Netty UDP in same Spring Boot Application

I'm writing an application that needs to be able to listen to UDP datagrams and also expose some HTTP endpoints.

I'm using a Spring Boot with Web Starter for HTTP part and Netty for UDP.

To set up UDP I'm using the following code:

@Configuration
class NettyConfig(
        @Value("\${netty.port}")
        val port: Int,
        @Value("\${netty.epoll}")
        val epoll: Boolean
) {

    val log = LoggerFactory.getLogger(NettyConfig::class.java)!!

    @Bean
    fun executor() = Executors.newFixedThreadPool(threads)!!

    @Bean
    fun bootstrap(handlerService: HandlerService): Bootstrap {

        val master: EventLoopGroup
        val clazz: Class<out Channel>

        log.info("Starting Netty Server. EPoll enable: $epoll")

        if (epoll) {
            master = EpollEventLoopGroup(1) 
            clazz = EpollDatagramChannel::class.java
        } else {
            master = NioEventLoopGroup(1) 
            clazz = NioDatagramChannel::class.java
        }

        val bootstrap = Bootstrap()
                .group(master)
                .channel(clazz)
                .option(ChannelOption.SO_SNDBUF, 1048576)
                .option(ChannelOption.SO_RCVBUF, 1048576)
                .option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator(2048, 2048, 64 * 2048))
                .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                .option(ChannelOption.SO_BROADCAST, true)
                .handler(object: ChannelInitializer<DatagramChannel>() {
                    override fun initChannel(ch: DatagramChannel) {
                        ch.pipeline().addLast(
                                LineBasedFrameDecoder(1048576),
                                handler(handlerService))
                    }
                })

        val channel = bootstrap.bind(port).sync().channel()
        log.info("Started Netty Server. Listening on {}", port)
        channel.closeFuture().sync()
        log.info("Stopped Netty Server.")
        return bootstrap
    }
...

And an HTTP part is managed by Spring Starter Web, which is implemented by Tomcat.

What I want to achieve:

  • An application listens to UDP events on the given port (say 5432)
  • An application listens to HTTP requests on 8080 port
  • An application implements it's own HTTP API
  • An application exposes actuator endpoints via HTTP to manage/monitor it

Both parts are working, but not together. By trial and error I've got the following problem: If I comment out this line (ie do not start Netty):

val channel = bootstrap.bind(port).sync().channel()

HTTP part works (and UDP obv. do not). If I leave it in place - everything starts, but, any request to 8080 port gives a connection refused error.

From my understing there should be no problems, as both servers are listening on the different ports (5432 vs 8080) and that's what I'm getting in the log messages. So there is some non-trivial interaction between two servers, which I'm not aware of.

How can I solve it, so both servers will run together in the same JVM/Spring Boot App?

I suspect that your application isn't completing startup due to channel.closeFuture().sync() in the bootstrap @Bean method. Due to startup not completing, Tomcat doesn't get a chance to start so it cannot accept any incoming connections.

Rather than blocking the bean's creation, you should use a destroy method to call channel.closeFuture().sync() as part of application context close processing. To do this you could implement DisposibleBean or annotated a method with @PreDestroy .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM