Netty入门(NIO编程和Netty客户端服务端示例)

1.NIO编程

1.1 什么是NIO编程

一种理解是New I/O ,原因是相较之前的I/O类库是新增的。更多的人喜欢称之为非阻塞I/O(Non-block I/O),由于非阻塞I/O更能体现NIO的特点,所以后续NIO都指的是非阻塞I/O

1.2NIO类库介绍

1.缓冲区Buffer

	在面向流的I/O中,可以直接写入或者将数据直接读取到Stream对象中,在NIO库中,所有数据都是用缓冲区来处理的。在写入数据的时候,也是写入到缓冲区的。

缓冲区实质是一个数组,通常是一个字节数组,缓冲区提供了对数据的结构化访问以及维护读写limit等信息

2.通道channel

	通道与流不同之处在于通道是双向的,流知识在一个方向上面移动(一个流必须是InputStream或者OutputStream的子类),而通道可以用于读、写或者二者同时进行。
	因为CHannel是双全工的,所以它比流更好的映射底层操作系统的API。特别是在UNIX网络编程模型中,底层的操作系统都是全双工的,同时支持读写操作。
	Channel可以分为两大类:用于网络读写的SelectableChannel和用于文件操作的FileChannel。ServerSocket和SocketChannel都是SelectableChannel的子类。

3.多路复用器Selector

	多路复用器提供选择已经就绪的任务的能力。简单的说,Selector会不断的轮询地注册在上面的Channel,如果某个 Channel上面发生读或者写事件,这个Channel就会处于就绪状态,会被Selector轮询出来

4.NIO client和NIO server举例

public class NioClient {
    
    
    public static void client() throws IOException {
    
    
        // 获取通道
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
        FileChannel inChannel = FileChannel.open(Paths.get("C:\\Users\\renyun\\Desktop\\图片\\logo2.png"), StandardOpenOption.READ);
        // 分配指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);
        // 读取本地文件,并发送到服务端
        while (inChannel.read(buf) != -1) {
    
    
            // 切换到读数据模式
            buf.flip();
            // 将缓冲区的数据写入管道
            sChannel.write(buf);
            // 清空缓冲区
            buf.clear();
        }

        //关闭通道
        inChannel.close();
        sChannel.close();
    }
    public static void main(String[] args) {
    
    
        new Thread(() -> {
    
    
            try {
    
    
                server();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }, "t1").start();
        try {
    
    
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        new Thread(() -> {
    
    
            try {
    
    
                client();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }, "t2").start();
    }
}
public class NioServer {
    
    
    /**
     * 服务端
     */
    public static void server() throws IOException {
    
    
        // 获取通道
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        FileChannel fileChannel = FileChannel.open(Paths.get("C:\\Users\\renyun\\Desktop\\图片\\logo2.png"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
        // 绑定端口号
        ssChannel.bind(new InetSocketAddress(9898));
        // 获取客户端连接的通道
        SocketChannel socketChannel = ssChannel.accept();
        // 分配指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);
        // 读取客户端的数据,并保存到本地
        while(socketChannel.read(buf) != -1) {
    
    
            // 切换成读模式
            buf.flip();
            // 写入
            fileChannel.write(buf);
            // 清空缓冲区
            buf.clear();
        }
        // 关闭通道
        ssChannel.close();
        socketChannel.close();
        fileChannel.close();
    }

}

5为什么不选择原生NIO编程的原因

1.NIO的类库和API繁杂,使用麻烦
2.需要具备其他额外技能,例如熟悉多线程编程,NIO涉及到Reactor模式,必须对多线程和网络编程非常熟悉。
3.可高兴能力不起,工作量和难度都非常大
4.JDK NIO存在bug

2 Netty NIO入门指南

有Netty4和Netty两个版本,最好还是选择Netty4。第一步搭建工程,可以直接引用Maven的依赖包,也可以直接卸载jar包复制到lib目录里。
在这里插入图片描述

<!--netty依赖-->
    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-all</artifactId>
      <version>4.1.20.Final</version>
    </dependency>
  </dependencies>

2.1Netty入门服务端开发


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

        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup worker = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();
        try {
    
    
            bootstrap.group(bossGroup, worker)  //设置两个线程组
                    .channel(NioServerSocketChannel.class)  //作为服务器的通道实现
                    .option(ChannelOption.SO_BACKLOG,128) //设置线程队列等待连接的个数
                    .childOption(ChannelOption.SO_KEEPALIVE,true) //设置保持活动连接状态
                    .childHandler(new ChannelInitializer<SocketChannel>() {
    
     //创建一个通道初始化对象
                        //给pipline 设置处理器
                        @Override
                        protected void initChannel(SocketChannel channel) throws Exception {
    
    
                            channel.pipeline().addLast(new NettyServerHandler());

                        }

                    });  //
            System.out.println("服务器已经准备好了");
            //绑定一个端口并且同步处理,生成一个ChannelFuture对象

            ChannelFuture cf = bootstrap.bind(6668).sync();

            //对关闭通道进行监听 异步模型 channelFuture
            cf.channel().closeFuture().sync();
        }finally {
    
    
            bossGroup.shutdownGracefully();
            worker.shutdownGracefully();
        }



    }
}

介绍重点类
NioEventLoopGroup 专门用于网络事件的处理,实际上就是Reator线程组,创建两个的原因一个用于服务端接受客户端的连接,另一个用于SocketChannel的网络读写。
ServerBootstrap是Netty用于启动服务端的辅助启动类,目的降低服务端的开发复杂程度,通过调用group方法吧两个线程组当做入参传递到ServerBootstrap里面
NioServerSocketChannel对应的JDK NIO类库里面的ServerSocketChannel类

public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    
    
    //读取数据实际(读取客户端消息) ChannelHandlerContext上下文对象
    // Object msg 客户端发送的消息 默认是object形式的
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
        System.out.println("server ctx ="+ctx);
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("客户端发送消息"+buf.toString(CharsetUtil.UTF_8));
        System.out.println("客户端地址"+ctx.channel().remoteAddress());
//        super.channelRead(ctx, msg);
        //将msg转成bytebuffer
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
    
    
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello 客户端",CharsetUtil.UTF_8));

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

NettyServerHandler 继承自ChannelInboundHandlerAdapter ,对网络事件进行读写操作;
重点关注channelRead和exceptionCaught方法。
ByteBuf buf = (ByteBuf) msg;将msg转为Netty 的ByteBuf对象。ByteBuf的方法可以获取缓冲区的字节数
通过上述的代码可以看出,相比较于传统的JDK NIO原生类库的服务端,代码量大大减少,开发量减少了很多。

2.2Netty客户端开发

public class NettyClient {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        //客户端需要仅一个事件循环组
        EventLoopGroup eventExecutors = new NioEventLoopGroup();

        try{
    
    
            //创建客户端启动对象
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventExecutors)
                    .channel(NioSocketChannel.class) //设置客户端通道的处理
                    .handler(new ChannelInitializer<SocketChannel>() {
    
     //创建一个通道初始化对象
                        //给pipline 设置处理器
                        @Override
                        protected void initChannel(SocketChannel channel) throws Exception {
    
    
                            channel.pipeline().addLast(new NettyClientHandler());

                        }

                    });
            System.out.println("客户端已经好了");
            ChannelFuture cf = bootstrap.connect("127.0.0.1", 6668).sync();
            cf.channel().closeFuture().sync();
        }finally {
    
    
            eventExecutors.shutdownGracefully();
        }
    }
}
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    
    

    //通道就绪就会触发该方法
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
        System.out.println("client" +ctx);
        ctx.writeAndFlush(Unpooled.copiedBuffer("HELLO SERVER", CharsetUtil.UTF_8));
    }

    //当通道有读取事件时会触发
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
        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();
    }
}

客户端的服务相较于服务端较为简单;

2.3运行和调试

在这里插入图片描述
在这里插入图片描述
需要指出的是,本例程没有考虑读半包的处理,对于功能演示或者测试没有问题,但是如果进行压力测试,就不能正常工作,就需要处理半包相关的知识点。利用一些方法来解决TCP粘包和拆包。

猜你喜欢

转载自blog.csdn.net/qq_21561833/article/details/122775815