java架构学习——10. NIO高级编程与Netty入门

本篇博文主要包含:

  • IO的分类与概念
    -IO(BIO):同步阻塞式IO
    -NIO:同步非阻塞式IO
    -AIO(NIO.2):异步非阻塞式IO
  • 同步、异步与伪异步的概念
  • NIO非阻塞案例
  • Netty的简单介绍
  • Netty的特点
  • Netty的简单案例

一、NIO同步阻塞与同步非阻塞

  1. IO(BIO)和NIO区别:其本质就是阻塞和非阻塞的区别
    阻塞概念:应用程序在获取网络数据的时候,如果网络传输数据很慢,就会一直等待,直到传输完毕为止。
    非阻塞概念:应用程序直接可以获取已经准备就绪好的数据,无需等待。
    IO为同步阻塞形式,NIO为同步非阻塞形式,NIO并没有实现异步,在JDK1.7后升级NIO库包,支持异步非阻塞。
    通讯模型NIO2.0(AIO)
  • BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
  • NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
  • AIO(NIO.2):异步非阻塞式IO,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
  1. 同步、异步与伪异步
  • 同步时,应用程序会直接参与IO读写操作,并且我们的应用程序会直接阻塞到某一个方法上,直到数据准备就绪;或者采用轮训的策略实时检查数据的就绪状态,如果就绪则获取数据。

  • 异步时,则所有的IO读写操作交给操作系统,与我们的应用程序没有直接关系,我们程序不需要关心IO读写,当操作系统完成了IO读写操作时,会给我们应用程序发送通知,我们的应用程序直接拿走数据极即可。

  • 伪异步,由于BIO一个客户端需要一个线程去处理,因此我们进行优化,后端使用线程池来处理多个客户端的请求接入,形成客户端个数M:线程池最大的线程数N的比例关系,其中M可以远远大于N,通过线程池可以灵活的调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽。
    原理:
    当有新的客户端接入时,将客户端的Socket封装成一个Task(该Task任务实现了java的Runnable接口)投递到后端的线程池中进行处理,由于线程池可以设置消息队列的大小以及线程池的最大值,因此,它的资源占用是可控的,无论多少个客户端的并发访问,都不会导致资源的耗尽或宕机。

  1. IO模型关系
    在这里插入图片描述

  2. 什么是阻塞
    应用程序在获取网络数据的时候,如果网络传输很慢,那么程序就一直等着,直接到传输完毕。

  3. 什么是非阻塞
    应用程序直接可以获取已经准备好的数据,无需等待.IO为同步阻塞形式,NIO为同步非阻塞形式。NIO没有实现异步,在JDK1.7之后,升级了NIO库包,支持异步费阻塞通讯模NIO2.0(AIO)

  4. NIO非阻塞代码演示
    客户端:

class NioCliet{
	
	public static void main(String[] args) throws IOException {
		
		System.out.println("客户端已经启动。。。。。");
		//1.创建通道
		SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8090));
		//2.切换异步非阻塞
		sChannel.configureBlocking(false);
		//3.指定缓冲区大小
		ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
		Scanner scanner = new Scanner(System.in);
		while(scanner.hasNext()) {
			String str = scanner.next();
			byteBuffer.put((new Date().toString()+"\n"+str).getBytes());
			//4.切换为读取模式
			byteBuffer.flip();
			sChannel.write(byteBuffer);
			byteBuffer.clear();
		}
	}
}

服务器端:

class NioServer{
	
	public static void main(String[] args) throws IOException {
		System.out.println("服务器端已经启动.......");
		//1.创建通道
		ServerSocketChannel sChannel = ServerSocketChannel.open();
		//2.切换读取模式: 切换异步非阻塞
		sChannel.configureBlocking(false);
		//3.绑定连接
		sChannel.bind(new InetSocketAddress(8090));
		//4.获取选择器
		Selector selector = Selector.open();
		//5.将通道注册到选择器,并且指定监听接收事件
		sChannel.register(selector, SelectionKey.OP_ACCEPT);
		//6.轮训式 获取选择 已经准备就绪 的事件
		while(selector.select()>0) {
			//7.获取当前选择器所有注册的 选择键(已经就绪的监听事件)
			Iterator<SelectionKey> it= selector.selectedKeys().iterator();
			while(it.hasNext()) {
				//8.获取准备就绪的事件
				SelectionKey sk = it.next();
				//9.判断具体是什么事件准备就绪
				if(sk.isAcceptable()) {
					//10.若接收就绪,获取客户端连接
					SocketChannel socketChannel = sChannel.accept();
					//11.设置阻塞模式
					socketChannel.configureBlocking(false);
					//12.将该通道注册到服务器上
					socketChannel.register(selector, SelectionKey.OP_READ);
				}else if(sk.isReadable()) {
					//13.获取当前选择器 就绪 状态的通道
					SocketChannel socketChannel = (SocketChannel)sk.channel();
					//14.读取数据
					ByteBuffer buf = ByteBuffer.allocate(1024);
					int len = 0;
					while((len = socketChannel.read(buf))>0) {
						buf.flip();
						System.out.println(new String(buf.array(),0,len));
						buf.clear();
					}
				}
				it.remove();
			}
		}
	}
}

启用服务器后分别用浏览器和客服端访问,运行结果如图:
在这里插入图片描述
在SelectionKey类的源码中我们可以看到如下的4中属性:
1、SelectionKey.OP_CONNECT:可连接
2、SelectionKey.OP_ACCEPT:可接受连接
3、SelectionKey.OP_READ:可读
4、SelectionKey.OP_WRITE:可写
如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来,如下:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

二、Netty快速入门

  1. 什么是Netty
    Netty 是一个基于 JAVA NIO 类库的异步通信框架,它的架构特点是:异步非阻塞、基于事件驱动、高性能、高可靠性和高可定制性。
  2. Netty应用场景
    1).分布式开源框架中dubbo、Zookeeper,RocketMQ底层rpc通讯使用就是netty。
    2).游戏开发中,底层使用netty通讯。
  3. 为什么选择netty
    在本小节,我们总结下为什么不建议开发者直接使用JDK的NIO类库进行开发的原因:
    1). NIO的类库和API繁杂,使用麻烦,你需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等;
    2). 需要具备其它的额外技能做铺垫,例如熟悉Java多线程编程,因为NIO编程涉及到Reactor模式,你必须对多线程和网路编程非常熟悉,才能编写出高质量的NIO程序;
    3). 可靠性能力补齐,工作量和难度都非常大。例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等等,NIO编程的特点是功能开发相对容易,但是可靠性能力补齐工作量和难度都非常大;
    4). JDK NIO的BUG,例如臭名昭著的epoll bug,它会导致Selector空轮询,最终导致CPU 100%。官方声称在JDK1.6版本的update18修复了该问题,但是直到JDK1.7版本该问题仍旧存在,只不过该bug发生概率降低了一些而已,它并没有被根本解决。
    4.代码演示
    4.1 导入maven依赖:
        <dependency>
			<groupId>io.netty</groupId>
			<artifactId>netty</artifactId>
			<version>3.3.0.Final</version>
		</dependency>

4.2 服务器端代码

class ServerHandler extends SimpleChannelHandler{
	/**
	 * 通道关闭的时候触发
	 */
	@Override
	public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		System.out.println("channelClosed");
	}

	/**
	 * 必须是连接已经建立,关闭通道的时候才会触发.
	 */
	@Override
	public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		super.channelDisconnected(ctx, e);
		System.out.println("channelDisconnected");
	}

	/**
	 * 捕获异常
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
		super.exceptionCaught(ctx, e);
		System.out.println("exceptionCaught");

	}

	/**
	 * 接收消息
	 */
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
		super.messageReceived(ctx, e);
		System.out.println("服务器端收到客户端消息:"+e.getMessage());
		//回复内容
		ctx.getChannel().write("好的");
	}

}

//netty 服务器端
class NettyServer{
	
	public static void main(String[] args) {
		//1.创建服务类对象
		ServerBootstrap serverBootstrap = new ServerBootstrap();
		//2.创建两个线程池 分别为监听 监听端口 、 nio监听
		ExecutorService boos = Executors.newCachedThreadPool();
		ExecutorService worker = Executors.newCachedThreadPool();
		//3.设置工程并把两个线程池加入
		serverBootstrap.setFactory(new NioServerSocketChannelFactory(boos, worker));
		//4.设置管道工厂
		serverBootstrap.setPipelineFactory(new ChannelPipelineFactory() {
			
			@Override
			public ChannelPipeline getPipeline() throws Exception {
				ChannelPipeline pipeline = Channels.pipeline();
				/*ChannelPipeline是ChannelHandler的容器,它负责ChannelHandler的管理和事件拦截与调度。
				 * Netty的ChannelPipeline和ChannelHandler机制类似于Servlet 和Filter 过滤器,这类拦截器实际上是职责链模式的一种变形,
				 * 主要是为了方便事件的拦截和用户业务逻辑的定制。

				Netty的channel运用机制和Filter过滤器机制一样,它将Channel 的数据管道抽象为ChannelPipeline. 消息在ChannelPipeline中流动和传递。
				ChannelPipeline 持有I/O事件拦截器ChannelHandler 的链表,由ChannelHandler 对I/0 事件进行拦截和处理,
				可以方便地通过新增和删除ChannelHandler 来实现小同的业务逻辑定制,不需要对已有的ChannelHandler进行修改,能够实现对修改封闭和对扩展的支持。
				 */
				
				//5.将数据转换为String类型
				pipeline.addLast("decoder", new StringDecoder());
				pipeline.addLast("encode",new StringEncoder());
				pipeline.addLast("serverHandler", new ServerHandler());
				
				return pipeline;
			}
		});
		
		//6.绑定端口号
		serverBootstrap.bind(new InetSocketAddress(9999));
		System.out.println("netty server启动.......");

		while(true) {
					try {
						Thread.sleep(1000);
						System.out.println("异步非阻塞.......");
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
		}
}

4.3 客户端代码

class ClientHandler extends SimpleChannelHandler {

	
	/**
	 * 通道关闭的时候触发
	 */
	@Override
	public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		System.out.println("channelClosed");
	}

	/**
	 * 必须是连接已经建立,关闭通道的时候才会触发.
	 */
	@Override
	public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		super.channelDisconnected(ctx, e);
		System.out.println("channelDisconnected");
	}

	/**
	 * 捕获异常
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
		super.exceptionCaught(ctx, e);
		System.out.println("exceptionCaught");

	}

	/**
	 * 接受消息
	 */
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
		super.messageReceived(ctx, e);
		System.out.println("服务器端向客户端回复内容:"+e.getMessage());
	}

}

//netty客户端
class NettyClient{
	
	public static void main(String[] args) {
		System.out.println("netty client 启动.....");
		//创建客户端
		ClientBootstrap clientBootstrap = new ClientBootstrap();
		ExecutorService boos = Executors.newCachedThreadPool();
		ExecutorService work = Executors.newCachedThreadPool();
		
		clientBootstrap.setFactory(new NioClientSocketChannelFactory(boos, work));
		 
		clientBootstrap.setPipelineFactory(new ChannelPipelineFactory() {
			
			@Override
			public ChannelPipeline getPipeline() throws Exception {
				ChannelPipeline pipeline = Channels.pipeline();
				pipeline.addLast("decoder", new StringDecoder());
				pipeline.addLast("encoder", new StringEncoder());
				
				pipeline.addLast("clientHandler", new ClientHandler());
				
				return pipeline;
			}
		});
		ChannelFuture connect = clientBootstrap.connect(new InetSocketAddress("127.0.0.1", 9999));
		Channel channel = connect.getChannel();
		System.out.println("client start");
		Scanner scanner = new Scanner(System.in);
		while(true) {
			System.out.println("输入内容:");
			channel.write(scanner.next());
		}
	}
}

启用服务器后分别用浏览器和客服端访问,运行结果如图:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值