Netty4关于ChannelInboundHandler的使用说明

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_26323323/article/details/84226845

前言:

    Netty中处理输入输出字节的最重要的类就是ChannelInboundHandler(处理输入字节)、ChannelOutboundHandler(处理输出字节)

    下面我们从最简单的使用到复杂使用来回顾一下关于这些Handler的处理

    Netty版本说明:笔者使用的是netty-4.0.23版本,以下代码均基于此编写

1.服务端

    服务端负责接收客户端请求、接收客户端数据、返回响应

    我们来创建一下服务端的代码,下面是一段模板代码,先不添加Handler处理

public class Server {

	public static void main(String[] args) {
		//服务类
		ServerBootstrap bootstrap = new ServerBootstrap();
		
		//boss和worker
		EventLoopGroup boss = new NioEventLoopGroup();
		EventLoopGroup worker = new NioEventLoopGroup();
		
		try {
			//设置线程池
			bootstrap.group(boss, worker);
			
			//设置socket工厂
			bootstrap.channel(NioServerSocketChannel.class);
			
			//设置管道工厂
			bootstrap.childHandler(new ChannelInitializer<Channel>() {

				@Override
				protected void initChannel(Channel ch) throws Exception {
					// TODO
				}
			});
			
			//设置参数,TCP参数
			bootstrap.option(ChannelOption.SO_BACKLOG, 2048);//serverSocketchannel的设置,链接缓冲池的大小
			bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);//socketchannel的设置,维持链接的活跃,清除死链接
			bootstrap.childOption(ChannelOption.TCP_NODELAY, true);//socketchannel的设置,关闭延迟发送
			
			//绑定端口8088
			ChannelFuture future = bootstrap.bind(8088).sync();
			
			System.out.println("server start...");
			//等待服务端关闭
			future.channel().closeFuture().sync();
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			//释放资源
			boss.shutdownGracefully();
			worker.shutdownGracefully();
		}
	}
}

    注意:以上就是一段模板代码,我们真正要做的就是在TODO这里添加合适的Handler

    此时服务端不具有业务处理功能

protected void initChannel(Channel ch) throws Exception {
    // TODO
}

2.原生使用ChannelInboundHandler的方式

    对于输入信息,我们可以通过实现ChannelInboundHandler的方式来处理,最重要的方法如下:

void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;
void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;

    我们通过实现channelRead方法来实现msg的获取

    当然,我们一般不会直接使用该接口,而是使用一个Adapter类,ChannelInboundHandlerAdapter,该类做了接口的基本实现,我们一般都是通过继承该类

    下面我们就来实现一个最简单版本的,获取客户端输入信息,并打印出来

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

/**
 * 初级版处理
 * 将字节数组转换为字符串
 */
public class SimpleInHandler extends ChannelInboundHandlerAdapter {

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		if(msg instanceof ByteBuf){
			
			ByteBuf in = (ByteBuf)msg;
			try {
				if(in.readableBytes() > 0){
					// 转换为String
					String str = in.toString(CharsetUtil.UTF_8);
					// TODO logic
					System.out.println("receive data: " + str);
				}
            } finally {
            	// 释放资源
                ReferenceCountUtil.release(in);
            }
		}
	}
}

    注意:我们使用完ByteBuf之后,需要主动去释放资源,否则,资源一直在内存中加载,容易造成内存泄漏

    那么,我们每个Handler都需要这些释放的逻辑,可不可以把这些操作抽象成一个模板呢?答案是可以的

3.这里简单说一下如何测试Handler

    * 首先在Server类中的initChannel方法添加SimpleInHandler实例,然后启动Server类,此时Server会监听在本机8088端口

protected void initChannel(Channel ch) throws Exception {
    ch.pipeline().addLast(new SimpleInHandler())
}

    * 客户端发送消息

    笔者在这里使用了一种比较讨巧的方式,就是使用Windows命令窗的telnet命令

    点击回车,如果出现以下即说明连接成功,连接失败的话会有失败提醒

这种情况下,我们可以输入字符,然后按回车,即发送成功

但是这个会有一个问题,就是输入一个字符就自动回车,无法输入整行字符,我们可以 按 Ctrl+]键,进入

这时可以使用send xxx 来发送整行信息

4.SimpleChannelInboundHandler的使用

    SimpleChannelInboundHandler是一个抽象类,主要实现如下:

public abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter {
        @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        boolean release = true;
        try {
            if (acceptInboundMessage(msg)) {
                @SuppressWarnings("unchecked")
                I imsg = (I) msg;
                channelRead0(ctx, imsg);
            } else {
                release = false;
                ctx.fireChannelRead(msg);
            }
        } finally {
            if (autoRelease && release) {
                ReferenceCountUtil.release(msg);
            }
        }
    }
    
    // 需要我们主动实现的接口
    protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception;
    
    ...
}

    SimpleChannelInboundHandler就是我们在2中提问的一个抽象模板,资源的释放已经被抽象出去了,我们只需要关注channelRead0方法,去实现我们的业务即可

    具体使用如下:

/**
 * 继承SimpleChannelInboundHandler版本,省去release处理
 * 将字节数组转换为字符串
 */
public class SimpleChannelInHandler extends SimpleChannelInboundHandler<ByteBuf> {

	@Override
	protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {

		if(msg.readableBytes() > 0){
			// 转换为String
			String str = msg.toString(CharsetUtil.UTF_8);
			// TODO logic
			System.out.println("receive data: " + str);
		}
	}
}

    注意:我们可以把所有的输入当做字符串来处理,这样暂时是没什么问题的,那么实际以上代码也可以算作是一个模板,因为堆ByteBuf的字节转换为字符串处理都是相同 的处理,那么Netty有这样的模板类嘛?答案是肯定的

5.StringDecoder

    StringDecoder就是专门用来将字节数组转换为String的Handler类,如果没有其他特殊情况的话,我们就可以直接将StringDecoder添加到我们的Server中,然后后面的Handler处理就可以直接处理String类型的数据就可以。具体使用如下:

    * 创建一个用于打印客户端输入信息的Handler

/**
 * 用于处理服务端接收到的信息,目前就是打印出来
 * @author Administrator
 *
 */
public class PrintHandler extends SimpleChannelInboundHandler<String> {

	@Override
	protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
		System.out.println("receive data: " + msg);
	}
}

    注意:这里继承SimpleChannelInboundHandler的时候,泛型可以直接写String

    * 服务端代码

bootstrap.childHandler(new ChannelInitializer<Channel>() {

    @Override
    protected void initChannel(Channel ch) throws Exception {
        ch.pipeline().addLast(new StringDecoder());
        ch.pipeline().addLast(new PrintHandler());
    }
}

    我们把StringDecoder放在前面,PrintHandler放在后面,这样请求就会先经过StringDecoder处理,处理成String之后,再交给PrintHandler处理

    注意:我们在使用String处理的时候,会碰到一种情况,在发送TCP请求的时候,客户端什么时候才算是一次输入呢?大部分就是使用换行符来表示一次输入,如果我们直接使用StringDecoder,那么其对一次输入的划分是有问题的,这时我们在StringDecoder之前有一个Handler可以先将输入字符按照换行符来截断,截断后的ByteBuf再作为StringDecoder的一次输入,那么有没有这样的Handler类呢?答案是肯定的

6.LineBasedFrameDecoder

/**
 * A decoder that splits the received {@link ByteBuf}s on line endings.
 * <p>
 * Both {@code "\n"} and {@code "\r\n"} are handled.
 * For a more general delimiter-based decoder, see {@link DelimiterBasedFrameDecoder}.
 */
public class LineBasedFrameDecoder extends ByteToMessageDecoder {

    通过注解可以了解到该类主要是对输入字节进行划分的,按照\n或者\r\n来进行划分

    注意:还要一个类DelimiterBasedFrameDecoder,可以让用户自定义字节划分符

参考:Netty in Action

猜你喜欢

转载自blog.csdn.net/qq_26323323/article/details/84226845