前面在Tomcat中讲解了两个通道,BIO和NIO,我们这里来通过两端程序,简单模拟两个通道,找找异同点:

BIO:

1.
public class SocketServer {  
 public SocketServer() {  
  try { 
   int clientcount = 0; // 统计客户端总数  
   boolean listening = true; // 是否对客户端进行监听 
   ServerSocket server = null; // 服务器端Socket对象  
   try {  
    // 创建一个ServerSocket在端口2121监听客户请求  
    server = new ServerSocket(2121);  
    System.out.println("Server starts...");  
   } catch (Exception e) {  
    System.out.println("Can not listen to. " + e);  
   }  
   while (listening) {  
    // 客户端计数  
    clientcount++;  
    // 监听到客户请求,根据得到的Socket对象和客户计数创建服务线程,并启动之  
    new ServerThread(server.accept(), clientcount).start();  ====》一请求,一线程,这个就是模拟Tomcat的前端的
   }  
  } catch (Exception e) {  
   System.out.println("Error. " + e);  
  }  
 }   
 public static void main(String[] args) {  
  new SocketServer();  
 }  
}

上面的这一段代码,在Tomcat中实质就是Acceptor线程。

2.

对应Tomcat的工作线程实际上就是ServerThread这个类:
public class ServerThread extends Thread {  
 private static int number = 0; // 保存本进程的客户计数  
 Socket socket = null; // 保存与本线程相关的Socket对象 
 public ServerThread(Socket socket, int clientnum) {  
  this.socket = socket;  
 }  
 public void run() {  
  try {  
  
   BufferedReader in = new BufferedReader(new InputStreamReader(socket  
     .getInputStream()));  
   PrintWriter out = new PrintWriter(socket.getOutputStream()); //从socket中获取流 
   BufferedReader sysin = new BufferedReader(new InputStreamReader(  
     System.in));  
   System.out.println("[Client " + number + "]: " + in.readLine()); 
   String line;
   line = sysin.readLine(); //流式读取
   while (!line.equals("bye")) { // 如果该字符串为 "bye",则停止循环  
    out.println(line);  
    out.flush();  流式写入与刷新
    System.out.println("[Server]: " + line);  
    System.out.println("[Client " + number + "]: " + in.readLine());  
    line = sysin.readLine();  
   }  
  
   out.close(); // 关闭Socket输出流  
   in.close(); // 关闭Socket输入流  
   socket.close(); // 关闭Socket  
  } catch (Exception e) {  
   System.out.println("Error. " + e);  
  }  
 }  
  
}  

上述的工作线程,直接在这里进行了处理,在Tomcat中,这个流程非常复杂,后续的代码一直延伸到Tomcat的后端容器当中。


总结一下,其实BIO的模式很明白,一线程一个请求,从上面的例子中已经看得很清晰,
而socket的读取是流式的读取,也就是从socket中获得流,直接通过java,io进行流的读取。


NIO:
public class NIOServer {  
      
    /*标识数字*/  
    private  int flag = 0;  
    /*缓冲区大小*/  
    private  int BLOCK = 4096;  
    /*接受数据缓冲区*/  
    private  ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);  
    /*发送数据缓冲区*/  
    private  ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);  
    private  Selector selector;  
  
    public NIOServer(int port) throws IOException {  
       //======>这个相当于Tomcat中Acceptor线程,只不过这里对通道进行了再次包装,对SelectionKey进行传入,
        // 打开服务器套接字通道  
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();  
        // 服务器配置为非阻塞  
        serverSocketChannel.configureBlocking(false);  
        // 检索与此通道关联的服务器套接字  
        ServerSocket serverSocket = serverSocketChannel.socket();  
        // 进行服务的绑定  
        serverSocket.bind(new InetSocketAddress(port));  
        // 通过open()方法找到Selector  
        selector = Selector.open();  
        // 注册到selector,等待连接  
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);  
        System.out.println("Server Start----8888:");  
    }  
  
   
    private void listen() throws IOException {   
      //======> 这个相当于Tomcat中Poller线程,对前面包装的SelectionKey进行轮询,并通过handleKey传递到工作线程中去
        while (true) {  
            // 选择一组键,并且相应的通道已经打开  
            selector.select();  
            // 返回此选择器的已选择键集。  
            Set<SelectionKey> selectionKeys = selector.selectedKeys();  
            Iterator<SelectionKey> iterator = selectionKeys.iterator();  
            while (iterator.hasNext()) {          
                SelectionKey selectionKey = iterator.next();  
                iterator.remove();  
                handleKey(selectionKey);  
            }  
        }  
    }  
  
    // 处理请求  
   //======>这下面直接就是工作线程,进入Tomcat容器的环节中,并最终返回调用的结果
    private void handleKey(SelectionKey selectionKey) throws IOException {  
        // 接受请求  
        ServerSocketChannel server = null;  
        SocketChannel client = null;  
        String receiveText;  
        String sendText;  
        int count=0;  
        // 测试此键的通道是否已准备好接受新的套接字连接。  
        if (selectionKey.isAcceptable()) {  
            // 返回为之创建此键的通道。  
            server = (ServerSocketChannel) selectionKey.channel();  
            // 接受到此通道套接字的连接。  
            // 此方法返回的套接字通道(如果有)将处于阻塞模式。  
            client = server.accept();  
            // 配置为非阻塞  
            client.configureBlocking(false);  
            // 注册到selector,等待连接  
            client.register(selector, SelectionKey.OP_READ);  
        } else if (selectionKey.isReadable()) {  
            // 返回为之创建此键的通道。  
            client = (SocketChannel) selectionKey.channel();  
            //将缓冲区清空以备下次读取  
            receivebuffer.clear();  
            //读取服务器发送来的数据到缓冲区中  
            count = client.read(receivebuffer);   
            if (count > 0) {  
                receiveText = new String( receivebuffer.array(),0,count);  
                System.out.println("服务器端接受客户端数据--:"+receiveText);  
                client.register(selector, SelectionKey.OP_WRITE);  
            }  
        } else if (selectionKey.isWritable()) {  
            //将缓冲区清空以备下次写入  
            sendbuffer.clear();  
            // 返回为之创建此键的通道。  
            client = (SocketChannel) selectionKey.channel();  
            sendText="message from server--" + flag++;  
            //向缓冲区中输入数据  
            sendbuffer.put(sendText.getBytes());  
             //将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位  
            sendbuffer.flip();  
            //输出到通道  
            client.write(sendbuffer);  
            System.out.println("服务器端向客户端发送数据--:"+sendText);  
            client.register(selector, SelectionKey.OP_READ);  
        }  
    }  
  
    /** 
     * @param args 
     * @throws IOException 
     */  
    public static void main(String[] args) throws IOException {  
        // TODO Auto-generated method stub  
        int port = 8888;  
        NIOServer server = new NIOServer(port);  
        server.listen();  
    }  
}  

总结一下,上述的NIO程序中的几个阶段,可以反映出Tomcat的NIO的前端线程池的工作模式,只不过上面的代码是串行的,而Tomcat中是通过几个线程池进行交接的,每个线程池之间需要共享一些数据用于传递。

最后,在笔者编写NIO的程序出了几个几个错误,提醒一下各位,我们需要注意一下:

1.上述在每一次selectkey轮询出来的时候,需要注意最后一句话,例如当Accept事件发生后,SocketChannel需要改变自身关注的事件,因为这个时候如果还关注Accept事件,那相当于什么都发生不了,因为这个时候SocketChannel已经再发送数据了,相当于只有监测Read和Write事件才能拿到Socket的输入和输出。因此,可以看到当Accept事件发生后:
client.register(selector, SelectionKey.OP_READ);  
重新注册Read事件。
再来看看Read事件的最后一句话,注册的是Write事件
client.register(selector, SelectionKey.OP_WRITE);  
这个是因为读取完成后,立刻就要发送一些内容,所以需要改变SocketChannel的工作模式,将其置为WRITE模式。
同理,对于READ事件之后,同样立刻要读取客户端的数据,这个还得重新注册Read事件;
对于上述的通道模式的改变,Tomcat是将这些封装到NioChannel中,不断基于socket的工作模式进行切换。

2.还有一个值得注意的事情,就是buffer缓冲区的flip操作,首先需要注意的是三个buffer的属性:

这三个属性,根据读模式和写模式的不同而不同
   capacity:
  • 容量,无论是写模式,还是读模式,容量都是固定的
  • position:指当前的位置
  • 写模式时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity – 1.
  • 读模式时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0. 当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。

    limit:最大能读/写的限制
    在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。
    当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)

我们明白上述的原理后,就不难理解,为啥上述的buffer经常要flip一下,flip方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值,这样就相当于做好了读的准备,而上述代码中的sendbuffer立马要进行:
/输出到通道  
client.write(sendbuffer);  
这个client.write方法中,实际是先从buffer中进行读取字节,然后再发送到socket缓冲区中,因此虽然这个表面上看来没有读的意思,但实际上隐藏着读,因此flip操作也是必不可少的。


d.BIO连接器与NIO连接器的对比之二的更多相关文章

  1. c.BIO连接器与NIO连接器的对比

    前面两节,我们分别看了BIO和NIO的两种模式Tomcat的实现方式. BIO的方式,就是传统的一线程,一请求的模式,也就是说,当同时又1000个请求过来,如果Tomcat设置了最大Accept线程数 ...

  2. Java--Stream,NIO ByteBuffer,NIO MappedByteBuffer性能对比

    目前Java中最IO有多种文件读取的方法,本文章对比Stream,NIO ByteBuffer,NIO MappedByteBuffer的性能,让我们知道到底怎么能写出性能高的文件读取代码. pack ...

  3. 连接器|网络滤波连接器|电脑连接器|RJ45变压器-华联威电子有限公司

    连接器|网络滤波连接器|电脑连接器|RJ45变压器-华联威电子有限公司  

  4. Revit MEP API找到连接器连接的连接器

    通过conn.AllRefs;可以找到与之连接的连接器. //连接器连接的连接器 [TransactionAttribute(Autodesk.Revit.Attributes.Transaction ...

  5. Java NIO 学习笔记(七)----NIO/IO 的对比和总结

    目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...

  6. EMS设置发送连接器和接收连接器邮件大小

    任务:通过EMS命令设置发送接收连接器和接收连接器的邮件大小限制值为50MB. 以Exchange管理员身份打开EMS控制台.在PowerShell命令提示符下. 键入以下命令设置接收-连接器的最大邮 ...

  7. 1、nio说明 和 对比bio

    nio和bio的区别 bio: 面向流的. 单向的. 阻塞的,这也是b这个的由来. nio: 面向块的.(buffer) 双向的. 非阻塞的.同步的编程方式.是一种select模型 nio编程的常规步 ...

  8. Java NIO学习系列四:NIO和IO对比

    前面的一些文章中我总结了一些Java IO和NIO相关的主要知识点,也是管中窥豹,IO类库已经功能很强大了,但是Java 为什么又要引入NIO,这是我一直不是很清楚的?前面也只是简单提及了一下:因为性 ...

  9. Java NIO学习笔记九 NIO与IO对比

    Java NIO与IO Java nio 和io 到底有什么区别,以及什么时候使用nio和io,本文做一个比较. Java NIO和IO之间的主要区别 下表总结了Java NIO和IO之间的主要区别, ...

随机推荐

  1. inittab 分析

    内核初始化后,启动init进程/sbin/init,读取/etc/inittab文件进行初始化. 参考链接 http://wenku.baidu.com/view/5a82b5f67c1cfad619 ...

  2. !! 据说年薪30万的Android程序员必须知道事

    http://www.th7.cn/Program/Android/201512/742423.shtml Android中国开发精英 目前包括: Android开源项目第一篇——个性化控件(View ...

  3. session跟cookies区别

    Session和Cookie的使用总结: Session和cookie都是asp.Net中的内置对象,至于他们有什么区别,在这里就不在多说,现在来说说一些比较实用点的东西: 我们知道网站都有一个后台管 ...

  4. RobotFrameWork http/https oauth接口测试 (一)

    感觉自己最近销声匿迹快一个月了,应该总结下自己这个月学习的东西了~~~折腾完公司私有协议的接口测试(c++接口),开始折腾公司的http/https接口和webservice接口的测试,想着把所有的这 ...

  5. 《zw版·Halcon-delphi系列原创教程》 Halcon分类函数002·AI人工智能

    <zw版·Halcon-delphi系列原创教程> Halcon分类函数002·AI人工智能 AI人工智能:包括knn.gmm.svm等 为方便阅读,在不影响说明的前提下,笔者对函数进行了 ...

  6. 最快速的Android开发环境搭建ADT-Bundle及Hello World

    ADT-Bundle for Windows 是由Google Android官方提供的集成式IDE,已经包含了Eclipse,你无需再去下载Eclipse,并且里面已集成了插件,它解决了大部分新手通 ...

  7. 使用boost的asio,io_service无法初始化

    今天用vs编一个用asio写的程序,发现在tcp::acceptor::open()失败,查了好久,发现是acceptor绑定的io_service没有正确的初始化,又查了半天,发现是需要加一个预编译 ...

  8. CSS3 笔记一(Rounded Corners/Border Images/Backgrounds)

    CSS3 Rounded Corners The border-radius property is a shorthand property for setting the four border- ...

  9. HTTP中Get与Post的区别

    Http定义了与服务器交互的不同方法,最基本的方法有4种,分别是GET,POST,PUT,DELETE.URL全称是资源描述符,我们可以这样认 为:一个URL地址,它用于描述一个网络上的资源,而HTT ...

  10. asp.net服务控件的生命周期

    1. 初始化 - Init事件 (OnInit 方法)   2. 加载视图状态 - LoadViewState方法   3. 处理回发数据 - LoadPostData方法           对实现 ...