献给各位:

Satisfied Mind
Red Hayes and Jack Rhodes
How many times have you heard someone say,
"If I had his money I would do things my way."
How little they know, well, it's so hard to find,
One rich man in ten with a satisfied mind.
Oh, once I was waiting for fortune and fame,
Had everything that I needed to get a start in life's game.
Then suddenly it happened, I lost every dime,
But I'm richer by far with a satisfied mind.
No money can buy back your youth when you're old,
Or a friend when you're lonely or a love that's grown cold.
And the world's richest person is a pauper at times,
Compared to the one with a satisfied mind.
When my life is over and my time has run out,
All my friends and my loved ones I'm gonna leave them no doubt.
But there's one thing for certain, when it comes my time,
I'm gonna leave this old world with a satisfied mind.

  

主题:

一直想研读netty的框架,看了些后发现的确能学到好些东西,又牵涉到各种知识,所以回过头来再复习一下NIO的几个细节。

1,非阻塞 和 阻塞区别:

也许会在面试的时候会被问道吧,理解为需要做一件事不能立即得到完成后的应答,需要等待,那就阻塞了,否则就可以理解为非阻塞。

最常举的例子就是socket的例子了:

传统的serversocket阻塞模式:

public class ServerSocketApp {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(8989);
ss.accept(); //等待
System.out.println(1);
}
}

运行这个程序 为什么没有输出1 ?
因为ServerSocket 是阻塞模式的 ,在没有任何连接之前,accept方法一直在那里阻塞着,直到有connection来继续往下执行,所以在运行程序的时候,并没输出1,若要输出 telnet一下就可以了

nio中的 非阻塞:
public static void main(String[] args) throws Exception {
ServerSocketChannel ssc = ServerSocketChannel.open();
ServerSocket ss = ssc.socket();
ss.bind(new InetSocketAddress(8989));
// 设置成非阻塞
ssc.configureBlocking(false);
ssc.accept(); // 不需要等待
System.out.println(1);
}

运行这个程序 有1 输出!!
这就是因为 它是非阻塞模式的。

 

2,readiness selection的设计

  事实上,socket的底层读写操作会阻塞,前面的第一个例子就是这个原因导致的,第二个例子只是忽略了这个原因都返回而已。

弄明白为什么socket的read/write会阻塞:

  write成功返回,只是buf中的数据被复制到了kernel中的TCP发送缓冲区至于数据什么时候被发往网络,什么时候被对方主机接收,什么时候被对方进程读取,系统调用层面不会给予任何保证和通知。当kernel的该socket的发送缓冲区已满时,write操作就会被阻塞了,那么调用这个write的线程就会阻塞。read调用阻塞,通常是发送端的数据没有到达。
 
传统的解决方案是:每一个socket对应一个线程,在read的时候阻塞,事实上就是不断在问准备好没有,一旦准备好之后就由这个线程执行逻辑。这样就把每一个线程当做监听器,来监听每一个socket的就绪状态。这个其实就是非阻塞模式
 
这样就又来个readiness selection的解决方案:
  在操作socket的时候,我们只主动方,读,写,不管是否可以读了,是否可以写了,也就是上面提到的缓冲区是不是有空间啊,数据是不是已经穿过来了这些判断。
这儿有牵涉到一个好莱坞原则: “不要给我们打电话,我们会给你打电话(don‘t call us, we‘ll call you)”

  因为我们去read/write的时候并不知道是否会线程阻塞,所以我们把主被换一下,当我们知道已经符合read/write条件时,通知线程来进行read/write的真正操作。
有了上面的解释,关于readiness selection的设计其实已经差不多明了了。
把各个channel注册到Selecotor上,每一个channel绑定一个SelectionKey,selectionKey上包括了一个这个channel感兴趣的key,然后访问Selecotor上的keys来查看已经符合就绪条件的channel,然后无阻塞的操作。

结构图:
 
 
readiness selection的设计和实现中,有三个重要角色SelectableChannel、SelectionKey和Selecotor。 
它们之间的关系和使用方法,可以参考它们的源码~
1,Selecotor也只是封装了调用select( ), poll( ) ,等其他本地操作而已。管理这所有的key,内部封装了3个key set,来维护注册的key,就绪的key,删除的key:

Registered key set
Selected key set
Cancelled key set
2,SelectionKey类似是chennel的指针
3,SelectableChannel 带有Selectable属性的channel
 
具体的使用代码:
public class NIOServer {

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

    /* 标识数字 */
private static int flag = 0;
/* 缓冲区大小 */
private static int BLOCK = 4096;
/* 接受数据缓冲区 */
private static ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
/* 发送数据缓冲区 */
private static ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);
/* 服务器端地址 */
private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress(
"localhost", 10000); 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(SERVER_ADDRESS); 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_READ);
} } else if (selectionKey.isWritable()) {
sendbuffer.clear();
client = (SocketChannel) selectionKey.channel();
sendText = "message from client--" + (flag++);
sendbuffer.put(sendText.getBytes());
// 将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
sendbuffer.flip();
client.write(sendbuffer);
System.out.println("客户端向服务器端发送数据--:" + sendText);
client.register(selector, SelectionKey.OP_READ);
}
}
selectionKeys.clear();
}
}
}

3,Reactor-反应堆模式

参考Doug Lea的多线程反应堆模式的实现代码如下,详细可以参考他的ppt:摸我

结构图:

class Reactor implements Runnable {
final Selector selector;
final ServerSocketChannel serverChannel;
static final int WORKER_POOL_SIZE = 10;
static ExecutorService workerPool; Reactor(int port) throws IOException {
selector = Selector.open();
serverChannel = ServerSocketChannel.open();
serverChannel.socket().bind(new InetSocketAddress(port));
serverChannel.configureBlocking(false); // Register the server socket channel with interest-set set to ACCEPT operation
SelectionKey sk = serverChannel.register(selector, SelectionKey.OP_ACCEPT);
sk.attach(new Acceptor());
} public void run() {
try {
while (true) { selector.select();
Iterator it = selector.selectedKeys().iterator(); while (it.hasNext()) {
SelectionKey sk = (SelectionKey) it.next();
it.remove();
Runnable r = (Runnable) sk.attachment();
if (r != null)
r.run();
}
}
}
catch (IOException ex) {
ex.printStackTrace();
}
} class Acceptor implements Runnable {
public void run() {
try {
SocketChannel channel = serverChannel.accept();
if (channel != null)
new Handler(selector, channel);
}
catch (IOException ex) {
ex.printStackTrace();
}
}
} public static void main(String[] args) {
workerPool = Executors.newFixedThreadPool(WORKER_POOL_SIZE); try {
new Thread(new Reactor(9090)).start();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
class Handler implements Runnable {
final SocketChannel channel;
final SelectionKey selKey; static final int READ_BUF_SIZE = 1024;
static final int WRiTE_BUF_SIZE = 1024;
ByteBuffer readBuf = ByteBuffer.allocate(READ_BUF_SIZE);
ByteBuffer writeBuf = ByteBuffer.allocate(WRiTE_BUF_SIZE); Handler(Selector sel, SocketChannel sc) throws IOException {
channel = sc;
channel.configureBlocking(false); // Register the socket channel with interest-set set to READ operation
selKey = channel.register(sel, SelectionKey.OP_READ);
selKey.attach(this);
selKey.interestOps(SelectionKey.OP_READ);
sel.wakeup();
} public void run() {
try {
if (selKey.isReadable())
read();
else if (selKey.isWritable())
write();
}
catch (IOException ex) {
ex.printStackTrace();
}
} // Process data by echoing input to output
synchronized void process() {
byte[] bytes; readBuf.flip();
bytes = new byte[readBuf.remaining()];
readBuf.get(bytes, 0, bytes.length);
System.out.print("process(): " + new String(bytes, Charset.forName("ISO-8859-1"))); writeBuf = ByteBuffer.wrap(bytes); // Set the key's interest to WRITE operation
selKey.interestOps(SelectionKey.OP_WRITE);
selKey.selector().wakeup();
} synchronized void read() throws IOException {
int numBytes; try {
numBytes = channel.read(readBuf);
System.out.println("read(): #bytes read into 'readBuf' buffer = " + numBytes); if (numBytes == -1) {
selKey.cancel();
channel.close();
System.out.println("read(): client connection might have been dropped!");
}
else {
Reactor.workerPool.execute(new Runnable() {
public void run() {
process();
}
});
}
}
catch (IOException ex) {
ex.printStackTrace();
return;
}
} void write() throws IOException {
int numBytes = 0; try {
numBytes = channel.write(writeBuf);
System.out.println("write(): #bytes read from 'writeBuf' buffer = " + numBytes); if (numBytes > 0) {
readBuf.clear();
writeBuf.clear(); // Set the key's interest-set back to READ operation
selKey.interestOps(SelectionKey.OP_READ);
selKey.selector().wakeup();
}
}
catch (IOException ex) {
ex.printStackTrace();
}
}
}

注意:clent端可以参考前面的例子,可以通用。

那么这个所谓的反应堆模式其实已经比较接近netty的结构了,为了构造出高性能的服务端前人做了很多的努力~,在进入netty的世界前打好基础先。

总结:

1,在学习NIO的时候牵涉到很多jvm,甚至操作系统底层的原理知识,发现自己对计算机底层原理的理解几乎是不及格的,在大学的时候没有打好基础,这部分需要补充。

2,牵涉到很多位置的知识领域只能死啃的办法,多看书,多实验。

让我们继续前行

----------------------------------------------------------------------

努力不一定成功,但不努力肯定不会成功。

java NIO-我们到底能走多远系列(39)的更多相关文章

  1. ArrayBlockingQueue-我们到底能走多远系列(42)

    我们到底能走多远系列(42) 扯淡: 乘着有空,读些juc的源码学习下.后续把juc大致走一边,反正以后肯定要再来. 主题: BlockingQueue 是什么 A java.util.Queue t ...

  2. 服务调用方案(Spring Http Invoker) - 我们到底能走多远系列(40)

    我们到底能走多远系列(40) 扯淡:  判断是否加可以效力于这家公司,一个很好的判断是,接触下这公司工作几年的员工,了解下生活工作状态,这就是你几年后的状态,如果满意就可以考虑加入了. 主题: 场景: ...

  3. node模拟http服务器session机制-我们到底能走多远系列(36)

    我们到底能走多远系列(36) 扯淡: 年关将至,总是会在一些时间节点上才感觉时光飞逝,在平时浑浑噩噩的岁月里都浪费掉了太多的宝贵.请珍惜! 主题:      我们在编写http请求处理和响应的代码的时 ...

  4. Bean实例化(Spring源码阅读)-我们到底能走多远系列(33)

    我们到底能走多远系列(33) 扯淡: 各位:    命运就算颠沛流离   命运就算曲折离奇   命运就算恐吓着你做人没趣味   别流泪 心酸 更不应舍弃   ... 主题: Spring源码阅读还在继 ...

  5. Spring3整合Hibernate4-我们到底能走多远系列(30)

    我们到底能走多远系列(30) 扯淡: 30篇啦!从2012-08-15开始的系列,东平西凑将近一年的时间也就这么几篇.目标的100篇,按这个速度也要再搞两年呢. 发博客果然不是件容易的事,怪不得更多的 ...

  6. ThreadPoolExecutor机制探索-我们到底能走多远系列(41)

    我们到底能走多远系列(41) 扯淡: 这一年过的不匆忙,也颇多感受,成长的路上难免弯路,这个世界上没人关心你有没有变强,只有自己时刻提醒自己,不要忘记最初出发的原因. 其实这个世界上比我们聪明的人无数 ...

  7. Spring mvc源码url路由-我们到底能走多远系列(38)

    我们到底能走多远系列38 扯淡: 马航的事,挺震惊的.还是多多珍惜身边的人吧. 主题: Spring mvc 作为表现层的框架,整个流程是比较好理解的,毕竟我们做web开发的,最早也经常接触的就是一个 ...

  8. node实现http上传文件进度条 -我们到底能走多远系列(37)

    我们到底能走多远系列(37) 扯淡: 又到了一年一度的跳槽季,相信你一定准备好了,每每跳槽,总有好多的路让你选,我们的未来也正是这一个个选择机会组合起来的结果,所以尽可能的找出自己想要的是什么再做决定 ...

  9. js中this和回调方法循环-我们到底能走多远系列(35)

    我们到底能走多远系列(35) 扯淡: 13年最后一个月了,你们在13年初的计划实现了吗?还来得及吗? 请加油~ 主题: 最近一直在写js,遇到了几个问题,可能初入门的时候都会遇到吧,总结下. 例子: ...

随机推荐

  1. 如何打开asp.net中的出错提示?在程序发布后

    答案:修改其中的一个配置节点,<customErrors mode="On"/>

  2. redhat enterprixe 5.0 下DHCP服务器rpm安装配置及其测试

    一.了解DHCP DHCP服务提供动态指定IP地址和配置参数的机制.有动态和静态两种方式. 二.rpm安装 因为配过Samba,所以感觉挺简单. 首先找到主程序和几个附属程序的rpm的安装包.应该都是 ...

  3. Android调用远程Service的参数和返回值都需要实现Parcelable接口

    import android.os.Parcel;import android.os.Parcelable; public class Person implements Parcelable{ pr ...

  4. java之socket

    socket系java中网络编程的内容. 1客户端: package com.wtd.socket; import java.io.BufferedReader; import java.io.IOE ...

  5. C# 正则表达式 验证:数字、带小数点数字、电话和手机

    一.带小数点数字 public static bool IsNumber(string input) { string pattern = "^-?\\d+$|^(-?\\d+)(\\.\\ ...

  6. MONGODB 查询

    1,mongoDB 取模运算:db.person.find({index:{$mod:[5,1]}})db.person.find({index:{$not:{$mod:[5,1]}}})2,name ...

  7. html中offsetTop、clientTop、scrollTop、offsetTop

    HTML精确定位:scrollLeft,scrollWidth,clientWidth,offsetWidth scrollHeight: 获取对象的滚动高度. scrollLeft:设置或获取位于对 ...

  8. 倍增 LCA

    以NOIP2013提高组day1 最后一道题为例来学的倍增和lca.其实这套题早就做过了,倍增和lca也学过,只不过当时没有理解清楚,所以今天再次学了一遍,虽然没有时间编程序了,但是先把思路和做法在这 ...

  9. xcode6 ios launchimage

    1.点击Image.xcassets 进入图片管理,然后右击,弹出"New Launch Image" 2.右侧的勾选可以让你选择是否要对ipad,横屏,竖屏,以及低版本的ios系 ...

  10. 后台框架--HUI 的学习跟使用1

    下载跟查看说明文档:官方 https://github.com/jackying/ 官网:http://www.h-ui.net/H-ui.admin.shtml 后台,http://www.h-ui ...