NIO原理剖析与Netty初步----浅谈高性能服务器开发(一)
除特别注明外,本站所有文章均为原创,转载请注明地址
在博主不长的工作经历中,NIO用的并不多,由于使用原生的Java NIO编程的复杂性,大多数时候我们会选择Netty,mina等开源框架,但理解NIO的原理就不重要了吗?恰恰相反,理解NIO底层机制是理解这一切的基础,由此我总结一下当初学习NIO时的笔记,以便后续复习。
以下是我理解的Java原生NIO开发大致流程:

上图大致描述的是服务端的NIO操作。
第一步,绑定一个服务的端口
这与传统阻塞IO中的ServerSocket类似,没什么好说的
第二步,打开通道管理器Selector并在Selector上注册一个事件
当注册的事件发生时,Selector.select()会返回,否则一直阻塞。这一步很有意思,也是NIO第一个与传统IO不同的地方,NIO通过一个Selector线程可以管理大量客户端连接,反之传统IO一个客户端连接进来必须创建一个新的线程为它服务(当然你可以使用连接池),我们知道线程对服务端来说是十分宝贵的资源,一个服务端进程所包含的线程是有 限的;此外,每个线程会占用一定的内存空间,过多的线程可能导致内存溢出,这种情况下你可能会到想对虚拟机进行调优,比如通过修改参数-Xss限制单个线程大小,但这又可 能导致StackOverFlow;另外,线程调度需要切换上下文,对于操作系统,它需要通过TCB(线程控制块)来对线程进行调度,过多的上下文切换浪费了CPU时间,降低了系统效 率。
第三步,轮循访问Selector,当注册的事件到达时,方法返回
下面的代码可以看到,方法整体是一个死循环,轮询访问Selector,发生某些已经注册在Selector上的事件时,该方法返回。可以通过selector.selectedKeys获取发生的事件,
该方法返回的是一个泛型集合Set<SelectionKey>,遍历这个集合与遍历普通集合没有什么不同,这里我们通过迭代器迭代的原因是我们需要删除已经处理的Key,避免重复处理:
public void listen() throws IOException {// 轮询访问selector
while (true) {
// 当注册的事件到达时,方法返回;否则,该方法会一直阻塞
selector.select();
// 获得selector中选中的项的迭代器,选中的项为注册的事件
Iterator<?> ite = this.selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = (SelectionKey) ite.next();
//删除已选的key,以防重复处理
ite.remove();
//这里可以写我们自己的处理逻辑
handle(key);
}
}
}
在第二步时,已经在Selector上注册了Accept事件,当这里的selector.select()返回时,代表客户端已经可以连接了,在handle方法里可以处理这个事件:
public void handle(SelectionKey key) throws IOException {
// 客户端请求连接事件
if (key.isAcceptable()){
//从Key里可以很方便的取到注册这个事件的Channel
ServerSocketChannel server = (ServerSocketChannel) key.channel();
// 获得和客户端连接的通道
SocketChannel channel = server.accept();
// 设置成非阻塞
channel.configureBlocking(false);
logger.info("客户端已经连接!");
//客户端连接在通道管理器Selector上注册读事件
channel.register(this.selector, SelectionKey.OP_READ);
}
}
上面的代码很简单,我们通过key获取到注册它的那个Channel,在这里是ServerSocketChannel,通过server.accept()获取客户端连接,这里同样可以类比到传统的阻塞
IO,在阻塞IO中我们可以通过ServerSocket.accept获取到socket,唯一不同的是,阻塞IO中的accept方法是阻塞操作,而NIO中是非阻塞的。
当然,仅仅是连接到客户端并没有什么用处,服务端需要有读写数据的能力,比如你可以用NIO实现一个Http服务器(当然最佳实践使用Netty等框架)。所以我们需要在Selector
上注册读事件,同样,当读事件发生时,执行我们自己的业务逻辑。下面是修改后的代码:
public void handle(SelectionKey key) throws IOException {
if (key.isAcceptable()){
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel channel = server.accept();
channel.configureBlocking(false);
logger.info("客户端已经连接!");
channel.register(this.selector, SelectionKey.OP_READ);
} else if(key.isReadable()){
SocketChannel channel = (SocketChannel) key.channel();
// 创建读取缓冲
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取到Buffer中
int read = channel.read(buffer);
if(read > 0){
byte[] data = buffer.array();
String msg = new String(data).trim();
logger.info("receive msg: {}",msg)
//回写数据
ByteBuffer outBuffer = ByteBuffer.wrap("OK".getBytes());
channel.write(outBuffer);
}else{
logger.info("client closed!!!");
key.cancel();
}
}
}
总结
本文大致讲述了使用NIO进行服务器端开发的大致流程,但代码显然仍然存在问题,其一是我们只使用了一个线程执行所有操作,包括接收客户端连接,读取数据,返回数据,对于这个简单的Demo来说已经足够了,但在实际的服务器开发中,例如你想使用NIO开发自己的HTTP服务器,服务器本地需要做大量操作,包括解析用户请求,根据请求路由到某一个Action执行业务逻辑,这其中又很可能某些数据从数据库读取,渲染模板等操作,十分耗时,这无疑又称为系统的瓶颈,再者,使用单一线程不能充分利用多核CPU提供的计算能力。下一篇中会看到,在基于Reactor模型的Netty中,会使用一个Boss线程接收客户端请求,使用多个Worker线程执行具体的业务逻辑。
NIO原理剖析与Netty初步----浅谈高性能服务器开发(一)的更多相关文章
- 浅谈iOS视频开发
浅谈iOS视频开发 这段时间对视频开发进行了一些了解,在这里和大家分享一下我自己觉得学习步骤和资料,希望对那些对视频感兴趣的朋友有些帮助. 一.iOS系统自带播放器 要了解iOS视频开发,首先我们从 ...
- 浅谈|WEB 服务器 -- Caddy
浅谈|WEB 服务器 -- Caddy 2018年03月28日 12:38:00 yori_chen 阅读数:1490 标签: caddyserverwebhttps反向代理 更多 个人分类: ser ...
- 网络(最大)流初步+二分图初步 (浅谈EK,Dinic, Hungarian method:]
本文中 N为点数,M为边数: EK: (brute_force) : 每次bfs暴力找到一条增广路,更新流量,代码如下 : 时间复杂度:O(NM²): #include<bits/stdc++ ...
- 浅谈Android样式开发之布局优化
引言 今天我们来谈一下Android中布局优化常用的一些手段.官方给出了3种优化方案,分别是</include>.</viewstub>.</merge>标签,下面 ...
- 浅谈移动Web开发(上):深入概念
PPI 什么是PPI PPI的复杂之处在于如果他所属的上下文环境不同,意义也会完全不一样. 当我们在谈论显示设备的PPI时,它代指的屏幕的像素密度:当我们在谈论和图片相关时,我们谈论的是打印时的分辨率 ...
- 浅谈 Linux 内核开发之网络设备驱动
转自http://www.ibm.com/developerworks/cn/linux/l-cn-networkdriver/ 网络设备介绍 网络设备是计算机体系结构中必不可少的一部分,处理器如果想 ...
- 浅谈web服务器的编写之http协议
本书是介绍怎么编写一个Web服务器,而Web服务器是基于HTTP(HyperText Transfer Protocol)协议实现的,所以要实现一个Web服务器就必须了解HTTP协议,本章主要介绍HT ...
- 浅谈Android移动开发程序员的职业发展之路
现在几乎每个it公司都在开发移动产品,我最早知道Android还是在09年成都某学院上大学的时候,从新闻上知道有这么一家公司,创始人安迪·鲁宾很有名,但安卓到底是做什么的,我并没有关注. 到2010年 ...
- 浅谈web前端开发
我个人认为前端攻城狮其实就是编程技术人员,用一句话来形容“比UI设计懂技术,比技术人员更懂交互”,当然也有人说前端工程师是工程师中的设计师,是设计师中的工程师. 好了废话不多说了,下面进入正题吧! ...
随机推荐
- 老李分享:导出xml报告到手机
老李分享:导出xml报告到手机 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大家咨询qq:908821 ...
- 设计模式(四)—观察者模式
一.概述 观察者模式类似于邮件订阅和RSS订阅,当我们浏览一些博客或wiki时,经常会看到RSS图标,就这的意思是,当你订阅了该文章,如果后续有更新,会及时通知你.其实,简单来讲就一句话:当 ...
- 解决tomcat debug 调试时间过长的问题
做java web很早就碰到一个问题,eclipse 的调试有时候忽然启动时间奇慢 ,但是正常启动速度没问题,其他项目也完全正常 后来想想也不影响项目运行,也没太在意 不过今天又碰到这问题了,而且启动 ...
- SVG格式转Visio的vsd格式方法,附带C#动态调用Office的Com组件方法
SVG格式可以直接显示在网页上面,用来实现诸如统计Chart图表.流程图.组织结构图的功能.为了使图像可以下载下来以便于修改,可以将SVG转为Visio的vsd格式.方法很简单,主要是使用Visio组 ...
- onclick = xxx这种赋值写法绑定事件的原理是什么?
本文转自知乎貘吃馍香的回答 提问:刚入门不久,能力有限,这个问题我描述起来有点困难,只有劳烦各位大神细看了 我之前一直以为js底层存在类似下面这样的代码: //给所有dom对象定义好onclick值为 ...
- [编织消息框架][JAVA核心技术]动态代理应用10-水平扩展方案
服务分为系统服务同用户服务两种 水平扩展是基于系统服务,而拆分方式又有几种方案,按数据跟业务情况来做决策 1.每个服务独立存储(图1):每个服务只负责一个或多个领域实体存储,A服务不能直接修改B服务的 ...
- 和我一步步部署 kubernetes 集群
和我一步步部署 kubernetes 集群 本系列文档介绍使用二进制部署最新 kubernetes v1.6.1 集群的所有步骤,而不是使用 kubeadm 等自动化方式来部署集群: 在部署的过程中, ...
- call和apply的异同
共同点: 作用:调用一个对象的一个方法,以另一个对象替换当前对象.将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象.如果没有提供 thisObj 参数,那么 Global 对 ...
- linux入门之用户管理
用户管理 添加用户 以root用户执行 adduser 或 useradd [new_account] -u UID -d 指定家目录 -g GID 指定一个基本组ID -G指定一个附加组 ...
- Cf #353 D. Tree Construction
题目链接:http://codeforces.com/problemset/problem/675/D 题目大意是将一个没有相同数字的数列中的数字依次插入到二叉搜索树中,问除了第一个数字以外,其他数字 ...