多线程是解决socket的阻塞问题的一种方法,但该方法的问题是线程的最大数一般有限制,而且多个线程
频繁的上下文切换需要消耗大量的系统资源。一个解决方法是采用线程池,但对于长连接采用线程池显然无法解决问题,通常采用的方式为NIO(非阻塞IO或IO复用)、AIO(异步IO)。java在1.7版本的时候开始支持AIO,BIO跟NIO早已支持。
linux中io复用有select/poll跟epoll两种方式。select的缺陷是监听的文件描述符数量有限制,并且每次select调用会把描述符从用户空间拷贝到内核,有事件通知时需要遍历整个描述符集合。而epoll解决了select这两个缺陷,它只通知有事件发生的文件描述符,通常epoll的性能比select要高。另外作为非阻塞IO, select或者
epoll_wait仍为阻塞函数,若该函数返回,表示其监听的描述符中有一个或多个有事件发生,比如有一个客户端连接、有读写事件等, 而接受到通知并不表示IO事件已经完成,表示我们可以进行相应IO操作了,此时该操作不阻塞。NIO的编程模型如下:
-
while(true){
-
finfds = select(fds);
-
for(fd : fdsfinfds ){
-
if(fd is readable) handleRead(fd);
-
else if(fd is writeable) handleWrite(fd);
-
else if(fd is acceptable) handleAccept(fd);
-
else
-
....
-
}
-
}
一、Java NIO
java的NIO主要有3个部分组成:
1. 缓冲区(Buffer):实际上就是一个经过包装的数组
2. 通道(Channel):相当于一根水管,连接IO设备与Buffer。跟输入输出流很相似
3. 监听器(Selector):监听器,监听IO设备是否就绪
二、Channel
Channel根据作用不同分为
1、FileChannel:文件读写。FileChannel总是阻塞的,所以NIO应该是new io的简称
2、ServerSocketChannel:用于监听TCP连接的请求
3、SocketChannel:处理TCP读写事件
4、DatagramChannel:处理UDP
Channel总是配合Buffer使用,FileChannel例子如下:
-
File file = new File(Thread.currentThread().getContextClassLoader()
-
.getResource("1.txt").getPath());
-
-
RandomAccessFile f = new RandomAccessFile(file, "rw");
-
FileChannel channel = f.getChannel();
-
-
ByteBuffer bytes = ByteBuffer.allocate(1024);
-
CharBuffer chars = CharBuffer.allocate(1024);
-
-
Charset charset = Charset.forName("GBK");
-
CharsetDecoder decoder = charset.newDecoder();
-
int reads = channel.read(bytes);
-
while (reads != -1) {
-
bytes.flip();
-
chars = decoder.decode(bytes);
-
-
-
while(chars.hasRemaining())
-
System.out.print(chars.get());
-
chars.flip();
-
-
bytes.clear();
-
chars.clear();
-
reads = channel.read(bytes);
-
}
-
f.close();
三、Buffer
Buffer有很多种,但本质都是对数组的包装。内部结构如图:
1. capacity: 总容量
2. position:当前读或者写的位置
3. limit:写时表示还剩容量,读时表示总可读容量,在写转读时,设置limit=position
4. filp():写转读。
limit=position,position=0
5. clear():清空缓存区 position=0,limit=capacity
6. rewind():position=0, 重读数据
7. compact():将为读完的数据拷贝到缓存起始部分,设置position为最后一个元素后面
8. mark()与reset():一个old = position值,一个position=old
9. equals():针对缓冲区而已,跟里面元素无关。类型一致并且剩余容量相同,认为相等。
9. compareTo():比较元素
10. DirectByteBuffer:这是跟其他缓冲有本质区别的一种缓冲区。实际上所有的通道操作都需要一个DirectByteBuffer。过程:
创建临时的direct ByteBuffer
复制non-direct buffer中的内容到临时buffer
使用临时buffer执行IO操作
临时buffer不被引用,最终被垃圾收集
四、Selector
selector是一个监听器,一个channel(必须配置成非阻塞)可以自己感兴趣的事件绑定到selector上. select阻塞返回会生成一系列SelectionKey对象,该对象描述了通道的一些属性,比如interest跟ready集合,另外还可以携带一个附加对象。selector监听的事件如下:
1. OP_ACCEPT: 连接请求
2. OP_READ:读
3. OP_WRITE:这个比较特殊。通常写缓冲区都是有空闲空间的,那么会一直才生该事件。所以一般做法是在真正需要写的时候注册该事件,写完后取消该事件
4. OP_CONNECT:客户端连接成功
-
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();
-
}
-
}
阅读(906) | 评论(0) | 转发(0) |