NIO
为什么会有 Netty? 为了解决什么问题? 怎么用??
高性能体现在哪里?? 怎么就高效了??? 底层的通过网络的传播过程没有发生改变吧。
Netty 用于通信,比如RPC远程调用。
IO模型:
BIO: 同步阻塞
NIO: 同步非阻塞 用到多路复用器
AIO: 异步非阻塞
BIO代码:
有两个地方会发生阻塞: 一个是在accept 阻塞等待客户端连接这里
一个是 inputStream.read() 阻塞等到接收客户端传来数据这里。(如果客户端不发来数据,那么这个线程就会一直在这里阻塞。)
Bio的 缺点 很显然,就是对于每一个的客户端,都有一个专门的线程来进行处理,客户端较多时,服务端扛不住;
即使客户端没有消息要发送时,服务端这里的这个线程还是要进行阻塞接收消息,造成资源的浪费;
import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class BioServerTest { public static void main(String[] args) throws IOException { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(8,10,1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(5)); ServerSocket ss = new ServerSocket(7777); while(true){ System.out.println("线程"+Thread.currentThread().getName()+"在阻塞等待连接"); Socket socket = ss.accept(); System.out.println("线程"+Thread.currentThread().getName()+"接收了新的客户端连接"); threadPoolExecutor.execute(()-> { try { handler(socket); } catch (IOException e) { e.printStackTrace(); } }); } } static void handler(Socket socket) throws IOException { InputStream inputStream = socket.getInputStream(); byte[]bytes = new byte[1024]; int len = -1; System.out.println("线程"+Thread.currentThread().getName()+"在等待接收数据"); while((len=inputStream.read(bytes))!=-1){ System.out.println("线程"+Thread.currentThread().getName()+"接收到数据:"); System.out.println(new String(bytes,0,len)); } System.out.println("线程"+Thread.currentThread().getName()+"处理完数据"); socket.close(); } }
NIO: Buffer Channel Selector
Buffer其实就是一种数据结构。 底层就是一个数组,然后对这个数组进行操作,仅此而已。
常用的方法: allocate put get flip
四个属性: position limit capacity mark
HeapByteBuffer: 分配堆内存 (jvm的堆内存)
DirectByteBuffer: 堆外内存? (直接用的系统的内存 与jvm无关)
Buffer也可以使用数组的形式 来进行 输出的输出 和 读取。
Channel : ServerSocketChannel SocketChannel FileChannel
FileChannel 常用的方法: read write transferTo transferFrom
Selector: 用一个线程来管理多个 Channel
NIO 服务端代码:
public class NioServerTest { public static void main(String[] args) throws IOException { // 创建服务端 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 绑定端口 serverSocketChannel.bind(new InetSocketAddress(7777)); // 设置为非阻塞 (这是什么意思?? 什么被设置为了非阻塞) serverSocketChannel.configureBlocking(false); // 创建Selector Selector selector = Selector.open(); // 将通道注册到Selector serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while(true){ // 监听事件 if(selector.select(1000)==0){ continue; } System.out.println("监听到事件》》》》》"); // 当Selctor中监听到事件后,获取所有的事件 Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectionKeys.iterator(); while(keyIterator.hasNext()){ SelectionKey selectionkey = keyIterator.next(); // 如果是连接事件 if(selectionkey.isAcceptable()){ // 获取SocketChannel SocketChannel socketChannel = serverSocketChannel.accept(); // 设置为非阻塞 socketChannel.configureBlocking(false); // 注册到Selector中 socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024)); }else if(selectionkey.isReadable()){ // 获取到对应的 SocketChannel 强转为子类 这是因为它本就是 子类,然后被强制转为了父类,又转为了子类, 所以没报错
// 如果直接将父类转为字类是会报错的
SocketChannel channel = (SocketChannel)selectionkey.channel(); ByteBuffer byteBuffer = (ByteBuffer) selectionkey.attachment(); // 从通道中读取数据到 Buffer中 channel.read(byteBuffer); byteBuffer.flip(); // 输出数据 System.out.println(new String(byteBuffer.array())); } // 移除事件 keyIterator.remove(); } } } }
NIO客户端代码:
public class NioClientTest { public static void main(String[] args) throws IOException { // 获取客户端 SocketChannel socketChannel = SocketChannel.open(); // 与服务端建立连接 socketChannel.connect(new InetSocketAddress("127.0.0.1",7777)); // String str = "Hello Nio"; ByteBuffer wrap = ByteBuffer.wrap(str.getBytes()); // 发送数据 socketChannel.write(wrap); System.in.read(); } }
NIO的服务端还是要主动去判断是否有事件发生了没有。 是不是???
相对于传统的IO, 它的优点就是 用一个线程来处理多个客户端的连接,通过Selctor,(底层用到的是io多路复用,epoll ).
传统io的阻塞,一个是阻塞等待客户端的连接;一个是阻塞接收客户端的消息。 (为什么会阻塞? 就是因为不知道客户端什么时候会连接,不知道什么时候会发送数据)
NIO: 通过事件驱动, 不会阻塞等待客户端的连接和阻塞接收客户端的消息。通过事件机制,当事件发生后,就会执行相应的操作。但是需要一直去查询是否有事件发生。?
SelectionKey: 表示Chnnel 注册到Selctor时的注册的关系。
读写操作是非阻塞的,靠的是底层的缓冲(缓存)来实现的。
群聊系统:
服务端:
public class GroupChatServerTest { private ServerSocketChannel serverSocketChannel= null; private Selector selector ; public static void main(String[] args) { GroupChatServerTest server = new GroupChatServerTest(); while(true){ server.listen(); } } GroupChatServerTest(){ try { serverSocketChannel = ServerSocketChannel.open(); selector = Selector.open(); serverSocketChannel.configureBlocking(false); serverSocketChannel.bind(new InetSocketAddress(7777)); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); } catch (IOException e) { e.printStackTrace(); } } void listen(){ try { int n = selector.select(); if(n>0){ // 监听到事件后 Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while(iterator.hasNext()){ SelectionKey selectionKey = iterator.next(); // 连接事件 if(selectionKey.isAcceptable()){ SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector,SelectionKey.OP_READ); System.out.println(socketChannel.getRemoteAddress()+"--上线了--"); }else if(selectionKey.isReadable()){ // 可读事件 SocketChannel channel = (SocketChannel)selectionKey.channel(); // 读取数据 String msg = readDate(channel); // 转发数据 transferToOther(channel,msg); } // 移除事件 iterator.remove(); } }else{ } } catch (IOException e) { e.printStackTrace(); } } // 读取客户端传来的数据 String readDate(SocketChannel socketChannel){ ByteBuffer byteBuffer = ByteBuffer.allocate(1024); String msg = null; try { socketChannel.read(byteBuffer); System.out.println(socketChannel.getRemoteAddress()+"发来了消息:"); msg = new String(byteBuffer.array()); System.out.println(msg); } catch (IOException e) { e.printStackTrace(); }finally { return msg; } } // 转发消息 void transferToOther(SocketChannel srcChannel,String msg){ Set<SelectionKey> keys = selector.keys(); Iterator<SelectionKey> iterator = keys.iterator(); while(iterator.hasNext()){ SelectionKey next = iterator.next(); SelectableChannel channel = next.channel(); if(channel instanceof SocketChannel && channel!=srcChannel){ SocketChannel socketChannel = (SocketChannel) channel; try { socketChannel.write(ByteBuffer.wrap(msg.getBytes())); System.out.println("to->"+socketChannel.hashCode()+" "+socketChannel.getRemoteAddress()); } catch (IOException e) { e.printStackTrace(); try { System.out.println(socketChannel.getRemoteAddress()+"离线了--"); socketChannel.close(); next.cancel(); } catch (IOException ex) { ex.printStackTrace(); } } } } } }
客户端:
public class GroupChatClientTest { private SocketChannel socketChannel; private String remoteHost = "127.0.0.1"; private int port = 7777; private Selector selector; public static void main(String[] args) { GroupChatClientTest client = new GroupChatClientTest(); new Thread(()->{ while(true){ // 刚开始 这里由于没有设置 循环,导致打印一次数据就结束了。 client.receive(); try { Thread.currentThread().sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); Scanner scanner = new Scanner(System.in); while(scanner.hasNext()){ String s = scanner.nextLine(); client.send(s); } } GroupChatClientTest(){ try { socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress(remoteHost,port)); while(!socketChannel.finishConnect()){ try { Thread.currentThread().sleep(200); System.out.println("未连接成功"); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(socketChannel.getRemoteAddress()); selector = Selector.open(); socketChannel.register(selector, SelectionKey.OP_READ); } catch (IOException e) { e.printStackTrace(); } } // 发送数据 void send(String info){ try { socketChannel.write(ByteBuffer.wrap(info.getBytes())); } catch (IOException e) { e.printStackTrace(); } } // 接收数据 void receive(){ try { int num=selector.select(); if(num>0){ Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while(iterator.hasNext()) { SelectionKey sk = iterator.next(); if(sk.isReadable()) { SocketChannel sc = (SocketChannel) sk.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); sc.read(byteBuffer); System.out.println(new String(byteBuffer.array())); } /// to do iterator.remove(); } }else{ } } catch (IOException e) { e.printStackTrace(); } } }
零拷贝: 不需要CPU参与拷贝
主要用到 tranferTo方法
Selector的 select方法。
浙公网安备 33010602011771号