自己的一个NIO的小例子,代码:
public class HelloServer {
private Selector selector;
private ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
private String name;
public HelloServer() throws IOException {
selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(8888));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
public Selector getSelector() {
return selector;
}
public void setSelector(Selector selector) {
this.selector = selector;
}
// 开始监听
public void listen() {
try {
for (;;) {
// 选择已经准备好的通道,返回通道数,这个值是上次调用select()后到现在这个阶段
//已经准备好的通道
int i = selector.select();
Iterator iter = selector.selectedKeys().iterator();
if (iter.hasNext()) {
SelectionKey selectionKey = (SelectionKey) iter.next();
// 处理完一个就需要删除一个SelectionKey 不然的话,一直堆积,cpu100%
iter.remove();
process(selectionKey);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void process(SelectionKey selectionKey) throws IOException {
// 准备好接受新的套接字连接 这个只有ServerSocketChannel绑定的SelectionKey才有效
if (selectionKey.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) selectionKey
.channel();
SocketChannel channel = server.accept();
// 设置非阻塞模式
channel.configureBlocking(false);
// 得到client的socket后可以使其在OP_READ状态,下个时段的selector.select()中就是selectionKey.isReadable()的了,可以执行下面这个分支了
channel.register(selector, SelectionKey.OP_READ);
System.out.println("accept:"+selectionKey.interestOps()+" "+selectionKey.readyOps());
} else if (selectionKey.isReadable()) {
// 可读,下面就是client读数据
SocketChannel channel = (SocketChannel) selectionKey.channel();
// int count = 0;
int count = channel.read(byteBuffer);
if (count > 0) {
byte[] bbb = new byte[1024];
byteBuffer.flip();
bbb = byteBuffer.array();
name = new String(bbb);
System.out.println(name + "aaa");
byteBuffer.clear();
}
// channel.close();这个可以关闭,可以不关闭,一般需要关闭(count==0),不然client的socket堆积的太多
// channel.register(selector, SelectionKey.OP_WRITE);
// 可以在注册感兴趣的时间,这个是往客户端写,下个select()
// 时段就可以往client里面写了
System.out.println("isReadable:"+selectionKey.interestOps()+" "+selectionKey.readyOps());
} else if (selectionKey.isWritable()) {
System.out.println("isWritable:"+selectionKey.interestOps());
SocketChannel channel = (SocketChannel) selectionKey.channel();
byteBuffer.reset();
byteBuffer.put(name == null ? "acb".getBytes() : name.getBytes());
}
}
public static void main(String[] args) {
int port = 8888;
try {
HelloServer server = new HelloServer();
System.out.println("listening on " + port);
server.listen();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class HelloClient {
static InetSocketAddress ip = new InetSocketAddress("localhost", 8888);
static ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
static class Message implements Runnable {
protected String name;
String msg = "";
public Message(String index) {
this.name = index;
}
public void run() {
try {
long start = System.currentTimeMillis();
// 打开Socket通道
SocketChannel client = SocketChannel.open();
// 设置为非阻塞模式
client.configureBlocking(false);
// 打开选择器
Selector selector = Selector.open();
// 注册连接服务端socket动作
client.register(selector, SelectionKey.OP_CONNECT);
// 连接
client.connect(ip);
// 分配内存
ByteBuffer buffer = ByteBuffer.allocate(1);
int total = 0;
_FOR: for (;;) {
selector.select();
Iterator iter = selector.selectedKeys().iterator();
while (iter.hasNext()) {
SelectionKey key = (SelectionKey) iter.next();
iter.remove();
if (key.isConnectable()) {
SocketChannel channel = (SocketChannel) key
.channel();
if (channel.isConnectionPending())
channel.finishConnect();
byte [] aaa = name.getBytes();
channel.write(buffer.wrap(aaa));
//写完可以监听读,server的响应就可以在下个select()时段检测到了
channel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel channel = (SocketChannel) key
.channel();
int count = channel.read(buffer);
if (count > 0) {
total += count;
buffer.flip();
while (buffer.remaining() > 0) {
byte b = buffer.get();
msg += (char) b;
}
buffer.clear();
} else {
client.close();
break _FOR;
}
}
}
}
double last = (System.currentTimeMillis() - start) * 1.0 / 1000;
System.out.println(msg + "used time :" + last + "s.");
msg = "";
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
String names[] = new String[10];
for (int index = 0; index < 10; index++) {
names[index] = "jeff[" + index + "]";
new Thread(new Message(names[index])).start();
}
}
}
基本代码解释都有,需要注意的是:
1 selector的selectedKeys()方法返回的是上次select()方法调用后到现在为止的放入到就绪集里面的SelectionKey
,这个跟SelectionKey的interest和ready集合是没有关系的
2.用完的SelectionKey必须从就绪集里面删除,不然的话会有堆积的
3.在调用SelectableChannel.register方法时,就是channel和selector进行关联的时候,此时SelectionKey的interest和ready集合是一致的
当调用selector的select()方法,查看api知道需要做的事情如下:
对应步骤2我的理解是:对应一个selectionKey,如果就绪集合里面有的话,只是把这个key的状态和集合里面这个key已有的状态或操作,集合没有的话就直接set进去.
下面看NioReceiver的代码:
NioReceiver实现了Runnable接口,肯定会在线程中被执行,我们看他的run()方法:
调用了listen()方法,看代码:
前面是设置了一些标志位,接下来调用了events()方法,执行NioReplicationTask的里面的一些任务的一个列表。
每一个event都是Runnable,比较怪的是在events()方法中,执行的是runnable的run()的方法,也不是线程的执行方 法.
接着是socketTimeouts()方法调用,关闭一些已经超时的channel,接着就是根据SelectionKey的ready集合进行操作,包括注册channel,读channel的数据,之后如果NioReceiver不需要监听,进行一些资源的关闭和回收
看下readDataFromSocket()方法的定义:
protected void readDataFromSocket(SelectionKey key) throws Exception {
NioReplicationTask task = (NioReplicationTask) getTaskPool().getRxTask();
if (task == null) {
// No threads/tasks available, do nothing, the selection
// loop will keep calling this method until a
// thread becomes available, the thread pool itself has a waiting mechanism
// so we will not wait here.
if (log.isDebugEnabled()) log.debug("No TcpReplicationThread available");
} else {
// invoking this wakes up the worker thread then returns
//add task to thread pool
task.serviceChannel(key);
getExecutor().execute(task);
}
}
首先从一个资源池中拿到NioReplicationTask,有可能资源池没有空闲资源,此时返回null,那么这次读就丢失了,不过没有关系,readDataFromSocket()方法是在一个循环中,会一直调用这个方法,只是这次的数据丢失了,如果返回不为空,则执行这个Task.
比较经典的是:在这个Task执行前,传入的SelectionKey的OP_READ事件被屏蔽,在这个方法中数据读完后,重新开时这个事件,这里不是很明白,暂且认为,这样子SelectionKey带的数据不会被下一次的数据所污染,因为下一次通道发的数据在下一次select的时候是不会在SelectionKey的interest集合里面的,也就不可能在ready集合里面了。
最后面只是发送一个ACK的应答数据.
相比自己写的例子和tomcat的例子,差距在细节,包括异常的处理,各种临界状态的处理,线程的同步和阻塞唤醒。写一个主体常常很容易,但是各个细节的考虑却是功力的体现