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方法。

 

posted @ 2020-10-08 13:56  你眼里的星辰  阅读(96)  评论(0)    收藏  举报