zoukankan      html  css  js  c++  java
  • 第二章 BIO与NIO

    《netty权威指南》读书笔记

    一、BIO

    1、服务端程序:

     1 package bio;
     2 
     3 import java.io.BufferedReader;
     4 import java.io.IOException;
     5 import java.io.InputStreamReader;
     6 import java.io.PrintWriter;
     7 import java.net.ServerSocket;
     8 import java.net.Socket;
     9 import java.util.Date;
    10 
    11 public class BioServer {
    12 
    13     public static void main(String[] args) throws IOException {
    14         ServerSocket serverSocket = new ServerSocket(8081);
    15         Socket clientSocket = null;
    16         while(true){
    17             clientSocket = serverSocket.accept();//如果没有客户端接入,主线程阻塞在这里
    18             new Thread(new ServerHandler(clientSocket)).start();
    19         }
    20         //finall关闭serverSocket
    21     }
    22 
    23 }
    24 
    25 class ServerHandler implements Runnable{
    26     private Socket clientSocket;
    27 
    28     public ServerHandler(Socket clientSocket) {
    29         this.clientSocket = clientSocket;
    30     }
    31 
    32     @Override
    33     public void run() {
    34         try {
    35             BufferedReader reader = new BufferedReader(new InputStreamReader(this.clientSocket.getInputStream()));
    36             PrintWriter writer = new PrintWriter(this.clientSocket.getOutputStream(), true);
    37             while(true){
    38                 String body = reader.readLine();
    39                 if (body==null){
    40                     break;
    41                 }
    42                 System.out.println(body);
    43                 writer.println(new Date().toString() + "->" + body);
    44             }
    45         } catch (IOException e) {
    46             e.printStackTrace();
    47         }
    48         //finally关闭资源:流和socket
    49     }
    50 }
    View Code
    • 服务端使用8081端口打开服务,不断接入客户端请求,每接入一个请求,都创建一个线程来处理这个请求。
    • 处理逻辑:读取客户端传来的信息,之后想客户端写信息。

    2、客户端程序:

     1 package bio;
     2 
     3 import java.io.BufferedReader;
     4 import java.io.IOException;
     5 import java.io.InputStreamReader;
     6 import java.io.PrintWriter;
     7 import java.net.Socket;
     8 
     9 public class BioClient {
    10     public static void main(String[] args) throws IOException {
    11         Socket clientSocket = new Socket("127.0.0.1", 8081);
    12         BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
    13         PrintWriter writer = new PrintWriter(clientSocket.getOutputStream(), true);
    14         writer.println("haha");
    15         String resp = reader.readLine();
    16         System.out.println(resp);
    17         //finall关闭serverSocket
    18     }
    19 }
    View Code
    • 客户端创建socket去连接服务端,之后想服务端写信息,并且读取服务端传来的信息。

    服务端阻塞的几个点:

    • serverSocket.accept();//如果没有客户端接入,主线程阻塞在这里
    • 输入流InputStream的read操作也会阻塞:直到“有数据可读”或者“可用数据读取完毕”或者“发生异常”
    • 输出流OutputStream的write操作也会阻塞:直到“所有要发送的字节全部写入”或者“发生异常”

    二、NIO

    1、服务端程序

     1 package nio;
     2 
     3 import java.io.IOException;
     4 import java.net.InetSocketAddress;
     5 import java.nio.ByteBuffer;
     6 import java.nio.channels.SelectionKey;
     7 import java.nio.channels.Selector;
     8 import java.nio.channels.ServerSocketChannel;
     9 import java.nio.channels.SocketChannel;
    10 import java.util.Iterator;
    11 
    12 public class NioServer {
    13     public static void main(String[] args) throws IOException {
    14         Selector selector = Selector.open();
    15 
    16         ServerSocketChannel serverChannel = ServerSocketChannel.open();
    17         serverChannel.configureBlocking(false);
    18         serverChannel.socket().bind(new InetSocketAddress(8083));//监听链接8082端口的客户端socket
    19         serverChannel.register(selector, SelectionKey.OP_ACCEPT);//将serverChannel注册到selector,并监听接受连接事件
    20 
    21         while (selector.select() > 0) {//该方法会发生阻塞,直到至少有一个事件"准备就绪"为止
    22             Iterator<SelectionKey> it = selector.selectedKeys().iterator();
    23             while (it.hasNext()) {
    24                 SelectionKey sk = it.next();
    25                 if (sk.isAcceptable()) {
    26                     SocketChannel clientChannel = serverChannel.accept();//相当于客户端三次握手
    27                     clientChannel.configureBlocking(false);
    28                     clientChannel.register(selector, SelectionKey.OP_READ);
    29                 } else if (sk.isReadable()) {
    30                     SocketChannel clientChannel = (SocketChannel) sk.channel();
    31                     ByteBuffer buf = ByteBuffer.allocate(1024);
    32                     while (clientChannel.read(buf) > 0) {//将通道中的数据读到缓冲区,因为clientChannel已经是非阻塞的,所以这里的read是非阻塞的
    33                         buf.flip();//将缓冲区由写模式切换到读模式
    34                         byte[] bytes = new byte[buf.remaining()];
    35                         buf.get(bytes);//将缓冲区中的数据读取到bytes中
    36                         String body = new String(bytes, "UTF-8");
    37                         System.out.println("接收到来自客户端的信息:" + body);
    38                         buf.clear();
    39                     }
    40                 }
    41                 it.remove();
    42             }
    43         }
    44     }
    45 }
    View Code
    • nio三组件:
      • Buffer:用于存取数据,最主要的是ByteBuffer
        • position:下一个将被操作的字节位置
        • limit:在写模式下表示可以进行写的字节数,在读模式下表示可以进行读的字节数
        • capacity:Buffer的大小
      • Channel:用于传输数据,与Buffer相互配合
      • Selector:多路复用器。轮询注册在其上的Channel,当发现某个或者多个Channel处于“就绪状态”后(有新的TCP链接接入、读、写事件),从阻塞状态返回就绪的Channel的SelectionKey集合,之后进行IO操作。
    • selector.select():阻塞,直到有“就绪事件”发生或抛出异常

    2、客户端程序

     1 package nio;
     2 
     3 import java.io.IOException;
     4 import java.net.InetSocketAddress;
     5 import java.nio.ByteBuffer;
     6 import java.nio.channels.SocketChannel;
     7 
     8 public class NioClient {
     9     public static void main(String[] args) throws IOException {
    10         SocketChannel clientChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8083));//向服务端发出连接请求,服务端会通过accept()方法实现三次握手后,建立连接
    11         clientChannel.configureBlocking(false);
    12         ByteBuffer buf = ByteBuffer.allocate(1024);//分配在JVM堆中:capacity=1024;position=0;limit=1024
    13 //        ByteBuffer buf = ByteBuffer.allocateDirect(1024);//分配在堆外内存(物理内存)中
    14         buf.put("abcde".getBytes());//将数据写入缓冲区buf中:capacity=1024;position=5;limit=1024
    15         buf.flip();//将缓冲区从写模式切换到读模式:capacity=1024;position=0;limit=5
    16         clientChannel.write(buf);//将缓冲区的数据写入通道:capacity=1024;position=5;limit=5
    17         buf.clear();//清空缓冲区:capacity=1024;position=0;limit=1024
    18         clientChannel.close();
    19     }
    20 }
    View Code

    附:ByteBuffer使用JVM堆内存和使用堆外内存的区别:(图片来自尚硅谷的NIO教程)

    • 使用堆内存:
      • 应用程序将数据写入用户地址空间(JVM)中,之后将用户地址空间中的数据拷贝到内核地址空间(操作系统)
    • 使用堆外内存:
      • 直接在物理内存开辟空间,应用程序直接将数据写入物理内存,之后操作系统将其写入硬盘 - “零拷贝”

    三、BIO与NIO的比较

    1、线程数

    • BIO:一个客户端连接就要使用一个服务端线程来进行处理
      • 可能会有大量的想爱你成处于休眠状态,只是等待输入或输出(阻塞)
      • 为每个线程分配调用栈,大约1M,服务端内存吃紧
      • 即使服务端内存很大,线程数太大会导致线程上下文切换浪费大量时间
    • NIO:一个服务端线程操作一个Selector,就可以处理成千上万的客户端连接

    2、阻塞情况

    • BIO:读、写、接受连接都会发生阻塞
    • NIO:只有Selector.select()会阻塞,其实是等待Channel上的“就绪事件”

    3、面向对象

    • BIO:面向流
    • NIO:面向Buffer

    4、适合点

    • BIO:如果你有少量的连接使用非常高的带宽,一次发送大量的数据,也许典型的IO服务器实现可能非常契合
    • NIO:如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,例如聊天服务器,实现NIO的服务器可能是一个优势。

    四、Reactor模型

    主从模型:

    • 主线程池:
      • 全部由NIO线程组成,使用线程池是因为担心性能问题
      • 接收客户端连接请求,可能包含认证
      • 接收到客户端的连接请求并处理完成(比如认证)后,将创建出来的SocketChannel(查看上边的NIOServer类)注册到次线程池的某一条NIO线程上,之后这条NIO线程进行IO操作。
    • 次线程池:
      • 全部由NIO线程组成
      • 进行IO操作(编解码、业务逻辑等)
  • 相关阅读:
    iphone备忘录存储路径
    wp7常用Task,启动器与选择器
    sql ce 修改表数据
    MessageBox.Show()中的换行
    莫名异常
    wp7中设置toolkit的工具栏图标不能正常显示(DatePicker和TimePicker)
    json 解析
    wp7弹出提示框退出程序
    第一个ios程序
    TextBox只显示数字
  • 原文地址:https://www.cnblogs.com/java-zhao/p/7044809.html
Copyright © 2011-2022 走看看