java NIO-我们到底能走多远系列(39)
献给各位:
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一下就可以了
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会阻塞:
因为我们去read/write的时候并不知道是否会线程阻塞,所以我们把主被换一下,当我们知道已经符合read/write条件时,通知线程来进行read/write的真正操作。
有了上面的解释,关于readiness selection的设计其实已经差不多明了了。
把各个channel注册到Selecotor上,每一个channel绑定一个SelectionKey,selectionKey上包括了一个这个channel感兴趣的key,然后访问Selecotor上的keys来查看已经符合就绪条件的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();
}
}
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)的更多相关文章
- ArrayBlockingQueue-我们到底能走多远系列(42)
我们到底能走多远系列(42) 扯淡: 乘着有空,读些juc的源码学习下.后续把juc大致走一边,反正以后肯定要再来. 主题: BlockingQueue 是什么 A java.util.Queue t ...
- 服务调用方案(Spring Http Invoker) - 我们到底能走多远系列(40)
我们到底能走多远系列(40) 扯淡: 判断是否加可以效力于这家公司,一个很好的判断是,接触下这公司工作几年的员工,了解下生活工作状态,这就是你几年后的状态,如果满意就可以考虑加入了. 主题: 场景: ...
- node模拟http服务器session机制-我们到底能走多远系列(36)
我们到底能走多远系列(36) 扯淡: 年关将至,总是会在一些时间节点上才感觉时光飞逝,在平时浑浑噩噩的岁月里都浪费掉了太多的宝贵.请珍惜! 主题: 我们在编写http请求处理和响应的代码的时 ...
- Bean实例化(Spring源码阅读)-我们到底能走多远系列(33)
我们到底能走多远系列(33) 扯淡: 各位: 命运就算颠沛流离 命运就算曲折离奇 命运就算恐吓着你做人没趣味 别流泪 心酸 更不应舍弃 ... 主题: Spring源码阅读还在继 ...
- Spring3整合Hibernate4-我们到底能走多远系列(30)
我们到底能走多远系列(30) 扯淡: 30篇啦!从2012-08-15开始的系列,东平西凑将近一年的时间也就这么几篇.目标的100篇,按这个速度也要再搞两年呢. 发博客果然不是件容易的事,怪不得更多的 ...
- ThreadPoolExecutor机制探索-我们到底能走多远系列(41)
我们到底能走多远系列(41) 扯淡: 这一年过的不匆忙,也颇多感受,成长的路上难免弯路,这个世界上没人关心你有没有变强,只有自己时刻提醒自己,不要忘记最初出发的原因. 其实这个世界上比我们聪明的人无数 ...
- Spring mvc源码url路由-我们到底能走多远系列(38)
我们到底能走多远系列38 扯淡: 马航的事,挺震惊的.还是多多珍惜身边的人吧. 主题: Spring mvc 作为表现层的框架,整个流程是比较好理解的,毕竟我们做web开发的,最早也经常接触的就是一个 ...
- node实现http上传文件进度条 -我们到底能走多远系列(37)
我们到底能走多远系列(37) 扯淡: 又到了一年一度的跳槽季,相信你一定准备好了,每每跳槽,总有好多的路让你选,我们的未来也正是这一个个选择机会组合起来的结果,所以尽可能的找出自己想要的是什么再做决定 ...
- js中this和回调方法循环-我们到底能走多远系列(35)
我们到底能走多远系列(35) 扯淡: 13年最后一个月了,你们在13年初的计划实现了吗?还来得及吗? 请加油~ 主题: 最近一直在写js,遇到了几个问题,可能初入门的时候都会遇到吧,总结下. 例子: ...
随机推荐
- oracle Redhat64 安装错误3
问题描述 /usr/bin/ld: cannot find -lxxx 其中xxx即表示函式库文件名称,其命名规则是:lib+库名(即xxx)+.so. 可能原因: 1 安装了,但相对应的lib版本 ...
- 经典DP 二维换一维
HDU 1024 Max Sum Plus Plus // dp[i][j] = max(dp[i][j-1], dp[i-1][t]) + num[j] // pre[j-1] 存放dp[i-1] ...
- 小记:xml画一个爱心。
aixin.xml: <!-- height:width=viewportHeight:viewportWidth --> <vector xmlns:android=" ...
- Windows XP PRO SP3 - Full ROP calc shellcode
/* Shellcode: Windows XP PRO SP3 - Full ROP calc shellcode Author: b33f (http://www.fuzzysec ...
- 谷歌 不支持 activeX插件
因为Chrome浏览器42以上版本已经陆续不再支持NPAPI插件,也就是说,目前的迅雷插件.FLASH插件.支付宝插件.阿里旺旺插件.百度贴吧.网银等网站都受到一定程度的影响,本文分享给大家如何让谷歌 ...
- C++-高效的swap
原始版本: template<typename T> void swap(T& a, T& b) { T tmp(a); a = b; b = tmp; } 此版本不重视效 ...
- win7 web开发遇到的问题-由于权限不足而无法读取配置文件,无法访问请求的页面
错误一: HTTP Error 500.19 - Internal Server Error配置错误: 不能在此路径中使用此配置节.如果在父级别上锁定了该节,便会出现这种情况.锁定是默认设置的 (ov ...
- Problem C 链表
Description 某部队进行新兵队列训练,将新兵从一开始按顺序依次编号,并排成一行横队,训练的规则如下:从头开始一至二报数,凡报到二的出列,剩下的向小序号方向靠拢,再从头开始进行一至三报数,凡报 ...
- android 录音的断点续传
系统没有暂停的功能 只能把每次的录音进行拼接... package com.example.zrecord; import java.io.File;import java.io.FileInput ...
- Linux登陆和欢迎信息修改
edit file: /etc/issue,/etc/motd 这样还改不了,生成的脚本在目录/etc/update-motd.d/中: 假如要打开一个文本文件,可以修改10-help-text: ( ...