Netty入门自我学习

​​​​​​cjava - 超详细Netty入门,看这篇就够了! - 个人文章 - SegmentFault 思否

服务端处理步骤

1. 定义线程组

2. 定义启动服务类

3. 初始化事件处理器

4. 定义事件处理器的处理逻辑

5. 绑定端口、启动服务

客户端处理步骤

1. 定义线程组

2. 定义启动类

3. 初始化事件处理器

4. 定义事件处理器的处理逻辑

5. 绑定端口、启动客户端

相同点

1. 都是EventLoopGroup和NioEventLoopGroup。

2. 都有group,channel,handler(ChannelInitializer方法)

不同点:

区别点客户端服务端
EventLoopGroup个数12
工厂类NioSocketChannelNioServerSocketChannel
启动类BootstrapServerBootstrap
绑定方法connectbind

代码示例

package com.example.testnetty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyServer {

    public static void main(String[] args) throws Exception{
        // 创建两个线程组bossGroup、workerGroup
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            // 创建服务端的启动对象,设置参数
            ServerBootstrap bootstrap = new ServerBootstrap();
            // 设置两个线程组bossGroup、workerGroup
            bootstrap.group(bossGroup, workerGroup)
                    // 设置服务端通道实现类型
                    .channel(NioServerSocketChannel.class)
                    // 设置线程队列得到的连接个数
                    /**
                     * SO_BACKLOG Socket参数,服务端接受连接的队列长度,如果队列已满,客户端连接将被拒绝。默认值,Windows为200,其他为128。
                     */
                    .option(ChannelOption.SO_BACKLOG, 128)
                    // 设置保持活动连接状态
                    /**
                     * SO_RCVBUF Socket参数,TCP数据接收缓冲区大小。
                     * TCP_NODELAY TCP参数,立即发送数据,默认值为Ture。
                     * SO_KEEPALIVE Socket参数,连接保活,默认值为False。启用该功能时,TCP会主动探测空闲连接的有效性。
                     */
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    // 使用匿名内部类初始化通道对象
                    /**
                     * ChannelPipeline是Netty处理请求的责任链,ChannelHandler则是具体处理请求的处理器。实际上每一个channel都有一个处理器的流水线。
                     */
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            // 给pipeline管道设置自定义的处理器
                            // 在一个Channel中,只有一个ChannelPipeline。
                            // 该pipeline在Channel被创建的时候创建。
                            // ChannelPipeline包含了一个ChannelHander形成的列表
                            // 且所有ChannelHandler都会注册到ChannelPipeline中。
                            socketChannel.pipeline().addLast(new MyserverHandler());
                        }
                    });
            // 给workerGroup的EventLoop对应的管道设置处理器
            System.out.println("服务端准备就绪");
            // 绑定端口号,启动服务端
            // 提供用于服务端或者客户端绑定服务器地址和端口号,默认是异步启动。如果加上sync()方法则是同步。
            ChannelFuture channelFuture = bootstrap.bind(6666).sync();
            // 对关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        }finally {
            // 释放掉所有的资源,包括创建的线程 释放底层资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
package com.example.testnetty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelOption;
import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.EventExecutorGroup;

import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * ChannelInboundHandlerAdapter(入站处理器)
 * ChannelOutboundHandler(出站处理器)
 * 入站指的是数据从底层java NIO Channel到Netty的Channel。
 * 出站指的是通过Netty的Channel来操作底层的java NIO Channel
 *
 *
 * MyserverHandler--给pipeline管道设置自定义的处理器--
 *
 *
 * 自定义Handler继承netty规定好的HandlerAdapter
 * 才能被netty框架所关联,类似springmvc的适配器模式
 * 2 * @Author: sxd
 * 3 * @Date: 2023/2/17 14:55
 *
 *
 *
 * ChannelInboundHandlerAdapter处理器常用的事件有:
 * 注册事件 fireChannelRegistered。
 * 连接建立事件 fireChannelActive。
 * 读事件和读完成事件 fireChannelRead、fireChannelReadComplete。
 * 异常通知事件 fireExceptionCaught。
 * 用户自定义事件 fireUserEventTriggered。
 * Channel 可写状态变化事件 fireChannelWritabilityChanged。
 * 连接关闭事件 fireChannelInactive。
 *
 *
 *
 * ChannelOutboundHandler处理器常用的事件有:
 * 端口绑定 bind。
 * 连接服务端 connect。
 * 写事件 write。
 * 刷新时间 flush。
 * 读事件 read。
 * 主动断开连接 disconnect。
 * 关闭 channel
 * 事件 close。
 */
public class MyserverHandler extends ChannelInboundHandlerAdapter {



    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // 发送消息给客户端
        ctx.writeAndFlush(Unpooled.copiedBuffer("服务端已收到一个到消息,并给你发送一个?", CharsetUtil.UTF_8));
    }

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


    // helloworld示例
//    @Override
//    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//        // 获取客户端发来的消息
//        ByteBuf byteBuf = (ByteBuf) msg;
//        System.out.println("收到客户端"+ctx.channel().remoteAddress()+"发送的消息:"+byteBuf.toString(StandardCharsets.UTF_8));
//    }

//    /**
//     * 如果Handler处理器有一些长时间的业务处理,可以交给taskQueue异步处理
//     */
//
//    @Override
//    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//        //获取到线程池eventLoop,添加线程,执行
//        ctx.channel().eventLoop().execute(new Runnable() {
//            @Override
//            public void run() {
//                try {
//                    //长时间操作,不至于长时间的业务操作导致Handler阻塞
//                    Thread.sleep(1000);
//                    System.out.println("长时间的业务处理");
//                } catch (Exception e) {
//                    e.printStackTrace();
//                }
//            }
//        });
//    }
    /**
     * scheduleTaskQueue延时任务队列
     */

        @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

            /**
             * channel为用户提供:
             *
             * 通道当前的状态(例如它是打开?还是已连接?)
             * channel的配置参数(例如接收缓冲区的大小)
             * channel支持的IO操作(例如读、写、连接和绑定)
             * 以及处理与channel相关联的所有IO事件和请求的ChannelPipeline。
             *
             *
             * 获取channel的状态
             * boolean isOpen(); //如果通道打开,则返回true
             * boolean isRegistered();//如果通道注册到EventLoop,则返回true
             * boolean isActive();//如果通道处于活动状态并且已连接,则返回true
             * boolean isWritable();//当且仅当I/O线程将立即执行请求的写入操作时,返回true。
             */

            // 获取单条配置信息使用getOption()
            // 由于在Myserver中 .option(ChannelOption.SO_BACKLOG, 128) 所以option=128
//            Integer option = ctx.channel().config().getOption(ChannelOption.SO_BACKLOG);

            // 获取多条配置信息,使用getOptions()
//            Map<ChannelOption<?>, Object> options = ctx.channel().config().getOptions();
//            for (Map.Entry<ChannelOption<?>, Object> entry : options.entrySet()) {
//                System.out.println(entry.getKey() + " : " + entry.getValue());
//            }
//            SO_REUSEADDR : false
//            WRITE_BUFFER_LOW_WATER_MARK : 32768
//            WRITE_BUFFER_WATER_MARK : WriteBufferWaterMark(low: 32768, high: 65536)
//            SO_BACKLOG : 128
//            以下省略...
            //获取到线程池eventLoop,添加线程,执行
            ctx.channel().eventLoop().schedule(new Runnable() {
            @Override
            public void run() {
                try {
                    // 长时间不操作,不至于长时间的业务操作导致Handler阻塞
                    Thread.sleep(1000);
                    System.out.println("长时间业务处理");
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        },5, TimeUnit.SECONDS);
    }
}


/**
 * channel支持的io操作
 */

//    @Override
//    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//        ctx.channel().writeAndFlush(Unpooled.copiedBuffer("这波啊,这波是肉蛋葱鸡~", CharsetUtil.UTF_8));
// 客户端控制台:收到服务端/127.0.0.1:6666的消息:这波啊,这波是肉蛋葱鸡~



//        获取ChannelPipeline对象
//        ChannelPipeline pipeline = ctx.channel().pipeline();
//往pipeline中添加ChannelHandler处理器,装配流水线
//        pipeline.addLast(new MyServerHandler());
//    }


package com.example.testnetty;

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;

/**
 * 2 * @Author: sxd
 * 3 * @Date: 2023/2/17 15:15
 * 4
 */
public class MyClient {
    public static void main(String[] args) throws Exception{
        // 创建线程组
        NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
        try {
            // 创建bootstrap对象,配置参数
            Bootstrap bootstrap = new Bootstrap();
            // 设置线程组
            bootstrap.group(eventExecutors)
                    // 设置客户端的通道实现类型
                    .channel(NioSocketChannel.class)
                    //使用匿名内部类初始化通道
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            // 添加客户端的通道处理器
                            ch.pipeline().addLast(new MyClientHandler());
                        }
                    });
            System.out.println("客户端准备就绪");
            /**
             * ChannelFuture提供操作完成时一种异步通知的方式
             * 一般在Socket编程中,等待响应结果都是同步阻塞的,而Netty则不会造成阻塞,
             * 因为ChannelFuture是采取类似观察者模式的形式进行获取结果
             */
            // 连接服务端
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1",6666).sync();
            // 对通道关闭进行监听
            channelFuture.channel().closeFuture().sync();
        }finally {
            // 关闭线程组
            eventExecutors.shutdownGracefully();
        }
    }
}
package com.example.testnetty;

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

/**
 * 2 * @Author: sxd
 * 3 * @Date: 2023/2/17 15:28
 * 4
 */
public class MyClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 发送消息到服务端
        ctx.writeAndFlush(Unpooled.copiedBuffer("歪比巴卜~茉莉", CharsetUtil.UTF_8));
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 接收服务端发送过来的消息
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println("收到服务端"+ctx.channel().remoteAddress()+"的消息"+byteBuf.toString(CharsetUtil.UTF_8));
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

那山川

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值