Netty入门

Hello World

1 maven导入依赖

 <dependency>
     <groupId>io.netty</groupId>
     <artifactId>netty-all</artifactId>
     <version>4.1.63.Final</version>
</dependency>

2 编写服务器端

public class NettyServer {


    public static void main(String[] args) {

        //负责处理链接操作 1表示创建一个线程
        EventLoopGroup boss = new NioEventLoopGroup(1);
        //负责会话操作
        NioEventLoopGroup server = new NioEventLoopGroup(10);

        ServerBootstrap serverBootstrap = new ServerBootstrap();

        serverBootstrap.group(boss,server);

        serverBootstrap.channel(NioServerSocketChannel.class);

        //定义channel创建handler
        serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {

            @Override
            protected void initChannel(NioSocketChannel channel) throws Exception {
                System.out.println("建立channel--->"+channel.remoteAddress());

                //为channel添加管道,用于处理数据
                //添加解析为字符串的管道
                channel.pipeline().addLast(new StringDecoder());

                //添加接受到数据时的处理

                channel.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

                        System.out.println("接收到数据-->"+msg);
                        super.channelRead(ctx, msg);
                    }
                });

            }
        });
        serverBootstrap.bind(8888);
    }
}

3编写客户端

public class NettyClient {
    public static void main(String[] args) throws InterruptedException {


        Bootstrap bootstrap = new Bootstrap();

        bootstrap.group(new NioEventLoopGroup(1));

        bootstrap.channel(NioSocketChannel.class);

        bootstrap.handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                ChannelPipeline pipeline = socketChannel.pipeline();

                //添加将字符串转为bytebuf的
                pipeline.addLast(new StringEncoder());
            }
        });
        //设置连接地址
        ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8888));
        //开始链接,同步模式 阻塞至链接成功
        channelFuture.sync();

        //获取channel 写入数据
        Channel channel = channelFuture.channel();

        channel.writeAndFlush("hello World");

    }
}

先启动服务器,再启动客户端,服务器能成功收到客户端的消息。

netty内组件的使用

Nettty 有如下几个核心组件:

  • EventLoop

  • Channel

  • ChannelFuture

  • ChannelHandler

  • ChannelPipeline

EventLoopGroup

EventLoopGroup:事件循环对象是netty重要组件之一,类似于线程池用于处理任务

public static void main(String[] args) {
        
    	//最常用的 功能最全面,能处理 io任务,普通任务和定时任务
        //设置 loopGroup 内的线程数量为2(默认值为cpu核心数*2)
        EventLoopGroup loopGroup = new NioEventLoopGroup(2);
    
        // 获取下一时间循环对象,循环对象是按顺序获取的
        loopGroup.next(); //获取的是第1个 eventloop
        loopGroup.next(); //获取的是第2个 eventloop
        loopGroup.next();//获取的是第1个 eventloop
		//获取一个eventloop 用于执行任务
        EventLoop eventLoop = loopGroup.next();
        //往线程内添加任务
        eventLoop.submit( () -> {
            System.out.println("打印-----");
        });

        //执行一个延时任务 5秒后执行
        eventLoop.schedule( ()->{
            System.out.println("延时任务");
        },5, TimeUnit.SECONDS);

        //定时任务 interval 10秒后开始 每1秒打印一次
        eventLoop.scheduleAtFixedRate(()->{
            System.out.println("定时任务");
        },10,1,TimeUnit.SECONDS);

        //停止
        loopGroup.shutdownGracefully();
    }

Channel和ChannelFuture

Channel 是 Netty 网络操作抽象类,它除了包括基本的 I/O 操作,如 bind、connect、read、write 之外,还包括了 Netty 框架相关的一些功能,如获取该 Channe l的 EventLoop。

public class NettyClient {
    public static void main(String[] args) throws InterruptedException {

        NioEventLoopGroup loopGroup = new NioEventLoopGroup(1);

        Bootstrap bootstrap = new Bootstrap();

        bootstrap.group(loopGroup); //设置事件循环组

        bootstrap.channel(NioSocketChannel.class); //设置管道类型

        bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            pipeline.addLast(new StringEncoder()); //添加转换string的handler
            }
        });

        //连接服务器
        ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8888));

        //同步
        //ChannelFuture channelFuture = localhost.sync();
        //System.out.println("连接服务器成功");
        //start(channelFuture.channel(),loopGroup);

        //异步
        channelFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                System.out.println("-------与服务器建立连接成功!");
                start(channelFuture.channel(),loopGroup);
            }
        });
    }

    public static void start(Channel channel,EventLoopGroup  group) throws InterruptedException {

        Scanner scanner = new Scanner(System.in);

        String s = "";
        do{

            System.out.println("请输入===>");
            s = scanner.nextLine();
            channel.writeAndFlush(s); //向客户端写入并刷出数据
        }while(!"n".equals(s));

        System.out.println("正在与服务器断开连接");
        ChannelFuture close = channel.close(); //断开连接

//        close.sync(); //会阻塞直到连接断开 同步

        close.addListener((ChannelFutureListener) future -> { //设置断开连接成功后的回调
            System.out.println("服务器连接以断开!");
            group.shutdownGracefully();
        });
    }
}

Handler

我们可以通过获取channel的pipeline后为channel添加handler处理channel的各种事件,常用的Handler有:

ChannelInboundHandlerAdapter : 处理客户端方面的操作

ChannelOutboundHandlerAdapter: 处理服务器端方面的操作

ChannelInboundHandlerAdapter

public static void main(String[] args) {

        ServerBootstrap bootstrap = new ServerBootstrap();

        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup worker = new NioEventLoopGroup(10);

        bootstrap.group(boss,worker);

        bootstrap.channel(NioServerSocketChannel.class);

        bootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                //获取 pipeline
                ChannelPipeline pipeline = ch.pipeline();

                //将ByteBuf转换为String的handler(netty内置的)
                pipeline.addLast(new StringDecoder());

                //添加自定义的handler
                pipeline.addLast( new ChannelInboundHandlerAdapter(){
                    @Override
                    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
                        System.out.println("注册事件");
                    }
                    @Override
                    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
                        System.out.println("取消注册事件");
                    }
                    @Override
                    public void channelActive(ChannelHandlerContext ctx) throws Exception {
                        System.out.println("有新客户端连接接入。。。"+ctx.channel().remoteAddress());
                    }

                    @Override
                    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                        System.out.println("失去连接(客户端关闭)");
                    }

                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        System.out.println("接受到channel发来的数据-->"+msg);
                    }

                    @Override
                    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                        System.out.println("channel(客户端异常关闭)");
                    }
                });


            }
        });
    }

执行结果:

注册事件
有新客户端连接接入。。。/127.0.0.1:52061
接受到channel发来的数据-->a
channel(客户端异常关闭)
失去连接(客户端关闭)
取消注册事件

ChannelOutboundHandlerAdapter

pipeline.addLast(new ChannelOutboundHandlerAdapter(){
                    @Override
                    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                        System.out.println("服务器向客户端写入数据");
                        super.write(ctx,msg,promise);
                    }
                });

注意: 添加的handler是按顺序执行的,从写了handler的某一个方法后必须调用父类的方法才能够执行下一个handler。

ChannelInboundHandler是按照先后顺序执行的,ChannelOutboundHandler相反。

示例:

 public static void main(String[] args) {
        ServerBootstrap bootstrap = new ServerBootstrap();

        NioEventLoopGroup boss = new NioEventLoopGroup(1);

        NioEventLoopGroup worker = new NioEventLoopGroup(10);

        bootstrap.group(boss,worker);
        bootstrap.channel(NioServerSocketChannel.class);

        bootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {

                ChannelPipeline pipeline = ch.pipeline();

                pipeline.addLast(new StringDecoder());

                pipeline.addLast(new ChannelInboundHandlerAdapter(){
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        System.out.println("handler[1] 接受数据-->"+msg);
                        //对数据进行修改。
                        super.channelRead(ctx, msg+"__abc"); //必须调用父类的该方法才能执行下一个handler
                    }
                });
                pipeline.addLast(new ChannelInboundHandlerAdapter(){
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        System.out.println("handler[2] 接受数据-->"+msg);
                        super.channelRead(ctx, msg); //必须调用父类的该方法才能执行下一个handler
                    }
                });
                pipeline.addLast(new ChannelInboundHandlerAdapter(){
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        System.out.println("handler[3] 接受数据-->"+msg);
                        ctx.channel().writeAndFlush("aabbcc");
                        super.channelRead(ctx, msg); //必须调用父类的该方法才能执行下一个handler
                    }
                });

                pipeline.addLast(new ChannelOutboundHandlerAdapter(){
                    @Override
                    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                        System.out.println("handler[4] 向客户端写入数据--->"+msg);
                        super.write(ctx, msg, promise);
                    }
                });

                pipeline.addLast(new ChannelOutboundHandlerAdapter(){
                    @Override
                    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                        System.out.println("handler[5] 向客户端写入数据--->"+msg);
                        super.write(ctx, msg, promise);
                    }
                });

                pipeline.addLast(new ChannelOutboundHandlerAdapter(){
                    @Override
                    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                        System.out.println("handler[6] 向客户端写入数据--->"+msg);
                        super.write(ctx, msg, promise);
                    }
                });
            }
        });
        bootstrap.bind(8888);
    }

执行结果:可以看到OutBoundHandler是从后往前运行的

handler[1] 接受数据-->hello
handler[2] 接受数据-->hello__abc
handler[3] 接受数据-->hello__abc
handler[6] 向客户端写入数据--->aabbcc
handler[5] 向客户端写入数据--->aabbcc
handler[4] 向客户端写入数据--->aabbcc

ByteBuf

public static void main(String[] args) {

        //创建 堆内存
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);
        //创建 直接内存
        //直接内存读写效率高,但是创建和销毁的速度慢,不受堆内存管理,要注意关闭
        ByteBuf byteBuf1 = ByteBufAllocator.DEFAULT.directBuffer(10);

        //写入boolean
        byteBuf.writeBoolean(true);
        //写入字符串
        byteBuf.writeCharSequence("hello", StandardCharsets.UTF_8);

        //写入byte数组
        byteBuf.writeBytes("aa".getBytes());
        //写入short
        byteBuf.writeShort(1);
        //写入int
        byteBuf.writeInt(10);
        byteBuf.writeLong(1L);
        byteBuf.writeFloat(1F);
        byteBuf.writeDouble(1D);
    }

处理粘包半包

FixedLengthFrameDecoder

定长结构解析器可以分割客户端发来的数据,只有数据达到指定长度后才会触发一次操作

代码示例:

public static void main(String[] args) throws InterruptedException {
        Bootstrap bootstrap = new Bootstrap();

        bootstrap.group(new NioEventLoopGroup(1));

        bootstrap.channel(NioSocketChannel.class);

        bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast(new StringEncoder());
            }
        });
        ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8888));
        channelFuture.sync();
        Channel channel = channelFuture.channel();

        channel.writeAndFlush("aaaaa"); //写入5个
        channel.writeAndFlush("bbbbb"); //写入5个
        channel.writeAndFlush("cccccd");//写入6个
        channel.writeAndFlush("dddd");  //写入4个
    }

server端

 public static void main(String[] args) {

        ServerBootstrap bootstrap = new ServerBootstrap();
        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup worker = new NioEventLoopGroup(1);
        bootstrap.group(boss,worker);
        bootstrap.channel(NioServerSocketChannel.class);

        bootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();

                //必须放在第一位 5表示5个字符
                pipeline.addLast(new FixedLengthFrameDecoder(5));
                pipeline.addLast(new StringDecoder());

                pipeline.addLast(new ChannelInboundHandlerAdapter(){

                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        System.out.println("获取到客户端消息-->"+msg);
                    }
                });
            }
        });
        bootstrap.bind(8888);
    }

控制台打印结果: 每五个数据打印一次

获取到客户端消息-->aaaaa
获取到客户端消息-->bbbbb
获取到客户端消息-->ccccc
获取到客户端消息-->ddddd

LineBasedFrameDecoder和DelimiterBaseFrameDecoder

以分隔符的形式分割字符

LineBasedFrameDecoder:以换行符(\n)分割

DelimiterBaseFrameDecoder: 自定义分隔符

代码示例:

server端

public static void main(String[] args) {

        ServerBootstrap bootstrap = new ServerBootstrap();

        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup worker = new NioEventLoopGroup(1);
        bootstrap.group(boss,worker);

        bootstrap.channel(NioServerSocketChannel.class);

        bootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();

                //以$_$分割
                ByteBuf delimiter = Unpooled.copiedBuffer("$_$".getBytes());
                //必须放在第一位 10 代表如果10个字节内没有换行符则抛出 TooLongFrameException
                pipeline.addLast(new DelimiterBasedFrameDecoder(10,delimiter));
                pipeline.addLast(new StringDecoder());

                pipeline.addLast(new ChannelInboundHandlerAdapter(){

                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        System.out.println("获取到客户端消息-->"+msg);
                    }
                });
            }
        });

        bootstrap.bind(8888);
    }

client端

public static void main(String[] args) throws InterruptedException {
        Bootstrap bootstrap = new Bootstrap();

        bootstrap.group(new NioEventLoopGroup(1));

        bootstrap.channel(NioSocketChannel.class);

        bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast(new StringEncoder());
            }
        });

        ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8888));

        channelFuture.sync();

        Channel channel = channelFuture.channel();

        channel.writeAndFlush("张张$_$"); //写入五个字节
        channel.writeAndFlush("bbbbbbb$_$");//写入五个字节
        channel.writeAndFlush("cccccd$_$");//写入6个字节
        channel.writeAndFlush("dddd$_$");//写入4个字节
    }

LengthFieldBasedFrameDecoder

指定消息的长度分割数据

代码示例:

server端

public static void main(String[] args) {

        ServerBootstrap bootstrap = new ServerBootstrap();

        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup worker = new NioEventLoopGroup(1);
        bootstrap.group(boss,worker);

        bootstrap.channel(NioServerSocketChannel.class);

        bootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();

                //必须放在第一位 表示最大长度为1024字节
                //第一个0,4表示从下标第0个到第3个为消息长度值
                //最后的04表示去掉4个字节长度标识(int4个字节)
                pipeline.addLast(new LengthFieldBasedFrameDecoder(1024,0,4,0,4));
                pipeline.addLast(new StringDecoder());

                pipeline.addLast(new ChannelInboundHandlerAdapter(){

                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        System.out.println("获取到客户端消息-->"+msg);
                    }
                });
            }
        });

        bootstrap.bind(8888);
    }

clent端

public static void main(String[] args) throws InterruptedException {
        Bootstrap bootstrap = new Bootstrap();

        bootstrap.group(new NioEventLoopGroup(1));

        bootstrap.channel(NioSocketChannel.class);

        bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast(new StringEncoder());
            }
        });


        ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8888));

        channelFuture.sync();

        Channel channel = channelFuture.channel();

        send(channel,"张张张张张");
        send(channel,"cccccd");
        send(channel,"dddd");

    }

    public static void send(Channel channel,String msg){
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer();
        byteBuf.writeInt(msg.getBytes().length); //发消息之前先写内容长度
        byteBuf.writeCharSequence(msg, StandardCharsets.UTF_8);//写入内容
        channel.writeAndFlush(byteBuf); //发送到服务器
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值