一、文章来由
研究Nio也有几天了,在网上看了很多文章,给人整体的感觉就是,一个原本简简单单的东西,被说的好复杂。或者是类似 http://ifeve.com/selectors/ 这种百科全书式的教你如何用接口,这种文章看似介绍了每个函数,面面俱到,却很难串起来。
但是人学东西本来就是一个感性认识到理性认识的过程,如果看到的都是单点,怎么在脑海中形成一张图?
(1)是什么(功能)
(2)为什么这么设计(应用场景)
(3)怎么实现的(具体代码)
这三个问号搞完了以后,才是如何去使用,不能本末倒置。
另外最近实习,需要学习很多东西,文章大都是网上质量高一些的文章的转载,原创文章就相对少一些~~
二、NIO设计原理
一个IO操作分成了两个步骤:①发起IO请求 ②实际的IO操作
(1)阻塞IO和非阻塞IO的区别在于第一步,发起IO请求是否会被阻塞,如果阻塞直到完成那么就是传统的阻塞IO,如果不阻塞,那么就是非阻塞IO。
(2)同步IO和异步IO的区别就在于第二个步骤是否阻塞
传统的 IO 称为 BIO,是一种同步阻塞 IO 。要知道 IO 都是交给操作系统完成的,系统在 IO 的时候,如果当前线程等待在那里,效率是很低的。在网络编程里,传统的socket也是 BIO,一个连接一个线程。
于是 NIO 出现了,它是一种同步非阻塞 IO,其实说白了,就是用一个线程不断轮询,去看有没有事件去处理…
服务员不断询问顾客有没有什么要求,然后同步去处理~~
用户自己实现的一个while循环,while(selector.select() > 0)
,通过select阻塞的轮询,来看是否有事件处理。如果有处理就完了。
道理就这么简单。
三、selector
selector就是那个轮询的处理器,它采用register的模式,要被selector轮询,一定要登记。就像你去餐馆吃东西,一定要点菜一样。
selector可以监听四种不同类型的事件:
Connect
Accept
Read
Write
这四种事件用SelectionKey的四个常量来表示:
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
对应成代码就是:
Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
int readyChannels = selector.select();
if(readyChannels == 0) continue;
Set selectedKeys = selector.selectedKeys();
Iterator keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) {
// a connection was accepted by a ServerSocketChannel.
} else if (key.isConnectable()) {
// a connection was established with a remote server.
} else if (key.isReadable()) {
// a channel is ready for reading
} else if (key.isWritable()) {
// a channel is ready for writing
}
keyIterator.remove();
}
}
很明显的是:
但是客户端注册selector的时候,不可能有serverChannel的出现了,所以客户端不可能有isAcceptable事件,就像服务端不可能有isConnectable一样
当连接建立后两段都需要获得socketchannel来通信 ,跟socket一样,事实上,如果不用selector的话,跟socket就是一毛一样的,socketChannel底层也就是socket。如下:
/////NioServer.java
package nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
/**
* Created by hupo.wh on 2016/7/16.
*/
public class NioServer {
public static void main(String args[]) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(8888));
serverSocketChannel.configureBlocking(false); //非阻塞
//字符序列和字节序列的编码和解码
Charset charset = Charset.forName("UTF-8");
ByteBuffer buf = ByteBuffer.allocate(48);
while(true){
SocketChannel socketChannel =
serverSocketChannel.accept();
String mesg = "";
if(socketChannel != null){
//do something with socketChannel...
System.out.println("Connection has been built!!!");
while (socketChannel.read(buf) > 0) {
buf.flip();
mesg += charset.decode(buf);
}
System.out.println(mesg);
}
}
}
}
/////NioClient.java
package nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* Created by hupo.wh on 2016/7/15.
*/
public class NioClient {
public static void main(String args[]) throws IOException {
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress("127.0.0.1",8888));
sc.configureBlocking(false);
String newData = "Hello Wanghu..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
sc.write(buf);
}
sc.close();
}
}
四、例程
server例程一:
package nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
/**
* Created by hupo.wh on 2016/7/16.
*/
public class App2 {
public static void main(String[] args) throws IOException {
int port = 30;
ServerSocketChannel serverChannel = ServerSocketChannel.open( );
ServerSocket serverSocket = serverChannel.socket( );
Selector selector = Selector.open( );
serverSocket.bind (new InetSocketAddress(port));
serverChannel.configureBlocking (false);
serverChannel.register (selector, SelectionKey.OP_ACCEPT);
while (true) {
int n = selector.select( );
if (n == 0) {
continue; // nothing to do
}
Iterator it = selector.selectedKeys().iterator( );
while (it.hasNext( )) {
SelectionKey key = (SelectionKey) it.next( );
if (key.isAcceptable( )) {
ServerSocketChannel server =
(ServerSocketChannel) key.channel( );
SocketChannel channel = server.accept( );
if (channel == null) {
;//handle code, could happen
}
channel.configureBlocking (false);
channel.register (selector, SelectionKey.OP_READ);
}
if (key.isReadable( )) {
//readDataFromSocket (key);
}
it.remove( );
}
}
}
}
Server例程二:
package nio;
/**
* Created by hupo.wh on 2016/7/16.
*/
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.channels.spi.SelectorProvider;
import java.nio.charset.Charset;
import java.util.Set;
/**
* Created by wwh on 15-7-25.
*/
public class NioSocket {
//字符序列和字节序列的编码和解码
private Charset charset = Charset.forName("UTF-8");
void run(String ip, int port) throws IOException {
try {
//创建服务端套接字
ServerSocketChannel server = ServerSocketChannel.open();
//绑定ip和端口
server.socket().bind(new InetSocketAddress(ip, port));
//设置非阻塞
server.configureBlocking(false);
//创建selector事件选择器
Selector selector = Selector.open();
//将自己的监听套接字注册到selector上,监听 accept事件
//SelectionKey代表SelectableChannel和Selector的关系,Selectable是Selector可监听的事件channel.
server.register(selector, SelectionKey.OP_ACCEPT);
while(selector.select() > 0){
//selector.select()返回事件
for(SelectionKey sk : selector.selectedKeys()) {
//从事件集合中删除正要处理的事件
selector.selectedKeys().remove(sk);
//判断事件的类型,依次处理
if(sk.isAcceptable()){
//如果事件为接受连接accpet事件
System.out.println("accpet 事件");
//调用accept接受请求连接
SocketChannel client = server.accept();
//设置为非阻塞
client.configureBlocking(false);
//向selector上注册读事件
client.register(selector, SelectionKey.OP_READ);
//将sk对应的channel设置为准备接受其他请求
sk.interestOps(SelectionKey.OP_ACCEPT);
}
if(sk.isReadable()){
//如果事件为可读事件
System.out.println("read 事件");
SocketChannel client = (SocketChannel)sk.channel();
//定义缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
String mesg = "";
try {
while (client.read(buffer) > 0) {
buffer.flip();
mesg += charset.decode(buffer);
}
System.out.println("收到:" + mesg);
sk.interestOps(SelectionKey.OP_READ);
}catch (IOException e){
//如果出现异常,则取消当前的client连接
sk.cancel();
if(sk.channel() != null){
sk.channel().close();
}
}
//回复给发来消息的client
client.write(charset.encode(mesg));
System.out.println("回复:" + mesg);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
NioSocket Server = new NioSocket();
Server.run("localhost", 10000);
}
}