什么是BIO
BIO(Blocked Input Output)是一种同步阻塞IO。早期Java网络通信通过Socket(套接字)进行通信,这是一种阻塞式的通信。
通过BIO实现网络通信,需要一对套接字:
- 运行于服务端的ServerSocket
 
- 运行于客户端的Socket
 
Socket通信方式如下图所示:
Java使用Socket进行网络通信过程:
服务端:
1.创建ServerSocket对象,绑定地址(ip)和端口(port):serverSocket.bind(new InetSocketAddress(host,port))
2.通过accept方法监听客户端请求:accept方法调用后会阻塞住,直到客户端请求出现
3.连接建立,通过输入输出流进行通信:read方法调用会阻塞,直到数据可以读取
4.连接关闭,资源释放
客户端:
1.创建Socket对象,设置连接服务器的地址(ip)及端口(port):socket.connect(new InetSocketAddress(host,port))
2.连接建立,通过输入输出流进行通信:read方法调用会阻塞,直到数据可以读取
3.连接关闭,释放资源
Socket网络通信实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
   |  public class Server {
      public static void main(String[] args) {
          try{             ServerSocket serverSocket = new ServerSocket(8888);
              while(true){                 Socket socket = serverSocket.accept();    
                                   InputStream inputStream = socket.getInputStream();                 DataInputStream dataInputStream = new DataInputStream(inputStream);                 String str = dataInputStream.readUTF();                 System.out.println("server receive from client:" + str);
                                   OutputStream outputStream = socket.getOutputStream();                 DataOutputStream dataOutputStream = new DataOutputStream(outputStream);                 dataOutputStream.writeUTF("hello client, I am Server!");
                                   socket.close();             }         }         catch (IOException e){             e.printStackTrace();         }
      } }
 
 
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
   |  public class Client {
      public static void main(String[] args) {
          Socket socket;         try{             socket = new Socket("127.0.0.1", 8888);
                           OutputStream outputStream = socket.getOutputStream();             DataOutputStream dataOutputStream = new DataOutputStream(outputStream);             dataOutputStream.writeUTF("hello server, I am client!");
                           InputStream inputStream = socket.getInputStream();             DataInputStream dataInputStream = new DataInputStream(inputStream);             String str = dataInputStream.readUTF();             System.out.println("client receive from server:" + str);
                           socket.close();         }         catch (IOException e){             e.printStackTrace();         }
      }
  }
 
  | 
 
Server端控制台会输出:
1
   | server receive from client:hello server, I am client!
   | 
 
Client端控制台会输出:
1
   | client receive from server:hello client, I am Server!
   | 
 
为什么不推荐使用BIO
资源耗费严重:一个线程只能处理一个客户端连接,如果要管理多个客户端的话,必须为每个客户端连接创建一个线程。
1 2 3 4 5 6
   | while(true){     Socket socket = serverSocket.accept();     new Thread(() - > {              }).start(); }
  | 
 
线程池可以一定程度上缓解此问题,但无法根本改变:
1 2 3 4 5 6
   | while(true){     Socket socket = serverSocket.accept();     threadPool.execute(() -> {              }); }
  | 
 
无论如何优化,它底层还是同步阻塞的IO,无法从根本上解决问题。
同步和异步(消息通信机制)
- 同步:发出一个调用时,在没有得到结果之前,该调用就不返回
 
- 异步:调用在发出之后,这个调用就直接返回了,所以没有返回结果
 
阻塞和非阻塞(等待调用结果时的状态)
- 阻塞:调用结果返回之前,线程一直挂起
 
- 非阻塞:调用没有返回结果之前,线程不会阻塞
 
再看NIO
NIO(Non-blocking IO)是一种同步非阻塞IO,在java1.4时引入,对应java.nio包。
NIO提供了与传统BIO中的Socket和ServerSocket相对应的SocketChannel和ServerSocketChannel两种不同套接字通道的实现,两种通道都支持阻塞和非阻塞两种模式:
- 阻塞模式:基本不会使用。与传统网络编程一样,简单但性能和可靠性不好。
 
- 非阻塞模式:对高负荷、高并发应用非常好,但是编程麻烦。这也是Netty出现的重要原因。
 
NIO核心组件解读
NIO包含一下几个核心组件:
- Channel
 
- Buffer
 
- Selector
 
- Selector Key
 
它们之间的关系如下:
1.NIO通过Channel(通道)和Buffer(缓存区)来传输数据,数据总是从缓冲区写入通道,或从通道读取到缓冲区。在NIO中,所以数据都是通过Buffer处理的,Channel对应于JDK底层的Socket。
2.NIO通过Selector(选择器)来监视多个通道对象,如数据到达、连接打开等,单线程可以监视多个通道
3.将Channel注册到Selector时,会返回一个Selector Key,可以根据它获取那些IO事件已经就绪,也可以通过它获取对应的Channel进行操作。
Selector是NIO实现的关键,它使用了事件通知相关的API来选择已经就绪的通道。
简单来说,流程如下:
- 将Channel注册到Selector中
 
- 调用Selector的
select方法,这个方法会阻塞,直到有就绪的Channel出现 
- 注册到Selector的就绪态Channel会被轮询出来:新的连接、读就绪、写就绪
 
- 通过Selector Key获取就绪Channel的集和,进行后续的IO操作
 
使用NIO进行通信
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
   | public class NIOServer {
      public static void main(String[] args) throws IOException {
                   ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();                  serverSocketChannel.configureBlocking(false);                  serverSocketChannel.socket().bind(new InetSocketAddress(8888));
                   Selector selector = Selector.open();                  serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
 
          while(true){                          int select = selector.select();                          Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();                          while(iterator.hasNext()){                 SelectionKey key = iterator.next();                 if(key.isAcceptable()) {                        createChannel(key);                 }                 else if(key.isReadable()){                       doRead(key);                 }                 else if(key.isWritable()){                        doWrite(key);                 }                                  iterator.remove();             }         }     }
      private static void doWrite(SelectionKey key) throws IOException {
                   SocketChannel socketChannel = (SocketChannel)key.channel();
                   ByteBuffer byteBuffer = ByteBuffer.wrap("send to Client".getBytes());         socketChannel.write(byteBuffer);
                   key.interestOps(SelectionKey.OP_READ);     }
      private static void doRead(SelectionKey key) throws IOException {
                   SocketChannel socketChannel = (SocketChannel)key.channel();
                   ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
          try{                          int read = socketChannel.read(byteBuffer);             byteBuffer.flip();             System.out.println("Server receive:" + new String(byteBuffer.array(), 0, read));                          key.interestOps(SelectionKey.OP_WRITE);         }         catch (IOException e){             key.cancel();         }     }
           private static void createChannel(SelectionKey key) throws IOException {
                   ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel();
                   SocketChannel socketChannel = serverSocketChannel.accept();         System.out.println("Accept connection from " + socketChannel);
                   socketChannel.configureBlocking(false);
                   ByteBuffer byteBuffer = ByteBuffer.wrap(("Welcome:" + socketChannel.getRemoteAddress()                 + " assigned to" + Thread.currentThread().getName()).getBytes());         socketChannel.write(byteBuffer);
          
                   socketChannel.register(key.selector(),SelectionKey.OP_READ );     } }
 
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | public class NIOClient {
      public static void main(String[] args) throws IOException {
          SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(8888));
          socketChannel.configureBlocking(false);
          ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
          byteBuffer.put("Hello I am Client".getBytes());         byteBuffer.flip();         socketChannel.write(byteBuffer);
          byteBuffer.clear();         socketChannel.read(byteBuffer);         byteBuffer.flip();         System.out.println(new String(byteBuffer.array()));     } }
  | 
 
NIO为什么更好
- 使用比较少的线程就可以管理多个客户端连接,提高了并发量且减少了资源消耗
 
- 没有IO操作的时候,线程可以去执行其他任务,非阻塞。
 
使用NIO编写代码太难了
NIO非常难用,而且存在许多bug,开发和维护的成本比较大。一般情况下会使用Netty这个成熟的框架。
重要角色Netty
还在学习中…..