码迷,mamicode.com
首页 > 其他好文 > 详细

d.BIO连接器与NIO连接器的对比之二

时间:2016-04-10 12:50:51      阅读:204      评论:0      收藏:0      [点我收藏+]

标签:

前面在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连接器的对比之二

标签:

原文地址:http://www.cnblogs.com/yuantongaaaaaa/p/5373683.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!