好久没写博客了,最近打算花些时间把Netty的源码好好读一读,下面是本人在学习的过程中的一些笔记,不能确保自己思考的完全是正确的,如果有错误,欢迎大家指正。

由于本人的语文功底烂的很,通篇使用大白话来讲解0.0,有一些概念上的东西,博主可能不会明确的给出定义,建议使用过Netty的同学一起来研究。

好了,我们一起来看下吧。

Netty 是一款用于快速开发的高性能的网络应用程序的Java框架。说到Netty, 我们先对几种I/O模型进行一下比对:

那么伪异步IO是啥呢?

其实就是加入了线程池(ThreadPoolExecutor),对接入的客户端的Socket封装成task,实现了Runnable接口,然后投递到线程池中处理,这样就避免了BIO那种一个客户端连接一个IO线程的情况,防止资源耗尽和宕机。但是这种方式底层的通信依然采用了同步阻塞模型,无法从根本上解决问题。

那么AIO又是啥呢?

NIO2.0 引入了新的一步通道的概念,并提供了异步文件通道和异步套接字的实现。它不需要通过多路复用器对注册的通道进行轮询操作即可实现异步读写,属于真正意义上的异步非阻塞IO。

1、通过java.util.concurrent.Future 类来异步获取操作的结果。

2、在执行异步操作的时候传入一个CompletionHandler接口的实现类,作为操作完成的回调。

接口有以下两个方法。

 /**
* Invoked when an operation has completed.
*
* @param result
* The result of the I/O operation.
* @param attachment
* The object attached to the I/O operation when it was initiated.
*/
void completed(V result, A attachment); /**
* Invoked when an operation fails.
*
* @param exc
* The exception to indicate why the I/O operation failed
* @param attachment
* The object attached to the I/O operation when it was initiated.
*/
void failed(Throwable exc, A attachment);

好的,下面也稍微回顾一下NIO,以及NIO涉及的几个关键组件:

  • 缓冲区 Buffer
  • 通道 Channel
  • 多路复用器 Selector
  1. Buffer : 看什么都不如看官方文档来的更准确,下面是官方Buffer javadoc内容,我们来看下:

里面讲述了,buffer抽象类 是一个数据容器,除了内容,还有一些属性,capacity、limit、position。

capacity 是容器的容量,这个值一旦被创建,就无法修改。 limit 是 不应该被读或写的第一个元素的位置。 position 是指下一个将会被读或写的位置,这个值一定小于等于limit。

另外javadoc中还提到了mark和reset, 其中mark其实就是打一个标记,把当前的position赋给mark。  那么 reset 的 描述是这样的 把当前的position 改成之前mark的位置。

ok,由上面的文档可以得出下面的顺序  0 <= mark <= position <= limit <= capacity

其实Buffer中还有一个非常重要的方法必须要说一下,那就是 flip() ,看下javadoc

这个其实就是把 当前的limit = position, position = 0, 当然如果之前有mark也会失效,设置成-1, 当你往buffer中写了数据的时候,只有执行flip()方法, 才可以正确的读取数据,  doc中还指出这个方法经常和compact()方法连着用。同样,贴出javadoc:

相当于什么呢,就相当于是清理掉已经读取过得数据,比如 position = 5 , limit = 10,前5个数据经读取过了,那么将新建一个buffer,将当前position到limit的数据拷贝到一个新的Buffer中,那么新的buffer的postion = limit-postion, limit = capacity, 好了,看源码是这样的,接下来就是验证一下了:

 ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("helloworld".getBytes());
System.out.println(buffer.position() + ":" + buffer.limit());
buffer.flip();
System.out.println(buffer.position() + ":" + buffer.limit());
byte[] bytes = new byte[buffer.limit() + 1];
for(int i=0; i<6; i++) {
bytes[i] = buffer.get();
}
System.out.println(new String(bytes));
System.out.println(buffer.position() + ":" + buffer.limit());
System.out.println(buffer);
buffer.compact();
System.out.println(buffer.position() + ":" + buffer.limit());
System.out.println(buffer);

测试结果如下:

10:10
0:10
hellow
6:10
java.nio.HeapByteBuffer[pos=6 lim=10 cap=10]
4:10
java.nio.HeapByteBuffer[pos=4 lim=10 cap=10]

好了,Buffer的源码看到这里也算是差不多了。

2、Channel

Channel是一个通道, 它就像自来水管一样,网络数据通过Channel读取与写入,通道与流的不同之处在于通道是双向的,流只是在一个方向上移动(一个流必须是InputStream或者OutStream的子类),而通道可以用于读、写或者二者同时进行, 属于全双工。

这里我们也来看下源码吧,就看ServerSocketChannel

提供了几个比较重要的api:

public static ServerSocketChannel open() throws IOException; // 通过该方法创建一个Channel

看下javadoc , 明确说明了 新创建的channel是没有任何绑定的,在进行accepted之前需要绑定一个地址。

public final ServerSocketChannel bind(SocketAddress local);// 绑定一个端口号

public abstract SocketChannel accept() throws IOException; // 接收新的客户端

3、Selector 多路复用器 ,简单来说呢,Selector 会不断的轮询注册在其上的Channel, 如果某个Channel上面发生了读写等事件,这个Channel就会处理就绪状态, 会被Selector轮询出来,然后拿到SelectionKey Set集合,从而获取到每一个就绪状态的Channel,进行后续的I/O操作。

由于JDK使用了epoll() 代替传统的select实现,所以没有最大句柄的1024/2048的限制, 只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端。NB

channel将会通过一个SelectionKey注册到一个selector上,一个selector 通过 open方法去创建。

这一段着重指出,selectionKey集合只能通过 set 集合的 remove() 方法 或者 一个迭代器的 remove() 方法来移除。其余的方法都不可以修改 selected-key 。

好了,看到这里,有些朋友可能似懂非懂,但是看下下面的单元测试一下子就懂了。

这段代码实现了Nio的服务器端,接收到客户端消息后,然后通知所有的客户端。

     private static final Map<String, SocketChannel> clientMap = new ConcurrentHashMap();

     public static void main(String[] args) {

         try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 创建一个Channel
serverSocketChannel.configureBlocking(false); // 设置为非阻塞
serverSocketChannel.bind(new InetSocketAddress(8899)); // 绑定端口 Selector selector = Selector.open(); // 创建一个Selector
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 将Channel注册到Selector上,设置selectionKey 为 accept, 准备接收新的客户端连接 while (true) { // 死循环不断轮询,查看 是否有准备就绪的channel
selector.select(); // 阻塞等到就绪的channel
Set<SelectionKey> selectionKeys = selector.selectedKeys(); // 获取到就绪的selectionKeys集合
selectionKeys.forEach(value -> {
try {
if(value.isAcceptable()) { // 接收新的客户端事件
ServerSocketChannel channel = (ServerSocketChannel)value.channel(); // 获取channel
SocketChannel clientChannel = channel.accept(); // 获取客户端的 socketChannel
clientChannel.configureBlocking(false); // 设置为非阻塞
String clientId = UUID.randomUUID().toString();
System.out.println("客户端接入" + clientId);
clientMap.put(clientId, clientChannel);
clientChannel.register(selector, SelectionKey.OP_READ); // 这里重点说下, 当接收到新的客户端后,接下来就是准备接收数据,所以这里就是注册的是Read事件
// 并且这里注册到selector上的是客户端对应的SocketChannel, 而不是ServerSocketChannel,
// 因为ServerScoketChannel只负责接收新的客户端
} else if(value.isReadable()) { // 接收到read事件
SocketChannel clientChannel = (SocketChannel)value.channel(); // 所以这里是SocketChannel
ByteBuffer buffer = ByteBuffer.allocate(1024); // 分配内存
int count = clientChannel.read(buffer); // 写channel中的数据到Buffer中
if (count > 0) {
buffer.flip(); // 写完之后,一定要执行flip。转化成读
Charset charset = Charset.forName("utf-8");
String receiveMsg = String.valueOf(charset.decode(buffer).array());
System.out.println("receiveMsg = " +receiveMsg);
Iterator<Map.Entry<String, SocketChannel>> it = clientMap.entrySet().iterator();
String sendClient = null;
while (it.hasNext()) {
Map.Entry<String, SocketChannel> next = it.next();
if(next.getValue() == clientChannel) {
sendClient = next.getKey();
break;
}
}
it = clientMap.entrySet().iterator();
ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
while (it.hasNext()) {
SocketChannel socketChannel = it.next().getValue();
writeBuffer.clear();
writeBuffer.put(("sendClient:" + sendClient + "发送了消息").getBytes());
writeBuffer.flip();
socketChannel.write(writeBuffer);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
selectionKeys.clear(); // 每次处理完这一批selectionKeys,一定要清空掉集合。
} } catch (IOException e) {
e.printStackTrace();
} finally {
}
}

ok, 上面是我自己的一些理解,如果有问题欢迎大家指正。下一篇,我们将开始学习Netty的源码。

Netty源码分析--NIO(一)的更多相关文章

  1. Netty源码分析-- 处理客户端接入请求(八)

    这一节我们来一起看下,一个客户端接入进来是什么情况.首先我们根据之前的分析,先启动服务端,然后打一个断点. 这个断点打在哪里呢?就是NioEventLoop上的select方法上. 然后我们启动一个客 ...

  2. 【Netty源码分析】发送数据过程

    前面两篇博客[Netty源码分析]Netty服务端bind端口过程和[Netty源码分析]客户端connect服务端过程中我们分别介绍了服务端绑定端口和客户端连接到服务端的过程,接下来我们分析一下数据 ...

  3. netty源码分析之揭开reactor线程的面纱(二)

    如果你对netty的reactor线程不了解,建议先看下上一篇文章netty源码分析之揭开reactor线程的面纱(一),这里再把reactor中的三个步骤的图贴一下 reactor线程 我们已经了解 ...

  4. netty源码分析之二:accept请求

    我在前面说过了server的启动,差不多可以看到netty nio主要的东西包括了:nioEventLoop,nioMessageUnsafe,channelPipeline,channelHandl ...

  5. Netty源码分析(前言, 概述及目录)

    Netty源码分析(完整版) 前言 前段时间公司准备改造redis的客户端, 原生的客户端是阻塞式链接, 并且链接池初始化的链接数并不高, 高并发场景会有获取不到连接的尴尬, 所以考虑了用netty长 ...

  6. Netty源码分析第1章(Netty启动流程)---->第2节: NioServerSocketChannel的创建

    Netty源码分析第一章:  Server启动流程 第二节:NioServerSocketChannel的创建 我们如果熟悉Nio, 则对channel的概念则不会陌生, channel在相当于一个通 ...

  7. Netty源码分析第1章(Netty启动流程)---->第4节: 注册多路复用

    Netty源码分析第一章:Netty启动流程   第四节:注册多路复用 回顾下以上的小节, 我们知道了channel的的创建和初始化过程, 那么channel是如何注册到selector中的呢?我们继 ...

  8. Netty源码分析第2章(NioEventLoop)---->第5节: 优化selector

    Netty源码分析第二章: NioEventLoop   第五节: 优化selector 在剖析selector轮询之前, 我们先讲解一下selector的创建过程 回顾之前的小节, 在创建NioEv ...

  9. Netty源码分析第3章(客户端接入流程)---->第1节: 初始化NioSockectChannelConfig

    Netty源码分析第三章: 客户端接入流程 概述: 之前的章节学习了server启动以及eventLoop相关的逻辑, eventLoop轮询到客户端接入事件之后是如何处理的?这一章我们循序渐进, 带 ...

随机推荐

  1. TabHost两种实现方式

    第一种:继承TabActivity,从TabActivity中用getTabHost()方法获取TabHost.只要定义具体Tab内容布局就行了. <?xml version="1.0 ...

  2. Qt常用函数 记录(update erase repaint 的区别)

    一界面重载函数使用方法:1在头文件里定义函数protected: void paintEvent(QPaintEvent *event); 2 在CPP内直接重载void ----------::pa ...

  3. Android系统联系人全特效实现(上),分组导航和挤压动画

    记得在我刚接触Android的时候对系统联系人中的特效很感兴趣,它会根据手机中联系人姓氏的首字母进行分组,并在界面的最顶端始终显示一个当前的分组.如下图所示: 最让我感兴趣的是,当后一个分组和前一个分 ...

  4. WPF中,怎样将XAML代码加载为相应的对象?

    原文:WPF中,怎样将XAML代码加载为相应的对象? 在前面"在WPF中,如何得到任何Object对象的XAML代码?"一文中,我介绍了使用System.Windows.Marku ...

  5. linux process management

    CREAT PROCESS fork() | clone(參数,决定父子函数的共享内容) | do_fork() | copy_process() | dup_task_struct() 创建子进程的 ...

  6. 统计web訪问前10的ip

    cat access.log|awk '{print $0}'|sort|uniq -c|sort -nr|head -n 10

  7. OnNavigatedTo 和 Loaded 的比较

    直接上结果: OnNavigateTo :是在导航完成,在控件或者页面加载前(之间)调用. Loaded :是在页面准备好并且在控件加载完成后调用. 参考资料: 1.https://stackover ...

  8. ThreadPoolExecutor原理和使用

    大家先从ThreadPoolExecutor的整体流程入手: 针对ThreadPoolExecutor代码.我们来看下execute方法: public void execute(Runnable c ...

  9. IdentityServer的基本概念与特性

    基本概念 IdentityServer4是一个基于OpenID Connect和OAuth 2.0的针对ASP.NET Core 2.0的框架. IdentityServer4可以帮助我们实现什么 I ...

  10. IIS Express 启用目录浏览

    IIS Express 启用目录浏览,有需要的朋友可以参考下. 今天刚刚使用visual studio 2013创建第一个hello world,结果就发现提示错误. HTTP 错误 403.14 - ...