简介
通常在项目中我们都是结合spring或者springboot使用netty,本章节介绍springboot整合netty。项目源码地址:https://github.com/itwwj/netty-learn.git中的netty-day06-boot项目。
一、依赖导入
<properties>
<netty_version>4.1.24.Final</netty_version>
</properties>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty_version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>
<!-- test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.0.4.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
二、代码
netty启动类:
/**
* netty服务端启动类
*
* @author jie
*/
@Data
@Slf4j
@Component
public class NettyServer {
private EventLoopGroup boosGroup = new NioEventLoopGroup();
private EventLoopGroup workerGroup = new NioEventLoopGroup();
private Channel channel;
@Autowired
private ChannelInitializer initializer;
public ChannelFuture init(int port) {
ChannelFuture f=null;
try {
//用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度
ServerBootstrap b = new ServerBootstrap();
b.group(boosGroup, workerGroup)
//对应JDK NIO类库中的ServerSocketChannel
.channel(NioServerSocketChannel.class)
//配置NioServerSocketChannel的TCP参数
.option(ChannelOption.SO_BACKLOG, 1024)
//绑定I/O的事件处理类
.childHandler(initializer);
//调用它的bind操作监听端口号,调用同步阻塞方法sync等待绑定操作完成
f = b.bind(port).sync();
channel = f.channel();
} catch (InterruptedException e) {
log.error(e.getMessage());
}
return f;
}
/**
* 关闭
*/
public void close() {
if (null == channel) {
return;
}
channel.close();
boosGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
事件处理类:
/**
* MyChannelInitializer的主要目的是为程序员提供了一个简单的工具,用于在某个Channel注册到EventLoop后,对这个Channel执行一些初始
* 化操作。ChannelInitializer虽然会在一开始会被注册到Channel相关的pipeline里,但是在初始化完成之后,ChannelInitializer会将自己
* 从pipeline中移除,不会影响后续的操作。
*
* @author jie
*/
@Slf4j
@Component
public class MyServerChannelInitializer extends ChannelInitializer<SocketChannel> {
/**
* 这个方法在Channel被注册到EventLoop的时候会被调用
*
* @param socketChannel
* @throws Exception
*/
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
log.info("=========有客户端连接服务器=========");
log.info("ip:" + socketChannel.localAddress().getHostString() + " port:" + socketChannel.localAddress().getPort());
// 基于换行符号
socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
// 解码转String,注意调整自己的编码格式GBK、UTF-8
socketChannel.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));
// 解码转String,注意调整自己的编码格式GBK、UTF-8
socketChannel.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
socketChannel.pipeline().addLast(new MyServerHandler());
}
}
事件操作类:
/**
* 操作类
*
* @author jie
*/
@Slf4j
public class MyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 当客户端主动连接服务端,通道活跃后触发
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//在接收到客户端连接的时候通知客户端连接成功
String msg = "与服务端建立连接成功" + new Date();
ByteBuf buf = Unpooled.buffer(msg.getBytes().length);
buf.writeBytes(msg.getBytes(CharsetUtil.UTF_8));
}
/**
* 通道有消息触发
*
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "接收到消息:");
log.info(msg.toString());
ctx.writeAndFlush(msg);
}
/**
* 当客户端主动断开连接,通道不活跃触发
*
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
log.info("===================客户端:" + ctx.channel().localAddress().toString() + " 断开连接===================");
}
/**
* 当连接发生异常时触发
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//在发生异常时主动关掉连接
ctx.close();
log.error("发现异常:\r\n" + cause.toString());
}
}
springboot启动类:
/**
* @author jie
*/
@SpringBootApplication
public class NettyApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(NettyApplication.class, args);
}
@Value("${netty.port}")
private int port;
@Autowired
private NettyServer nettyServer;
@Override
public void run(String... args) throws Exception {
ChannelFuture init = nettyServer.init(port);
Runtime.getRuntime().addShutdownHook(new Thread(() -> nettyServer.close()));
init.channel().closeFuture().syncUninterruptibly();
}
}
web监控类:
/**
* @author jie
*/
@Slf4j
@RestController
@RequestMapping("/netty")
public class NettyController {
@Autowired
private NettyServer nettyServer;
@RequestMapping("/localAddress")
public String localAddress() {
return "tcp服务端地址:" + nettyServer.getChannel().localAddress();
}
@RequestMapping("/isOpen")
public String isOpen() {
if (nettyServer.getChannel().isOpen()){
return "tcp服务已启动";
}else {
return "tcp服务未启动";
}
}
@RequestMapping("/close")
public String close() {
if (nettyServer.getChannel().isOpen()) {
nettyServer.close();
}
return "服务端关闭成功";
}
}
配置文件application.yml:
netty:
port: 1100
server:
port: 5320