Netty学习笔记

什么是netty?

百度百科描述

Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。

也就是说,Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty 相当于简化和流线化了网络应用的编程开发过程,例如:基于 TCP 和 UDP 的 socket 服务开发。

如上摘录自百度百科的描述。


废话不多说,直接上代码:
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓


NettyServer.java

public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        //创建两个线程组,一个用于处理服务器端接受客户端的连接,一个用于进行网络通讯(数据读写)
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();
        //创建辅助工具类,用于服务器通道的一系列配置
        ServerBootstrap serverBootStrap = new ServerBootstrap();
        serverBootStrap.group(bossGroup, workGroup)  //绑定两个线程组
                .channel(NioServerSocketChannel.class)  //指定NIO模式
                .option(ChannelOption.SO_BACKLOG,1024)  //设置TCP缓冲区
                .handler(new LoggingHandler(LogLevel.INFO))  //设置日志
                .childHandler(new NettyInitializer());  //指定自定义handler的初始化器
        //进行端口绑定
        ChannelFuture channelFuture = serverBootStrap.bind(8002).sync();
        //等待关闭
        channelFuture.channel().closeFuture().sync();
        bossGroup.shutdownGracefully();
        workGroup.shutdownGracefully();
    }
}

首先是Netty服务端启动代码,具体每一步的大概作用我也有写行尾注释,可以细心看一下代码,不用死记,模板代码基本上大同小异,对设置ServerBootstrap的各个参数有点印象就行。那么这里最主要的就是这个ServerBootstrap类了,其次就是对channelFuture也要熟悉一点,下面是来自官方英文的翻译。

ChannelFuture的作用是用来保存Channel异步操作的结果。

我们知道,在Netty中所有的I/O操作都是异步的。这意味着任何的I/O调用都将立即返回,而不保证这些被请求的I/O操作在调用结束的时候已经完成。取而代之地,你会得到一个返回的ChannelFuture实例,这个实例将给你一些关于I/O操作结果或者状态的信息。

对于一个ChannelFuture可能已经完成,也可能未完成。当一个I/O操作开始的时候,一个新的future对象就会被创建。在开始的时候,新的future是未完成的状态--它既非成功、失败,也非被取消,因为I/O操作还没有结束。如果I/O操作以成功、失败或者被取消中的任何一种状态结束了,那么这个future将会被标记为已完成,并包含更多详细的信息(例如:失败的原因)。请注意,即使是失败和被取消的状态,也是属于已完成的状态。

那么细心的你肯定已经发现了ServerBootstrap.childHandler()方法,可能注释有点不易理解。简单来说,就是指定一个自定义的handler,但是这个handler又是由一个initializer来帮我们初始化的,所以这里指定的并不是直接的handler,而是一个initializer对象,在该对象中,我们再来指定和初始化我们的自定义handler。



NettyInitializer.java

public class NettyInitializer extends ChannelInitializer<SocketChannel> {

        @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {

        ChannelPipeline pipeline = socketChannel.pipeline();
        pipeline.addLast(new StringEncoder())
                .addLast(new StringDecoder())
                .addLast(new ReadTimeoutHandler(3))
                .addLast(new NettyHandler());  //自定义的处理器
    }
}

到了我们的initlilizer类中,继承ChannelInitializer对象,重写initChannel()方法,这里我们首先是获取到了pipeline对象 ,为通道设置一些对象参数。这里需要注意的是,Encoder和 Decoder必须要指定,否则双方都接收不到数据(或者自己转字节来传递,java网络编程中,API所规定的的数据最小传输单元就是字节)。在最后一行代码可以看到,我们指定了自定义的handler---->NettyHandler



NettyHandler.java

public class NettyHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("---通道连接成功---");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("开始读取通道数据...");
        System.out.println("收到客户端信息为:"+msg.toString());
        ctx.writeAndFlush("已收到信息"+msg+"---(来自服务端的回应)");
    }
}

如代码所示,继承ChannelHandlerAdapter类(我这里是 ChannelInboundHandlerAdapter ),其中channelActive在通道激活的时候会被调用,可以理解为''连接上了服务器''这么一个环节,其中ChannnelHandlerContext对象是管理它所关联的ChannelHandler和在同一个ChannelPipeline中的其他ChannelHandler直接的交互,而msg则是接收到的所有数据。

-------小总结--------
简单来说:搭建Netty服务端需要三个类,NettySerrver.java、Nettyinitializer.java、NettyHandler.java。其中Server和initializer两个类几乎是模板写法,就是配置连接和通道参数,真正做数据处理的是在我们自定义的handler类中。,




因为Client端和服务端非常类似,就不再多说了,唯一需要注意的就是服务端中的ServerBootstrap对象在Client端使用的是Bootstrap,两个类的属性几乎大差不差,以及线程组只需要一个。总之相对服务端简单了很多,看看代码就能懂。
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

Client.java(其中写了一个内部类initializer)

public class Client {

    private static class SingletonHolder {
        static final Client INSTANCE = new Client();
    }

    public static Client getInstance(){
        return SingletonHolder.INSTANCE;
    }

    private EventLoopGroup group;
    private Bootstrap bootstrap;
    private ChannelFuture channelFuture;

    private Client(){
        group = new NioEventLoopGroup();
        bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .handler(new LoggingHandler(LogLevel.INFO))
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel sc) throws Exception {
                        //通过JBOSS的Marshalling进行编码解码
//                        sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
//                        sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
                        sc.pipeline().addLast(new StringEncoder());
                        sc.pipeline().addLast(new StringDecoder());
                        //客户端与服务端在3s内没有任何通信则关闭响应通道 节省服务器资源
                        sc.pipeline().addLast(new ReadTimeoutHandler(3));
                        //
                        sc.pipeline().addLast(new ClientHandler());
                    }
                });
    }


    public void connect(){
        try {
            this.channelFuture = bootstrap.connect("127.0.0.1", 8002).sync();
            System.out.println("远程服务器已经连接, 可以进行数据交换..");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public ChannelFuture getChannelFuture(){

        if(this.channelFuture == null){
            this.connect();
        }
        if(!this.channelFuture.channel().isActive()){
            this.connect();
        }

        return this.channelFuture;
    }

    public static void main(String[] args) throws Exception{
        final Client c = Client.getInstance();
        //c.connect();

        ChannelFuture cf = c.getChannelFuture();
        for(int i = 1; i <= 3; i++ ){
            String request = new String();
            request+=("数据信息---" + i+"---次");
            cf.channel().writeAndFlush(request);
            Thread.sleep(3000);
        }

        cf.channel().closeFuture().sync();

        //通过new一个子线程来重新连接服务端
//        new Thread(new Runnable() {
//            @Override
//            public void run() {
//                try {
//                    System.out.println("进入子线程...");
//                    ChannelFuture cf = c.getChannelFuture();
//                    System.out.println(cf.channel().isActive());
//                    System.out.println(cf.channel().isOpen());
//                    //再次发送数据
//                    String request = new String();
//                    request+=("数据信息---4---次");
//                    cf.channel().writeAndFlush(request);
//                    cf.channel().closeFuture().sync();
//                    System.out.println("子线程结束.");
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }
//        }).start();

        System.out.println("断开连接,主线程结束..");
    }
}




ClientHandler.java

public class ClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("客户端收到数据...");
        System.out.println("收到服务器端信息为:"+msg.toString());
    }
}

本文只是一个简单的快速入门案例,其中一些细节可能不全,解释也相对简单。后续我继续学习的总结会继续更新到该分类中。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容