Netty 是基于Java NIO 封装的网络通讯框架,只有充分理解了 Java NIO 才能理解好Netty的底层设计。Java NIO 由三个核心组件组件:

  • Buffer

  • Channel

  • Selector

缓冲区 Buffer

Buffer 是一个数据对象,我们可以把它理解为固定数量的数据的容器,它包含一些要写入或者读出的数据。

在 Java NIO 中,任何时候访问 NIO 中的数据,都需要通过缓冲区(Buffer)进行操作。读取数据时,直接从缓冲区中读取,写入数据时,写入至缓冲区。NIO 最常用的缓冲区则是 ByteBuffer。下图是 Buffer 继承关系图:

每一个 Java 基本类型都对应着一种 Buffer,他们都包含这相同的操作,只不过是所处理的数据类型不同而已。

通道 Channel

Channel 是一个通道,它就像自来水管一样,网络数据通过 Channel 这根水管读取和写入。传统的 IO 是基于流进行操作的,Channle 和类似,但又有些不同:

区别 通过Channel
支持异步 不支持 支持
是否可双向传输数据 不能,只能单向 可以,既可以从通道读取数据,也可以向通道写入数据
是否结合 Buffer 使用 必须结合 Buffer 使用
性能 较低 较高

正如上面说到的,Channel 必须要配合 Buffer 一起使用,我们永远不可能将数据直接写入到 Channel 中,同样也不可能直接从 Channel 中读取数据。都是通过从 Channel 读取数据到 Buffer 中或者从 Buffer 写入数据到 Channel 中,如下:

简单点说,Channel 是数据的源头或者数据的目的地,用于向 buffer 提供数据或者读取 buffer 数据,并且对 I/O 提供异步支持。

下图是 Channel 的类图

Channel 为最顶层接口,所有子 Channel 都实现了该接口,它主要用于 I/O 操作的连接。定义如下:

public interface Channel extends Closeable {

    /**

     * 判断此通道是否处于打开状态。 

     */

    public boolean isOpen();

    /**

     *关闭此通道。

     */

    public void close() throws IOException;
}

最为重要的Channel实现类为:

  • FileChannel:一个用来写、读、映射和操作文件的通道

  • DatagramChannel:能通过 UDP 读写网络中的数据

  • SocketChannel: 能通过 TCP 读写网络中的数据

  • ServerSocketChannel:可以监听新进来的 TCP 连接,像 Web 服务器那样。对每一个新进来的连接都会创建一个 SocketChannel

多路复用器 Selector

多路复用器 Selector,它是 Java NIO 编程的基础,它提供了选择已经就绪的任务的能力。从底层来看,Selector 提供了询问通道是否已经准备好执行每个 I/O 操作的能力。简单来讲,Selector 会不断地轮询注册在其上的 Channel,如果某个 Channel 上面发生了读或者写事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以获取就绪 Channel 的集合,进行后续的 I/O 操作。

Selector 允许一个线程处理多个 Channel ,也就是说只要一个线程复杂 Selector 的轮询,就可以处理成千上万个 Channel ,相比于多线程来处理势必会减少线程的上下文切换问题。下图是一个 Selector 连接三个 Channel :

实例

服务端

public class NIOServer {
/*接受数据缓冲区*/ private ByteBuffer sendbuffer = ByteBuffer.allocate(1024); /*发送数据缓冲区*/ private ByteBuffer receivebuffer = ByteBuffer.allocate(1024); private Selector selector; public NIOServer(int port) throws IOException { // 打开服务器套接字通道
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----:"); } private void listen() throws IOException { 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); } } } 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--"; //向缓冲区中输入数据
sendbuffer.put(sendText.getBytes()); //将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
sendbuffer.flip(); //输出到通道
client.write(sendbuffer); System.out.println("服务器端向客户端发送数据--:"+sendText); client.register(selector, SelectionKey.OP_READ); } } public static void main(String[] args) throws IOException { int port = 8080; NIOServer server = new NIOServer(port); server.listen(); } }

客户端

public class NIOClient {
/*接受数据缓冲区*/
private static ByteBuffer sendbuffer = ByteBuffer.allocate(1024); /*发送数据缓冲区*/
private static ByteBuffer receivebuffer = ByteBuffer.allocate(1024); public static void main(String[] args) throws IOException { // 打开socket通道
SocketChannel socketChannel = SocketChannel.open(); // 设置为非阻塞方式
socketChannel.configureBlocking(false); // 打开选择器
Selector selector = Selector.open(); // 注册连接服务端socket动作
socketChannel.register(selector, SelectionKey.OP_CONNECT); // 连接 socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080)); Set<SelectionKey> selectionKeys; Iterator<SelectionKey> iterator; SelectionKey selectionKey; SocketChannel client; String receiveText; String sendText; int count=0; while (true) { //选择一组键,其相应的通道已为 I/O 操作准备就绪。
//此方法执行处于阻塞模式的选择操作。
selector.select(); //返回此选择器的已选择键集。
selectionKeys = selector.selectedKeys(); //System.out.println(selectionKeys.size());
iterator = selectionKeys.iterator(); while (iterator.hasNext()) { selectionKey = iterator.next(); if (selectionKey.isConnectable()) { System.out.println("client connect"); client = (SocketChannel) selectionKey.channel(); // 判断此通道上是否正在进行连接操作。
// 完成套接字通道的连接过程。
if (client.isConnectionPending()) { client.finishConnect(); System.out.println("完成连接!"); sendbuffer.clear(); sendbuffer.put("Hello,Server".getBytes()); sendbuffer.flip(); client.write(sendbuffer); } 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 client--"; sendbuffer.put(sendText.getBytes()); //将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
sendbuffer.flip(); client.write(sendbuffer); System.out.println("客户端向服务器端发送数据--:"+sendText); client.register(selector, SelectionKey.OP_READ); } } selectionKeys.clear(); } }
}

运行结果

【死磕NIO】— NIO基础详解的更多相关文章

  1. tomcat启动nio,apr详解以及配置

    tomcat启动nio,apr详解以及配置 前言 在正文开始之前,我们先在idea工具中看看启动的信息,顺便看下启动的基本信息 在这里插入图片描述可以看到信息有tomcat版本操作系统版本java版本 ...

  2. Dom探索之基础详解

    认识DOM DOM级别 注::DOM 0级标准实际并不存在,只是历史坐标系的一个参照点而已,具体的说,它指IE4.0和Netscape Navigator4.0最初支持的DHTML. 节点类型 注:1 ...

  3. Android中Canvas绘图基础详解(附源码下载) (转)

    Android中Canvas绘图基础详解(附源码下载) 原文链接  http://blog.csdn.net/iispring/article/details/49770651   AndroidCa ...

  4. javaScript基础详解(1)

    javaScript基础详解 首先讲javaScript的摆放位置:<script> 与 </script> 可以放在head和body之间,也可以body中或者head中 J ...

  5. Python学习一:序列基础详解

    作者:NiceCui 本文谢绝转载,如需转载需征得作者本人同意,谢谢. 本文链接:http://www.cnblogs.com/NiceCui/p/7858473.html 邮箱:moyi@moyib ...

  6. Python学习二:词典基础详解

    作者:NiceCui 本文谢绝转载,如需转载需征得作者本人同意,谢谢. 本文链接:http://www.cnblogs.com/NiceCui/p/7862377.html 邮箱:moyi@moyib ...

  7. 深入浅出DOM基础——《DOM探索之基础详解篇》学习笔记

    来源于:https://github.com/jawil/blog/issues/9 之前通过深入学习DOM的相关知识,看了慕课网DOM探索之基础详解篇这个视频(在最近看第三遍的时候,准备记录一点东西 ...

  8. 三剑客基础详解(grep、sed、awk)

    目录 三剑客基础详解 三剑客之grep详解 1.通配符 2.基础正则 3.grep 讲解 4.拓展正则 5.POSIX字符类 三剑客之sed讲解 1.sed的执行流程 2.语法格式 三剑客之Awk 1 ...

  9. java继承基础详解

    java继承基础详解 继承是一种由已存在的类型创建一个或多个子类的机制,即在现有类的基础上构建子类. 在java中使用关键字extends表示继承关系. 基本语法结构: 访问控制符 class 子类名 ...

  10. java封装基础详解

    java封装基础详解 java的封装性即是信息隐藏,把对象的属性和行为结合成一个相同的独立单体,并尽可能地隐藏对象的内部细节. 封装的特性是对属性来讲的. 封装的目标就是要实现软件部件的"高 ...

随机推荐

  1. SpringBoot 默认json解析器详解和字段序列化自定义

    前言 在我们开发项目API接口的时候,一些没有数据的字段会默认返回NULL,数字类型也会是NULL,这个时候前端希望字符串能够统一返回空字符,数字默认返回0,那我们就需要自定义json序列化处理 Sp ...

  2. js控制单选按钮选中某一项

    <!DOCTYPE html><html> <head> <meta charset="utf-8"> <title>& ...

  3. http request 请求拦截器,有token值则配置上token值

    // http request 请求拦截器,有token值则配置上token值 axios.interceptors.request.use( config => { if (token) { ...

  4. time_formatter writeup

    攻防世界time_formatter writeup UAF漏洞和命令注入. 前置知识 1.strdup函数 char * __strdup(const char *s) { size_t len = ...

  5. XXXMapper.xml中嵌套查询

    XXXMapper.xml中嵌套查询 <resultMap id="LiveUserNocticeMap" type="com.fxkj.common.vo.Liv ...

  6. 如何保证前端项目上线后的安全?webfunny已总结前端最关键的12大指标

    实时监控大屏   众所周知:实时流量大屏,是用来监控前端项目上线质量的. 如大家所知,监控系统会监控线上应用的各项指标,如:错误.白屏.耗时等等,但是仔细一想,即使有这些监控,我们也不一定能够保证线上 ...

  7. 10BASE—T的主要技术特性

    1)数据传输速率10Mbps基带传输 2)每段双绞线最大长度100m 3)一条通路允许连接HUB数4个,最多5段传输介质 4)拓扑结构星型 5)访问控制方式CSMA/CD 6)帧长度可变,最大1518 ...

  8. HTML5内嵌文本编辑器

    1.这个编辑器用的是KindEditor 先看下效果: 2.准备: a):从官网下载KindEditor--->http://kindeditor.net/down.php b):解压到桌面测试 ...

  9. SSM自学笔记(二)

    3.SpringMVC入门 1.Spring与Web环境集成 1.1 ApplicationContext应用上下文获取方式 应用上下文对象是通过new ClasspathXmlApplication ...

  10. FileUtils 文件工具类

    FileUtils 下载jar中的文件 package com.meeno.chemical.common.utils; import lombok.extern.slf4j.Slf4j; impor ...