第11章非阻塞I/O
来源:互联网 发布:caffe python 数据层 编辑:程序博客网 时间:2024/05/22 10:53
第11章非阻塞I/O
11.1 一个实例客户端
在实现新I/O的客户端时,调用静态工厂方法SocketChannel.open()来创建一个新的java.nio.channels.SocketChannel对象。这个方法的参数是一个java.net.SocketAddress对象,指示要连接的主机和端口。
例如:下面的代码段连接指向rama.poly.edu端口19的通道:
SocketAddress rama = new InetSocketAddress("rama.poly.edu“,19);
SocketChannel client = SocketChannel.open(rama);
利用通道,可以直接写入通道本身,而不是写入ByteBuffer对象。
ByteBuffer buffer = ByteBuffer.allocate(74);
将这个ByteBuffer对象传递给通道的read()方法。通道会用从Socket读取的数据填充这个缓冲区。它返回成功读取并存储在缓存区的字节数:
int bytesRead = client.read(buffer);
这会至少读取一个字节,或者返回-1指示数据结束。
示例11-1:一个基于通道的chargen客户端
<span style="font-size:18px;"><span style="font-size:18px;">import java.nio.*;import java.nio.channels.*;import java.net.*;import java.io.IOException;public class ChargenClient { public static int DEFAULT_PORT = 19; public static void main(String[] args) { if (args.length == 0) { System.out.println("Usage: java ChargenClient host [port]"); return; } int port; try { port = Integer.parseInt(args[1]); } catch (RuntimeException ex) { port = DEFAULT_PORT; } try { SocketAddress address = new InetSocketAddress(args[0], port); SocketChannel client = SocketChannel.open(address); ByteBuffer buffer = ByteBuffer.allocate(74); WritableByteChannel out = Channels.newChannel(System.out); while (client.read(buffer) != -1) { buffer.flip(); out.write(buffer); buffer.clear(); } } catch (IOException ex) { ex.printStackTrace(); } }}</span></span>
可以在阻塞或非阻塞模式下允许这个连接,在非阻塞模式下,即使没有任何可用的数据,read()也会立即返回。这就允许程序在试图读取前做其他操作。它不必等待慢速的网络连接。要改变阻塞模式,可以向configureBlocking()方法传入true(阻塞)或false(不阻塞)。
client.configureBlocking(false);
在非阻塞模式下,read()可能因为读不到任何数据而返回0。因此循环需要有些差别:
while(true){
//把每次循环都要允许的代码都放在这里,无论有没有读到数据
int n = client.read(buffer);
if(n > 0) {
buffer.flip();
out.write(buffer);
buffer.clear();
}else if( n == -1) {
//这不应当发生,除非服务器发送故障
break;
}
}
11.2 一个实例服务器
示例11-2:一个非阻塞的chargen服务器
<span style="font-size:18px;"><span style="font-size:18px;">import java.nio.*;import java.nio.channels.*;import java.net.*;import java.util.*;import java.io.IOException;public class ChargenServer { public static int DEFAULT_PORT = 19; public static void main(String[] args) { int port; try { port = Integer.parseInt(args[0]); } catch (RuntimeException ex) { port = DEFAULT_PORT; } System.out.println("Listening for connections on port " + port); byte[] rotation = new byte[95*2]; for (byte i = ' '; i <= '~'; i++) { rotation[i -' '] = i; rotation[i + 95 - ' '] = i; } ServerSocketChannel serverChannel; Selector selector; try { serverChannel = ServerSocketChannel.open(); ServerSocket ss = serverChannel.socket(); InetSocketAddress address = new InetSocketAddress(port); ss.bind(address); serverChannel.configureBlocking(false); selector = Selector.open(); serverChannel.register(selector, SelectionKey.OP_ACCEPT); } catch (IOException ex) { ex.printStackTrace(); return; } while (true) { try { selector.select(); } catch (IOException ex) { ex.printStackTrace(); break; } Set<SelectionKey> readyKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = readyKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); iterator.remove(); try { if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key.channel(); SocketChannel client = server.accept(); System.out.println("Accepted connection from " + client); client.configureBlocking(false); SelectionKey key2 = client.register(selector, SelectionKey. OP_WRITE); ByteBuffer buffer = ByteBuffer.allocate(74); buffer.put(rotation, 0, 72); buffer.put((byte) '\r'); buffer.put((byte) '\n'); buffer.flip(); key2.attach(buffer); } else if (key.isWritable()) { SocketChannel client = (SocketChannel) key.channel(); ByteBuffer buffer = (ByteBuffer) key.attachment(); if (!buffer.hasRemaining()) { // Refill the buffer with the next line buffer.rewind(); // Get the old first character int first = buffer.get(); // Get ready to change the data in the buffer buffer.rewind(); // Find the new first characters position in rotation int position = first - ' ' + 1; // copy the data from rotation into the buffer buffer.put(rotation, position, 72); // Store a line break at the end of the buffer buffer.put((byte) '\r'); buffer.put((byte) '\n'); // Prepare the buffer for writing buffer.flip(); } client.write(buffer); } } catch (IOException ex) { key.cancel(); try { key.channel().close(); } catch (IOException cex) {} } } } }}</span></span>
11.3 缓冲区
流和通道之间的关键区别在于流是基于字节的,而通道是基于块的。
第二个关键区别是通道和缓冲区支持同一对象的读/写。
Java的所有基本数据类型都有特定的Buffer子类,但网络程序几乎只会使用ByteBuffer.
无论缓冲区是何种类型,都有相同的方法来获取和设置缓冲区4个关键部分地信息。
位置
缓存区中将读取或写入的下一个位置。
public final int position()
public final Buffer position(int newPosition)
容量
缓冲区可以保存的元素的最大数目。容量值在创建缓冲区时设置,此后不能改变。
public final int capacity()
限度
缓冲区中可访问数据的末尾位置。只要不改变限度,就无法读/写超过这个位置的数据,即使缓冲区有更大的容量也没有用。
public final int limit()
public final Buffer limit(int newLimit)
标记
缓冲区中客户端指定的索引。通过调用mark()可以将标记设置为当前位置。调用reset()可以将当前位置设置为所标记的位置。
公共的Buffer超类提供了另外几个方法:
public final Buffer clear()
clear()方法将位置设置为0,并将限度设置为容量,从而将缓冲区“清空”。
public final Buffer rewind()
将位置设置为0,但不改变限度,还允许重新读取缓冲区
public final Buffer flip()
将限度设置为当前位置,位置设置为0,希望排空刚刚填充的缓冲区时可以调用这个方法。
public final int remaining()
返回缓冲区中当前位置与限度之间的元素数。
public final boolean hasRemaining()_
如果剩余元素大于0,hasRemaining()方法返回true
创建缓冲区
11.4通道
异步通道(Java7)
Java 7 引入了AsynchronousSocketChannel和AsynchronousServerSocketChannel类。
读/写异步通道会立即返回,甚至在I/O完成之前就会返回。所读/写的数据会由一个Future或CompletionHandler进一步处理。connect()和accept()方法也会异步执行,并返回Future。这里不使用选择器。
例子:
SocketAddress address = new InetSocketAddress(args[0],port);
AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
Future<void> connected = client.connect(address);
ByteBuffer buffer = ByteBuffer.allocate(74);
//等待连接
connected.get();
//从连接读取
Future<Integer> future = client.read(buffer);
//做其他工作。。。
//等待读取完成。。。
future.get();
//回绕并排空缓冲区
buffer.flip();
WritableByteChannel out = Channels.newChannel(System.out);
out.write(buffer);
网络连接在并行运行,与此同时程序可以做其他事情。准备好处理来自网络的数据时,会停下来,通过调用Future.get()等待这些数据,但在此之前不用停下来。
如果不关心获取顺序,可以生成大量AsynchronousSocketChannel请求,并为每个请求提供一个CompletionHandler,由它在后端存储结果。
通过CompletionHandler接口声明了两个方法:completed()和failed(),如果成功读取则调用completed(),另外出现I/O错误时会调用failed()。
socket选项(Java7)
从Java 7 开始,SocketChannel,ServerSocketChannel,AsynchronousServerSocketChannel,AsynchronousSocketChannel和DatagramChannel都实现了新的NetworkChannel接口。这个接口的主要用途是支持各种TCP选项。
通道类分别有3个方法来获取、设置和列出所支持的选项:
<T> T getOption(SocketOption<T> name) throws IOException
<T> NetworkChannel setOption(SocketOption<T> name,T value) throws IOException
Set<SocketOption<?>> supportedOptions()
StandardSocketOptions类为Java能识别的11个选项提供了相应的常量。
例如:
NetworkChannel channel = SocketChannel.open();
channel.setOption(StandardSocketOptions.SO_LINGER,240);
11.5 就绪选择
为了完成就绪选择,要将不同的通道注册到一个Selector对象。每个通道分配有一个SelectionKey。然后程序可以询问这个Selector对象,那些通道已经准备就绪可以无阻塞地完成你希望完成的操作,可以请求Selector对象返回相应的键集合。
Selector类
创建新的选择器:
public static Selector open() throws IOException
向选择器增加通道。register()方法在SelectableChannel类中声明。通过将选择器传递给通道的一个注册方法,就可以向选择器注册这个通道:
public final SelectionKey register(Selector sel,int ops) throws ClosedChannelException
public final SelectionKey reigster(Selector sel,int ops, Object att) throws ClosedChannelException
第一个参数是通道要向哪个选择器注册。
第二个参数是SelectionKey类中的一个命名常量,标识通道所注册的操作。
第三个参数是可选的,这是键的附件。
有三个方法可以选择就绪的通道。它们的区别在于寻找就绪通道等待的时间。
完成非阻塞选择。如果当前没有准备好要处理的连接,它会立即返回。
public abstract int select() throws IOException
阻塞。在返回前等待,直到至少有一个注册的通道准备好可以进行处理。
public abstract int select(long timeout) throws IOException
阻塞,在返回0前只等待不超过timeout毫秒。
当有通道已经准备好处理时,可以使用selectedKeys()方法获取就绪通道:
public abstract Set<SelectionKey> selectedKeys()
迭代处理返回的集合时,要依次处理各个SelectionKey.
当准备关闭服务器或不再需要选择器时,应当将它关闭:
public abstract void close() throws IOException
释放与选择器关联的所有资源。
SelectionKey类
SelectionKey对象相当于通道的指针。
用channel()方法来获取这个通道:
public abstract SelectableChannel channel()
如果结束使用连接,就要撤销其SelectionKey对象的注册,这样选择器就不会浪费资源再去查询它是否准备就绪。调用这个键的cancel()方法来撤销注册:
public abstract void cancel()
- 第11章非阻塞I/O
- 第16章 非阻塞式I/O
- 非阻塞I/O
- 非阻塞I/O
- 非阻塞I/O
- 第8章 Linux设备驱动中的阻塞与非阻塞I/O
- linux驱动学习--第十四天:第八章 Linux 阻塞与非阻塞I/O
- 第十六章 非阻塞I/O
- 非阻塞文件I/O
- 非阻塞I/O简介
- 非阻塞I/O笔记
- 非阻塞I/O简介
- 83-非阻塞 I/O
- Unix网络编程代码 第16章 非阻塞式I/O
- UNIX网络编程卷1:套接字联网-第16章:非阻塞式I/O
- 阻塞与非阻塞I/O
- 阻塞与非阻塞I/O
- 阻塞与非阻塞I/O
- 截取部分图片并显示-ios例子[转载]
- 大图片中截取其中一部分小图显示 两图合成一图[转载]
- iOS代码搜索网站
- \"No previous prototype for function\" warning警告错误
- Please ensure that adb is correctly located 问题的解决
- 第11章非阻塞I/O
- 如何在程序中加载iAD广告[转]
- 如何在iphone-and-ipad应用程序添加admob
- 如何使用新浪微博的sdk
- iOS单例模式使用
- 腾讯微博sdk开发debug
- 腾讯微博分享 SDK开发流程
- iPad金融应用界面设计
- iOS home键直接退出