最近在研究javaNIO发现,这里面的坑不少,而且资料很少,或许有人会说,baidu,google一大篇,可是真的很少,特别是关于Socket多线程读写这块的资料.大部分来源这里Architecture of a Highly Scalable NIO-Based Server.我承认这篇文章比较有官方权威,但对于像我这种入门级别的人来说理解起来还真费劲.
这篇文章的主要目的在抛砖引玉,加上自己的一些理解,希望能完全的详细解析下java NIO的原理及其开发过程中面对的各种坑.
java NIO多线程这块非常的坑爹.非常!
概念性的东西,这里不再详细的赘述,后面有机会会追加博文.
这里主要解析下个人觉得比较重要的部分或者说比较难以理解的一些API。
select():
API:Selects a set of keys whose corresponding channels are ready for I/O operations.This method performs a blocking selection operation. It returns only after at least one channel is selected, this selector'swakeup method is invoked, or the current thread is interrupted, whichever comes first.
选择对应的通道 I/O 操作(ACCEPT,CONNECT,READ,WRITE)准备就绪的SelectionKey集合。此方法阻塞模式下执行选择操作。如果没有对应SelectionKey选中则继续阻塞,直到至少选择一个通道、调用此选择器的 wakeup 方法或者当前的线程已中断此阻塞方中断.
下述情况注意:
服务端:
package org.reven.nio; 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.Arrays; import java.util.Iterator; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ServerSocketChannelTest { private static Logger logger = LoggerFactory.getLogger(ServerSocketChannelTest.class); public static void main(String[] args) throws IOException { int port = 1234; ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.socket().bind(new InetSocketAddress(port)); ssc.configureBlocking(false); Selector s = Selector.open(); ssc.register(s, SelectionKey.OP_ACCEPT); logger.debug("启动监听端口:{}", port); while (true) { int n = s.select(); if (n == 0) { logger.debug("未触发有效的key"); continue; } Set<SelectionKey> interestKeySet = s.selectedKeys(); Iterator<SelectionKey> ii = interestKeySet.iterator(); while (ii.hasNext()) { SelectionKey sk = ii.next(); ii.remove(); if (sk.isAcceptable()) { ServerSocketChannel $ssc = (ServerSocketChannel) sk.channel(); SocketChannel sc = $ssc.accept(); logger.debug("接受客户端{}连接",sc); sc.configureBlocking(false); sc.register(s, SelectionKey.OP_READ); } if (sk.isReadable()) { SocketChannel sc = (SocketChannel) sk.channel(); ByteBuffer bb = ByteBuffer.allocate(1); sc.read(bb); logger.debug("(触发)读取数据event:SocketChannel{},读取数据:{}", sc,Arrays.toString(bb.array())); } } } } }客户端:
package org.reven.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; public class SocketChannelTest { public static void main(String[] args) throws IOException, InterruptedException { SocketChannel sc = SocketChannel.open(new InetSocketAddress(1234)); /* sc.write(ByteBuffer.wrap(new byte[]{(byte)-1})); for (int i = 0; ; i++) { Thread.sleep(2000); sc.write(ByteBuffer.wrap(new byte[]{(byte)i})); }*/ //主要是为了让该应用别退出,就这样写了,方法多的是,可应用各种场景 synchronized (sc) { sc.wait(); } } }输出:
14:36:01.360 [main] DEBUG o.reven.nio.ServerSocketChannelTest - 启动监听端口:1234 14:36:04.126 [main] DEBUG o.reven.nio.ServerSocketChannelTest - 接受客户端java.nio.channels.SocketChannel[connected local=/172.16.60.16:1234 remote=/172.16.60.16:3938]连接
一个标记(a token),表示一个SelectableChannel注册到了一个Selector(如socketChannel.register(selector, 0),这里注册是否有效,token不会做校验,只是一个标记而已)。
channel():
selector()
isValid()
cancel()
interestOps()
interestOps(int)
readyOps()
isReadable()
isWritable()
isConnectable()
isAcceptable()
attach(Object)
attachment()
这个相对比较特殊的通道,在整个通信的过程中“服务端”<->“客户端”的数据传输均通过此通道完成(ServerSocketChannel是不能进行数据的)。