Netty-1 基础学习

Netty的介绍

  1. Netty是一个异步的、基于事件驱动的网络应用框架,用于快速开发高性能、高可用的网络IO程序
  2. Netty主要针对在TCP协议下,面向客户端的高并发应用,或者P2P场景下,大量数据持续传输的应用
  3. 基于NIO

I/O模型

  1. BIO 传统阻塞IO
  • 每个请求都需要创建独立的线程,与对应的客户端进行数据读写处理。
  • 当并发数较大时,需要创建大量线程来处理连接,系统资源占用较大。
  • 连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞在Read操作上,造成线程资源浪费
/**
 * description:BIO阻塞点:
 * 1 serverSocket.accept() 没有连接时,会一直阻塞
 * 2 inputStream.read(bytes) 没有数据时会一直阻塞,等待数据
 *
 *
 * @author : huaneng
 * @date : 2020/9/8 20:29
 */
public class BioServer {
    public static void main(String[] args) throws IOException {
        /*
        * 思路:
        * 1 创建线程池
        * 2 来一个请求,则分配一个线程与之通信
        * */

        ExecutorService pool = Executors.newCachedThreadPool();
        ServerSocket serverSocket = new ServerSocket(6666);
        System.out.println("服务器启动。。。");
        while (true) {
            // 监听,等待客户端连接 如果没有会一值阻塞在这里,不会执行后面的语句
           final Socket socket = serverSocket.accept();
            System.out.println("有客户端连接");
            pool.execute(()->{
                // 与客户的通信
                handle(socket);
            });
        }
    }
    /**
     * description: 与客户端通信的方法
     * @author : huaneng 2020/9/8 20:38
     */
    public static void handle(Socket socket) {
        byte[] bytes = new byte[1024];
        // 通过socket获取输入流
        try {
            InputStream inputStream = socket.getInputStream();
            // 循环读取客户端数据
            while (true) {
                int read = inputStream.read(bytes);
                if (read != -1) {
                    // 将读取的内容输出
                    System.out.println(new String(bytes,0,read));
                }else {
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            System.out.println("关闭连接");
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
  1. NIO 同步非阻塞IO

三大核心模块channel、 buffer 、selector
NIO是面向缓冲区编程。数据读取到它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络
在这里插入图片描述

  1. AIO 异步非阻塞IO

NIO和BIO的比较

  1. BIO以流的方式处理数据,而NIO以块的方式处理数据。块I/O的效率比流I/O高很多
  2. BIO是阻塞的,NIO则是非阻塞的
  3. BIO基于字节流和字符流进行操作,而NIO基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道

NIO Selector Channel Buffer 的关系

  1. 每个channel都会对应一个buffer
  2. Selector对应一个线程,一个线程对应多个Channel
  3. 程序切换到哪个Channel是由事件决定的
  4. selector 会根据不同的事件,在各个通道上切换
  5. buffer就是一个内存块,底层有一个数组
  6. 数据的读取/写入是通过buffer,BIO中要么是输入流,要么是输出流,但是NIO可以写也可以读,使用flip切换
  7. channel也是双向的,可以返回底层操作系统的情况

NIO

buffer

  1. capacity :可以容纳的最大数量;在缓存区创建时设置,不能改变
  2. limit :表示缓冲区当前的终点,不能对超过缓冲区limit的位置进行读写
  3. position :当前位置,一个要被读或者写的元素的索引,每次读写都会改变
  4. mark : 标记
  5. MappedByteBuffer : 可以对数据直接在堆外内存进行操作,性能较高
  6. Scattering: 将数据写入buffer时,可以采用buffer[],依次写入
  7. Gatherind:将buffer读取数据时,采用buffer[],依次读取
public class ScatteringAndGatheringTest {
    public static void main(String[] args) throws IOException {
        // 使用ServerSocketChannel 和ServerChannel 来测试
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
        // 绑定端口到socket 并启动
        serverSocketChannel.socket().bind(inetSocketAddress);
        // 创建 Buffer 数组
        ByteBuffer[] buffers = new ByteBuffer[2];
        buffers[0] = ByteBuffer.allocate(5);
        buffers[1] = ByteBuffer.allocate(5);
        int length = 10 ; // 假定从客户端读取10个字节
        SocketChannel socketChannel = serverSocketChannel.accept();
        // 循环读取
        while (true) {
            int byteRead = 0;
            while (byteRead < length) {
                long read = socketChannel.read(buffers);
                // 累计字节数
                byteRead += read;
                System.out.println(" byteRead = " + byteRead);
                // 使用流打印出每个buffers 的状态
                Arrays.asList(buffers).stream().map(byteBuffer -> "position = " + byteBuffer.position() +
                        " limit = " +byteBuffer.limit()).forEach(System.out::println);
            }
            // buffer反转flip
            Arrays.asList(buffers).forEach(Buffer::flip);

            // 输出数据到客户端
            long writeByte = 0;
            while (writeByte < length) {
                long write = socketChannel.write(buffers);
                writeByte += write;
            }
            // 将所有的Buffer clear
            Arrays.asList(buffers).forEach(Buffer::clear);
        }
    }
}

channel

  1. 双向的,可以同时读写
  2. 可以实现异步读写数据
  3. 常用的channel有FeilChannel、SocketServerChannel、SockerChannel
public class NioFileChannel01 {
    public static void main(String[] args) throws Exception {
        copyFile2();
    }

    private static void copyFile2() throws IOException {
        // 读取文件test01
        FileInputStream fileInputStream = new FileInputStream("D:\\test01.txt");
        FileChannel readChannel = fileInputStream.getChannel();

        FileOutputStream fileOutputStream = new FileOutputStream("D:\\test02.txt");
        FileChannel writeChannel = fileOutputStream.getChannel();
        // copy
        writeChannel.transferFrom(readChannel, 0, readChannel.size());
        fileInputStream.close();
        fileOutputStream.close();
    }

    private static void copyFile() throws IOException {
        // 读取文件test01
        FileInputStream fileInputStream = new FileInputStream("D:\\test01.txt");
        FileChannel readChannel = fileInputStream.getChannel();

        FileOutputStream fileOutputStream = new FileOutputStream("D:\\test02.txt");
        FileChannel writeChannel = fileOutputStream.getChannel();

        ByteBuffer buffer = ByteBuffer.allocate(5);

        while (true) {
            // 这里一定要复位,否则读到最后position = limit read就会返回0,永远不能为-1
            buffer.clear();
            int read = readChannel.read(buffer);
            System.out.println(read);
            if (read == -1) {
                break;
            }
            buffer.flip();
            // 写入文件test02
            writeChannel.write(buffer);
        }

        fileInputStream.close();
        fileOutputStream.close();
    }


    private static void readFromFile() throws IOException {
        FileInputStream fileInputStream = new FileInputStream("D:\\test01.txt");
        FileChannel fileChannel = fileInputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        // 从fileChannel读到byteBuffer
        fileChannel.read(byteBuffer);
        System.out.println(new String(byteBuffer.array()));
        fileInputStream.close();
    }

    private static void writeToFile() throws IOException {
        String s = "hello world 天天向上";
        // 创建输出流 获取channel
        FileOutputStream fileOutputStream = new FileOutputStream("D:\\test01.txt");
        FileChannel fileChannel = fileOutputStream.getChannel();
        // 创建一个Buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        // 将字符串放入buffer
        byteBuffer.put(s.getBytes());
        // 读写切换
        byteBuffer.flip();
        // 将byteBuffer写入channel
        fileChannel.write(byteBuffer);
        fileOutputStream.close();
    }
}

Selector

Selector能够检查多个注册到选择器的通道是否有事件发生。如果有事件发生,便获取事件然后对每个事件进行相应的处理,这样就可以实现一个线程管理多个通道。

  1. Netty 的IO线程 NioEventLoop聚合了 Selector(选择器也叫多路复用器),可以同时并发处理成百上千个客户端连接。
  2. 当线程从某客户端 Socket通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。
  3. 线程通常将非阻塞IO的空闲时间用于在其他通道上执行IO操作,所以单独的线程可以管理多个输入和输出通道
  4. 由于读写操作都是非阻塞的,这就可以充分提升IO线程的运行效率,避免由于频繁I/O阻塞导致的线程挂起
  5. 一个I/O线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统同步阻塞I/O一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。

在这里插入图片描述
实例1:
服务端

public class NioServer {
    public static void main(String[] args) throws IOException {
        // 创建ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 获取Selector
        Selector selector = Selector.open();
        // 绑定端口
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        // 设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        // 首先将服务端注册到Selector,并且关心的事件为accpt
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        // 轮询等待客户端连接
        while (true) {
            // 每隔1s检测一次,看看是否由事件发生
            int channelCount = selector.select(1000);
            if (channelCount == 0) {
                continue;
            }
            // 有客户端
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            // 通过key获取channel
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                // 根据key中channel的状态处理对应的业务
                //  发生了连接事件
                doAccept(serverSocketChannel, selector, key);
                //  发生了读事件
                doRead(key);
                // 结束后删除 key,防止多线程下的重复操作
                iterator.remove();
            }
        }

    }

    private static void doRead(SelectionKey key) throws IOException {
        if(key.isReadable()){
            // 根据key 获取到 SocketChannel
            SocketChannel socketChannel =(SocketChannel) key.channel();
            // 在根据key 得到socketChannel 绑定的 buffer
            ByteBuffer buffer =(ByteBuffer) key.attachment();
            socketChannel.read(buffer);
            System.out.println("来自客户端的消息:" + new String(buffer.array()));
        }
    }

    private static void doAccept(ServerSocketChannel serverSocketChannel, Selector selector, SelectionKey key) throws IOException {
        if (key.isAcceptable()) {
            System.out.println("有新的客户端连接,应该为客户端创建channel");
            SocketChannel socketChannel = serverSocketChannel.accept();
            // 将channel注册到selector ,并绑定一个buffer
            socketChannel.register(selector, SelectionKey.OP_WRITE, ByteBuffer.allocate(1024));
        }
    }
}

实例2:简单的群聊

服务端
public class GroupChatServer {
    private Selector selector;

    private ServerSocketChannel listenChannel;

    private static final int port = 6667;

    public GroupChatServer() {
        try {
            selector = Selector.open();

            listenChannel = ServerSocketChannel.open();
            listenChannel.socket().bind(new InetSocketAddress(port));
            listenChannel.configureBlocking(false);

            listenChannel.register(selector, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    // 监听
    public void listen() {
        try {
            while (true) {
                int count = selector.select(2000);
                if (count == 0) {
                    continue;
                }
                Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
                while (keyIterator.hasNext()) {
                    SelectionKey key = keyIterator.next();
                    if (key.isAcceptable()) {
                        SocketChannel channel = listenChannel.accept();
                        channel.configureBlocking(false);
                        channel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                        System.out.println(channel.getRemoteAddress() + " 上线");
                    }
                    if (key.isReadable()) {
                        // 读取客户端消息
                        read(key);
                    }

                    keyIterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /***
     * description: 读取
     * @author : huaneng 2020/9/15 20:02
     */
    private void read(SelectionKey key) {
        SocketChannel channel = (SocketChannel) key.channel();
        try {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int read = channel.read(buffer);
            if (read > 0) {
                String msg = new String(buffer.array());
                System.out.println("收到客户端数据:" + msg);
                // 发送给其他客户端
                sendMsg(channel, msg);
            }
        } catch (IOException e) {
            try {
                System.out.println(channel.getRemoteAddress() + " 下线");
                // 取消注册
                key.cancel();
                // 关闭通道
                channel.close();
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        }
    }

    /**
     * description:  发送给其他客户端
     *
     * @author : huaneng 2020/9/15 19:56
     */
    private void sendMsg(SocketChannel channel, String msg) throws IOException {
        // 遍历所有注册的 selector
        Set<SelectionKey> keys = selector.keys();
        for (SelectionKey otherKey : keys) {
            if(otherKey.channel() instanceof SocketChannel){
                SocketChannel targetChannel = (SocketChannel) otherKey.channel();
                if (targetChannel.equals(channel)) {
                    continue;
                }
                // 将buffer中的数据写的channel
                targetChannel.write(ByteBuffer.wrap(msg.getBytes()));
            }

        }
    }

    public static void main(String[] args) {
        GroupChatServer server = new GroupChatServer();
        server.listen();
    }
}
客户端
public class GroupChatClient {

    private final String HOST = "127.0.0.1";
    private final int PORT = 6667;
    private Selector selector;
    private SocketChannel socketChannel;
    private String name;

    public GroupChatClient() throws IOException {
        selector = Selector.open();
        socketChannel= SocketChannel.open(new InetSocketAddress(HOST,PORT));
        socketChannel.configureBlocking(false);
        // 注册
        socketChannel.register(selector, SelectionKey.OP_READ);
        // 得到 name
        name = socketChannel.getLocalAddress().toString();
        System.out.println( name +" 初始化完成");
    }

    public void sendInfo(String info) {
        info = name + ":" + info;
        try {
            socketChannel.write(ByteBuffer.wrap(info.getBytes()));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private void readInfo() throws IOException {
        int count = selector.select();
            if (count == 0) {
//                System.out.println("没有消息");
                return;
            }
            Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                if (key.isReadable()) {
                    SocketChannel channel =(SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    channel.read(buffer);
                    // 把读到的数据转为字符串
                    System.out.println(new String(buffer.array()));
                }
            keyIterator.remove();
        }

    }

    public static void main(String[] args) throws IOException {
       final GroupChatClient client = new GroupChatClient();
       // 读取数据
        new Thread(()->{
            while (true) {
                try {
                    client.readInfo();
                    Thread.sleep(2000);

                } catch (IOException | InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {
            String s = scanner.nextLine();
            client.sendInfo(s);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值