`
shaobenbin
  • 浏览: 18186 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

nio&netty系列之一nio基础

    博客分类:
  • java
阅读更多

 

写在文章初始

 

 

       很久没有读源码了,近来据说netty的源码很漂亮,而且自身对nio的理解其实一直也不到位,所以有天突然心血来潮,决定沉下心,重新学习nio的知识,并通过读netty的源码来加深理解,但是现实生活中,工作,娱乐,心情等各种原因,导致整个过程很漫长,所以我决定一篇一篇的整理我的笔记内容发布到我的博客。算是一个进步过程吧.

 

JAVA NIO基础

 

        NIO是jdk1.4推出的新概念,同JDK1.5版本concurrent并发包,是当前java应用中非常火的两个玩意,前者提升了网络传输编码的性能,后者整个将并发编程的高度提升了一个档次。此文首先从NIO的基础入手(jdk7都有nio2了,得赶紧补旧知识的说),然后再去慢慢深入netty的设计与实现,仅当做我最近一段时间从事netty使用这个工作的一个笔记。

      NIO有三个重要概念:缓冲区(ByteBuffer),通道(Channel),选择器(Selected),通道传输的数据必须是缓冲区对象,通道注册在选择器上,选择器上有四个事件,op_accept(服务器上特有,接受连接事件),op_connection(客户端特有,建立连接事件),op_writer(写事件),op_read(读事件).

     我们先略微介绍下这三个概念,如果对这三个概念需要细究的同学,附件有上传我学习的nio知识的PDF,个人认为已经很详细了.

  1. 缓冲区:首先在NIO中,我们不再以传统的socket的字符流传递数据,而是以块传递数据,是将数据放到缓存区然后推送到通道的。也就是说通道只接受Buffer对象,通常我们使用ByteBuffer.
  2.  通道:字面意思,客户端和服务器端连接的一条通道,用于传输数据,只接受Buffer对象。通道可以注册到选择器上。
  3. 选择器:这个概念较复杂,如果熟悉linux的IO系统的童鞋,那么恭喜,概念是类似的,java的nio的思想应该是来源于linux的异步非阻塞模式select.选择器用于管理所有的通道,是个事件触发模式,当通道就绪的时候,就会触发响应的事件并使对应的key就绪,然后主程序只要获取所有的就绪key(每一个key对应一个唯一的通道),然后做对应的业务逻辑即可。

NIO的过程

      JAVA的NIO模型是借鉴了操作系统,应该可以说是直接借助了操作系统的IO模型,由于本人对NIO的源码未曾仔细深入,且对linux太菜,所以这些资料基本都是从别人的博客资料查询而知。因没自信介绍NIO的模型,所以暂时用生活的一个场景来描述NIO的工作过程。

      餐厅点菜是一个很好的场景,我记得也有人用过。在以前,我们点菜的时候,服务员会等在你旁边记录你要点的菜,有时候你思考的越久,服务员等待的越久,这就是传统的SOCKET,这个服务员在你点菜的过程中被阻塞了。那么现在点菜就不一样了,服务员给你一张菜单和点菜单,你自己写完菜单然后叫服务员即可,这就是NIO模型,那我们就来分析这个新的点菜过程。

      1、客户进入餐厅,餐厅总台指定一个服务员上来迎接,帮你安排座位,给你菜单和点菜单。

      2、服务员去总台服务员那里登记有新客户到来,并且由我服务,客户正在准备点餐。

      3、客户思考今天要吃什么菜,并且记录到点菜单上,让服务员把点菜单放到总台哪边去

      4、总台服务员定时扫描点菜单

      5、将点菜内容发送给厨房。

      6、厨房厨师开始准备指定的餐

      7、总台服务员定时扫描,看那些客户的菜做完了,就通知与客户对应的服务员过来

      8、服务员就把菜送到客户那里。

      这里我们客户初略的概括为,餐厅总台是服务器,客户是客户端,服务员是通道(channel)。对于餐厅而言,服务员收取点餐单是读事件,送餐是写事件,点菜单和餐都是缓冲区(buffer),总台的服务员是选择器(selector),厨房的厨师就是业务逻辑实现者。所以上述六个过程概括为

      1、餐厅与客户创建一个通道,这个通道是指服务员。

      2、然后将此通道注册到selector(总台服务员)上,并为此selector注册了一个读事件(告诉总台服务员客户要准备点餐)

      3、客户写数据到点菜单,并有通道(服务员)交给餐厅总台哪里。

      4、服务器端的选择器selector查看哪边的点菜单写完了,触发服务器的读事件

      5、总台读取缓存区(点菜单)数据发送到业务逻辑那里

      6、业务逻辑(做菜),把做好的菜放到缓冲区(餐盘)

      7、selector扫描哪个客户的数据处理完毕,就通知指定通道

      8、通道将数据传输给客户。

NIO与传统socket对比      

那么说到此处,概念仍旧模糊,那么这个NIO与传统的Socket编程的优势体现在哪里?我来比较下传统socket编程的流程和nio编程的流程来分析下孰优孰劣.

 传统socket编程流程

     1、客户端创建一个socket连接到服务器

     2、客户端等待socket建立连接(阻塞)

     3、客户端发起请求

     4、客户端读取数据(阻塞)

     5、结束。

     注意:

     第一步:创建socket建立连接是个阻塞模式,所以说创建socket的成本很高,以前编程我们会使用一个socket连接池,创建一批socket连接放到连接池中去,从连接池获取socket进行操作,操作完成归还socket回连接池,例如数据库连接池是一样的设计。我们通常称之为长连接。

     第三步,客户端读取数据是个阻塞模式,在读取数据的时候我在等待服务器返回数据。如果服务器响应时间为10S,那么我这里要等待10秒才能获取数据。这个很难优化。

     NIO编程流程

     1、客户端创建一个通道(SocketChannel)

     2、创建一个选择器(Selector,如果没有选择器的话)

     3 、通道注册到选择器上,注册事件为OP_CONNECT

     4、客户端选择器触发OP_CONNECT事件。(编码中移除该就绪key)

     5、捕捉到OP_CONNECT后等待连接创建完毕(阻塞)

     6、连接创建完毕,直接发送数据。并且注册当前通道为OP_READ事件。

     7、当服务器端有数据返回的时候,触发通道的OP_READ事件

     8、读取内容。

     注意:
     1、第五步其实也是一个阻塞模式,所以不要频繁的创建通道,一般一个服务器和一个客户端之间只需要一个通道存在,最好是长期存在的。
     两者比较:NIO编程比传统的socket编程从逻辑上看就复杂很多,但是它主要是优化了传统socket编程的第四步,读取数据的时候可以不再是个阻塞模式,而是一个异步模式,在NIO状态下,发送完数据,注册一个OP_READ事件后就不管了,执行接下去的业务逻辑,直到有数据从服务器返回才会触发读取操作。
     疑问二:我客户端就是要同步操作,发送完数据后就是想等待返回数据,那么NIO的优势不是没有了么?

     其实大家如果换位思考,上面我换成服务器端,那么大家思考下,传统socket编程模式下,客户端与服务器端建立socket后,服务器首先也是在等待客户端传输数据过来,这个过程是一直阻塞的,而服务器端此过程完全没必要使用同步,这是很消耗服务器性能的。在NIO模式下,服务器与客户端建立完通道,服务器注册一个OP_READ事件后,服务器的CPU就可以去忙其它事情了,等客户端数据传输过来后,服务器才会触发读事件并处理相应业务逻辑,所以NIO对于服务器的性能提升是很明显的,并且由于NIO使用了块的传输方式,充分的利用了当今操作系统的性能,在传输性能上也会有明显的提升。

      BTW:这是我对传统socket编程和NIO的一些浅薄理解

 JAVA NIO 简单实例

//服务器端类
public class NioService {
       private static final int port = 4444;
       private Selector selector = null;

       // 启动服务
       public void startUp() {
             try {
                   int channels = 0;
                   int nKeys = 0;
                   int currentSelector = 0;

                   // 创建一个通道
                  ServerSocketChannel serverSocketChannel = ServerSocketChannel
                              . open();
                   // 绑定地址,类似 sokcet的绑定
                  serverSocketChannel.socket().bind(
                               new InetSocketAddress(InetAddress.getLocalHost(), 4444));
                   // 必须设置成非同步模式,在要注册到选择器的前提下,否则在注册的时候会报异常
                  serverSocketChannel.configureBlocking( false);
                   // 使用Selector
                   selector = Selector.open();
                   // 注册一个感兴趣的事件,这里是OP_ACCEPT事件.
                  SelectionKey s = serverSocketChannel.register(selector ,
                              SelectionKey. OP_ACCEPT);
                  System. out.println("服务器启动成功" );
                  listen();

            } catch (IOException e) {
                   // TODO Auto-generated catch block
                  e.printStackTrace();
            }
      }

       private void listen() throws IOException {
             int nKeys = 0;
             while (true ) {
                   // 阻塞线程,至到有就绪通道,返回值为有多少key已经就绪
                  nKeys = selector.select();
                   //获取就绪key的集合
                   Set selectedKeys = selector.selectedKeys();
                   for (Iterator keys = selectedKeys.iterator(); keys.hasNext();) {
                        SelectionKey key = (SelectionKey) keys.next();
                         //将就绪的key从就绪key中移除掉,否则每次获取都是就绪的,而非更新的就绪key
                        keys.remove();
                         if (key.isAcceptable()) {
                              SocketChannel socketChannel= ((ServerSocketChannel) key.channel()).accept().socket()
                              .getChannel();
                              socketChannel.configureBlocking( false);
                              socketChannel.register( selector,
                                                      SelectionKey. OP_READ);
                        } else if (key.isReadable()) {
                              Socket socket = ((SocketChannel) key.channel()).socket();
                              SocketChannel sc = socket.getChannel();
                              sc.configureBlocking( false);
                              ByteBuffer bb = ByteBuffer. allocate(1024);
                              sc.read(bb);
                              System. out.println("客户端接受到的数据为:" +new String(bb.array()));
                              sc.register( selector,
                                                      SelectionKey. OP_WRITE);
                        } else if (key.isWritable()) {
                              Socket socket = ((SocketChannel) key.channel()).socket();
                              SocketChannel sc = socket.getChannel();
                              ByteBuffer bb = ByteBuffer. wrap("hello world again!"
                                          .getBytes());
                              sc.write(bb);
                              sc.configureBlocking( false);
                              sc.register( selector,
                                                      SelectionKey. OP_READ);
                        }
                  }
            }
      }

       public static void main(String[] ben) {
            NioService ns = new NioService();
            ns.startUp();
      }

 

//客户端类
public class NioClient {
       public static void main(String[] ben) throws IOException{
             int selectNum;
            
            Selector selector = Selector. open();
            SocketChannel socketChannel = SocketChannel. open();
            socketChannel.configureBlocking( false);
            socketChannel.register(selector, SelectionKey. OP_CONNECT);
            socketChannel.connect( new InetSocketAddress(InetAddress.getLocalHost(), 4444));
            
            
             while(true ){
                  selectNum = selector.select();
                   if(selectNum>0){
                        Set<SelectionKey> keys= selector.selectedKeys();
                         for(Iterator<SelectionKey> it = keys.iterator();it.hasNext();){
                              SelectionKey key = it.next();
                              it.remove();
                               if(key.isConnectable()){
                                    SocketChannel channel = ((SocketChannel)key.channel());
                                    channel.configureBlocking( false);
                                     // 判断此通道上是否正在进行连接操作。 
                          // 完成套接字通道的连接过程。 
                          if (channel.isConnectionPending()) { 
                              channel.finishConnect();
                              channel.write(ByteBuffer. wrap("client connection over!".getBytes()));
                                          channel.register(selector, SelectionKey.OP_WRITE );
                          }
                              } else if (key.isWritable()){
                                    key.channel().configureBlocking( false);
                                    ((SocketChannel)key.channel()).write(ByteBuffer. wrap("hello,i'm from client!".getBytes()));
                                    key.channel().register(selector, SelectionKey.OP_READ );
                                     //客户端线程去处理其它业务逻辑了
                                    System. out.println("cc" );
                                    System. out.println("cc" );
                                    System. out.println("cc" );
                                    System. out.println("cc" );
                                    System. out.println("cc" );
                                    System. out.println("cc" );
                              } else if (key.isReadable()){
                                    ByteBuffer bb = ByteBuffer.allocate(1024);
                                    ((SocketChannel)key.channel()).read(bb);
                                    System. out.println(new String(bb.array()));
                                    key.channel().configureBlocking( false);
//                                  key.channel().register(selector, SelectionKey.OP_WRITE);
                                     //本次模拟的是一次读取过程,所以读完后就关闭
                                    key.channel().close();
                                    selector.close();
                              }
                              
                        }
                  }
            }
      }

}

 

 

 

 

 

 

 

 

分享到:
评论

相关推荐

    java nio&netty系列之三netty网络模型代码以及简化版代码示例

    Java NIO(New IO)是Java 1.4版本引入的一种新的IO API,用来替代标准的Java IO API。NIO提供了非阻塞的I/O操作,可以提高在处理多个连接时的性能。Netty是一个高性能、异步事件驱动的网络应用框架,用于快速开发可...

    NIO+Netty5视频教程与Netty源码剖析视频教程

    标签关键词"nio"、"netty教程"、"netty源码"、"netty5"和"nio"表明,本教程不仅关注于基础的NIO技术,更专注于Netty5这一特定版本,以及源码级别的理解和应用。通过学习这个教程,开发者不仅能提升网络编程能力,还...

    从NIO到Netty,编程实战出租车905协议-08172347.pdf

    905.4-2014协议,是交通运输部公路科学研究院起草定制的一个协议标准,它也是基于TCP之上的一个应用层传输协议。 第2章,介绍在Socket编程过程中一些基础知识,让大家建立起对这块知识内容的一个整体轮廓; 第3章,...

    【项目实战】Netty源码剖析&NIO;+Netty5各种RPC架构实战演练三部曲视频教程(未加密)

    尤其是在RPC架构领域,Netty凭借其强大的功能和灵活的设计成为了构建分布式系统的首选工具之一。希望本教程能够帮助大家更好地理解和运用Netty进行项目实战。 以上就是对Netty源码剖析及NIO与Netty5在RPC架构中的...

    NIO netty开发

    netty开发之nio netty开发之nio netty开发之nio netty开发之nio netty开发之nio netty开发之nio netty开发之nio netty开发之nio netty开发之nio netty开发之nio netty开发之nio netty开发之nio netty开发之nio netty...

    Java_NIO框架Netty教程.pdf

    4. **编解码处理**:Netty提供了一系列预定义的编解码器,如ByteToMessageDecoder和MessageToByteEncoder,用于将原始字节流转换为有意义的对象,反之亦然。 5. **异步通信**:Netty基于事件驱动模型,所有的I/O...

    Java-NIO-Netty框架学习

    学习Netty,可以从基础的Socket编程开始,然后深入理解Java NIO的基本概念,接着熟悉Netty提供的各种组件和API,通过编写简单的服务端和客户端程序来实践。随着对Netty的理解加深,可以尝试实现更复杂的网络应用,如...

    Java_NIO框架Netty教程

    资源名称:Java_NIO框架Netty教程资源截图: 资源太大,传百度网盘了,链接在附件中,有需要的同学自取。

    详细介绍 NIO与Netty编程-实战讲义详细pdf.7z

    **NIO(非阻塞I/O)与Netty编程**是现代Java网络应用开发中的重要技术,它们在处理高并发、低延迟的网络通信场景中起着关键作用。本讲义详细介绍了这两种技术,旨在帮助开发者更好地理解和运用它们。 ### 一、BIO...

    Java NIO框架Netty教程.pdf

    通过本教程,我们不仅了解了Netty的基础概念,还通过实际代码示例学习了如何使用Netty构建一个简单的服务器和客户端应用程序。这对于理解Netty的工作原理和如何在实际项目中应用Netty都是非常有帮助的。

    Socket 之 BIO、NIO、Netty 简单实现

    Socket是网络编程中的基础组件,它提供了进程间通信(IPC)和网络通信的能力。...通过阅读《Socket 之 BIO、NIO、Netty 简单实现》的博客,你可以了解如何在Java中实现这些通信模型,从而提升网络服务的性能和可靠性。

    NIO+Netty5视频教程2018

    【压缩包子文件的文件名称列表】"netty"很可能包含了一系列与Netty相关的资料,比如代码示例、配置文件、课件或笔记。这些材料可能辅助学习者更深入地实践和理解Netty框架的应用。 总的来说,这个教程适合有Java...

    NIO框架Netty实现高性能高并发

    最近一个圈内朋友通过私信告诉我,通过使用Netty4 + Thrift压缩二进制编解码技术,他们实现了10W TPS(1K的复杂POJO对象)的跨 节点远程服务调用。相比于传统基于Java序列化+BIO(同步阻塞IO)的通信框架,性能提升...

    java网络编程 nio-netty

    java网络编程 nio-netty,想要学习netty的同学,这本书是非常好的资源。

    自己手写nio和netty,不建议下载

    NIO(Non-blocking Input/Output)是Java提供的一种...总之,NIO是一种改进的I/O模型,它提高了处理并发连接的能力,而Netty是建立在NIO之上的网络通信框架,提供了更高级别的抽象和优化,使得网络编程更为便捷高效。

    jvm、nio、netty优化使用.txt

    Netty是一个NIO客户端服务器框架,可以快速轻松地开发网络应用程序,例如协议服务器和客户端。它极大地简化和简化了TCP和UDP套接字服务器等网络编程。 “快速简便”并不意味着最终的应用程序将遭受可维护性或性能...

    NIO和Netty框架的学习

    通过深入学习NIO和Netty,你将能够构建出高效、稳定的网络应用,无论是开发WebSocket服务、TCP服务器还是其他协议的网络应用,Netty都能为你提供强大的支持。不断实践和探索,你将在网络编程的道路上越来越熟练。

Global site tag (gtag.js) - Google Analytics