0≤mark≤position≤limit ≤capacity
Method | Prepares Buffer for | Position | Limit | Mark |
---|---|---|---|---|
ByteBuffer.clear() | read()/put() into buffer | 0 | capacity | undefined |
ByteBuffer.flip() | write()/get() from buffer | 0 | position | undefined |
ByteBuffer.rewind() | rewrite()/get() from buffer | 0 | unchanged | undefined |
ByteBuffer.compact():为了后续的put()/read()调用让出空间,所以将position与limit之间的元素复制到缓冲区的开始位置,position设为要复制的数据的长度,limit设为capacity,mark设为未定义
while (channel.read(buffer) != -1) {
buffer.flip();
channel.write(buffer);
buffer.compact();
}
while (buffer.hasRemaining())
channel.write(buffer);
ByteBuffer.duplicate():创建一个与原始缓冲区共享内容的新缓冲区.新缓冲区的position,limit,capacity,mark都初始化为原始缓冲区的索引值.新旧缓冲区的索引都是独立的.
Method | Capacity | Position | Limit | Mark |
---|---|---|---|---|
ByteBuffer duplicate() | capacity | position | limit | mark |
ByteBuffer slice() | remaining() | 0 | remaining() | undefined |
ByteBuffer asReadOnlyBuffer() | capacity | position | limit | mark |
CharBuffer asCharBuffer() | remaining()/2 | 0 | remaining()/2 | undefined |
DoubleBuffer asDoubleBuffer() | remaining()/8 | 0 | remaining()/8 | undefined |
FloatBuffer asFloatBuffer() | remaining()/4 | 0 | remaining()/4 | undefined |
IntBuffer asIntBuffer() | remaining()/4 | 0 | remaining()/4 | undefined |
LongBuffer asLongBuffer() | remaining()/8 | 0 | remaining()/8 | undefined |
ShortBuffer asShortBuffer() | remaining()/2 | 0 | remaining()/2 | undefined |
一. 创建一个Selector实例.
二. 将其注册到各种信道,指定每个信道上感兴趣的I/O操作.
三. 重复执行:
1. 调用一种select方法
2. 获取选取的键列表
3. 对于已选键集中的每个键.
a. 获取信道,并从键中获取附件(如果合适的话)
b.确定准备就绪的操作并执行.如果是accept操作,将接受的信道设置为非阻塞模式,并将其与选择器注册
c.如果需要,修改键的兴趣操作集
d.从已选键集中移除键
TCP例子:
服务端:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
public class TCPServerSelector {
private static final int BUFSIZE = 256; // Buffer size (bytes)
private static final int TIMEOUT = 3000; // Wait timeout (milliseconds)
public static void main(String[] args) throws IOException {
if (args.length < 1) { // Test for correct # of args
throw new IllegalArgumentException("Parameter(s): <Port> ...");
}
//创建一个selector来多路复用监听与连接
Selector selector = Selector.open();
//为每个端口创建监听socketChannel,并注册selector
for (String arg : args) {
ServerSocketChannel listnChannel = ServerSocketChannel.open();
listnChannel.socket().bind(new InetSocketAddress(Integer.parseInt(arg)));
listnChannel.configureBlocking(false); //必须配置为非阻塞才能注册selector
listnChannel.register(selector, SelectionKey.OP_ACCEPT);//注册selector
}
// Create a handler that will implement the protocol
TCPProtocol protocol = new EchoSelectorProtocol(BUFSIZE);
while (true) { //循环处理可用的I/O操作
// Wait for some channel to be ready (or timeout)
if (selector.select(TIMEOUT) == 0) { //方法是阻塞的,返回准备好的信道数
System.out.print(".");
continue;
}
//获取所选择的键集进行处理
Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
while (keyIter.hasNext()) {
SelectionKey key = keyIter.next(); // Key is bit mask
// Server socket channel有挂起的连接请求?
if (key.isAcceptable()) {
protocol.handleAccept(key);
}
// Client socket channel有挂起的数据?
if (key.isReadable()) {
protocol.handleRead(key);
}
// Client socket channel可用写操作,并且key是有效的(例如,信道没有关闭)?
if (key.isValid() && key.isWritable()) {
protocol.handleWrite(key);
}
keyIter.remove(); //从选择键集中移除
}
}
}
}
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class EchoSelectorProtocol implements TCPProtocol {
private int bufSize; // Size of I/O buffer
public EchoSelectorProtocol(int bufSize) {
this.bufSize = bufSize;
}
public void handleAccept(SelectionKey key) throws IOException {
SocketChannel clntChan = ((ServerSocketChannel) key.channel()).accept();//从键中取出信道,这个就是Client socket channel
clntChan.configureBlocking(false); //必须配置为非阻塞
clntChan.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufSize));//使用新信道注册选择器,用于读,并附加字节缓冲
}
public void handleRead(SelectionKey key) throws IOException {
// Client socket channel已挂起数据
SocketChannel clntChan = (SocketChannel) key.channel();
ByteBuffer buf = (ByteBuffer) key.attachment();
long bytesRead = clntChan.read(buf);
if (bytesRead == -1) { // Did the other end close?
clntChan.close();
} else if (bytesRead > 0) {
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);//通过读/写都是感兴趣的key来表明
}
}
public void handleWrite(SelectionKey key) throws IOException {
//Channel is available for writing, and key is valid (i.e., client channel not closed).
ByteBuffer buf = (ByteBuffer) key.attachment();//返回之前读到的数据
buf.flip(); //为写操作准备缓冲
SocketChannel clntChan = (SocketChannel) key.channel();
clntChan.write(buf);
if (!buf.hasRemaining()) { //缓冲完全写出去了?
key.interestOps(SelectionKey.OP_READ);//没有什么可做的,所以不再对写感兴趣
}
buf.compact(); //压缩缓冲:为下次读入更多的数据腾出空间
}
}
客户端:
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class TCPEchoClientNonblocking {
public static void main(String args[]) throws Exception {
//创建信道并设为非阻塞
SocketChannel clntChan = SocketChannel.open();
clntChan.configureBlocking(false);
//发起连接到服务端并重复轮询直至完成
if (!clntChan.connect(new InetSocketAddress("127.0.0.1", 9000))) {
while (!clntChan.finishConnect()) {//轮询连接状态,如果返回前建立了连接,返回true,否则返回false
System.out.print("."); //做些其它事情
}
}
byte[] argument = "sendData".getBytes();
ByteBuffer writeBuf = ByteBuffer.wrap(argument);//使用已有字节数组创建写缓冲
ByteBuffer readBuf = ByteBuffer.allocate(argument.length);//分配固定长度读缓冲
int totalBytesRcvd = 0; //已接收的总字节数
int bytesRcvd; //最后一次读到的字节数
while (totalBytesRcvd < argument.length) {
if (writeBuf.hasRemaining()) {//只要缓冲区还有数据
clntChan.write(writeBuf);//就调用write()
}
if ((bytesRcvd = clntChan.read(readBuf)) == -1) {//read方法不是阻塞的,但没有数据可读时,返回0
throw new SocketException("Connection closed prematurely");
}
totalBytesRcvd += bytesRcvd;
System.out.print("."); //做些其它事情
}
System.out.println("Received: " + new String(readBuf.array(), 0, totalBytesRcvd));//打印接收到的数据
clntChan.close();//关闭信道
}
}