干货|精读Netty源码---NIO之Channel

Posted 牛客NOIP竞赛学

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了干货|精读Netty源码---NIO之Channel相关的知识,希望对你有一定的参考价值。

NIO简介

1、概述

Java NIO( New IO 或者 Non Blocking IO ) ,从 Java 1.4 版本开始引入的非阻塞 IO ,用于替换标准( 有些文章也称为传统,或者 Blocking IO 。下文统称为 BIO ) Java IO API 的 IO API 。


2、核心组件

Java NIO由如下三个核心组件组成

Channel

Buffer

Selector


3、NIO和BIO对比

NIO BIO
基于缓冲区 基于流
非阻塞IO 阻塞IO
选择器


BIO 是面向字节流或者字符流的,而在 NIO 中,它摒弃了传统的 IO 流,而是引入 Channel 和 Buffer 的概念:从 Channel 中读取数据到 Buffer 中,或者将数据从 Buffer 中写到 Channel 中。


① 那么什么是基于 Stream呢?

在一般的 Java IO 操作中,我们以流式的方式,顺序的从一个 Stream 中读取一个或者多个字节,直至读取所有字节。因为它没有缓存区,所以我们就不能随意改变读取指针的位置。


② 那么什么是基于 Buffer 呢?

基于 Buffer 就显得有点不同了。我们在从 Channel 中读取数据到 Buffer 中,这样 Buffer 中就有了数据后,我们就可以对这些数据进行操作了。并且不同于一般的 Java IO 操作那样是顺序操作,NIO 中我们可以随意的读取任意位置的数据,这样大大增加了处理过程中的灵活性。


Java NIO 引入 Selector ( 选择器 )的概念,它是 Java NIO 得以实现非阻塞 IO 操作的最最最关键。


我们可以注册多个 Channel 到一个 Selector 中。而 Selector 内部的机制,就可以自动的为我们不断的执行查询操作,判断这些注册的 Channel 是否有已就绪的 IO 事件( 例如可读,可写,网络连接已完成)。


通过这样的机制,一个线程通过使用一个 Selector ,就可以非常简单且高效的来管理多个 Channel 了。


Channel


1、概述

在 Java NIO 中,基本上所有的 IO 操作都是从 Channel 开始。数据可以从 Channel 读取到 Buffer 中,也可以从 Buffer 写到 Channel 中


2、与流的区别

  • 对于同一个 Channel ,我们可以从它读取数据,也可以向它写入数据。而对于同一个 Stream ,通常要么只能读,要么只能写

  • Channel 可以非阻塞的读写 IO 操作,而 Stream 只能阻塞的读写 IO 操作。

  • Channel 必须配合 Buffer 使用,总是先读取到一个 Buffer 中,又或者是向一个 Buffer 写入。也就是说,我们无法绕过 Buffer ,直接向 Channel 写入数据。


3、Channel的实现

Channel源码如下:public interface Channel extends Closeable { public boolean isOpen();//判断此通道是否打开    public void close() throws IOException;//关闭通道    Channel比较重要的实现类有以下三个:SocketChannel:客户端发起tcp的ChannelServerSocketChannel:服务端用来监听新的tcp连接的Channel。每个请求都会创建一个对应的SocketChannelDatagramChannelServerSocketChannelJava NIO中的ServerSocketChannel是一个可以监听新进来的TCP连接的通道ServerSocketChannel
通过调用 ServerSocketChannel.open() 方法来打开ServerSocketChannel.如:ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();//关闭 ServerSocketChannel//通过调用ServerSocketChannel.close() 方法来关闭ServerSocketChannel. 如:
serverSocketChannel.close();


监听新进来的连接


通过 ServerSocketChannel.accept() 方法监听新进来的连接。当 accept()方法返回的时候,它返回一个包含新进来的连接的 SocketChannel。因此, accept()方法会一直阻塞到有新连接到达。

通常不会仅仅只监听一个连接,在while循环中调用 accept()方法. 如下面的例子:


while(true){ SocketChannel socketChannel = serverSocketChannel.accept(); //do something with socketChannel...}


非阻塞模式


ServerSocketChannel可以设置成非阻塞模式。在非阻塞模式下,accept() 方法会立刻返回,如果还没有新进来的连接,返回的将是null。因此,需要检查返回的SocketChannel是否是null.如:


ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.socket().bind(new InetSocketAddress(9999));serverSocketChannel.configureBlocking(false);while(true){ SocketChannel socketChannel = ServerSocketChannel.accept(); if(socketChannel != null){ //do something with socketChannel... }}


SocketChannel


Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。可以通过以下2种方式创建SocketChannel:


1、个SocketChannel并连接到互联网上的某台服务器。
2、新连接到达ServerSocketChannel时,会创建一个SocketChannel。

打开SocketChannel


SocketChannel socketChannel = SocketChannel.open();socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
关闭SocketChannel
socketChannel.close();
从 SocketChannel 读取数据
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);


首先,分配一个Buffer。从SocketChannel读取到的数据将会放到这个Buffer中。

然后,调用SocketChannel.read()。该方法将数据从SocketChannel 读到Buffer中。read()方法返回的int值表示读了多少字节进Buffer里。如果返回的是-1,表示已经读到了流的末尾(连接关闭了)


写入 SocketChannel

String newData = "New String to write to file..." + System.currentTimeMillis();ByteBuffer buf = ByteBuffer.allocate(48);buf.clear();buf.put(newData.getBytes());buf.flip();while(buf.hasRemaining()) { channel.write(buf);}


注意SocketChannel.write()方法的调用是在一个while循环中的。Write()方法无法保证能写多少字节到SocketChannel。所以,我们重复调用write()直到Buffer没有要写的字节为止。


非阻塞模式


可以设置 SocketChannel 为非阻塞模式(non-blocking mode).设置之后,就可以在异步模式下调用connect(), read() 和write()了。


如果SocketChannel在非阻塞模式下,此时调用connect(),该方法可能在连接建立之前就返回了。为了确定连接是否建立,可以调用finishConnect()的方法。


socketChannel.configureBlocking(false);socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));while(! socketChannel.finishConnect() ){ //wait, or do something else...}


DatagramChannel


Java NIO中的DatagramChannel是一个能收发UDP包的通道。因为UDP是无连接的网络协议,所以不能像其它通道那样读取和写入。它发送和接收的是数据包。

打开 DatagramChannel


DatagramChannel channel = DatagramChannel.open();channel.socket().bind(new InetSocketAddress(9999));


接收数据


通过receive()方法从DatagramChannel接收数据


ByteBuffer buf = ByteBuffer.allocate(48);buf.clear();channel.receive(buf);


receive()方法会将接收到的数据包内容复制到指定的Buffer. 如果Buffer容不下收到的数据,多出的数据将被丢弃。


发送数据

通过send()方法从DatagramChannel发送数据


String newData = "New String to write to file..." + System.currentTimeMillis();ByteBuffer buf = ByteBuffer.allocate(48);buf.clear();buf.put(newData.getBytes());buf.flip();int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));


这个例子发送一串字符到”jenkov.com”服务器的UDP端口80。因为服务端并没有监控这个端口,所以什么也不会发生。也不会通知你发出的数据包是否已收到,因为UDP在数据传送方面没有任何保证。




channel.connect(new InetSocketAddress("jenkov.com", 80));


当连接后,也可以使用read()和write()方法,就像在用传统的通道一样。只是在数据传送方面没有任何保证。


int bytesRead = channel.read(buf);int bytesWritten = channel.write(but);


更多有趣的内容

请长按


长按二维码关注



戳阅读原文与作者交流


以上是关于干货|精读Netty源码---NIO之Channel的主要内容,如果未能解决你的问题,请参考以下文章

#yyds干货盘点# Netty源码分析之Reactor线程模型详解

#yyds干货盘点#netty系列之:netty中各不同种类的channel详解

[Netty源码系列]-Nio入门

Java NIO 网络编程《Netty In Action》 #yyds干货盘点#

Netty精粹之JAVA NIO开发需要知道的

Netty和JDK源码来看Netty的NIO和JDK的NIO有什么不同