BIO
BIO 会导致线程逐渐增多,消耗cpu
BIO模型
通过线程池机制编写简单demon
@Slf4j
public class Server {
public static void main(String[] args) throws Exception {
//创建一个缓存线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//serverSocket
ServerSocket serverSocket = new ServerSocket(6666);
while (true){
final Socket socket = serverSocket.accept();//等待客户端进来
log.info("连接到一个线程");
// 加载到线程池里
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
handler(socket);
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
public static void handler(Socket socket) throws IOException {
// 接收数据
byte[] bytes = new byte[1024];
// 通过socket 获取输入流
InputStream inputStream = null;
int read = 0;
try {
inputStream = socket.getInputStream();
read = inputStream.read(bytes);
while (true){
if(read != -1 ){
log.info("获取到客户端数据的数据"+new String(bytes,0,read) );
}else{
break; // 退出
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
inputStream.close();
socket.close();
}
}
}
客户端如何证明 访问的时候是一个单独的一个线程?
通过输出线程的id 和名字
System.out.println("线程id"+Thread.currentThread().getId()+"名字"+Thread.currentThread().getName() );
当没有进入数据发送的时候 会默认产生阻塞效果 。 因此nio模型是事件驱动的。
bio jdk1.4之前独有。
客户端cmd 连接命令 telnet 127.0.0.1 6666
NIO同步非阻塞模型
同步非阻塞,服务器端是实现了一个线程进行多个处理,客户端发送的连接都会路由到多路复用上,多路复用在通过轮询对连接的请求进行io的读写操作。
什么是堆外内存和堆内内存?
堆内内存是被jvm管理的,堆外则不是。堆外是由操作系统管理的。
服务端产生一个selector 轮询器,把客户端的请求进行分发和处理,当客户端有事件发生的时候选择器会进行轮询获取。
nio 是jdk1.7以后,而且nio没有广泛应用,netty就是应用了NIO。
简单了解javaAIO就是NIO.2:异步非阻塞,对请求进行一个判断,连接使用较长的应用。 主要学习NIO
NIO 在1.4以后是同步非阻塞的,nio的类在java.nio.的包下
NIO3大核心
Channel(通道),Buffer(缓冲区),Selector(选择器)
在NIO里 selector 与 channel交互,channel与buffer交互,channel 是一个通道可以创建读写,buffer与客户端进行交互的一个场景。 通过buffer就可以实现非阻塞。一个selector 可以管理多个通道。
NIO面向缓冲或者面向块编程,数据读取到缓冲区,需要时在缓冲区去读取,从而实现同步非阻塞。
javaNio是非阻塞,如果没有数据可用的时候,就什么也不会获取,而不是保持阻塞的状态。
selector 当这个通道里没有消息执行的时候,就会去执行其他通道的消息,当其他通道也没有消息的时候,那么他就去执行其他任务。因此线程就是不会阻塞。
http2.0之后支持一个服务器去服务多个浏览器,做到一个连接 处理多个并发请求的情况。
selector 对应多个channel ,一个selector对应一个线程,一个channel 对应一个缓存buffer。
selector的切换根据channel通道的事件进行切换,(Event) ,selector 根据不同的事件在各个通道上切换。
buffer就是一个内存块, 底层是有一个数组的,在nio里数据的读取,数据的读写通过buffer和bio的io输入 输出是有去别的。但nio的buffer 是支持双向流动可以读也可以写的,需要bufffer.flip()方法来切换。其中channel 也是双向的非阻塞的。 channel 可以反应底层操作系统。其中linux的底层操作系统就是双向的。
buffer与channel是双向的,那NIO的是只能读或者只能写。 而且读写的时候必须经过buffer。
buffer 代码实现
public static void main(String[] args) {
// 创建一个缓存 支持存放3个 int
IntBuffer bufffer = IntBuffer.allocate(3);
//allocate.capacity() 容量
for (int i = 0; i < bufffer.capacity(); i++) {
bufffer.put(i*2);
}
// 读取数据 读写切换 当读或者写的时候 要用这个切换一下
bufffer.flip();
while (bufffer.hasRemaining()){
int i = bufffer.get();
System.out.println(i);
}
}
缓冲区基本介绍
缓冲区本质上是一个读写内存的块,可以理解成一个数组容器,能够对数据进行操控 。
buffer底层
ctrl+h 显示子类和方法
ctrl+n 查找所有
buffer 是一个抽象类,他的子类有IntBuffer,stringBuffer ,Charbuffer,LongBuffer,Bytebuffer
等,网络传输都是用byteBuffer,底层都有hb数组
buffer 定义的4下面四个属性是提供相关的数据元素的信息。
Capacity 一个容量 在缓冲区创建后 不可以改变
limit 是一个可以改变不能对缓冲区的极限进行读写
Postiion 就是当前的位置,下一个要被读或者写要发生变化的数据 ,为其做准备。
Mark 是一个标记
Channel 通道
channel是一个接口,然后channel是可以对数据进行读写的,而bio只能单向的,其中channel的主要实现类有
FileChannel ,DatagramChannel,ServersocketChannel,SocketChannel 。
FileChannel :用于文件的数据读写
DatagramChannel :用于UDP
ServersocketChannel、SocketChannel :做网络的TCP,ServersocketChannel 类似ServerSocket ,SocketChannel 类似于Socket
当连接进入的时候 先去寻找主线程,ServerSocketChannel 会生成一个SocketChannel
FileChannel demon
这里面 在缓冲区写入管道后要有一个缓存buffer.flip() 进行反转;
public static void main(String[] args) throws Exception{
// 一个汉字3个字节
String str ="jhha";
// 创建输出流管道
FileOutputStream fileOutputStream = new FileOutputStream("D://111.txt");
// 得到 Channel
FileChannel channel = fileOutputStream.getChannel();
//创建 缓存buffer
ByteBuffer allocate = ByteBuffer.allocate(1024);
// 将数据加入缓存中
allocate.put(str.getBytes());
// 反转
allocate.flip();
// 写入管道
channel.write(allocate);
fileOutputStream.close();
}
读取文件流
public static void main(String[] args) throws IOException {
File file = new File("D://111.txt");
FileInputStream fileInputStream = new FileInputStream(file);
FileChannel channel = fileInputStream.getChannel();
ByteBuffer byteBuffer =ByteBuffer.allocate((int) file.length());
int read = channel.read(byteBuffer);
// 把byte buffer 字节转换数组
System.out.println(new String(byteBuffer.array()));
fileInputStream.close();
}
BIO拷贝文件
public static void main(String[] args) throws Exception{
FileInputStream fileInputStream = new FileInputStream("1.txt");
FileChannel channel = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
FileChannel channel1 = fileOutputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true){
//先复位
buffer.clear();
int read = channel.read(buffer);
if (read == -1) break;
buffer.flip();
channel1.write(buffer);
}
channel.close();
channel1.close();
}
transferFrom实现拷贝功能
public static void main(String[] args)throws Exception {
// c创建流
FileInputStream in = new FileInputStream("D://1.JPG");
FileOutputStream out = new FileOutputStream("D://A2.JPG");
FileChannel inChannel = in.getChannel();
FileChannel outChannel = out.getChannel();
// 使用transferFrom
outChannel.transferFrom(inChannel,0,inChannel.size());
// 关闭相关通道和流
inChannel.close();
outChannel.close();
in.close();
out.close();
}
buffer与channel
1 buffer 中 ByteBuffer 放入什么类型,那么取出的时候就要取出什么类型 。 如果不按顺序取出 会发生异常。
2 可以将buffer 转换成一个只读buffer buffer.asReadOnlyBuffer 类型是HeapReadBuffer 是只读的意思
ByteBuffer readOnlyBuffer = byteBuffer.asReadOnlyBuffer();
3 nio提供了MappedByteBuffer,可以让文件在内存中进行修改。
4 nio 支持多个buffer 进行Scattering和Gathering 。
channel 与channel 如何进行通信
在FileInputStream通过 outChannel.transferFrom(inChannel,0,inChannel.size());
NIO 之Selector
selector 基本介绍
selector 由一个线程创建,实现同步非阻塞的模型,可以管理多个客户端的链接 。
1 selector 可以管理通道 ,并且检测是否有事件发生 。 也就是多个Channel 发生事件的时候 selector是可以监听到的。
2 只有在连接通道真正有事件发生啥的时候 才会进行读写,这样提高了性能的使用。
3 避免了频繁的多线程切换。
selector 特点
1 selector 可以处理成千上百的链接
2 空闲的时候可以做别的事情,哪个io有事件就去处理哪个。
3 读写操作本身是带有缓冲的操作。,避免了io频繁访问,导致挂起。
selector类和 方法
Selector 是一个抽象类
public abstract class Selector implements Closeable { }
常用方法有
1 open()得到一个选择器 创建一个选择器
2 select () 这个是调用的阻塞方法,当通道没有事件发生的时候 一直处于阻塞状态,当有一个以上的事件发生则将 selectKey 加入到内部集合队列中,之后在通过内部集合队列去把对应发生的channel管道对应数据取出来。
3 select(Long time) long time 是加入阻塞的时间,例如传入1秒那么则堵塞一秒钟
4 selectNow()直接获取当前堵塞的事件
5 selectedKeys() 查看所有管道的注册的seletorkey
6 wakeup() 唤醒在阻塞的。
NIO非阻塞网络编程
1 selector 注册之后 serverSocketChannel分配一个socketChannel
2 创建一个管道后同时在selector选择器内注册 交由selector管理
3 当管道有读写发生的时候selector 会监听到,得到一个selectKey
4 selector通过selectKey去得到对应管道的数据
BIO NIO AIO 场景 ?
bio是一个传统连接数较小,比较固定,在jdk1.4之前都是bio。易于了解
NIO连接数比较多,弹幕,聊天系统等,连接时间较短。
aio 连接时间较长,数目较多。
clear() : 情况buffer 进行复位,如果不复位的话 读取到最后 position 与limit 就想等,那么相等则就无数据 导致read 是 0 ,所以没有机会read =-1 就会导致死循环 。
代码实现 业务逻辑梳理服务端
服务端
1 先创建一个ServerSocketChannel
2 设置为 非阻塞状态
3 创建一个selector 选择器
4注册 serverSocketeChannel 加入到selector 选择器
5 创建端口号
6 轮询监听
7 监听事件 selector阻塞时间判断
8 创建socket通道
9注册
10 设置非阻塞
客户端
1 serverSocket 通道
2 添加链接端口
3 发送数据
服务端代码
@Slf4j
public class NioServer {
public static void main(String[] args) throws Exception {
// 创建 ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 创建 一个selector
Selector selector = Selector.open();
// 设置非阻塞
serverSocketChannel.configureBlocking(false);
// 注册
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
// 绑定端口号
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
while (true){
if(selector.select(1000) == 0){
log.info("服务器等待了一秒,无连接");
continue;
}
// 如果返回大于0 ,那么获取对应管道的key
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> keys = selectionKeys.iterator();
while (keys.hasNext()){
SelectionKey key = keys.next();
// 根据key 对应的通道事件
// 1 有新的客户端链接 产生新的通道 ,分配给客户端
if( key.isAcceptable()){
// 生成一个socketChannel
SocketChannel channel = serverSocketChannel.accept();
channel.configureBlocking(false);
// 关注读事件
channel.register(selector,SelectionKey.OP_READ,ByteBuffer.allocate(1024));
}
if(key.isReadable()){
// 得到通道
SocketChannel channel =(SocketChannel)key.channel();
// 获取当前的buffer
ByteBuffer byteBuffer =(ByteBuffer) key.attachment();
// 读入 buffer
channel.read(byteBuffer);
log.info("客户端读入数据"+new String(byteBuffer.array()));
}
//移除当前key
keys.remove();
}
}
}
}
客户端代码
public class NIOClient {
public static void main(String[] args) throws Exception {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
if(!socketChannel.connect(inetSocketAddress)){
while (!socketChannel.finishConnect()){
System.out.println("等待链接中");
}
}
String str = "您好!";
ByteBuffer wrap = ByteBuffer.wrap(str.getBytes());
int write = socketChannel.write(wrap);
//让代码停在这里
System.in.read();
}
}