背景
关于在Tomcat容器中启动Netty服务器的方法
最近在一个web应用上重构一个即时通信功能的,考虑到负载和性能的原因,所以决定采用Netty来作为服务器端,但是如果在主线程中启动netty就会陷入阻塞状态,导致Tomcat无法启动.
实现思路
我们知道,netty在主线程启动会陷入阻塞,那么我们就可以开启一个新线程来启动netty服务器,让主线程启动Tomcat即可
相关代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
<context:component-scan base-package="com.gxwzu.websocket"/> <bean id="bossGroup" class="io.netty.channel.nio.NioEventLoopGroup"/> <bean id="workerGroup" class="io.netty.channel.nio.NioEventLoopGroup"/> <bean id="serverBootstrap" class="io.netty.bootstrap.ServerBootstrap" scope="prototype"/> <bean id="webSocketServer" class="com.gxwzu.websocket.WebSocketServer"> <property name="port" value="${chat.server.port}"/> <property name="childChannelHandler" ref="webSocketChildChannelHandler" /> </bean> </beans>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| package com.gxwzu.websocket;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelInitializer; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.stream.ChunkedWriteHandler; import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component public class WebSocketChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Resource(name = "webSocketServerHandler") private ChannelHandler webSocketServerHandler;
@Resource(name = "httpRequestHandler") private ChannelHandler httpRequestHandler;
@Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast("http-codec", new HttpServerCodec()); ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536)); ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler()); ch.pipeline().addLast("websocket-handler",webSocketServerHandler); }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| kage com.gxwzu.websocket;
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.util.concurrent.Future; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired;
public class WebSocketServer implements Runnable{
private final Logger logger = LoggerFactory.getLogger(WebSocketServer.class);
@Autowired private EventLoopGroup bossGroup; @Autowired private EventLoopGroup workerGroup; @Autowired private ServerBootstrap serverBootstrap;
private int port;
private ChannelHandler childChannelHandler;
private ChannelFuture serverChannelFuture;
public WebSocketServer() {}
@Override public void run() { build(); }
public void build() { try { long begin = System.currentTimeMillis(); serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1024) .option(ChannelOption.TCP_NODELAY, true) .childOption(ChannelOption.SO_KEEPALIVE, true) .childOption(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(592048)) .childHandler(childChannelHandler); long end = System.currentTimeMillis();
logger.info("Netty Websocket服务器启动完成,耗时 " + (end - begin) + " ms,已绑定端口 " + port + " 阻塞式等候客户端连接");
serverChannelFuture = serverBootstrap.bind(port).sync(); } catch (Exception e) { logger.info(e.getMessage()); bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); e.printStackTrace(); }
}
public void close(){ serverChannelFuture.channel().close(); Future<?> bossGroupFuture = bossGroup.shutdownGracefully(); Future<?> workerGroupFuture = workerGroup.shutdownGracefully();
try { bossGroupFuture.await(); workerGroupFuture.await(); } catch (InterruptedException ignore) { ignore.printStackTrace(); } }
public ChannelHandler getChildChannelHandler() { return childChannelHandler; }
public void setChildChannelHandler(ChannelHandler childChannelHandler) { this.childChannelHandler = childChannelHandler; }
public int getPort() { return port; }
public void setPort(int port) { this.port = port; }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| package com.gxwzu.websocket;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelInitializer; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.stream.ChunkedWriteHandler; import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component public class WebSocketChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Resource(name = "webSocketServerHandler") private ChannelHandler webSocketServerHandler;
@Resource(name = "httpRequestHandler") private ChannelHandler httpRequestHandler;
@Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast("http-codec", new HttpServerCodec()); ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536)); ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler()); ch.pipeline().addLast("http-handler", httpRequestHandler); ch.pipeline().addLast("websocket-handler",webSocketServerHandler); }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| package com.gxwzu.websocket;
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct; import javax.annotation.PreDestroy;
@Component @Scope() public class AppContext {
private final Logger logger = LoggerFactory.getLogger(AppContext.class);
@Autowired private WebSocketServer webSocketServer;
private Thread nettyThread;
@PostConstruct public void init() { nettyThread = new Thread(webSocketServer); logger.info("开启独立线程,启动Netty WebSocket服务器..."); nettyThread.start(); }
@SuppressWarnings("deprecation") @PreDestroy public void close() { logger.info("正在释放Netty Websocket相关连接..."); webSocketServer.close(); logger.info("正在关闭Netty Websocket服务器线程..."); nettyThread.stop(); logger.info("系统成功关闭!"); } }
|
参考资料
- Netty: https://netty.io/