Netty4

Netty4.1官方在线参考API
Netty4.1官方在线参考文档示例
Netty官方架构概述
官方示例地址

Netty介绍

可参考这个 文档

Netty是一个NIO客户端服务器框架,可快速轻松地开发网络应用程序,例如协议服务器和客户端。它极大地简化和简化了网络编程,例如TCP和UDP套接字服务器。
“快速简便”并不意味着最终的应用程序将遭受可维护性或性能问题的困扰。Netty经过精心设计,结合了许多协议(例如FTP,SMTP,HTTP以及各种基于二进制和文本的旧式协议)的实施经验。结果,Netty成功地找到了一种无需妥协即可轻松实现开发,性能,稳定性和灵活性的方法

性能

  • 更高的吞吐量,更低的延迟
  • 减少资源消耗
  • 减少不必要的内存复制

官方示例

引入netty依赖

<dependencies>

        <dependency>

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

写一个服务端Handler

第一步:这个服务端通道类需要去继承这个 ChannelInboundHandlerAdapter类
第二步:重载这个ChannelInboundHandlerAdapter类下的channelRead


/**
 * @PackageName : com.rzk.official
 * @FileName : NettyServerHandler
 * @Description :处理服务端通道
 *                  自定义的一个Handler  需要继续使用netty 规定好的某个HandlerAdapter
 *                  自定义一个Handle,才能称为一个handle
 * @Author : rzk
 * @CreateTime : 2021/2/8 10:54
 * @Version : 1.0.0
 */
@ChannelHandler.Sharable
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    /**
     *
     * @param ctx:上下文对象,含有管道pouoekube,通道
     * @param msg:客户端发送的数据消息
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //调用write(Object)来逐字写入接收到的消息。请注意,与丢弃示例不同,我们没有释放接收到的消息。这是因为Netty在它被写入网络时为你释放了它。
        //write(Object)不会将消息写入到网络中。它在内部进行缓冲,然后通过ctx.flush()将其刷新到网络上
        //两个操作可以结合成一个writeAndFlush
        //        //将 msg 装成一个ByteBuf
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("客户端发送消息是"+buf.toString(CharsetUtil.UTF_8));
        System.out.println("客户端地址:"+ ctx.channel().remoteAddress());
    }

    /**
     * 这个方法就是将服务器处理的数据发送给客户端
     * channelRead去读取数据的时候,这个方法会只读取一次,数据多长多短这个方法也只会读取一次
     * @param ctx
     * @throws Exception
     */
    @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 {
        //当抛出异常时断开连接
        cause.printStackTrace();
        ctx.close();
    }
}

编写服务端


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;

/**
 * @PackageName : com.rzk.official
 * @FileName : NettyServer
 * @Description : 服务端
 * @Author : rzk
 * @CreateTime : 2021/2/8 10:54
 * @Version : 1.0.0
 */
public class NettyServer {
    //端口号
    private int port;
    //IP地址
    private String host;

    public NettyServer(String host,int port) {
        this.host = host;
        this.port = port;
    }
    public void run() throws InterruptedException {
        /*
            bossGroup:只是处理客户端连接请求
            workerGroup:连接之后的操作都交给他,处理已接受连接的流量。有多少线程被使用,如何映射到所创建的通道的
         */
        //1.创建两个 NioEventLoopGroup
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
        //2.创建服务端的启动对象 配置参数
        ServerBootstrap bootstrap = new ServerBootstrap();
        //使用链式编程来进行处理设置
        bootstrap.group(bossGroup,workerGroup)
                //3.使用NioServerSocketChannel 作为服务端的通道实现 实例化新通道以接受传入的连接  !注意  client不是Server
                .channel(NioServerSocketChannel.class)
                //4.配置新通道
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        //这里的NettyServerHandler是处理服务端通道类
                        ch.pipeline().addLast(new NettyServerHandler());
                    }
                })
                //5.设置线程对列等待连接个数, option()用于接受传入连接的NioServerSocketChannel
                .option(ChannelOption.SO_BACKLOG,128)
                //6.设置保持连接状态,childOption()用于父ServerChannel接受的通道
                .childOption(ChannelOption.SO_KEEPALIVE,true);

               //7.绑定一个端口并且同步,生成一个ChannelFuture 对象
                ChannelFuture future = bootstrap.bind(host, port).sync();

                //等待,直到服务套接字关闭。
                //在本例中,这种情况不会发生,但您可以优雅地这样做
                //对关闭通道进行监听
                future.channel().closeFuture().sync();
            } finally {
                //优雅地关闭
                workerGroup.shutdownGracefully();
                bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        String host = "127.0.0.1";
        int port = 8889;

        if (args.length > 0){
            port = Integer.parseInt(args[0]);
        }
        new NettyServer(host,port).run();
    }
}

编写一个客户端

Netty中服务端和客户端之间最大也是唯一的区别是使用了不同的引导和通道实现


/**
 * @PackageName : com.rzk.official
 * @FileName : NettyClient
 * @Description : 客户端
 * @Author : rzk
 * @CreateTime : 2021/2/8 14:29
 * @Version : 1.0.0
 */
public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        String host = "127.0.0.1";
        int port = 8889;
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            //1.创建客户端启动对象
            // Bootstrap与ServerBootstrap类似,不同之处是它适用于非服务器通道,比如客户端或无连接通道
            Bootstrap bootstrap = new Bootstrap();
            //2.如果只指定一个EventLoopGroup,它将同时用作boss组和worker组。不过,boss worker并不用于客户端
            bootstrap.group(workerGroup)
                    //3.这里使用NioSocketChannel来创建客户端通道,而不是NioServerSocketChannel
                    .channel(NioSocketChannel.class)
                    //4.ServerBootstrap时不同,这里我们没有使用childOption(),因为客户端SocketChannel没有父类。
                    .option(ChannelOption.SO_KEEPALIVE,true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new NettyClientHandler());
                        }
                    });

            //5.启动客户端  应该调用connect()方法,而不是bind()
            ChannelFuture future = bootstrap.connect(host,port).sync();

            //等待对关闭通道进行监听,直到连接关闭。
            future.channel().closeFuture().sync();
        } finally {
            //优雅地关闭
            workerGroup.shutdownGracefully();
        }
    }
}

客户端代码与服务端代码实际上并没有什么不同

编写客户端Handler

What about the ChannelHandler implementation? It should receive a 32-bit integer from the server, translate it into a human-readable format, print the translated time, and close the connection:
怎么编写客户端Handler呢

/**
 * @PackageName : com.rzk.official
 * @FileName : NettyClientHandler
 * @Description :
 * @Author : rzk
 * @CreateTime : 2021/2/8 15:05
 * @Version : 1.0.0
 */
public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    private ByteBuf buf;

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        buf = ctx.alloc().buffer(4); // (1)
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) {
        //ChannelHandler有两个生命周期侦听器方法:handlerAdded()和handlerRemoved()。
        // 您可以执行任意(de)初始化任务,只要它不会长时间阻塞。
        buf.release(); // (1)
        buf = null;
    }

    //当通道就绪就会触发该方法
    @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) {
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("服务器回复的消息:"+buf.toString(StandardCharsets.UTF_8));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //当抛出异常时断开连接
        cause.printStackTrace();
        ctx.close();
    }
}

启动服务端

启动客户端

返回服务端可以看到打印出客户端发送过来的数据

官方说这个处理程序有时会拒绝引发IndexOutOfBoundsException
一个关于套接字缓冲区的小警告,因为底层使用IO流,使用的是字节传输
即使将两条消息作为两个独立的包发送,操作系统也不会将它们视为两条消息,而只是一串字节

原文地址:https://www.cnblogs.com/rzkwz/p/14389588.html