上一篇博客,简单整理了一下Netty集成HTTP和websocket服务的实例,通过采用netty的开箱即用的编解码器完成了简陋的HTTP和websocket服务器,这一篇博客打算总结一下netty集成到springboot中的操作。为后面实现一个基于springboot的即时通信小程序做准备。这里我们依旧以上篇博客的websocket的实例代码为基础
搭建一个基于springboot的项目这里就不赘述了,这个很容易,在线的spring.io一键化搭建,我们需要做的无非就是将之前通过main函数启动的操作,封装成一个组件交给springboot去启动
//我们要做的无非就是将这段代码交给springboot去启动。
@Slf4j
public class SpringBootNettyWSServer {
public static void main(String[] args) {
new NettyWebSocketServer().start(9908,"127.0.0.1");
}
public void start(int port, String host) {
EventLoopGroup mainGroup = new NioEventLoopGroup();
EventLoopGroup subGroup = new NioEventLoopGroup();
try {
log.info("服务端启动");
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(mainGroup, subGroup);
serverBootstrap.localAddress(new InetSocketAddress(host,port));
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//websocket基于Http协议,需要所有HTTP编解码器
pipeline.addLast(new HttpServerCodec());
//加入对写大数据流的支持
pipeline.addLast(new ChunkedWriteHandler());
//对HttpMessage进行聚合,聚合成FullHttpRequest或FullHttpResponse
pipeline.addLast(new HttpObjectAggregator(1024 * 64));
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
pipeline.addLast(new NettyWebSocketServerHandler());
}
});
ChannelFuture serverFuture = serverBootstrap.bind(port).sync();
serverFuture.channel().closeFuture().sync();
} catch (Exception e) {
log.error("服务端出现异常,异常信息为:{}", e);
} finally {
subGroup.shutdownGracefully();
mainGroup.shutdownGracefully();
}
}
}
我们要做的无非就是将上述这段代码交给springboot去启动。整体思路其实就是在springboot启动完成之后,再额外的开一个线程去启动netty。
<dependency>
<groupId>io.nettygroupId>
<artifactId>netty-allartifactId>
<version>4.1.25.Finalversion>
dependency>
关于springboot的依赖,这里不会贴出。
上面说过,将netty的主类交给springboot托管无非就是将netty带有main方法的启动类,作为一个组件交给springboot托管,但是需要说明的是,netty的服务器只能有一个,不能有多个,因此需要改成单例
/**
* autor:liman
* createtime:2020/9/25
* comment:WebSocket服务端启动类
* springboot中整合Netty最单纯的就是将这个类交给spring管理,因此要保证只有一个
* 容器交给了spring管理之后,不用我们手动停止Netty
*/
@Component
@Slf4j
public class SpringBootNettyWSServer {
//用内部类的方式构建单例
private static class SingleSpringBootNettyWSServer{
static final SpringBootNettyWSServer nettyWSServerInstance = new SpringBootNettyWSServer();
}
public static SpringBootNettyWSServer getInstance(){
return SingleSpringBootNettyWSServer.nettyWSServerInstance;
}
private EventLoopGroup mainGroup;
private EventLoopGroup subGroup;
private ServerBootstrap server;
private ChannelFuture future;
public SpringBootNettyWSServer() {
mainGroup = new NioEventLoopGroup();
subGroup = new NioEventLoopGroup();
server = new ServerBootstrap();
server.group(mainGroup, subGroup)
.channel(NioServerSocketChannel.class);
server.childHandler(new NettyWSHandlerInit());//这里加入的就会netty的初始化器,这里头就和springboot没有关系了。后续会贴上代码。
}
public void start() {
future = server.bind(9908);///这里就是启动Netty,注意不要与springboot内嵌的tomcat端口冲突
log.info("netty websocket server 启动完毕...");
}
}
当springboot的容器加载完成之后,我们就可以启动Netty,可以利用springboot的事件监听机制
/**
* autor:liman
* createtime:2020/9/27
* comment:监听spring容器是否启动,如果启动了,netty也随之启动
*/
@Component
@Slf4j
public class NettyWSServerStartBoot implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
if (contextRefreshedEvent.getApplicationContext().getParent() == null) {//表示可启动Netty了
try {
SpringBootNettyWSServer.getInstance().start();
} catch (Exception e) {
log.error("netty服务启动异常,异常信息为:{}",e);
}
}
}
}
可以看到日志中已经打印netty启动的日志,直接采用上一篇博客的方式,可直接测试netty整合springboot是否成功
简单将netty利用springboot的事件通知机制进行了集成。
netty的业务代码,和上一篇博客差不多
NettyWSHandlerInit
/**
* autor:liman
* createtime:2020/9/27
* comment:netty中handler的initilizer
*/
public class NettyWSHandlerInit extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
//websocket基于Http协议,需要所有HTTP编解码器
pipeline.addLast(new HttpServerCodec());
//加入对写大数据流的支持
pipeline.addLast(new ChunkedWriteHandler());
//对HttpMessage进行聚合,聚合成FullHttpRequest或FullHttpResponse
pipeline.addLast(new HttpObjectAggregator(1024 * 64));
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
pipeline.addLast(new NettyWebSocketServerHandler());
}
}
NettyWebSocketServerHandler
/**
* autor:liman
* createtime:2020/9/25
* comment:webservice的后端处理
*/
@Slf4j
public class NettyWebSocketServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
//用于记录和管理所有客户端的channel
private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
super.channelReadComplete(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
@Override
public boolean acceptInboundMessage(Object msg) throws Exception {
return super.acceptInboundMessage(msg);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
String content = msg.text();
log.info("接收到客户端的消息为:{}",msg);
clients.writeAndFlush(new TextWebSocketFrame("[服务器在]" + LocalDateTime.now()
+ "接受到消息, 消息为:" + content));
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
clients.add(ctx.channel());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
// 当触发handlerRemoved,ChannelGroup会自动移除对应客户端的channel
log.info("客户端断开,channel对应的长id为:{}",ctx.channel().id().asLongText());
log.info("客户端断开,channel对应的短id为:{}",ctx.channel().id().asLongText());
}
}