NIO同步阻塞和非同步阻塞---过度到netty(一个异步事件驱动的网络应用程序框架)

BIO与NIO

IO为同步阻塞形式,NIO为同步非阻塞形式,NIO并没有实现异步,在JDK1.7后升级NIO库包,支持异步非阻塞
同学模型NIO2.0(AIO)
BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO(NIO.2):异步非阻塞式IO,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。

同步时,应用程序会直接参与IO读写操作,并且我们的应用程序会直接阻塞到某一个方法上,直到数据准备就绪:
或者采用轮训的策略实时检查数据的就绪状态,如果就绪则获取数据.
异步时,则所有的IO读写操作交给操作系统,与我们的应用程序没有直接关系,我们程序不需要关系IO读写,当操作
系统完成了IO读写操作时,会给我们应用程序发送通知,我们的应用程序直接拿走数据极即可。

伪异步

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

IO模型关系

在这里插入图片描述

什么是阻塞

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

什么是非阻塞

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

选择KEY

1、SelectionKey.OP_CONNECT
2、SelectionKey.OP_ACCEPT
3、SelectionKey.OP_READ
4、SelectionKey.OP_WRITE
如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来,如下:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
在SelectionKey类的源码中我们可以看到如下的4中属性,四个变量用来表示四种不同类型的事件:可读、可写、可连接、可接受连接

实例

非阻塞:

######## 客户端


public static void main(String[] args) throws IOException {
			System.out.println("客户端已经被启动:");
			//创建socket通道
			SocketChannel socketChannel=SocketChannel.open(new InetSocketAddress("127.0.0.1",8080));
			//切换异步非阻塞
			socketChannel.configureBlocking(false);
			//指定缓存区大小
			ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
			byteBuffer.put(new Date().toString().getBytes());
			//切换到读取模式
			byteBuffer.flip();
			socketChannel.write(byteBuffer);
			byteBuffer.clear();
			socketChannel.close();
		}

####### 服务端

public static void main(String[] ages) throws IOException {
	System.out.println("服务端启动..........");
	//创建服务通道
			ServerSocketChannel open = ServerSocketChannel.open();
			//异步非阻塞
			open.configureBlocking(false);
			//绑定连接
			open.bind(new InetSocketAddress(8080));
			//获取选择器
			Selector selector = Selector.open();
			//将通道注册到选择器中
			open.register(selector, SelectionKey.OP_ACCEPT);
			while(selector.select()>0) {
				//获取当前选择器,有注册已经监听到的事件
				Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
				//判断事件准备就绪
				while(iterator.hasNext()) {
					//获取准备接续事件
					SelectionKey next = iterator.next();
					//判断事件是否就绪(没有就绪的)
					if(next.isAcceptable()) {
						//获取客户端的连接
						SocketChannel accept = open.accept();
						//设置阻塞事件
						accept.configureBlocking(false);
						//将该通道注册到选择器上
						accept.register(selector, SelectionKey.OP_READ);
					}else if(next.isReadable()) {//就绪的
						//获取当前状态的通道
						SocketChannel channel = (SocketChannel) next.channel();
						//读取数据
						int len=0;
						ByteBuffer allocate = ByteBuffer.allocate(1024);
						while((len=(channel.read(allocate)))>0) {
							allocate.flip();
							System.out.println(new String(allocate.array(),0,len));
							allocate.clear();
						}
						
					}
					iterator.remove();
				}
			}
}

为什么选择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发生概率降低了一些而已,它并没有被根本解决。该BUG以及与该BUG相关的问题单如下:
    

在这里插入图片描述

扫描二维码关注公众号,回复: 8717047 查看本文章

Netty应用场景

1.分布式开源框架中dubbo、Zookeeper,RocketMQ底层rpc通讯使用就是netty。
2.游戏开发中,底层使用netty通讯。

单客户端的连接:

服务端:

public static void main(String[] args) {
        startServer();
    }

    public static void startServer(){
        //1.定义server启动类
        ServerBootstrap serverBootstrap = new ServerBootstrap();

        //2.定义工作组:boss分发请求给各个worker:boss负责监听端口请求,worker负责处理请求(读写)
        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup worker = new NioEventLoopGroup();

        //3.定义工作组
        serverBootstrap.group(boss,worker);

        //4.设置通道channel
        serverBootstrap.channel(NioServerSocketChannel.class);//A
        //serverBootstrap.channelFactory(new ReflectiveChannelFactory(NioServerSocketChannel.class));//旧版本的写法,但是此过程在A中有同样过程

        //5.添加handler,管道中的处理器,通过ChannelInitializer来构造
      serverBootstrap.childHandler(new ChannelInitializer<Channel>() {
            @Override
            protected void initChannel(Channel channel) throws Exception {
                //此方法每次客户端连接都会调用,是为通道初始化的方法

                //获得通道channel中的管道链(执行链、handler链)
                ChannelPipeline pipeline = channel.pipeline();
                pipeline.addLast(new StringDecoder());
                pipeline.addLast("serverHandler1",new ServerHandler());
                pipeline.addLast("serverHandler2",new ServerHandler2());
                pipeline.addLast(new StringEncoder());

                System.out.println("success to initHandler!");
            }
        });

        //6.设置参数
        //设置参数,TCP参数
        serverBootstrap.option(ChannelOption.SO_BACKLOG, 2048);         //连接缓冲池的大小
        serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);//维持链接的活跃,清除死链接
        serverBootstrap.childOption(ChannelOption.TCP_NODELAY, true);//关闭延迟发送

        //7.绑定ip和port
        try {
            ChannelFuture channelFuture = serverBootstrap.bind("127.0.0.1", 8080).sync();//Future模式的channel对象
            //7.5.监听关闭
            channelFuture.channel().closeFuture().sync();  //等待服务关闭,关闭后应该释放资源
        } catch (InterruptedException e) {
            System.out.println("server start got exception!");
            e.printStackTrace();
        }finally {
            //8.优雅的关闭资源
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }

    }
handle:
      public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
	super.channelRead(ctx, msg);
	System.out.println("服务端消息:"+msg.toString());
	ctx.channel().writeAndFlush("serverHandler"+System.currentTimeMillis());
	//把消息往下一个Handler传
    ctx.fireChannelRead(msg);
}

}
public class ServerHandler2 extends ChannelInboundHandlerAdapter{
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
System.out.println("服务端2消息 "+msg);
ctx.channel().writeAndFlush(“this is ServerHandler2”+System.currentTimeMillis());
}
}

客户端

public class NettySingleClient {
public static void main(String[] args) {
    startClient();
}

public static void startClient(){
    //1.定义服务类
    Bootstrap clientBootstap = new Bootstrap();

    //2.定义执行线程组
    EventLoopGroup worker = new NioEventLoopGroup();

    //3.设置线程池
    clientBootstap.group(worker);

    //4.设置通道
    clientBootstap.channel(NioSocketChannel.class);

    //5.添加Handler
    clientBootstap.handler(new ChannelInitializer<Channel>() {
        @Override
        protected void initChannel(Channel channel) throws Exception {
            System.out.println("客户端初始化:");
            ChannelPipeline pipeline = channel.pipeline();
            pipeline.addLast("StringDecoder",new StringDecoder());
            pipeline.addLast("StringEncoder",new StringEncoder());
            pipeline.addLast("ClientHandler",new ClientHandler());
        }
    });

    //6.建立连接
    ChannelFuture channelFuture = clientBootstap.connect("127.0.0.1",8080);
    try {
        //7.测试输入
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        while(true){
            System.out.println("请输入:");
            String msg = bufferedReader.readLine();
            channelFuture.channel().writeAndFlush(msg);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        //8.关闭连接
        worker.shutdownGracefully();
    }
}

}
handle:

public class ClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    System.out.println("client receive msg:"+msg.toString());
}

}

发布了26 篇原创文章 · 获赞 0 · 访问量 696

猜你喜欢

转载自blog.csdn.net/YHM_MM/article/details/104006727