NIO源码阅读
自己对着源码敲一遍练习,写上注释。发现NIO编程难度好高啊。。虽然很复杂,但是NIO编程的有点还是很多:
1、客户端发起的连接操作是异步的,可以通过在多路复用器注册OP_CONNECTION等待后续结果,不需要像BIO的客户端一样被同步阻塞。
2、SocketChannel的读写操作都是异步的,如果没有可读写的数据它不会同步等待,直接返回,这样I/O通信模型就可以处理其他的链路,不需要同步等待这个链路可用。
3、线程模型的优化:由于JDK的Selector在Linux等主流操作系统上通过epoll实现,没有连接句柄的限制,那么Selector线程可以同时处理成千上万个客户端连接,而且性能不会随着客户端的增加而线性下降。所以它非常适合做高性能、高负载的网络服务器。
TimeClient:
package nio; public class TimeClient {
public static void main(String args[]){
int port = 8080;
if(args != null && args.length > 0){
try{
port = Integer.valueOf(args[0]);
}catch(NumberFormatException e){
//采用默认值
}
}
new Thread(new TimeClientHandle("120.0.0.1",port),"TimeClient-001").start();
}
}
TimeClientHandler:
package nio; import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set; public class TimeClientHandle implements Runnable{
private String host;
private int port;
private Selector selector;
private SocketChannel socketChannel;
private volatile boolean stop; public TimeClientHandle(String host,int port){
this.host = host == null ? "127.0.0.1" : host;
this.port = port;
try{
selector = Selector.open();
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
}catch(IOException e){
e.printStackTrace();
System.exit(1);
}
} public void run() {
//发送请求连接
try{
doConnect();
}catch(IOException e){
e.printStackTrace();
System.exit(1);
}
while(!stop){
try{
selector.select(1000);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
SelectionKey key = null;
//当有就绪的Channel时,执行handleInput(key)方法
while(it.hasNext()){
key = it.next();
it.remove();
try{
handleInput(key);
}catch(Exception e){
if(key != null){
key.cancel();
if(key.channel() != null){
key.channel().close();
}
}
}
}
}catch(Exception e){
e.printStackTrace();
System.exit(1);
}
} //多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭,所以不需要重复释放资源
if(selector != null){
try{
selector.close();
}catch(IOException e){
e.printStackTrace();
}
} } private void handleInput(SelectionKey key) throws IOException{
if(key.isValid()){
SocketChannel sc = (SocketChannel)key.channel();
//判断是否连接成功
if(key.isConnectable()){
if(sc.finishConnect()){
sc.register(selector, SelectionKey.OP_READ);
}else{
System.exit(1);
}
} if(key.isReadable()){
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if(readBytes > 0){
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes,"UTF-8");
System.out.println("Now is :" + body);
this.stop = true;
}else if(readBytes < 0){
//对端链路关闭
key.cancel();
sc.close();
}else{
; //读到0字节,忽略
}
}
}
} private void doConnect() throws IOException{
//如果直接连接成功,则注册到多路复用器上,发送请求信息,读应答
if(socketChannel.connect(new InetSocketAddress(host,port))){
socketChannel.register(selector, SelectionKey.OP_READ);
doWrite(socketChannel);
}else{
//说明服务器没有返回TCP祸首应答消息,但这并不代表连接失败,当服务器返回TCP syn-ack消息后,Selector就能够轮训这个SocketChannel处于连接就绪状态
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
} private void doWrite(SocketChannel sc) throws IOException{
byte[] req = "QUERY TIME ORDER".getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
writeBuffer.put(req);
writeBuffer.flip();
sc.write(writeBuffer);
if(!writeBuffer.hasRemaining()){
System.out.println("Send order 2 server succeed.");
}
} }
TimeServer:
package nio; import java.io.IOException; public class TimeServer { public static void main(String[] args) throws IOException{
int port = 8080;
if(args != null && args.length >0){
try{
port = Integer.valueOf(args[0]);
}catch(NumberFormatException e){
//采用默认值
}
}
//多路复用类,是一个独立的线程,负责轮训多路复用器Selctor,处理多个客户端的并发接入。
MultiplexerTimeServer timeServer = new MultiplexerTimeServer(port);
new Thread(timeServer,"NIO-MultiplexerTimeServer-001").start();
}
}
MultiplexerTimeServer:
package nio; import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set; public class MultiplexerTimeServer implements Runnable { private Selector selector; private ServerSocketChannel servChannel; private volatile boolean stop; public MultiplexerTimeServer(int port){
try{ selector = Selector.open();
servChannel.configureBlocking(false);
//将ServerSocketChannel 设置为异步非阻塞,backlog设置为1024
servChannel.socket().bind(new InetSocketAddress(port),1024);
//将ServerSocket Channel注册到Selector,监听SelectionKey.OP_ACCEPT操作位,如果初始化失败,则退出
servChannel.register(selector,SelectionKey.OP_ACCEPT);
System.out.println("The time server is start in port:" + port);
}catch(IOException e){
e.printStackTrace();
System.exit(1);
}
} public void stop(){
this.stop = true;
} public void run() {
while(!stop){
try{
//遍历时间设置1秒,每隔一秒唤醒一次,当有处于就绪状态的Channel时,selector将返回就绪状态的Channel的SelectionKey集合
selector.select(1000);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
SelectionKey key = null;
//通过对就绪状态的Channel集合进行迭代,可以进行网络的异步读写操作
while(it.hasNext()){
key = it.next();
it.remove();
try{
handleInput(key);
}catch(Exception e){
if(key != null){
key.cancel();
if(key.channel() != null){
key.channel().close();
}
}
}
}
}catch(Throwable t){
t.printStackTrace();
}
} //多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭,所以不需要重复释放资源
if(selector != null){
try{
selector.close();
}catch(IOException e){
e.printStackTrace();
}
}
} //处理新接入的请求消息
private void handleInput(SelectionKey key) throws IOException{
if(key.isValid()){ //根据SelectionKey的操作位进行判断即可获知网络事件的类型,通过accept接收客户端的连接请求并创建SocketChannel实例,完成上述操作相当于
//完成了TCP的三次握手,TCP物理链路正式建立
if(key.isAcceptable()){
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
//Add the new connection tothe selector
sc.register(selector, SelectionKey.OP_READ);
} if(key.isReadable()){
//Read the data SocketChannel sc = (SocketChannel)key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if(readBytes > 0){
//将缓冲区当前的limit设置为position,position设置为0,用于后续对缓冲区的读取操作
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes,"UTF-8");
System.out.println("The time server receive order: + body");
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER";
doWrite(sc,currentTime);
}else if(readBytes < 0){
//对端链路关闭
key.cancel();
sc.close();
}else{
; //读到0字节,忽略
}
}
}
} private void doWrite(SocketChannel channel,String response) throws IOException{
if(response != null && response.trim().length() >0){
byte[] bytes = response.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
channel.write(writeBuffer);
}
}
}
NIO源码阅读的更多相关文章
- Spark源码阅读之存储体系--存储体系概述与shuffle服务
一.概述 根据<深入理解Spark:核心思想与源码分析>一书,结合最新的spark源代码master分支进行源码阅读,对新版本的代码加上自己的一些理解,如有错误,希望指出. 1.块管理器B ...
- JDK源码阅读(1)_简介+ java.io
1.简介 针对这一个版块,主要做一个java8的源码阅读笔记.会对一些在javaWeb中应用比较广泛的java包进行精读,附上注释.对于容易混淆的知识点给出相应的对比分析. 精读的源码顺序主要如下: ...
- JDK 1.8 源码阅读和理解
根据 一篇文章教会你,如何做到招聘要求中的“要有扎实的Java基础” 的指引,决定开始阅读下JDK源码. 本文将作为源码阅读总纲 一.精读部分 java.io java.lang java.util ...
- Mina源码阅读笔记(四)—Mina的连接IoConnector2
接着Mina源码阅读笔记(四)-Mina的连接IoConnector1,,我们继续: AbstractIoAcceptor: 001 package org.apache.mina.core.rewr ...
- netty(一)---服务端源码阅读
NIO Select 知识 select 示例代码 : //创建 channel 并设置为非阻塞 ServerSocketChannel serverChannel = ServerSocketCha ...
- 利用IDEA搭建JDK源码阅读环境
利用IDEA搭建JDK源码阅读环境 首先新建一个java基础项目 基础目录 source 源码 test 测试源码和入口 准备JDK源码 下图框起来的路径就是jdk的储存位置 打开jdk目录,找到sr ...
- JDK源码阅读-ByteBuffer
本文转载自JDK源码阅读-ByteBuffer 导语 Buffer是Java NIO中对于缓冲区的封装.在Java BIO中,所有的读写API,都是直接使用byte数组作为缓冲区的,简单直接.但是在J ...
- JDK源码阅读-FileDescriptor
本文转载自JDK源码阅读-FileDescriptor 导语 操作系统使用文件描述符来指代一个打开的文件,对文件的读写操作,都需要文件描述符作为参数.Java虽然在设计上使用了抽象程度更高的流来作为文 ...
- JDK源码阅读-DirectByteBuffer
本文转载自JDK源码阅读-DirectByteBuffer 导语 在文章JDK源码阅读-ByteBuffer中,我们学习了ByteBuffer的设计.但是他是一个抽象类,真正的实现分为两类:HeapB ...
随机推荐
- LayaAir引擎——(六)
LayaAir引擎——TiledMap地图图块属性获取和进行墙壁碰撞检测 需要的软件: TiledMap LayaAir IDE 1.0.2版本 所画的地图: pass层: floor层: pass层 ...
- dwr和spring的整合
1.dwr在spring配置文件的配置: <!-- 注意这里新增加的dwr tag, 为使其生效,文件头中要声明namespace --> <dwr:configuration /& ...
- genymotion模拟器访问本地服务器
测试环境为:Ubuntu + android studio + genymotion + wifi 我用模拟器访问电脑上的服务器,需要使用的IP为10.0.3.2,其他的什么10.0.2.2和10.0 ...
- extJS起步
万事开头难,不过还好有解决办法——参考如下文章 http://blog.csdn.net/leimengyuanlian/article/details/18748599 可以快速搭建好eclipse ...
- 原生js实现滚动条
var SimulateScroll = (function(){ var oParent = document.getElementById('wrap-scroll-bar'), oBox = d ...
- CSS hack方式一览【转】
做前端多年,虽然不是经常需要hack,但是我们经常会遇到各浏览器表现不一致的情况.基于此,某些情况我们会极不情愿的使用这个不太友好的方式来达到大家要求的页面表现.我个人是不太推荐使用hack的,要知道 ...
- Android驱动开发前的准备(一)
Android系统移植与驱动开发概述 1.1 Android 系统架构 1.2 Android系统移植的主要工作 1.3 查看linux内核版本 1.4 linux内核版本号的定义规则 1.5 lin ...
- iOS 动画
图层树.寄宿图以及图层几何学(一)图层的树状结构 技术交流新QQ群:414971585 巨妖有图层,洋葱也有图层,你有吗?我们都有图层 -- 史莱克 Core Animation其实是一个令人误解的命 ...
- codeforces 733D
明白了自己这么菜的原因多半是赛后不肯去补那些需要多花点时间思考的题目以及效率不高,但愿现在还不算晚... #include<bits/stdc++.h> #include<iostr ...
- [WARNING]考前必读?!
考试策略很重要. 1) 认真读懂每道题,把题意搞明白,把样例算出来,把题目要求的细节标出. 2) 推出每道题的模型,确定每道题的策略,是部分分还是要全部 ...