non-blocking io 非阻塞IO
1.三大组件
1.1Channel&Buffer
Channel:双向通道,可读可写 Buffer:缓冲区
1.2 Selector
来1000个socket,要1000个线程服务。损耗太大。
阻塞模式的含义:thread处理socket1的时候就不能处理socket3(eg:服务器服务第一个客人点菜,买单的过程不能服务下一个客人)
所以适合短连接,早点处理完去服务下一个线程
selector版设计
selector监视这些channel要求,有人有处理需求就去处理
和线程池版的区别就是线程不会吊死在一个连接上,可以服务多个请求,线程利用率就提高了很多。
所以适合连接数多,但是流量低 的情况,一个channel流量多的话就不能让线程很好的服务其他的channel。
2.ByteBuffer
public class TestByteBuffer {
@Test
public void test(){
//FileChannel
//1.输入输出流 2.RandomAccessFile
try(FileChannel channel = new FileInputStream("data").getChannel()){
//读取需要一个缓冲区
ByteBuffer buffer = ByteBuffer.allocate(10);
while(true){
//从channel读取数据,向buffer写入
int len = channel.read(buffer);
//Log.debug("读取到的字节数{}",len);
if(len==-1){//没有内容了
break;
}
//打印buffer内容
buffer.flip();//切换至读模式
while(buffer.hasRemaining()){//是否还有剩余未读数据
byte b = buffer.get();
System.out.println((char) b);
}
buffer.clear();//切换为写模式
}
}catch (IOException ex){
System.out.println(ex.toString());
}
}
}
2.2ByteBuffer结构
capacity:容量
position:索引 写入位置/读取位置
limit:写入/读取多少内容
调用flip会改两个指针的位置,position从写位置转到读的位置,limit从写的限制变为读的限制
compact:没读完但是现在要马上写,compact会把后面没读的移到起始位置,写只能从没读那些位置后面开始写。
package com.netty.demo;
import org.junit.jupiter.api.Test;
import java.nio.ByteBuffer;
import static com.netty.demo.ByteBufferUtil.debugAll;
public class TestBufferReadWrite {
@Test
public static void main(String[] args){
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put((byte)0x61);//'a'
debugAll(buffer);
}
}
显示限制是10,现在可以从位置1开始写
如果不切换读模式,直接读,那么会直接读当前位置也就是position=1,读到的会是0
package com.netty.demo;
import org.junit.jupiter.api.Test;
import java.nio.ByteBuffer;
import static com.netty.demo.ByteBufferUtil.debugAll;
public class TestBufferReadWrite {
@Test
public static void main(String[] args){
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put((byte)0x61);//'a'
debugAll(buffer);
System.out.println( buffer.get());
buffer.flip();
System.out.println(buffer.get());
}
}
2.3ByteBuffer常见方法
put,get,clean,字符串与bytebuffer几种转换方法
2.4 Scattering Reads
分散读集中写
粘包半包分析
package com.netty.demo;
import org.junit.jupiter.api.Test;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Random;
import static com.netty.demo.ByteBufferUtil.debugAll;
public class TestScatteringReads {
private static void split(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);
//从source读,向target写
for(int j=0;j<length;j++){
target.put(source.get());
}
debugAll(target);
}
}
source.compact();//第一次未读的部分向前移动
}
@Test
public static void main(String[] args){
ByteBuffer source = ByteBuffer.allocate(32);
source.put("hello,world\nI'm zhangsan\nHo".getBytes());
split(source);
source.put("w are you?\n".getBytes());
split(source);
}
}
3. 文件编程
3.1 FileChannel
FileChannel只能工作在阻塞模式下
data中文件写到to文件
package com.netty.demo;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
public class TestFileChannelTransferTo {
@Test
public static void main(String[] args){
try{
FileChannel from = new FileInputStream("data").getChannel();
FileChannel to = new FileOutputStream("to").getChannel();
//效率高,底层会使用操作系统的零拷贝进行优化
from.transferTo(0,from.size(),to);
}catch (Exception ex){
}
}
}
3.2 传输数据大于2g时,
可以多次使用FileChannel进行传输,
package com.netty.demo;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
public class TestFileChannelTransferTo {
@Test
public static void main(String[] args){
try{
FileChannel from = new FileInputStream("data").getChannel();
FileChannel to = new FileOutputStream("to").getChannel();
//效率高,底层会使用操作系统的零拷贝进行优化
long size = from.size();
//left表示每次传输完剩余的字节数量
for(long left = size;left>0;){
left-=from.transferTo(size-left,size,to);
}
from.transferTo(0,from.size(),to);
}catch (Exception ex){
}
}
}
3.3 Path
3.4 Files
遍历文件目录
package com.netty.demo;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.atomic.AtomicInteger;
public class TestWakFileTree {
@Test
public static void main(String[] args) throws IOException {
Path path = Paths.get("D:\\many tools\\JDK");
// 文件目录数目
AtomicInteger dirCount = new AtomicInteger();
// 文件数目
AtomicInteger fileCount = new AtomicInteger();
Files.walkFileTree(path, new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
System.out.println("===>"+dir);
// 增加文件目录数
dirCount.incrementAndGet();
return super.preVisitDirectory(dir, attrs);
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println(file);
// 增加文件数
fileCount.incrementAndGet();
return super.visitFile(file, attrs);
}
});
// 打印数目
System.out.println("文件目录数:"+dirCount.get());
System.out.println("文件数:"+fileCount.get());
}
}
4. 网络编程
package com.netty.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;
import static com.netty.demo.ByteBufferUtil.debugRead;
public class Server {
private static Logger logger = LoggerFactory.getLogger(Server.class);
public static void main(String[] args) throws IOException {
//使用nio来理解阻塞模式,单线程
ByteBuffer buffer = ByteBuffer.allocate(16);
//1.创建了服务器
ServerSocketChannel ssc = ServerSocketChannel.open();
//2.绑定监听端口
ssc.bind(new InetSocketAddress(8080));
//3.连接集合
List<SocketChannel> channels = new ArrayList<>();
while(true){
//4.accept建立与客户端连接 SocketChannel用来与客户端通信
logger.debug("connecting...");
SocketChannel sc = ssc.accept();//accept是阻塞方法,会让线程暂停
logger.debug("connection...{}",sc);
channels.add(sc);
for(SocketChannel channel:channels){
//5.接受客户端发送的数据
logger.debug("before read... {}",channel);
channel.read(buffer);
buffer.flip();
debugRead(buffer);
buffer.clear();
logger.debug("after read...{}",channel);
}
}
}
}
package com.netty.demo;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
public class Client {
public static void main(String[] args) throws IOException {
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress("localhost",8080));
System.out.println("waiting...");
}
}
处理消息的边界
服务器一次发送过多数据
服务端代码:
package com.netty.demo;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
public class WriteServer {
public static void main(String[] args) throws IOException {
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
Selector selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
ssc.bind(new InetSocketAddress(8080));
while(true){
// 若没有事件就绪,线程会被阻塞,反之不会被阻塞。从而避免了CPU空转
selector.select();
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while (iter.hasNext()){
SelectionKey key = iter.next();
iter.remove();
if(key.isAcceptable()){
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
//1.向客户端发送大量数据
StringBuilder sb = new StringBuilder();
for(int i=0;i<30000000;i++){
sb.append("a");
}
ByteBuffer buffer = Charset.defaultCharset().encode(sb.toString());
while (buffer.hasRemaining()){
//2.返回值代表实际写入的字节数
int write = sc.write(buffer);
System.out.println(write);
}
}
}
}
}
}
客户端代码:
package com.netty.demo;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class WriteClient {
public static void main(String[] args) throws IOException{
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress("localhost",8080));
//3.接收数据
int count=0;
while (true){
ByteBuffer buffer = ByteBuffer.allocate(1024*1024);
count+=sc.read(buffer);
System.out.println(count);
buffer.clear();
}
}
}
客户端分多次接收,但是服务端需要一直轮询,查询缓冲区是不是满的,还能不能继续写进数据
下面用可写事件来处理,避免轮询
package com.netty.demo;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
public class WriteServer {
public static void main(String[] args) throws IOException {
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
Selector selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
ssc.bind(new InetSocketAddress(8080));
while(true){
// 若没有事件就绪,线程会被阻塞,反之不会被阻塞。从而避免了CPU空转
selector.select();
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while (iter.hasNext()){
SelectionKey key = iter.next();
iter.remove();
if(key.isAcceptable()){
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
SelectionKey scKey = sc.register(selector,0,null);
//1.向客户端发送大量数据
StringBuilder sb = new StringBuilder();
for(int i=0;i<30000000;i++){
sb.append("a");
}
ByteBuffer buffer = Charset.defaultCharset().encode(sb.toString());
//2.返回值代表实际写入的字节数
int write = sc.write(buffer);
System.out.println(write);
//3.判断是否有剩余内容
if (buffer.hasRemaining()){
//4.关注可写事件
//scKey可能原来关注了其他事件,比如读事件
//不想覆盖掉他,就+scKey.interestOps()
scKey.interestOps(scKey.interestOps()+SelectionKey.OP_WRITE);
//5.把未写完的数据挂在scKey上
scKey.attach(buffer);
}
}else if(key.isWritable()){
ByteBuffer buffer = (ByteBuffer) key.attachment();
SocketChannel sc = (SocketChannel) key.channel();
int write = sc.write(buffer);
System.out.println(write);
//6.清理一下,写完了buffer一直挂着,消耗大
if(!buffer.hasRemaining()){
key.attach(null);
key.interestOps(key.interestOps()-SelectionKey.OP_WRITE);
}
}
}
}
}
}
小结: