netty(一)--入门应用

简介

 本案例会从两个简单的demo入手,从无到有手写一个netty简单的服务端和客户端,体验一下netty的强大和简便。
案例源码:https://github.com/itwwj/netty-learn.git 中的 netty-day02-introduction项目

一、服务端

1.1启动类

/**
 * netty服务端启动类
 *
 * @author jie
 */
public class NettyServer {
    /**
     * 设置端口号
     */
    private static int port = 1100;

    public static void main(String[] args) {
        //用于处理服务端接受客户端连接
        EventLoopGroup boosGroup = new NioEventLoopGroup();
        //用于SocketChannel的网络读写操作
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度
            ServerBootstrap b = new ServerBootstrap();
            b.group(boosGroup, workerGroup)
                    //对应JDK NIO类库中的ServerSocketChannel
                    .channel(NioServerSocketChannel.class)
                    //配置NioServerSocketChannel的TCP参数
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    //绑定I/O的事件处理类
                    .childHandler(new MyServerChannelInitializer());
            //调用它的bind操作监听端口号,调用同步阻塞方法sync等待绑定操作完成
            ChannelFuture f = b.bind(port).sync();
            //异步操作的通知回调
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //优雅的退出,释放线程池资源
            boosGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

说明:
NioEventLoopGroup: 在代码的一开始创建了两个NioEventLoopGroup实例。NioEventLoopGroup是线程组,它包含了一组NIO线程,专门用于网络事件的处理,实际上它们就是Reactor线程组.这里创建两个的原因是一个用于服务端接受客户端连接,一个用于SocketChannel的网络读写操作。
ServerBootstrap: 是用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度,ServerBootstrap的group方法,将两个NIO线程组当做参数传递到ServerBootstrap中,接着设置创建Channel为NioServerSocketChannel,它的功能是对应JDK NIO类库中的ServerSocketChannel,然后配置NioServerSocketChannel的TCP参数,将他的backlog设置为1024,最后绑定I/O的事件处理类:MyServerChannelInitializer,主要作用为处理消息编解码及消息的粘包及io操作。
 启动辅助类完成以后,调用它的bind操作监听端口号,随后调用同步阻塞方法sync等待绑定操作完成,完成之后会返回一个ChannelFuture,用于异步操作的通知回调

1.2事件处理类:

/**
 *
 * MyChannelInitializer的主要目的是为程序员提供了一个简单的工具,用于在某个Channel注册到EventLoop后,对这个Channel执行一些初始
 * 化操作。ChannelInitializer虽然会在一开始会被注册到Channel相关的pipeline里,但是在初始化完成之后,ChannelInitializer会将自己
 * 从pipeline中移除,不会影响后续的操作。
 * @author jie
 */
public class MyServerChannelInitializer extends ChannelInitializer<SocketChannel> {
    /**
     * 这个方法在Channel被注册到EventLoop的时候会被调用
     * @param socketChannel
     * @throws Exception
     */
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        System.out.println("=========有客户端连接服务器=========");
        System.out.println("ip:"+socketChannel.localAddress().getHostString()+"         port:"+socketChannel.localAddress().getPort());
        socketChannel.pipeline().addLast(new MyServerHandler());

    }
}

事件处理类,继承了抽象类ChannelInitializer ,类图:
在这里插入图片描述
 MyChannelInitializer的主要目的是为程序员提供了一个简单的工具,用于在某个Channel注册到EventLoop后,对这个Channel执行一些初始 化操作。ChannelInitializer虽然会在一开始会被注册到Channel相关的pipeline里,但是在初始化完成之后,ChannelInitializer会将自己从pipeline中移除,不会影响后续的操作。
 继承ChannelInitializer需要重写方法 initChannel ,initChannel 在Channel被注册到EventLoop的时候会被调用,可以在initChannel 中通过对socketChannel的操作进行对消息的编码、解码、以及自定义消息的处理。

1.3 事件操作类

/**
 * 操作类
 *
 * @author jie
 */
public class MyServerHandler extends ChannelInboundHandlerAdapter {
    /**
     * 当客户端主动连接服务端,通道活跃后触发
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //在接收到客户端连接的时候通知客户端连接成功
        String msg = "与服务端建立连接成功" + new Date();
        ByteBuf buf = Unpooled.buffer(msg.getBytes().length);
        buf.writeBytes(msg.getBytes("utf-8"));
        ctx.writeAndFlush(buf);
    }

    /**
     * 通道有消息触发
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //接收msg消息
        ByteBuf buf = (ByteBuf) msg;
        byte[] msgByte = new byte[buf.readableBytes()];
        buf.readBytes(msgByte);
        System.out.print(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())  + "接收到消息:");
        System.out.println(new String(msgByte, Charset.forName("utf-8")));
    }

    /**
     * 当客户端主动断开连接,通道不活跃触发
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("===================客户端:" + ctx.channel().localAddress().toString() + " 断开连接===================");
    }

    /**
     * 当连接发生异常时触发
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //在发生异常时主动关掉连接
        ctx.close();
        System.out.println("发现异常:\r\n" + cause.getMessage());
    }
}

在介绍此类的作用之前先来看一下父类ChannelInboundHandlerAdapter
在这里插入图片描述
在这里插入图片描述
在这里主要介绍ChannelInboundHandlerAdapter的以下几个常用方法:
channelActive: 当客户端主动连接服务端,通道活跃后触发
channelRead: 通道有消息触发
channelInactive: 当客户端主动断开连接,通道不活跃触发
exceptionCaught: 当连接发生异常时触发

二、客户端

2.1启动类


/**
 * netty客户端启动类
 *
 * @author jie
 */
public class NettyClient {

    /**
     * 服务端ip
     */
    private static String ip = "127.0.0.1";
    /**
     * 服务端监听端口
     */
    private static int port = 1100;

    public static void main(String[] args) {
        //用于SocketChannel的网络读写操作
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //客户端启动辅助类
            Bootstrap b = new Bootstrap();
            b.group(group).
                    //设置为NioSocketChannel
                    channel(NioSocketChannel.class).
                    option(ChannelOption.AUTO_READ, true).
                    //事件处理类
                    handler(new MyClientChannelInitializer());
            //调用它的connect操作连接服务端,调用同步阻塞方法sync等待绑定操作完成
            ChannelFuture future = b.connect(ip, port).sync();
            //异步操作的通知回调
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //优雅的退出,释放线程池资源
            group.shutdownGracefully();
        }
    }
}

客户端的创建与服务端很相像,客户端在创建时没有用于处理连接请求的线程组,只有一个处理I/O事件的线程组,另外在启动辅助类的创建上也有所不同,客户端用的是Bootstrap,服务端用的ServerBootstrap。客户端启动辅助类设置完成后调用的是connect方法发起异步连接,然后调用同步方法等待连接成功

2.2事件处理类:

/**
 *
 * MyChannelInitializer的主要目的是为程序员提供了一个简单的工具,用于在某个Channel注册到EventLoop后,对这个Channel执行一些初始
 * 化操作。ChannelInitializer虽然会在一开始会被注册到Channel相关的pipeline里,但是在初始化完成之后,ChannelInitializer会将自己
 * 从pipeline中移除,不会影响后续的操作。
 * @author jie
 */
public class MyClientChannelInitializer extends ChannelInitializer<SocketChannel> {
    /**
     * 这个方法在Channel被注册到EventLoop的时候会被调用
     * @param socketChannel
     * @throws Exception
     */
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        System.out.println("=========连接到服务端=========");
        System.out.println("channelId:"+socketChannel.id());
        socketChannel.pipeline().addLast(new MyClientHandler());
    }
}

此事件处理类与服务端功能一致

2.3事件操作类

/**
 * 客户端事件操作类
 * @author jie
 */
public class MyClientHandler extends ChannelInboundHandlerAdapter {

    /**
     * 当客户端主动链接服务端的链接后,通道就是活跃的了此时触发
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //通知服务端链接建立成功
        String str = "与客户端链接建立成功" + " " + new Date();
        ctx.writeAndFlush(str);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //接收msg消息
        ByteBuf buf = (ByteBuf) msg;
        byte[] msgByte = new byte[buf.readableBytes()];
        buf.readBytes(msgByte);
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 接收到消息:" );
        System.out.println(new String(msgByte, Charset.forName("utf-8")));
    }
    /**
     * 当客户端主动断开服务端的链接后,这个通道就是不活跃的。也就是说客户端与服务端的关闭了通信通道并且不可以传输数据
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("===================" + ctx.channel().localAddress().toString() + " 断开连接===================");
    }


    /**
     * 抓住异常,当发生异常的时候,可以做一些相应的处理,比如打印日志、关闭链接
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
        System.out.println("发现异常:\r\n" + cause.getMessage());
    }
}

此类与服务端功能及用法一致
案例源码:https://github.com/itwwj/netty-learn.git 中的 netty-day02-introduction项目

上一篇:
io模型及nio进阶
下一篇:
netty(二)–解决粘包拆包及编码解码问题

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值