《Netty权威指南 NIO 入门篇》

Netty简单介绍

  为什么选择Netty?开发高质量的NIO程序并不是一件简单的事情,出去NIO的复杂性和BUG不谈,作为一个NIO服务器,要能处理网络的闪断、客户端的重复接入、客户端的安全认证、消息的编解码、半包读写情况,如果没有足够的NIO编程经验累积,一个NIO框架的稳定往往需要半年甚至更长的实际。并且从维护性角度而言,NIO采用了异步非阻塞编程模型,而且是一个I/O线性处理多条链路,调试和跟踪非常麻烦,我们无法进行有效的调试和跟踪,定位难度很大。同时NIO编程设计到Reactor模式,你必须对多线程和网络编程非常熟悉,才能编写出高质量的NIO程序。
  而Netty具有以下优点:1、API使用简单,开发门槛低。2、预置了多种编解码功能。3、定制能力强,可以通过ChannelHandler对通信框架进行灵活的扩展。4、Netty修复了已经发现的NIO的BUG。Netyy在大数据、互联网等众多行业进行了成功的商用,正因这些优点,Netty成为了 Java NIO编程的首选框架。
  Netty相比原生的NIO做了哪些改变

搭建Netty入门应用

  从官网上 https://netty.io/downloads.html /maven repostory下载压缩包:新建java项目,将all in one中的netty-all .jar拷贝至工程lib下,并添加至构建路径即可。
服务端时间服务器:

  • 创建ServerBootstrap实例来引导绑定和启动服务器
  • 创建NioEventLoopGroup对象来处理事件,如接受新连接、接收数据、写数据等等
  • 指定InetSocketAddress,服务器监听此端口
  • 设置childHandler执行所有的连接请求
  • 都设置完毕了,最后调用ServerBootstrap.bind()

1、先创建两个NioEventLoopGroup线程组(Reactor线程组),一个NioEventLoopGroup包含了一组NIO线程,一个线程组用于服务端接收客户端的连接,另一个用于进行SocketChannel的网络读写。
EventLoopGroup bossGroup = new NioEventLoopGroup();// 用户服务端接收客户端的链接
EventLoopGroup workerGroup = new NioEventLoopGroup();// 用户进行SocketChannel的网络读写
2、创建ServerBootstrap对象,用于辅助NIO服务端启动,目的是降低服务端开发的复杂度。
ServerBootstrap b = new ServerBootstrap();
3、调用ServerBootstrap的group方法,将上面的两个NIO线程组传入该方法,同时要配置NioServerSocketChannel的TCP参数,还要绑定I/O事件的处理类ChildChannelHandler,事件处理类ChildChannelHandler类似Reactor模式中Handler类,处理网络I/O事件,例如记录日志、对消息进行编码等。ChildChannelHandler需要继承ChannelInitializer抽象类,实现initChannel方法。
b.group(bossGroup, workerGroup)// 将两个NIO线程组当做入参传递到ServerBootstrap中
.channel(NioServerSocketChannel.class)
//创建NioServerSocketChannel 对应JDK NIO库中的ServerSocketChannel类 , 指定通道类型为NioServerSocketChannel
.option(ChannelOption.SO_BACKLOG, 1024)
//然后配置NioServerSocketChannel的TCP参数,此处将它的backlog设置为1024

//最后绑定IO事件的处理类ChildChannelHandler,类似于Reactor模式中的Handler类,主要用于处理网络IO事件,例如记录日志,消息编解码等
.childHandler(new ChildChannelHandler());
4、服务器启动辅助类配置完成后,调用它的bind方法绑定监听端口,然后调用它的同步阻塞方法sync等待绑定完成,之后Netty返回一个ChannelFuture,功能类似java.util.concurrent.Future,用于异步操作的通知回调

ChannelFuture f = b.bind(port).sync();

5、使用f.channel().closeFuture().sync()方法进行阻塞,等待服务端链路关闭之后main函数才退出。

f.channel().closeFuture().sync();
	import io.netty.bootstrap.ServerBootstrap;
	import io.netty.channel.ChannelFuture;
	import io.netty.channel.ChannelInitializer;
	import io.netty.channel.ChannelOption;
	import io.netty.channel.EventLoopGroup;
	import io.netty.channel.nio.NioEventLoopGroup;
	import io.netty.channel.socket.SocketChannel;
	import io.netty.channel.socket.nio.NioServerSocketChannel;
	
	public class TimeServer {
   
	    public void bind(int port) throws Exception {
   
			// 配置服务端的NIO线程组 ,专门用于网络事件的处理,实际上他们就是Reactor线程组
			EventLoopGroup bossGroup = new NioEventLoopGroup();// 用户服务端接收客户端的链接
			EventLoopGroup workerGroup = new NioEventLoopGroup();// 用户进行SocketChannel的网络读写
			try {
   
				// ServerBootstrap对象实际上是netty用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度
				ServerBootstrap b = new ServerBootstrap();
				b.group(bossGroup, workerGroup)// 将两个NIO线程组当做入参传递到ServerBootstrap中
					.channel(NioServerSocketChannel.class)//创建NioServerSocketChannel 对应JDK NIO库中的ServerSocketChannel类 , 指定通道类型为NioServerSocketChannel
					.option(ChannelOption.SO_BACKLOG, 1024)//然后配置NioServerSocketChannel的TCP参数,此处将它的backlog设置为1024
						//最后绑定IO事件的处理类ChildChannelHandler,类似于Reactor模式中的Handler类,主要用于处理网络IO事件,例如记录日志,对消息进行编解码等
					.childHandler(new ChildChannelHandler());
				// 绑定端口,同步方法等待绑定操作完成 ,返回ChannelFuture主要用于异步操作的通知回调.
				ChannelFuture f = b.bind(port).sync();
	
				// 等待阻塞,等待服务端链路关闭之后main函数才退出
				f.channel().closeFuture().sync();
			} finally {
   
				// 优雅退出,释放线程池资源
				bossGroup.shutdownGracefully();
				workerGroup.shutdownGracefully();
			}
	    }
	    // 调用childHandler 来指定连接后调用的ChannelHandler,这个方法传ChannelInitalizer类型的参数
		// ChannelInitalizer是个抽象类,所以要实现initChannel方法, 这个方法及时用来设置ChannelHandler的
	
	    private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
   
			@Override
			protected void initChannel(SocketChannel arg0) throws Exception {
   
				arg0.pipeline().addLast(new TimeServerHandler());
			}
	
	    }
	    /**
	     * @param args
	     * @throws Exception
	     */
	    public static void main(String[] args) throws Exception {
   
			int port = 30000;
			if (args != null && args.length > 0) {
   
				try {
   
				port = Integer.valueOf(args[0]);
				} catch (NumberFormatException e) {
   
				// 采用默认值
				}
			}
			new TimeServer().bind(port);
	    }
	}
  • ChannelHandler : IO事件处理类 处理网络IO事件,例如记录日志,对消息进行编解码等…
  • ChannelHandlerAdapter : IO事件适配器类 ,进行链接、读、写、等事件的处理及扩展
    TimeServerHandler 实现业务器业务逻辑,继承了ChannelHandlerAdapter :Netty使用futures和回调概念,它的设计允许你处理不同的事件类型。你的channelhandler必须继承ChannelInboundHandlerAdapter并且重写channelRead方法,这个方法在任何时候都会被调用来接收数据,在这个例子中接收的是字节。

1、首先将接收到的数据做类型转换,将msg消息转换成Netty的ByteBuf对象,即相当于jdk中的java.nio.ByteBuffer对象,通过ByteBuf的readableBytes方法可以获取缓冲区可读的字节数,根据字节数创建byte数组,通过ByteBuf的readBytes方法将缓冲区的字节数组复制到新建的byte数组中,最后通过NewString构造字符串。由于没有加StringDecoder、StringEncoder因此,此处需要类型转换。

		ByteBuf buf = (ByteBuf) msg;
		byte[] req = new byte[buf.readableBytes()];
		buf.readBytes(req);
		String body = new String(req, "UTF-8");

2、在读完后,会调用channelReadComplete方法,由于在读方法中有异步发送应答操作,因此channelReadComplete方法中调用ChannelHandlerContext的flush方法,将消息发送队列中的消息写入到SocketChannel中发送给对象,为了防止频繁的换下Selector进行消息发送,Netty的write方法并不直接将消息写入SocketChannel中,调用write方法只是把待发送的消息放到发送缓冲数组中,再次调用flush方法,将缓冲区中的消息全部写入到SocketChannel。

		ctx.write(resp);// 异步发送应答消息给客户端
		// 再通过调用flush方法,将发送缓冲区中的消息全部写到SocketChannel中
		ctx.flush();

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

import java.util.logging.Logger;


public class TimeServerHandler extends ChannelHandlerAdapter {
   
	private static final Logger logger = Logger
			.getLogger(TimeServerHandler.class.getName());
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
	    throws Exception {
   
		ByteBuf buf = (ByteBuf) msg;
		byte[] req = new byte[buf.readableBytes()];
		buf.readBytes(req);
		String body = new String(req, "UTF-8");
		System.out.println("The time server receive order : " + body);
		String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new java.util.Date(
			System.currentTimeMillis()).toString() : "BAD ORDER";
		ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
		ctx.write(resp);// 异步发送应答消息给客户端
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
   
		// 将发送缓冲区中的消息全部写到SocketChannel中发送给对方。
		// 从性能角度考虑,为了防止频繁地唤醒Selector进行消息发送,Netty的write方法并不直接将消息写入SocketChannel中
		// 调用write方法只是把待发送的消息放到发送缓冲区数中,
		// 再通过调用flush方法,将发送缓冲区中的消息全部写到SocketChannel中
		ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
   
		logger.warning("Unexpected exception from downstream : "
				+ cause.getMessage());
		/**
		 * 当发生异常时,关闭ChannelHandlerContext,释放ChannelHandlerContext相关联的句柄等资源
		 */
		ctx.close();
    }
}

客户端类:

  • 创建Bootstrap对象用来引导启动客户端
  • 创建EventLoopGroup对象并设置到Bootstrap中,EventLoopGroup可以理解为是一个线程池,这个线程池用来处理连接、接受数据、发送数据
  • 创建InetSocketAddress并设置到Bootstrap中,InetSocketAddress是指定连接的服务器地址
  • 添加一个ChannelHandler,客户端成功连接服务器后就会被执行
  • 调用Bootstrap.connect()来连接服务器
  • 最后关闭EventLoopGroup来释放资源

1、客户端首先创建I/O读写的NioEventLoopGroup线程组,然后继续创建客户端辅助启动类Bootstrap,随后对其配置,与服务端不同的是,它的Channel需要设置为NioSocketChannel,然后为其添加Handler,此处为了简单直接创建了匿名内部类,实现initChannel方法,作用是当创建NioSocketChannel,成功之后,进行初始化时,将ChannelHandler设置到ChannelPipeline中,用于处理网络I/O事件。

EventLoopGroup group = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();// 客户端辅助启动类,然后对其进行配置
			b.group(group).channel(NioSocketChannel.class)
			 .option(ChannelOption.TCP_NODELAY, true)
					// 作用是当创建NioSocketChannel成功之后,在进行初始化时,
					// 将ChannelHandler设置到ChannelPipeline中,用于处理网络IO事件
			 .handler(new ChannelInitializer<SocketChannel>() {
					@Override
					public void initChannel(SocketChannel ch)
						throws Exception {
						ch.pipeline().addLast(new TimeClientHandler());
					}
				});
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class TimeClient {
   

    public void connect(int port, String host) throws Exception {
   
		// 配置客户端 NIO 线程组
		EventLoopGroup group = new NioEventLoopGroup();
		try {
   
			Bootstrap b = new Bootstrap();// 客户端辅助启动类,然后对其进行配置
			b.group(group).channel(NioSocketChannel.class)
				.option(ChannelOption.TCP_NODELAY, true)
					// 作用是当创建NioSocketChannel成功之后,在进行初始化时,
					// 将ChannelHandler设置到ChannelPipeline中,用于处理网络IO事件
				.handler(new ChannelInitializer<SocketChannel>() {
   
					@Override
					public void initChannel(SocketChannel ch)
						throws Exception {
   
						ch.pipeline().addLast(new TimeClientHandler());
					}
				});

			// 发起异步连接操作
			ChannelFuture f = b.connect(host, port).sync();

			// 当代客户端链路关闭
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值