NIO翻译
non-blocking IO 非阻塞IO
new IO 新的IO
NIO三大组件
channel 数据传输通道
常用channel
java.nio.channels.FileChannel
java.nio.channels.SocketChannel
buffer 缓冲区
常用buffer
java.nio.ByteBuffer
java.nio.HeapByteBuffer
java.nio.DirectByteBuffer
使用channel和ByteBuffer读取文件
@Test
public void readAllFile() {
// 获取文件流
try (FileChannel channel = new FileInputStream("a.txt").getChannel()) {
// 设置缓冲区
// java堆内存,受GC影响 java.nio.HeapByteBuffer
// ByteBuffer buffer = ByteBuffer.allocate(10);
// 直接内存,读写效率高,减少一次copy,分配效率低 java.nio.DirectByteBuffer
ByteBuffer buffer = ByteBuffer.allocateDirect(10);
while (true) {
// 从channel读取数据,写入到buffer中,使用read或者put
int len = channel.read(buffer);
if (len == -1) {
break;
}
// 切换至读模式
buffer.flip();
while (buffer.hasRemaining()) {
// 或者使用channel.write读取数据,get后position后移可以使用rewind将position置0
byte b = buffer.get();
byte[] byteArray = {b};
log.info(new String(byteArray, StandardCharsets.UTF_8));
}
// 清空buffer
buffer.clear();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
ByteBuffer常用属性
- 容量capacity
- 写入位置Position
- 限制limit
mark(设置标记)reset(将position重置到mark位置)hasRemaining(是否有剩余的空间)
黏包处理案例
@Test
public void stickyPacket(){
ByteBuffer source = ByteBuffer.allocate(60);
source.put("微信小程序\n马上行计划管理\n欢".getBytes(StandardCharsets.UTF_8));
reduce(source);
source.put("迎体验\n".getBytes(StandardCharsets.UTF_8));
reduce(source);
}
public static void reduce(ByteBuffer source){
source.flip();
for (int i = 0; i < source.limit(); i++) {
if (source.get(i) == '\n') {
int length = i+1-source.position();
ByteBuffer target = ByteBuffer.allocate(length);
for (int j = 0; j < length; j++) {
target.put(source.get());
}
target.flip();
log.info(StandardCharsets.UTF_8.decode(target).toString());
}
}
source.compact();
}
selector
java.nio.channels.Selector管理channel。
selector事件发生时机
- 客户端发起请求时,触发accept事件
- 客户端发送数据进入服务器,客户端正常异常关闭时,都会触发read事件,发送数据大于buffer缓冲区,会触发多次读取事件。
- channel可写,会触发write事件
- linux下niobug发生时
使用selector实现简单连接写入读取。
// 服务端
@Test
public void noBlockServer() throws IOException {
// 创建选择器
Selector selector = Selector.open();
// 创建服务器
ServerSocketChannel ssc = ServerSocketChannel.open();
// 绑定端口
ssc.bind(new InetSocketAddress(8089));
ssc.configureBlocking(false);
// 注册到选择器
SelectionKey sscKey = ssc.register(selector, 0, null);
// 关注事件
sscKey.interestOps(SelectionKey.OP_ACCEPT);
while(true){
// 没有事件就阻塞,有未处理的事件时不会阻塞
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()){
ServerSocketChannel channel = (ServerSocketChannel)key.channel();
// 连接
SocketChannel accept = channel.accept();
// 设置非阻塞
accept.configureBlocking(false);
// 注册,同时添加缓存每个通道都使用自己单独的缓存
// 读和写的attach需要区分
ChannelAttachment channelAttachment = new ChannelAttachment();
ByteBuffer buffer = ByteBuffer.allocate(8);
channelAttachment.setReadBuffer(buffer);
SelectionKey acceptKey = accept.register(selector, 0, channelAttachment);
// 只关注读事件
acceptKey.interestOps(SelectionKey.OP_READ);
// 向客户端发送信息
// StringBuilder stringBuilder = new StringBuilder();
// for (int i = 0; i < 19000; i++) {
// stringBuilder.append("欢迎使用马上行计划管理");
// }
ByteBuffer writeBuffer = StandardCharsets.UTF_8.encode("欢迎使用马上行计划管理");
int write = accept.write(writeBuffer);
log.info("写入字节"+write);
// 如果没有发送完关注写事件
if (writeBuffer.hasRemaining()){
acceptKey.interestOps(acceptKey.interestOps()+SelectionKey.OP_WRITE);
ChannelAttachment attachment = (ChannelAttachment) acceptKey.attachment();
attachment.setWriteBuffer(writeBuffer);
// acceptKey.attach(attachment);
}
}else if (key.isReadable()){
try {
SocketChannel readChannel = (SocketChannel)key.channel();
ChannelAttachment attachment = (ChannelAttachment) key.attachment();
ByteBuffer buffer = attachment.getReadBuffer();
int read = readChannel.read(buffer);
if (read>0){
reduce(buffer);
// buffer不足自动扩容
if (buffer.position()==buffer.limit()){
ByteBuffer newByteBuffer = ByteBuffer.allocate(buffer.capacity()*2);
buffer.flip();
newByteBuffer.put(buffer);
key.attach(newByteBuffer);
}
}else if (read==-1){
log.info("自动断开连接");
key.cancel();
}
}catch (IOException e){
// 移除断开的key
key.cancel();
log.error(e.getMessage(),e);
}
}else if (key.isWritable()){
ChannelAttachment attachmentChannel = (ChannelAttachment) key.attachment();
ByteBuffer attachment = attachmentChannel.getWriteBuffer();
SocketChannel channel = (SocketChannel)key.channel();
int write = channel.write(attachment);
log.info("写入字节"+write);
if (!attachment.hasRemaining()) {
key.interestOps(key.interestOps()-SelectionKey.OP_WRITE);
attachmentChannel.setWriteBuffer(null);
key.attach(attachmentChannel);
}
}
}
}
}
public static void reduce(ByteBuffer source){
source.flip();
for (int i = 0; i < source.limit(); i++) {
if (source.get(i) == '\n') {
int length = i+1-source.position();
ByteBuffer target = ByteBuffer.allocate(length);
for (int j = 0; j < length; j++) {
target.put(source.get());
}
target.flip();
log.info(StandardCharsets.UTF_8.decode(target).toString());
}
}
source.compact();
}
@Data
class ChannelAttachment {
private ByteBuffer readBuffer;
private ByteBuffer writeBuffer;
}
// 客户端读取
@Test
public void blockClientReceive() throws IOException {
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress("localhost",8089));
log.info("等待连接");
int count = 0;
while(true){
ByteBuffer buffer = ByteBuffer.allocate(8);
count += sc.read(buffer);
buffer.clear();
log.info(count+"");
}
}
// 客户端写入
@Test
public void blockClient() throws IOException {
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress("localhost",8089));
log.info("等待连接");
sc.write( StandardCharsets.UTF_8.encode("马上行计划管理2,马上行计划管理3,马上行计划管理4,马上行计划管理5\n"));
log.info("success");
sc.close();
}