Java 非阻塞IO常用于高性能的服务器程序。对于阻塞式IO常常需要多个线程来处理客户端的请求,由于线程的开销较大,往往使服务器性能下降很快。而非阻
塞IO只需几个线程就可以胜任大量的请求。对于p2p软件(例如BT软件),也常常使用非阻塞IO,来实现文件交换。
下面是一个典型的非阻塞IO程序。客户端向服务器端发起10个连接,服务器端向每个客户端发送”Hello”,并打印出来。
服务器端程序:
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 Server {
// 服务器端口
public static int port = 9994;
public Server() {
init();
}
public void init() {
Selector selector = null;
try {
// 获得Selector实例
selector = Selector.open();
// 获得ServerSocketChannel实例
ServerSocketChannel serverChannel = ServerSocketChannel.open();
InetSocketAddress add = new InetSocketAddress("localhost", port);
// 设为非阻塞模式,默认为阻塞模式
serverChannel.configureBlocking(false);
// channel与一个InetSocketAddress绑定
serverChannel.socket().bind(add);
// 向selector注册
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
return;
}
while (true) {
try {
// 如果没有准备好的channel,就在这一直阻塞
// 注意刚启动时,没有客户端与服务器端连接,会阻塞
selector.select();
} catch (IOException e) {
e.printStackTrace();
break;
}
// 返回已经就绪的SelctionKey,然后迭代执行
Set readyKeys = selector.selectedKeys();
for (Iterator it = readyKeys.iterator(); it.hasNext();) {
SelectionKey key = (SelectionKey) it.next();
// 为防止重复迭代要执行remove,在执行selector.select()时,会自动加入去掉的key
it.remove();
try {
// 对应于注册的OP_ACCEPT管道,在这里即ServerSocketChannel
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key
.channel();
// 由于ServerSocketChannel为非阻塞模式,因此不会在这阻塞
SocketChannel client = server.accept();
client.configureBlocking(false);
// 表明接受到一个客户端连接,将其注册到selector
// 执行selector.select()时可以自动选一个channel
client.register(selector, SelectionKey.OP_WRITE);
// 对应于注册的OP_WRITE管道,在这里即SocketChannel
} else if (key.isWritable()) {
SocketChannel client = (SocketChannel) key.channel();
// 开辟20个字节的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(20);
String str = "hello";
// 将"hello"封装到buffer
buffer = ByteBuffer.wrap(str.getBytes());
// 写入客户端
client.write(buffer);
// 写完hello后取消通道的注册
key.cancel();
}
} catch (IOException e) {
e.printStackTrace();
key.cancel();
try {
// 关闭通道
key.channel().close();
} catch (IOException e1) {
e.printStackTrace();
}
}
}// end for
}// end while
}
public static void main(String[] args) {
Server server = new Server();
}
}
客户端程序:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class Client {
public Client() {
init();
}
public void init() {
try {
SocketAddress add = new InetSocketAddress("localhost", Server.port);
// 返回SocketChannel实例,并绑定SocketAddress
SocketChannel client = SocketChannel.open(add);
client.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(20);
// 从通道中读取
client.read(buffer);
// 为读取做准备
buffer.flip();
String result = "";
// 每次读一个字符
while (buffer.hasRemaining())
result += String.valueOf((char) buffer.get());
System.out.println(result);
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Client client=null;
for(int i=0;i<10;i++){
client = new Client();
System.out.println("client "+i+" has connected");
}
}
}
运行结果:
hello
client 0 has connected
hello
client 1 has connected
hello
client 2 has connected
hello
client 3 has connected
hello
client 4 has connected
hello
client 5 has connected
hello
client 6 has connected
hello
client 7 has connected
hello
client 8 has connected
hello
client 9 has connected