【学习笔记】netty

教学视频地址:B站尚硅谷netty教学视频
demo代码:Gitee链接

一、IO模型

1、BIO

在这里插入图片描述
在这里插入图片描述

  • 代码:
public class BIOServer {
    public static void main(String[] args) throws IOException {

        ServerSocket serverSocket = new ServerSocket(6666);
        ExecutorService threadPool = Executors.newCachedThreadPool();

        while (true) {
            final Socket socket = serverSocket.accept();
            System.out.println("成功连接");
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    byte[] bytes = new byte[1024];
                    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) {
                        throw new RuntimeException(e);
                    }
                    ;

                }
            });
        }
    }
}

需要安装telnet,在命令行窗口使用命令telnet 127.0.0.1 6666进入连接,trl+]进入命令操作模式,
send “发送内容”即可向客户端发送信息
在这里插入图片描述

2、NIO

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

buffer

在这里插入图片描述

  • 实例代码:
/**
 * buffer的基本使用
 */
public class BasicBuffer {
    public static void main(String[] args) {
        //声明buffer
        IntBuffer intBuffer = IntBuffer.allocate(10);

        //写入
        for (int i = 0; i < intBuffer.capacity(); i++) {
            intBuffer.put(i);
        }
        //读写转换:读写的下标使用的是不同的变量,需要手动切换
        intBuffer.flip();
        //读取
        for (int i = 0; i < intBuffer.capacity(); i++) {
            System.out.println(intBuffer.get(i));
        }
    }
}

/**
     * 只读buffer
     */
    public static void readOnlyBuffer() {
        ByteBuffer buffer = ByteBuffer.allocate(50);
        for (int i = 0; i < 10; i++) {
            buffer.put((byte) i);
        }

        ByteBuffer asReadOnlyBuffer = buffer.asReadOnlyBuffer();
        for (int i = 0; i < 10; i++) {
            System.out.println(asReadOnlyBuffer.get());
        }
        //不可写入,会报错
        asReadOnlyBuffer.put((byte) 12);
    }


chanel

输入信息
在这里插入图片描述
输入信息在这里插入图片描述
文件拷贝
在这里插入图片描述

/**
     * 内存映射channel,不用将数据复制到cache
     */
    public static void mappered() throws FileNotFoundException {
        //randomAccessFile有多种模式同时可读可写
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(".\\file.txt","rw")) {
            FileChannel channel = randomAccessFile.getChannel();
            //映射的模式,开始位置,空间大小
            MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
            map.put(0, (byte) 'H');

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


/**
     * 文件的输入输出和复制
     * @throws IOException
     */
    private static void fileInputOutputCopy() throws IOException {
        //写入文件
        //信息
        String message = "hello word";
        //输出流
        FileOutputStream fileOutputStream = new FileOutputStream(new File(".\\file.txt"));
        //获取chanel
        FileChannel channel = fileOutputStream.getChannel();
        //声明buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        //将信息写入buffer
        byteBuffer.put(message.getBytes("UTF-8"));
        //读写转换
        byteBuffer.flip();
        //读取buffer里的信息,写入chanel
        int read = channel.write(byteBuffer);


        //读取文件
        File file = new File(".\\file.txt");
        FileInputStream fileInputStream = new FileInputStream(file);
        FileChannel channel1 = fileInputStream.getChannel();
        ByteBuffer byteBuffer1 = ByteBuffer.allocate((int) file.length());
        //读取channel里的信息,写入buffer
        channel1.read(byteBuffer1);
        System.out.println(new String(byteBuffer1.array()));

        //文件复制
        byteBuffer.clear();
        File file1 = new File(".\\file2.txt");
        FileOutputStream fileOutputStream1 = new FileOutputStream(file1);
        FileChannel channel2 = fileOutputStream1.getChannel();
        while (true) {
            //读取channel里的信息,写入buffer
            int read1 = channel1.read(byteBuffer);
            byteBuffer.flip();
            //判断channel是否已经没有信息了
            if (read1 == -1) {
                break;
            }
            //读取buffer里的信息,写入channel
            channel2.write(byteBuffer);
            //再次翻转,以便下次的读取
            byteBuffer.flip();
        }

        //关闭流
        fileOutputStream.close();
        fileInputStream.close();
        fileOutputStream1.close();
    }

    /**
     * API文件复制
     * @throws IOException
     */
    public static void APICopy() throws IOException {
        FileInputStream fileInputStream = new FileInputStream(".\\dog.jpg");
        FileOutputStream fileOutputStream = new FileOutputStream(".\\dogCopy.jpg");

        FileChannel inputStreamChannel = fileInputStream.getChannel();
        FileChannel outputStreamChannel = fileOutputStream.getChannel();

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        //用输出通道调用复制API
        outputStreamChannel.transferFrom(inputStreamChannel,0,inputStreamChannel.size());

        //关闭通道
        inputStreamChannel.close();
        outputStreamChannel.close();
        fileInputStream.close();
        fileOutputStream.close();
    }

类型化读取
在这里插入图片描述

ByteBuffer asReadOnlyBuffer = buffer.asReadOnlyBuffer(); 
/**
     * 内存映射channel,不用将数据复制到cache
     */
    public static void mappered() throws FileNotFoundException {
        //randomAccessFile有多种模式同时可读可写
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(".\\file.txt","rw")) {
            FileChannel channel = randomAccessFile.getChannel();
            MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
            map.put(0, (byte) 'H');

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
 /**
     * 多buffer
     */
    public static void scatterAndGatherBuffer() throws IOException {
        //使用网络IO
        //创建channel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //创建端口
        InetSocketAddress inetSocketAddress = new InetSocketAddress(6666);

        //绑定
        serverSocketChannel.bind(inetSocketAddress);
        //启动
        serverSocketChannel.socket();

        //声明buffer数组
        ByteBuffer[] byteBuffers = new ByteBuffer[2];
        byteBuffers[0] = ByteBuffer.allocate(5);
        byteBuffers[1] = ByteBuffer.allocate(1);

        //等待客户连接
        SocketChannel socketChannel = serverSocketChannel.accept();

        int messageLength = 6;
        int byRead = 0;
        while (true) {
            byRead = 0;
            while (byRead<messageLength) {
                socketChannel.read(byteBuffers);
                byRead++;

                //输出buffer读取后内存情况
                Arrays.stream(byteBuffers).map(buffer->{
                    return "position:"+buffer.position()+" limite:"+buffer.limit();
                }).forEach(System.out::println);
            }
            
            //读写转换
            Arrays.asList(byteBuffers).forEach(buffer->buffer.flip());

            //读取
            int byWrite = 0;
            while (byWrite < messageLength) {
                long write = socketChannel.write(byteBuffers);
                byWrite+=write;
            }
            Arrays.asList(byteBuffers).forEach(buffer->buffer.clear());

            //输出读取的长度,输出的长度,最大的长度
            System.out.println("readNum:"+byRead+" WriteNum:"+byWrite+" maxNum:"+messageLength);
        }

    }

运行结果:会先写入第一个buffer,写满后再写第二个
在这里插入图片描述

selector及nio的其他组件
  • 简介
    一个selector就可以管理多个channel,需要io时向selector发起调用,系统准备好之后,有数据了selector才会返回一个seletorKey通知线程,阻塞线程进行数据复制。
    其中selectorKey是保存在selector的一个集合,用来声明哪一个channel发生了什么事件的
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述
调用selector是阻塞的,但是也可以设置等待中断时间,手动唤醒,立马返回(不阻塞)等方法
在这里插入图片描述

  • selectionKey
    在这里插入图片描述
    在这里插入图片描述

  • serverSocketChannel
    在这里插入图片描述

  • socketChannel
    在这里插入图片描述

  • 案例:实现客户端与服务器之间的非阻塞通讯
    服务端代码:

public class NIOServer {
    public static void main(String[] args) throws IOException {
        /**
         * bio中使用serverSocket进行端口号设置和监听,真正的通讯需要用socket
         * nio中使用serverSocketChannel进行端口号的设置和监听,真正通讯需要用socketChannel(都是多了个channel的后缀)
         */

        //创建一个serverSocketChannel,并设置端口号,非阻塞模式
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        serverSocketChannel.configureBlocking(false);
        //创建一个selector
        Selector selector = Selector.open();
        //将serverSocketChannel绑定到selector,第二个参数需要设置关心的时间
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        //进行循环监听
        while (true) {
            //调用selector的监听方法,这里使用超时中断
            if (selector.select(1000) == 0) {
                System.out.println("服务器等待了一秒,没有事件");
                continue;
            }
            //select调用返回大于零,说明有连接事件,直接获取所有selectionKey集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();

            //遍历所有的key
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                //从iterrantor获取key
                SelectionKey key = iterator.next();
                //判断不同的时间类型
                if (key.isAcceptable()) {
                    System.out.println("收到连接事件,正在生成socketChannel通讯通道。。。");
                    //连接事件 OP_ACCEPT,生成一个socketChannel进行通讯
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    //先设置非阻塞模式,再将socketChannel注册到selector,还需要设置关注事件 、 buffer
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                    System.out.println("客户端连接成功,socketChannel:"+socketChannel.hashCode());
                }
                if (key.isReadable()) {
                    System.out.println("收到读取事件,正在通过selectionKey获取对应的socketChannel通讯通道。。。");
                    //读取事件 OP_READ
                    //通过key获取channel
                    SocketChannel channel = (SocketChannel)key.channel();
                    //通过key获取buffer
                    ByteBuffer buffer = (ByteBuffer)key.attachment();
                    //读取操作
                    channel.read(buffer);
                    System.out.println("一则来自客户端的消息:"+new String(buffer.array()));
                }

                //手动删除key,防止重复操作(必须有调用了next,才可以是用remove,不然会快速失败)
                iterator.remove();
            }

        }
    }
}

客户端代码:

public class NioClient {
    public static void main(String[] args) throws IOException {
        //直接声明socketChannel,并设置非阻塞模式
        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("因为连接需要时间,客户端不会阻塞,可以做其他工作......");
            }
        }
        //连接成功,发送数据(wrap会根据发送内容自动自动申请buffer大小)
        ByteBuffer buffer = ByteBuffer.wrap("hello nio!".getBytes());
        socketChannel.write(buffer);
        //阻塞主线程
        System.in.read();
    }
}
nio实战:nio群聊系统
  • 需求
    在这里插入图片描述
  • 代码:
package com.example.netty.nio.groupChat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class GroupchatServer {
    private ServerSocketChannel serverSocketChannel;
    private Selector selector;
    private static final int PORT = 6667;

    public GroupchatServer() throws IOException {
        //通道设置
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
        //selector设置
        selector = Selector.open();
        //将通道注册到selector
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    public void listen() throws IOException {
        //循环监听
        while (true) {
            int selectResult = selector.select(10000);
            if (selectResult > 0) {
                System.out.println("\n监听到事件:");
                Set<SelectionKey> selectionKeys = selector.selectedKeys();

                Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
                while (keyIterator.hasNext()) {
                    SelectionKey key = keyIterator.next();
                    if (key.isAcceptable()) {
                        System.out.println("链接事件,正在创建socketChannel。。。。");
                        SocketChannel socketChannel = serverSocketChannel.accept();
                        socketChannel.configureBlocking(false);
                        socketChannel.register(selector,SelectionKey.OP_READ,ByteBuffer.allocate(1024));
                        System.out.println("创建成功,已注册到selector中,"+
                                socketChannel.getRemoteAddress()+"已上线......");
                    }
                    if (key.isReadable()) {
                        System.out.println("读事件,正在获取socketChannel。。。。");
                        readData(key);
                    }
                    keyIterator.remove();
                }
            }
        }
    }

    private void readData(SelectionKey key) throws IOException {
        SocketChannel socketChannel = null;
        try {
            //接收数据
            socketChannel = (SocketChannel)key.channel();
//            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            ByteBuffer byteBuffer = (ByteBuffer)key.attachment();//在注册的时候如果没有绑定buffer的话,会报错
            socketChannel.read(byteBuffer);
            String message = new String(byteBuffer.array());
            System.out.println("已成功接收客户端的消息:"+message.trim());

            //将消息进行转发(要排除自己)
            for (SelectionKey selectionKey : selector.keys()) {
                //不可以在这里直接强转,会报错
                SelectableChannel channel = selectionKey.channel();
                if (channel instanceof SocketChannel && channel != socketChannel) {
                    SocketChannel destSocketChannel = (SocketChannel) channel;

                    destSocketChannel.write(ByteBuffer.wrap(message.getBytes()));
                }
            }
        } catch (IOException e) {
            //读取过程中发生异常,发送方已经断开连接即下线
//            e.printStackTrace();
            System.out.println(socketChannel.getRemoteAddress()+"已下线。。。。");
            //不再监听这个key,关闭通道
            key.cancel();
            socketChannel.close();
        }

    }

    public static void main(String[] args) throws IOException {
        GroupchatServer groupchatServer = new GroupchatServer();
        groupchatServer.listen();
    }
}
package com.example.netty.nio.groupChat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
import java.util.concurrent.Callable;

public class GroupChatClient {
    private Selector selector;
    private SocketChannel socketChannel;
    public static final String HOST = "127.0.0.1";
    public static final int ADDRESS = 6667;
    private String userName;

    public GroupChatClient() throws IOException {
        socketChannel = SocketChannel.open(new InetSocketAddress(HOST,ADDRESS));
        socketChannel.configureBlocking(false);

        selector = Selector.open();

        socketChannel.register(selector, SelectionKey.OP_READ,ByteBuffer.allocate(1024));

        userName = socketChannel.getLocalAddress().toString().substring(1);
    }

    public void sendMessage(String message) throws IOException {
        String info = userName+" 说:"+message;

        socketChannel.write(ByteBuffer.wrap(info.getBytes()));
    }

    public void receiveMessage() throws IOException {
        int selectResult = selector.selectNow();
        if (selectResult > 0) {
            Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                if (key.isReadable()) {
                    SocketChannel socketChannel = (SocketChannel)key.channel();
//                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    ByteBuffer byteBuffer = (ByteBuffer)key.attachment();//在注册的时候如果没有绑定buffer的话,会报错
                    socketChannel.read(byteBuffer);

                    System.out.println(new String(byteBuffer.array()).trim());
                }
                keyIterator.remove();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        //需要两个线程,循环机那挺读和写
        GroupChatClient groupChatClient = new GroupChatClient();
        //新建一个线程循环读
        new Thread(){
            public void run() {
                while (true) {
                    try {
                        groupChatClient.receiveMessage();
                        Thread.sleep(1000);
                    } catch (IOException | InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }.start();

        //用主线程监听写
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            String message = scanner.nextLine();
            groupChatClient.sendMessage(message);
        }
    }
}


nio与零拷贝
  • 基本概念:零次cpu拷贝
    在这里插入图片描述
  • 优化原理
    在这里插入图片描述
    DMA copy:直接内存拷贝,不使用cpu

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 总结
    在这里插入图片描述
    在这里插入图片描述
  • 案例
    在这里插入图片描述
package com.example.netty.nio.zeroCpy.java传统io;

import java.io.DataInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class OldIOServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(7001);

        while (true) {
            Socket socket = serverSocket.accept();
            DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());

            //这里只是为了测试速度,所以数据只读到byte里就不再做进一步的处理
            byte[] bytes = new byte[4096];
            while (true) {
                int readResult = dataInputStream.read(bytes, 0, bytes.length);
                if (readResult == -1) {
                    break;
                }
            }
        }
    }
}

package com.example.netty.nio.zeroCpy.java传统io;

import java.io.*;
import java.net.Socket;

public class OldIOClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 7001);
        FileInputStream fileInputStream = new FileInputStream(new File("./dog.jpg"));

        DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
        byte[] bytes = new byte[4096];
        long readCount = 0;
        long total = 0;
        long startTime = System.currentTimeMillis();

        //读取文件,发送到server端
        while ((readCount = fileInputStream.read(bytes)) > 0) {
            total+=readCount;
            dataOutputStream.write(bytes);
        }

        // 结果:发送总字结:392715,耗时:4
        System.out.println("发送总字结:"+total+",耗时:"+(System.currentTimeMillis()-startTime));

        dataOutputStream.close();
        socket.close();
        fileInputStream.close();
    }
}
package com.example.netty.nio.zeroCpy;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class NewIOServer {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(7001));
        ByteBuffer byteBuffer = ByteBuffer.allocate(4096);

        while (true) {
            SocketChannel socketChannel = serverSocketChannel.accept();
            int readCount = 0;
            while (readCount != -1) {
                readCount = socketChannel.read(byteBuffer);
                //重置position,使mark作废
                byteBuffer.rewind();
            }
        }
    }
}

package com.example.netty.nio.zeroCpy;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;

public class NewIOClient {
    public static void main(String[] args) throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1",7001);
        socketChannel.connect(inetSocketAddress);

        //获取文件channel
        FileChannel fileInputChannel = new FileInputStream("./dog.jpg").getChannel();

        long startTime = System.currentTimeMillis();
        //调用零拷贝api:windows调用一次最多只能穿8M,需要分批传
        long transferCount = fileInputChannel.transferTo(0, fileInputChannel.size(), socketChannel);
        //结果:总字结数:392715,耗时:16 3 2 2 2 3
        System.out.println("总字结数:"+transferCount+",耗时:"+(System.currentTimeMillis()-startTime));

        socketChannel.close();
        fileInputChannel.close();

    }
}

3、AIO

未广泛使用,不做介绍,这里只重点讲NIO

  • 三种IO模型的对比
    在这里插入图片描述

二、Netty入门

1、Netty简介

  • 概述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

2、线程模型

在这里插入图片描述

  • 传统阻塞IO模型
    在这里插入图片描述
  • Reacor模型总览:
    在这里插入图片描述
    在这里插入图片描述
  • Reactor单线程模型:
    之前写的NIO群聊系统就是这种模式
    在这里插入图片描述
    在这里插入图片描述
  • Reactor多线程模式
    在这里插入图片描述
    在这里插入图片描述
  • 主从Reactor模式
    在这里插入图片描述
    在这里插入图片描述
  • Netty模型
    简单版在这里插入图片描述
    进阶版
    在这里插入图片描述
    详细版
    在这里插入图片描述

3、入门案例-TCP服务

在这里插入图片描述

  • 代码:
package com.example.netty.netty.simpleExample;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class NettyServer {
    public static void main(String[] args) throws Exception{
        //创建Group
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //创建一个服务器对象,用于设置参数
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)    //设置两个group
                    .channel(NioServerSocketChannel.class)  //设置服务器bossGroup使用的通道
                    .option(ChannelOption.SO_BACKLOG, 128)  //设置bossGroup线程队列最大连接个数
                    .childOption(ChannelOption.SO_KEEPALIVE, true)   //设置workerGroup是否保持活动连接状态
                    //设置workerGroup通道初始化对象(匿名对象)
                    .childHandler(new ChannelInitializer<SocketChannel>() { //通道类型,netty包中的
                        //给pipline中设置处理器
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new NettyServerHandler());
                        }
                    });
            ChannelFuture channelFuture = serverBootstrap.bind(6668).sync();
            System.out.println("服务器启动成功。。。。。");

            //监听对通道的关闭
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //出现异常后优雅关闭group
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}



package com.example.netty.netty.simpleExample;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

//需要继承一个HandlerAdapter(有很多个)
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("上下文信息:"+ctx);
        //这是netty包的,不是NIO的bytebuffer,进行了二次分装性能要高一些
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println("接收到信息:"+byteBuf.toString(CharsetUtil.UTF_8));
        System.out.println("客户端地址为:"+ctx.channel().remoteAddress());
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        //读取完成后触发,一般用于数据返回
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello,client...",CharsetUtil.UTF_8));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //发生异常时触发,一般要关闭通道
        ctx.close();
    }
}

package com.example.netty.netty.simpleExample;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class NettyClient {
    public static void main(String[] args) throws Exception {
        NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
        try {
            //与server端的不一样
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventExecutors)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new NettyClientHandler());
                        }
                    });
            ChannelFuture sync = bootstrap.connect("127.0.0.1", 6668).sync();
            System.out.println("客户端启动成功。。。。");
            sync.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            eventExecutors.shutdownGracefully();
        }
    }
}



package com.example.netty.netty.simpleExample;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //通道准备好时触发
        ctx.channel().writeAndFlush(Unpooled.copiedBuffer("hello,server...", CharsetUtil.UTF_8));
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //有读取事件时触发
        System.out.println("上下文信息:"+ctx);
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("接受到信息:"+buf.toString(CharsetUtil.UTF_8));
        System.out.println("服务器地址:"+ctx.channel().remoteAddress());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}


4、任务队列

  • 简介
    如果有非常耗时的逻辑,又不想阻塞当前线程可以通过ctx获取管道,再获取NioEventLoop提交taskQueue,用另一个线程来异步执行
    taskQueue里面只有一个线程,会按顺序执行提交的任务
    在这里插入图片描述
    在这里插入图片描述
    普通代码实现:
    在这里插入图片描述
    定时任务代码实现:
    在这里插入图片描述

5、异步模型

不会阻塞主线程
在这里插入图片描述
代码:
在这里插入图片描述

6、入门案例-HTTP服务

  • 需求
    在这里插入图片描述

  • 组件
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    只能处理一个客户端请求只针对bossGroup中只有一个nioEventLoop而言的
    在这里插入图片描述

  • 代码

package com.example.netty.netty.HTTPExample;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;

import java.net.URI;

//通讯数据会被封装成 httpObject
public class HTTPServerHandler extends SimpleChannelInboundHandler<HttpObject> {

    //有读取事件时触发
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject httpObject) throws Exception {
        if (httpObject instanceof HttpRequest) {
            //在同一个bossGroup中每个浏览器对应一个workerGroup,但如果有多个bossGroup进行轮询就不会有对应关系
            System.out.println("handler hashcode:"+this.hashCode()+
                    ",pipLine hashCode:"+channelHandlerContext.channel().pipeline().hashCode());
            System.out.println("消息类型:"+httpObject.getClass());
            System.out.println("clientAddress:"+channelHandlerContext.channel().remoteAddress());
            //过滤特定请求路径
            HttpRequest httpRequest = (HttpRequest) httpObject;
            URI uri = new URI(httpRequest.uri());
            if (uri.getPath().equals("/favicon.ico")) {
                System.out.println("请求favicon.ico,不做响应");
                return;
            }

            //回复消息
            ByteBuf buf = Unpooled.copiedBuffer("hello,httpClient....", CharsetUtil.UTF_8);
            //构造一个http响应
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                    HttpResponseStatus.OK,buf);
            response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain;charset=utf-8")
                    .set(HttpHeaderNames.CONTENT_LENGTH,buf.readableBytes());
            channelHandlerContext.writeAndFlush(response);
        }
    }
}


public class ServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        //netty提供的http编码解码处理器
        socketChannel.pipeline().addLast(new HttpServerCodec())
                .addLast("myHTTPServerHandler",new HTTPServerHandler());
    }
}

7、Unpooled组件

  • 简介
    在这里插入图片描述
    不需要进行flip反转,底层维护了两个index,将buf空间分成了三个部分:
    0-readerIndex:已读区
    readerIndex-writerIndex:可读区
    writerIndex-capacity:可写区
  • 代码
package com.example.netty.netty.buf;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;

public class fubExample {
    public static void main(String[] args) {
        ByteBuf buffer = Unpooled.buffer(10);
        for (int i = 0; i < buffer.capacity(); i++) {
            buffer.writeByte(i);
        }
        for (int i = 0; i < buffer.capacity(); i++) {
            System.out.print(buffer.readByte()+" ");
        }
        System.out.println();

        ByteBuf buf = Unpooled.copiedBuffer("hello,北京", CharsetUtil.UTF_8);
        byte[] array = buf.array();
        buf.writeByte('w');
        System.out.println((char) buf.readByte());
        System.out.println(buf.getCharSequence(1, 3, CharsetUtil.UTF_8));
        buf.writeBytes(new byte[]{'a','d','s'});
        System.out.println(new String(array,CharsetUtil.UTF_8).trim());

    }
}

8、Netty群聊系统

  • 需求
    在这里插入图片描述
  • 代码
package com.example.netty.netty.groupChat;

import com.example.netty.nio.groupChat.GroupChatClient;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class GroupChatServer {
    private int port;

    public GroupChatServer(int port) {
        this.port = port;
    }

    public void run() throws InterruptedException {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            //加入string的编码解码器
                            pipeline.addLast("stringEncoder", new StringEncoder())
                                    .addLast("stringDecoder", new StringDecoder());
                            pipeline.addLast(new ServerHandler());
                        }
                    });

            ChannelFuture sync = serverBootstrap.bind(port).sync();
            sync.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new GroupChatServer(7000).run();
    }
}


package com.example.netty.netty.groupChat;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.HttpObject;
import io.netty.util.concurrent.GlobalEventExecutor;

import javax.lang.model.element.VariableElement;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;

public class ServerHandler extends SimpleChannelInboundHandler<String> {
    //相当于channel列表,来用管理所有的channel
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

    //与handler建立连接时触发
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        //将该通道加入管理列表
        channelGroup.add(ctx.channel());
        //通知信息,发送给所有人
        channelGroup.writeAndFlush(dateFormat.format(new Date())+"-客户端:"
                +ctx.channel().remoteAddress()+" 加入聊天\n");
    }

    //断开连接时触发
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        channelGroup.writeAndFlush(dateFormat.format(new Date())+"-客户端:"
                +ctx.channel().remoteAddress()+" 离开了\n");
    }

    // TODO: 2022/9/12 与上面两个方法的区别?
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(dateFormat.format(new Date())+ctx.channel().remoteAddress()+" 上线了");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(dateFormat.format(new Date())+ctx.channel().remoteAddress()+" 下线了");
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
        Channel channel = channelHandlerContext.channel();
        channelGroup.forEach(ch->{
            if (ch != channel) {
                ch.writeAndFlush(dateFormat.format(new Date())+"-客户端:" + channel.remoteAddress() + " 说:" + s + "\n");
            } else {
                //回显
                ch.writeAndFlush(dateFormat.format(new Date())+"-自己说:"+s+"\n");
            }
        });
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//        cause.printStackTrace();
        ctx.close();
    }
}

package com.example.netty.netty.groupChat;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.util.Scanner;

public class GroupChatClient {
    private final String host;
    private final int port;

    public GroupChatClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void run() throws InterruptedException {
        NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventExecutors)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            //加入string的编码解码器
                            pipeline.addLast("stringEncoder", new StringEncoder())
                                    .addLast("stringDecoder", new StringDecoder());
                            pipeline.addLast(new ClientHandler());
                        }
                    });

            ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
            //发消息
            Channel channel = channelFuture.channel();
            Scanner scanner = new Scanner(System.in);
            while (scanner.hasNext()) {
                channel.writeAndFlush(scanner.nextLine()+"\r\n");
            }
            channelFuture.channel().closeFuture().sync();
        } finally {
            eventExecutors.shutdownGracefully();
        }

    }

    public static void main(String[] args) throws InterruptedException {
        new GroupChatClient("127.0.0.1",7000).run();
    }
}


package com.example.netty.netty.groupChat;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class ClientHandler extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
        System.out.println(s);
    }
}

  • 私聊系统
    大部分一样,下面列出不同代码:
    在这里插入图片描述
    在这里插入图片描述

9、心跳机制

在这里插入图片描述
出现空闲直接关闭连接
在这里插入图片描述
客户端发送消息前先检验连接是否已经断开,若断开需要重连后再发送
在这里插入图片描述

10、WebSocket长连接开发

要求实现全双工长连接通讯
核心代码:
server:
在这里插入图片描述
handler:
在这里插入图片描述
client:

<script>
    var socket;
    //判断当前浏览器是否支持websocket
    if (window.WebSocket) {
        socket = new WebSocket("ws://localhost:7000/hello");
        socket.onmessage = function (ev) {
            var rt = document.getElementById("responseText");
            rt.value = rt.value + "\n" + ev.data;
        };
        socket.onopen = function (ev) {
            var rt = document.getElementById("responseText");
            rt.value = "连接开启了。。"
        };
        socket.onclose = function (ev) {
            var rt = document.getElementById("responseText");
            rt.value = rt.value + "\n" + "连接关闭了。。"
        };
    } else {
        alert("当前流浪器不支持websocket")
    }

    function send(message) {
        if (!window.socket) {
            return;
        }
        if (socket.readyState == WebSocket.OPEN) {
            socket.send(message);
        } else {
            alert("连接没有开启");
        }
    }
</script>
<form onsubmit="return false">
    <textarea name="message" style="height: 300px;width: 300px"></textarea>
    <input type="button" value="发送消息" onclick="send(this.form.message.value)">
    <textarea id="responseText" style="height: 300px;width: 300px"></textarea>
    <input type="button" value="清空内容" onclick="document.getElementById('responseText').value=''">
</form>

11、编码解码机制

  • 编码解码基本概念
    在这里插入图片描述
  • netty的编码解码
    在这里插入图片描述
  • ProtoBuf
    在这里插入图片描述
    在这里插入图片描述
  • ProtoBuf案例1
    1、引入依赖
<dependencies>
	<dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.21.5</version>
        </dependency>
</dependencies>

2、编写proto文件

syntax = "proto3";  //版本
option java_outer_classname = "StudentPOJO";  //生成的外部类名,同时也是文件名
//protobuf使用message管理数据
message Student{  //会在StudentPOJO外部生成一个内部类,他是真正发送的POJO对象
  int32 id = 1; //int32对应java中的int,1代表序号,不是值
  string name = 2;
}

3、下载protoc.exe(要与依赖版本对应),与proto文件放在同一目录下,打开命令行,执行一下命令即可生成对象文件
注意.后面有个空格

protoc.exe --java_out=. Student.proto

若生成的文件报错,是因为编译器修改了jdk版本,改回来就好了
在这里插入图片描述
在这里插入图片描述
4、编写传输代码:
加入编码解码器,这里只加一个,单向
client:
在这里插入图片描述
clientHandler:
在这里插入图片描述

server:
在这里插入图片描述
serverHandler:
在这里插入图片描述

  • ProtoBuf案例2-传输多种类型
    proto文件:
syntax = "proto3";
option optimize_for = SPEED;  //加快解析
option java_package = "com.example.netty.netty.codec2"; //生成的文件所在的包
option java_outer_classname = "MyDataInfo"; //外部类名称

message MyMessage{
  //定义一个枚举类
  enum DataType{
    StudentType = 0;  //proto3枚举类下标要求从0开始
    WorkerType = 1;
  }
  //生命一个变量,用来标识是哪一个枚举类
  DataType data_type = 1;
  //声明正真的数据对象,用oneof括起来可以只选其中一个已节省空间
  oneof dataBody{
    Student student = 2;
    Worker worder = 3;
  }
}

message Student{
  int32 id = 1;
  string name = 2;
}

message Worker{
  string name = 1;
  int32 age = 2;
}

传输关键代码:
加入编码解码器(这里每一端只加一个)
client
在这里插入图片描述
server
在这里插入图片描述
clientHandler:
在这里插入图片描述
serverHandler:
在这里插入图片描述

12、Handler调用链

  • 图解
    在这里插入图片描述
  • 案例
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 其他编解码器
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

13、Log4j整合到Netty

1、导入依赖

		<dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.25</version>
            <scope>test</scope>
        </dependency>

2、在resource中新建log4j.properties写入以下内容

log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%p] %c{1} - %m%n

3、使用
在这里插入图片描述

14、TCP粘包拆包问题

  • 简介
    在这里插入图片描述
  • 解决方案
    在这里插入图片描述
    协议-消息体:
    在这里插入图片描述
    编码解码:
    在这里插入图片描述
    在这里插入图片描述

三、Netty源码解读

1、启动过程

2、接受请求的过程

源码部分先挑过

四、利用Netty实现RPC框架

1、流程分析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、代码

在gitee中,这里就不贴了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值