在上一篇文章中讲了底层的NIO模型,并且写了一个netty的简单demo,这篇文章主要讲netty中一些具体的应用。
编解码器
首先需要了解netty中pipeline。
- 对于pipeline,有入站和出站的概念,以服务端举例,从客户端发送来数据就是入站,就是上图从head到tail。
- 对于每一个handler,我们在添加入管道的时候,都是使用addLast方法,可以看做直接加入tail的前一个。
- 自定义的handler都是实现了InboundHandler(入站)或者是OutboundHandler(出站)。然后对于入站事件只会执行入站的handler,出站事件只会执行出站的handler。
在网络传输中,数据是以字节数组的方式进行传输的(或者说是二进制数据),因此在我们自定义的handler中进行数据传输,Object类型都需要编码成byte数组,存到netty的操作ByteBuf中才能进行传输。
netty自带的编码器是基础jdk自带的序列化机制实现的,下面是展示String和对象的传输的demo。
public class NettyServer {
public static void main(String[] args) throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder());
//pipeline.addLast(new ObjectDecoder(10240, ClassResolvers.cacheDisabled(ClassLoader.getSystemClassLoader())));
pipeline.addLast(new NettyServerHandler());
}
});
System.out.println("netty server start...");
ChannelFuture cf = bootstrap.bind(9000).sync();
cf.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("从客户端读取的String:" + msg.toString());
// System.out.println("从客户端读取的Object:" + ((User)msg).toString());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
public class NettyClient {
public static void main(String[] args) throws Exception{
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group) //设置线程组
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new StringEncoder());
//pipeline.addLast(new ObjectEncoder());
pipeline.addLast(new NettyClientHandler());
}
});
System.out.println("netty client start..");
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1",9000).sync();
channelFuture.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("MyChannelHandler 发送数据");
ctx.writeAndFlush("测试String编解码");
//测试对象编解码
// ctx.writeAndFlush(new User(1,"xin"));
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
}
}
StringDecoder实际上就是个InboundHandler。
可以使用protostuff进行序列化操作,这样就可以自定义编解码器。
心跳机制
所谓心跳机制,就是客户端和服务端发送一些数据包,告诉对方在线,来确保连接的有效性。
public class HeartBeatServer {
public static void main(String[] args) throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup,workerGroup) //设置线程组
.channel(NioServerSocketChannel.class) // 使用 NioSocketChannel 作为客户端的通道实现
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new StringEncoder());
pipeline.addLast(new StringDecoder());
pipeline.addLast(new IdleStateHandler(3,0,0, TimeUnit.SECONDS));
pipeline.addLast(new HeartBeatServerHandler());
}
});
System.out.println("netty server start..");
ChannelFuture cf = bootstrap.bind(9000).sync();
cf.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
public class HeartBeatServerHandler extends ChannelInboundHandlerAdapter {
int readIdleTimes = 0;
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
IdleStateEvent event = (IdleStateEvent) evt;
String type = "读未超时";
if (event.state() == IdleState.READER_IDLE) {
readIdleTimes++;
type = "读超时";
}
System.out.println(ctx.channel().remoteAddress() + " "+ type);
if (readIdleTimes > 3) {
//超过3次断开连接
System.out.println("超过3次断开连接");
ctx.writeAndFlush("connection close");
ctx.channel().close();
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx.channel().remoteAddress()+" is alive..........");
}
}
public class HeartBeatClient {
public static void main(String[] args) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group) //设置线程组
.channel(NioSocketChannel.class) // 使用 NioSocketChannel 作为客户端的通道实现
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new StringEncoder());
pipeline.addLast(new StringDecoder());
pipeline.addLast(new HeartBeatClientHandler());
}
});
System.out.println("netty client start..");
Channel channel = bootstrap.connect("127.0.0.1", 9000).sync().channel();
String text = "HeartBeat";
Random random = new Random();
while (channel.isActive()) {
int num = random.nextInt(10);
Thread.sleep(num * 1000);
channel.writeAndFlush(text);
}
} finally {
group.shutdownGracefully();
}
}
}
public class HeartBeatClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception {
System.out.println(s.trim());
if (s.equals("connection close")) {
System.out.println("服务端关闭连接,因此关闭客户端");
ctx.channel().close();
}
}
}
实现心跳机制需要IdleStateHandler,下面进入源码看下这个类怎么实现功能
进入initialize方法
这里面执行了一个schedule,里面有个task,进入task看看
看下他的run方法
如果超时,就会执行红色的部分,然后下面继续执行fireUserEventTriggered方法,这个方法是调用下一个handler的UserEventTriggered方法,即我们自定义的handler中的userEventTriggered方法。