Netty入门学习笔记(三)

六、Netty 核心模块组件

Bootstrap、ServerBootstrap

在这里插入图片描述

Future、ChannelFuture

在这里插入图片描述

Channel

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

Selector

在这里插入图片描述

ChannelHandler 及其实现类

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

Pipeline 和 ChannelPipeline

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

ChannelHandlerContext

在这里插入图片描述

ChannelOption

在这里插入图片描述

EventLoopGroup 和其实现类 NioEventLoopGroup

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

Unpooled 类

在这里插入图片描述

API使用练习1

public class NettyByteBuf01 {
    public static void main(String[] args) {
        //创建一个ByteBuf
        //创建对象,该对象包含一个数组arr,是一个 byte[10]
        //netty的 buffer 不需要使用 flip 进行反转,因为底层维护了 readIndex 和 writeIndex
        //writeIndex 可写的范围为 0 ~ capacity
        //readIndex 可读的范围为 0 ~ writeIndex
        //0                                             capacity
        //0                     ↑ writeIndex ↑
        //     ↑ readIndex ↑
        ByteBuf buffer = Unpooled.buffer(10);

        //赋值
        for (int i = 0; i < 10; i++) {
            buffer.writeByte(i);
        }

        System.out.println("capacity=" + buffer.capacity());
        //输出
//        for (int i = 0; i < buffer.capacity(); i++) {
//            //指定索引读取
//            System.out.println(buffer.getByte(i));
//        }
        for (int i = 0; i < buffer.capacity(); i++) {
            //根据 readIndex 读取,每读一个,readIndex++
            System.out.println(buffer.readByte());
        }
    }
}

API使用练习2

public class NettyByteBuf02 {
    public static void main(String[] args) {
        //创建ByteBuf
        ByteBuf byteBuf = Unpooled.copiedBuffer("hello,world!", Charset.forName("utf-8"));

        //使用相关的方法
        if (byteBuf.hasArray()){
            byte[] content = byteBuf.array();
            System.out.println(new String(content, 0, byteBuf.writerIndex(), Charset.forName("utf-8")));

            System.out.println("byteBuf=" + byteBuf);

            System.out.println(byteBuf.arrayOffset());  //偏移量   0
            System.out.println(byteBuf.readerIndex());  //读索引   0
            System.out.println(byteBuf.writerIndex());  //写索引   12
            System.out.println(byteBuf.capacity());     //总容量   36

            //get方法为获取,不改变readIndex
            //read方法为读取,会改变readIndex
            //获取索引为0的元素
            System.out.println(byteBuf.getByte(0));
            //可读取的数量    12
            int len = byteBuf.readableBytes();
            System.out.println("len=" + len);

            //读取一个元素
            System.out.println(byteBuf.readByte());
            //可读取的数量    11
            len = byteBuf.readableBytes();
            System.out.println("len=" + len);

            //循环获取
            for (int i = 0; i < len; i++) {
                System.out.println((char) byteBuf.getByte(i));
            }

            //指定起始和长度的获取,起始为1,长度为2  el
            System.out.println(byteBuf.getCharSequence(1, 2, Charset.forName("utf-8")));
        }
    }
}

Netty应用实例-群聊系统

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

public class GroupCharServerHandler extends SimpleChannelInboundHandler<String> {

    //定义一个channel组,管理所有的channel,方便消息的转发
    //这个组是所有handler共享的,所以需要加上static
    //GlobalEventExecutor.INSTANCE 全局事件执行器,是一个单例
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    //处理时间时使用的对象
    //SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");   //非线程安全
    DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");


    //handlerAdded 表示连接建立,连接后第一个被执行
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        //当前时间
        String nowTime = dtf.format(LocalDateTime.now());

        Channel channel = ctx.channel();
        //把当前客户端上线的消息转发到其他已登录的客户端上
        //writeAndFlush 方法底层会循环遍历channelGroup并发送消息
        channelGroup.writeAndFlush(nowTime + "[用户]" + channel.remoteAddress() + "加入聊天~\n");

        //把连接的客户端的channel 加入到 channelGroup
        channelGroup.add(channel);
    }

    //表示channel处于活动的状态,提示xxx上线
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //当前时间
        String nowTime = dtf.format(LocalDateTime.now());

        //也可能在这里对客户端上线的消息进行转发,这里只在server端显示
        System.out.println(nowTime + " " + ctx.channel().remoteAddress() + "上线了~");
        System.out.println("此聊天室在线的人数:" + channelGroup.size());
    }

    //表示channel处于不活动的状态,提示xxx离线
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        //当前时间
        String nowTime = dtf.format(LocalDateTime.now());

        System.out.println(nowTime + " " + ctx.channel().remoteAddress() + "离线了~");
    }

    //断开连接,将xxx客户端离开的消息转发给当前在线的客户端
    //当触发 handlerRemoved 方法时,会自动把当前客户端的channel从 channelGroup 中移除remove
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        //当前时间
        String nowTime = dtf.format(LocalDateTime.now());

        channelGroup.writeAndFlush(nowTime + "[用户]" + ctx.channel().remoteAddress() + " 离开了...\n");
        System.out.println("此聊天室在线的人数:" + channelGroup.size());
    }


    //读取数据
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        //获取当前连接的客户端的channel
        Channel channel = ctx.channel();
        //遍历channelGroup,根据不同情况,回送不同消息
        channelGroup.forEach(ch -> {
            //当前时间
            String nowTime = dtf.format(LocalDateTime.now());

            //不是当前channel,转发消息
            if(channel != ch){
                ch.writeAndFlush(nowTime + "[用户]" + channel.remoteAddress() + ": " + msg + "\n");
            }else{
                //用户自己
                ch.writeAndFlush(nowTime + "me: " + msg + "\n");
            }
        });
    }

    //异常
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //关闭通道
        ctx.close();
    }
}

GroupCharServer

public class GroupCharServer {

    private int port;   //监听端口

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

    //处理客户端的请求
    public void run() throws Exception {
        //创建两个线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();   //CPU核数 * 2 = 8

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();

            //链式配置
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)    //使用 NioSocketChannel 作为服务器的通道实现
                    .option(ChannelOption.SO_BACKLOG, 128)      //设置线程队列得到连接的个数
                    .childOption(ChannelOption.SO_KEEPALIVE, true)  //设置保持活动连接状态
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        //handler 对应的是 bossGroup(指的是自己),childHandler 对应的是 workerGroup
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //获取pipeline
                            ChannelPipeline pipeline = ch.pipeline();
                            //向pipeline中添加处理器
                            //加入解码器
                            pipeline.addLast("decoder", new StringDecoder());
                            //加入编码器
                            pipeline.addLast("encoder", new StringEncoder());
                            //加入自定义的业务处理handler
                            pipeline.addLast(new GroupCharServerHandler());
                        }
                    });

            System.out.println("netty 服务器启动成功~");
            //绑定端口
            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
            //监听关闭事件
            channelFuture.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }


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

GroupCharClientHandler

public class GroupCharClientHandler extends SimpleChannelInboundHandler<String> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println(msg.trim());
    }
}

GroupCharClient

public class GroupCharClient {

    //主机地址
    private final String host;
    private final int port;

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

    public void run() throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();

        try {

            Bootstrap bootstrap = new Bootstrap()
                    .group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //得到pipeline
                            ChannelPipeline pipeline = ch.pipeline();
                            //向pipeline中添加处理器
                            //加入解码器
                            pipeline.addLast("decoder", new StringDecoder());
                            //加入编码器
                            pipeline.addLast("encoder", new StringEncoder());
                            //加入自定义的业务处理handler
                            pipeline.addLast(new GroupCharClientHandler());
                        }
                    });

            //连接
            ChannelFuture channelFuture = bootstrap.connect(host, port).sync();

            Channel channel = channelFuture.channel();
            System.out.println("----------- " + channel.localAddress() + " -----------");
            //监听关闭事件
            channel.closeFuture().sync();

            //客户端需要输入信息,创建一个扫描器
            Scanner scanner = new Scanner(System.in);
            while (scanner.hasNextLine()){
                String msg = scanner.nextLine();
                //通过channel发送给服务器端
                channel.writeAndFlush(msg + "\n");
            }


        }finally {
            group.shutdownGracefully();
        }
    }


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

在这里插入图片描述

Netty心跳检测机制案例

在这里插入图片描述
MyServerHandler

public class MyServerHandler extends ChannelInboundHandlerAdapter {

    /**
     *
     * @param ctx 上下文对象
     * @param evt 事件对象
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        //对传过来的对象进行判断
        if (evt instanceof IdleStateEvent) {
            //转型
            IdleStateEvent event = (IdleStateEvent) evt;

            String eventType = null;
            switch (event.state()) {
                //读空闲
                case READER_IDLE:
                    eventType = "读空闲";
                    break;
                //写空闲
                case WRITER_IDLE:
                    eventType = "写空闲";
                    break;
                //读写空闲
                case ALL_IDLE:
                    eventType = "读写空闲";
                    break;
            }

            //显示是哪一个客户端出现空闲
            System.out.println(ctx.channel().remoteAddress() + " 超时事件:" + eventType);
            System.out.println("服务器进行了相对应处理...");
            //ctx.channel().close();
        }
    }

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

MyServer

public class MyServer {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap()
                    .group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))  //在bossGroup里增加一个日志处理器
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            /*
                                说明:
                                    1. IdleStateHandler 是一个netty提供的处理空闲的处理器
                                    2. 第一个参数:表示多长时间没有读,就会发送一个心跳检测包检测是否还连接(存活)
                                    3. 第二个参数:表示多长时间没有写,就会发送一个心跳检测包检测是否还连接(存活)
                                    4. 第三个参数:表示多长时间没有读写,就会发送一个心跳检测包检测是否还连接(存活)
                                    5. 第四个参数:指定前三个参数的时间单位
                                文档说明:
                                     * Triggers an {@link IdleStateEvent} when a {@link Channel} has not performed
                                     * read, write, or both operation for a while.
                                    6. 当 IdleStateEvent 触发后,就会传递给管道的下一个handler去处理,
                                        通过调用(触发)下一个handler的 userEventTiggered 方法,我们
                                        可以在该方法中来去处理上一个handler的IdleStateEvent(读空闲,写空闲,读写空闲)
                            */
                            pipeline.addLast(new IdleStateHandler(
                                    3, 5, 7, TimeUnit.SECONDS));
                            //加入一个对空闲检测进一步处理的自定义handler
                            pipeline.addLast(new MyServerHandler());
                        }
                    });

            //启动服务器
            ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
            channelFuture.channel().closeFuture().sync();

        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

Netty 通过WebSocket编程实现服务器和客户端长连接

在这里插入图片描述
MyTextWebSocketFrameHandler

//TextWebSocketFrame 类型:表示一个文本帧(frame)
public class MyTextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        System.out.println("服务器收到消息:" + msg.text());

        //回复消息
        ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器时间" + LocalDateTime.now() + " " + msg.text()));
    }


    //当web客户端连接后,触发方法
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        //id 表示唯一的值,LongText 是唯一的,而 ShortText 不是唯一的
        System.out.println("handlerAdded 被调用 " + ctx.channel().id().asLongText());
        System.out.println("handlerAdded 被调用 " + ctx.channel().id().asShortText());
    }


    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerRemoved 被调用 " + ctx.channel().id().asLongText());
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("发生异常:" + cause.getMessage());
        ctx.close();
    }
}

MyServer

public class MyServer {

    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap()
                    .group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))  //在bossGroup里增加一个日志处理器
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();

                            //因为基于http协议,所以使用http的编码解码器
                            pipeline.addLast(new HttpServerCodec());
                            //是以块方式写,添加 ChunkedWriteHandler 处理器
                            pipeline.addLast(new ChunkedWriteHandler());
                            /*
                               说明:
                                1. http数据在传输过程中是分段HttpObjectAggregator,就是可以将多个段聚合
                                2. 这就是为什么,当浏器发送大量数据时,会发出多次http请求的原因
                             */
                            pipeline.addLast(new HttpObjectAggregator(8192));
                            /*
                               说明:
                                1. 对应 websocket,它的数据是以 帧(frame)形式传递
                                2. 可以看到 WebsocketFrame下面有六个子类
                                3. 浏览器请求时 ws://localhost:7000/hello 表示请求的uri
                                4. WebSocketServerProtocolHandler 核心功能是将http协议开级为ws协议,保持长连接
                                5. 是通过一个状态码 101
                             */
                            pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
                            //自定义的handler,处理业务逻辑
                            pipeline.addLast(new MyTextWebSocketFrameHandler());
                        }
                    });

            //启动服务器
            ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
            channelFuture.channel().closeFuture().sync();

        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

hello.html

	<script>
        var socket;
        //判断当前浏览器是否支持websocket
        if(window.WebSocket){
            //go on
            socket = new WebSocket("ws://localhost:7000/hello");
            //相当于channelReado,ev 收到服务器端回送的消息
            socket.onmessage = function (ev) {
                var rt = document.getElementById("responseText");
                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 += "\n连接已关闭...";
            }

        }else{
            alert("当前浏览器不支持websocket");
        }

        //发送消息到服务器
        function send(message) {
            if(!window.socket){
                //先判断socket是否创建好
                return;
            }
            if(socket.readyState == WebSocket.OPEN){
                //通过socket发送消息
                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>

下一篇笔记:Netty入门学习笔记(四)

学习视频(p56-p72):https://www.bilibili.com/video/BV1DJ411m7NR?p=56

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值