写在前面
Java NIO 由三个核心组成:Channels、Buffers、Selectors
Buffer是数据的载体, Channel里面如果有数据,通过 Channels.receive(Buffer) 接收, Channel里面需要数据,通过 Channels.send(Buffer,接收地址) 发送。
Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。
1、Buffers
Java NIO 有以下Buffer类型: ByteBuffer MappedByteBuffer CharBuffer DoubleBuffer FloatBuffer IntBuffer LongBuffer ShortBuffer
1.1、Buffer的方法
ByteBuffer buf = ByteBuffer.allocate(48);
buf.flip();
int bytesRead = channel.read(buf);
int bytesWritten = channel.write(buf);
buf.put(127);
byte aByte = buf.get();
buf.rewind();
buf.clear();
buf.compact();
buf.mark();
buf.reset();
buf.equals(buf1);
buf.compareTo(buf1);
1.2、Scatter/Gather
scatter:分散,从Channel中读取是指在读操作时将读取的数据写入多个buffer中。 gather:聚集,写入Channel是指在写操作时将多个buffer的数据写入同一个Channel。
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufs = { header, body };
channel.read(buffs);
Channel.write(buffs);
2、Channels
Channels有下面几个实现类:
实现类 | 用途 |
---|
FileChannel | 从文件中读写数据 | DatagramChannel | 通过UDP读写网络中的数据 | SocketChannel | 通过TCP读写网络中的数据 | ServerSocketChannel | 监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel |
2.1、FileChannel
FileChannel读取文件
2.1.1、从文件读取数据
RandomAccessFile aFile = new RandomAccessFile("1.txt", "rw");
FileChannel channel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = channel.read(buf);
while (bytesRead != -1) {
buf.flip();
while (buf.hasRemaining()) {
System.out.print((char) buf.get());
}
buf.clear();
bytesRead = channel.read(buf);
}
aFile.close();
}
2.1.2、将数据写入文件
transferFrom()方法可以将数据从源通道传输到FileChannel中
RandomAccessFile fromFile = new RandomAccessFile("1.txt", "rw");
FileChannel channel1 = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("2.txt", "rw");
FileChannel channel2 = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
channel1.transferFrom(position, count, channel2);
position=0表示从0处开始向目标文件写入数据,count表示最多传输的字节数。如果源通道的剩余空间小于计数 个字节,则所传输的字节数要小于请求的字节数。
要注意,在SoketChannel的实现中,SocketChannel只会传输此刻准备好的数据(可能不足计数字节)。因此,SocketChannel可能不会将请求的所有数据(count个字节)全部传输到FileChannel中。
transferTo()方法将数据从FileChannel传输到其他的channel中。
RandomAccessFile fromFile = new RandomAccessFile("1.txt", "rw");
FileChannel channel1 = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("2.txt", "rw");
FileChannel channel2 = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
channel1.transferTo(position, count, channel2);
2.1.3、FileChannel方法
long pos = channel.position();
channel.position(pos +123);
long fileSize = channel.size();
channel.truncate(1024);
channel.force(true);
2.2、SocketChannel
SocketChannelTCP网络套接字的通道
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://**.com", 8080));
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);
socketChannel.close();
写入数据
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://**.com", 8080));
ByteBuffer buf = ByteBuffer.allocate(48);
String data = "这是一串需要传递的文字";
buf.clear();
buf.put(data.getBytes());
buf.flip();
while(buf.hasRemaining()) {
channel.write(buf);
}
非阻塞模式
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("http://**.com", 8080));
while(! socketChannel.finishConnect() ){
}
非阻塞模式下,write()方法在尚未写出任何内容时可能就返回了。所以需要在循环中调用write()。 非阻塞模式下,read()方法在尚未读取到任何数据时可能就返回了。所以需要关注它的int返回值,它会告诉你读取了多少字节。
2.3、ServerSocketChannel
ServerSocketChannel 是一个可以监听新进来的TCP连接的通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(8000));
while(true){
SocketChannel socketChannel = serverSocketChannel.accept();
}
serverSocketChannel.accept();监听新进来的连接
while(true){
SocketChannel socketChannel = serverSocketChannel.accept();
}
2.4、DatagramChannel
DatagramChannel是一个能收发UDP包的通道。
接收数据
DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
channel.receive(buf);
发送数据
DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));
String data = "数据...";
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(data.getBytes());
buf.flip();
int bytesSent = channel.send(buf, new InetSocketAddress("127.0.0.1", 80));
连接到特定的地址
channel.connect(new InetSocketAddress("127.0.0.1", 80));
3、Selector
Selector可以用一个线程去管理多个channel
3.1、将channel注册到selector
Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, Selectionkey.OP_READ);
3.2、interest集合
register()方法的第二个参数,表示监听类型,是一个“interest集合”,有下列4种类型:
类型 | 说明 |
---|
SelectionKey.OP_CONNECT | 连接就绪 | SelectionKey.OP_ACCEPT | 接收就绪 | SelectionKey.OP_READ | 读就绪 | SelectionKey.OP_WRITE | 写就绪 |
对多个就绪状态监听:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
interest集合是你所选择的感兴趣的事件集合。可以通过SelectionKey读写interest集合
int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
3.3、SelectionKey
当向Selector注册Channel时,register()方法会返回一个SelectionKey对象。 selectionKey对象可调用的方法:
Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();
int interestSet = selectionKey.interestOps();
Object attachedObj = selectionKey.attachment();
int readySet = selectionKey.readyOps();
boolean b = selectionKey.isAcceptable();
boolean b = selectionKey.isConnectable();
boolean b = selectionKey.isReadable();
boolean b = selectionKey.isWritable();
下面是写入附加对象
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
sselector对象可调用的方法
selector.select();
selector.selectNow();
Set selectedKeys = selector.selectedKeys();
selector.wakeup();
selector.close();
遍历Set selectedKeys = selector.selectedKeys(); 这个已选择的键集合来访问就绪的通道
Set selectedKeys = selector.selectedKeys();
Iterator keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) {
} else if (key.isConnectable()) {
} else if (key.isReadable()) {
} else if (key.isWritable()) {
}
keyIterator.remove();
}
示例
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()) {
} else if (key.isConnectable()) {
} else if (key.isReadable()) {
} else if (key.isWritable()) {}
keyIterator.remove();
}
}
|