前面介绍了netty的基本使用以及和websocket的整合,下面就说说如何用springboot整合netty,毕竟我们是要把netty作为一个服务端的框架整合到我们的项目中去的,总不能用main函数启动吧,下面来看具体的整合过程,
项目的基本结构如图,这里为了省事,没有整合mybatis和mysql,有兴趣的同学可参考我之前的博客把相关的依赖加进去即可,
1、pom文件,springboot的基本配置和一个netty包,
org.springframework.boot
spring-boot-starter-parent
2.0.1.RELEASE
UTF-8
UTF-8
1.8
io.netty
netty-all
5.0.0.Alpha2
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.2
org.springframework.boot
spring-boot-maven-plugin
2、application.properties配置文件,如果还有其他的像mybatis之类的都可以配置在这个里面,
server.port=8089
spring.datasource.jdbc-url=jdbc:mysql://localhost:3306/babaytun?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#first.datasource.type=com.alibaba.druid.pool.DruidDataSource
3、netty服务端相关配置类,有了前面的整合篇,其实我在这里只会main函数里面的相关配置做了一些改进,通过注解在项目启动的时候被spring加载管理,几个类的代码前一篇都说过,这里直接贴出来,WssServer 是最主要的配置类,其他的基本都是上一篇讲到的,
package com.congge.netty;
import org.springframework.stereotype.Component;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* 服务端基本配置,通过一个静态单例类,保证启动时候只被加载一次
* @author asus
*
*/
@Component
public class WssServer {
/**
* 单例静态内部类
* @author asus
*
*/
public static class SingletionWSServer{
static final WssServer instance = new WssServer();
}
public static WssServer getInstance(){
return SingletionWSServer.instance;
}
private EventLoopGroup mainGroup ;
private EventLoopGroup subGroup;
private ServerBootstrap server;
private ChannelFuture future;
public WssServer(){
mainGroup = new NioEventLoopGroup();
subGroup = new NioEventLoopGroup();
server = new ServerBootstrap();
server.group(mainGroup, subGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new WssServerInitialzer()); //添加自定义初始化处理器
}
public void start(){
future = this.server.bind(8087);
System.err.println("netty 服务端启动完毕 .....");
}
}
package com.congge.netty;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
public class WssServerInitialzer extends ChannelInitializer{
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//websocket基于http协议,所以需要http编解码器
pipeline.addLast(new HttpServerCodec());
//添加对于读写大数据流的支持
pipeline.addLast(new ChunkedWriteHandler());
//对httpMessage进行聚合
pipeline.addLast(new HttpObjectAggregator(1024*64));
// ================= 上述是用于支持http协议的 ==============
//websocket 服务器处理的协议,用于给指定的客户端进行连接访问的路由地址
//比如处理一些握手动作(ping,pong)
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
//自定义handler
pipeline.addLast(new ChatHandler());
}
}
package com.congge.netty;
import java.time.LocalDateTime;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
/**
* 聊天的ehandler
* TextWebSocketFrame 用于为websockt处理文本的对象
* @author asus
*
*/
public class ChatHandler extends SimpleChannelInboundHandler{
//用于记录和管理所有客户端的channel
private static ChannelGroup clients =
new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
protected void messageReceived(ChannelHandlerContext ctx, TextWebSocketFrame msg)
throws Exception {
//客户端传递过来的消息
String content = msg.text();
System.out.println("接收到了客户端的消息是:" + content);
//将客户端发送过来的消息刷到所有的channel中
for(Channel channel : clients){
//channel.writeAndFlush(msg);
channel.writeAndFlush(
new TextWebSocketFrame("[服务器接收到了客户端的消息:]" + LocalDateTime.now()+",消息为:" + content));
}
// clients.writeAndFlush(
// new TextWebSocketFrame("[服务器接收到了客户端的消息:]" + LocalDateTime.now()+",消息为:" + content));
}
//客户端创建的时候触发,当客户端连接上服务端之后,就可以获取该channel,然后放到channelGroup中进行统一管理
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
clients.add(ctx.channel());
}
//客户端销毁的时候触发,
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
//当handlerRemoved 被触发时候,channelGroup会自动移除对应的channel
//clients.remove(ctx.channel());
System.out.println("客户端断开,当前被移除的channel的短ID是:" +ctx.channel().id().asShortText());
}
}
最后,添加一个初始化时候加载netty配置信息使之生效的配置文件,这里我实现了ApplicationListener这个接口,这样springboot容器启动完毕就可以加载netty的相关配置信息,
package com.congge.netty;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
/**
* netty服务端启动加载配置
* @author asus
*
*/
@Component
public class NettybootServerInitConfig implements ApplicationListener{
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if(event.getApplicationContext().getParent() == null){
WssServer.getInstance().start();
}
}
}
最后,我们使用springboot的main函数启动工程,
package com.congge;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}