译 3个netty5的例子,简单介绍netty的用法

来源:互联网 发布:易语言挂机软件源码 编辑:程序博客网 时间:2024/05/22 13:50


   这是一个netty快速入门的例子,也是我的学习笔记,比较简单,翻译于官方的文档整理后把所有代码注释放在每一行代码中间,简单明了地介绍一些基础的用法。

   首页这是基于netty5的例子,如果需要使用请依赖netty5的包。maven引用方式

1<dependency>
2    <groupId>io.netty</groupId>
3    <artifactId>netty-all</artifactId>
4    <version>5.0.0.Alpha2</version>
5</dependency>

或者去下载最新的jar下载页面

1.DISCARD服务(丢弃服务,指的是会忽略所有接收的数据的一种协议)

001import io.netty.bootstrap.ServerBootstrap;
002import io.netty.channel.ChannelFuture;
003import io.netty.channel.ChannelInitializer;
004import io.netty.channel.ChannelOption;
005import io.netty.channel.EventLoopGroup;
006import io.netty.channel.nio.NioEventLoopGroup;
007import io.netty.channel.socket.SocketChannel;
008import io.netty.channel.socket.nio.NioServerSocketChannel;
009 
010/**
011 * 处理数据
012 */
013public class NettyServer {
014    private int port;
015    public NettyServer(int port) {
016        this.port = port;
017    }
018    public void run() throws Exception {
019        /***
020         * NioEventLoopGroup 是用来处理I/O操作的多线程事件循环器,
021         * Netty提供了许多不同的EventLoopGroup的实现用来处理不同传输协议。
022         * 在这个例子中我们实现了一个服务端的应用,
023         * 因此会有2个NioEventLoopGroup会被使用。
024         * 第一个经常被叫做‘boss’,用来接收进来的连接。
025         * 第二个经常被叫做‘worker’,用来处理已经被接收的连接,
026         * 一旦‘boss’接收到连接,就会把连接信息注册到‘worker’上。
027         * 如何知道多少个线程已经被使用,如何映射到已经创建的Channels上都需要依赖于EventLoopGroup的实现,
028         * 并且可以通过构造函数来配置他们的关系。
029         */
030        EventLoopGroup bossGroup = new NioEventLoopGroup();
031        EventLoopGroup workerGroup = new NioEventLoopGroup();
032        System.out.println("准备运行端口:" + port);
033        try {
034            /**
035             * ServerBootstrap 是一个启动NIO服务的辅助启动类
036             * 你可以在这个服务中直接使用Channel
037             */
038            ServerBootstrap b = new ServerBootstrap();
039            /**
040             * 这一步是必须的,如果没有设置group将会报java.lang.IllegalStateException: group not set异常
041             */
042            b = b.group(bossGroup, workerGroup);
043            /***
044             * ServerSocketChannel以NIO的selector为基础进行实现的,用来接收新的连接
045             * 这里告诉Channel如何获取新的连接.
046             */
047            b = b.channel(NioServerSocketChannel.class);
048            /***
049             * 这里的事件处理类经常会被用来处理一个最近的已经接收的Channel。
050             * ChannelInitializer是一个特殊的处理类,
051             * 他的目的是帮助使用者配置一个新的Channel。
052             * 也许你想通过增加一些处理类比如NettyServerHandler来配置一个新的Channel
053             * 或者其对应的ChannelPipeline来实现你的网络程序。
054             * 当你的程序变的复杂时,可能你会增加更多的处理类到pipline上,
055             * 然后提取这些匿名类到最顶层的类上。
056             */
057            b = b.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
058                @Override
059                public void initChannel(SocketChannel ch) throws Exception {
060                   ch.pipeline().addLast(new DiscardServerHandler());
061                   //ch.pipeline().addLast(new ResponseServerHandler());
062                   // ch.pipeline().addLast(new TimeServerHandler());
063                }
064            });
065            /***
066             * 你可以设置这里指定的通道实现的配置参数。
067             * 我们正在写一个TCP/IP的服务端,
068             * 因此我们被允许设置socket的参数选项比如tcpNoDelay和keepAlive。
069             * 请参考ChannelOption和详细的ChannelConfig实现的接口文档以此可以对ChannelOptions的有一个大概的认识。
070             */
071            b = b.option(ChannelOption.SO_BACKLOG, 128);
072            /***
073             * option()是提供给NioServerSocketChannel用来接收进来的连接。
074             * childOption()是提供给由父管道ServerChannel接收到的连接,
075             * 在这个例子中也是NioServerSocketChannel。
076             */
077            b = b.childOption(ChannelOption.SO_KEEPALIVE, true);
078            /***
079             * 绑定端口并启动去接收进来的连接
080             */
081            ChannelFuture f = b.bind(port).sync();
082            /**
083             * 这里会一直等待,直到socket被关闭
084             */
085            f.channel().closeFuture().sync();
086        finally {
087            /***
088             * 优雅关闭
089             */
090            workerGroup.shutdownGracefully();
091            bossGroup.shutdownGracefully();
092        }
093    }
094 
095    public static void main(String[] args) throws Exception {
096        int port;
097        if (args.length > 0) {
098            port = Integer.parseInt(args[0]);
099        else {
100            port = 8000;
101        }
102        new NettyServer(port).run();
103    }
104}
01import io.netty.buffer.ByteBuf;
02import io.netty.channel.ChannelHandlerAdapter;
03import io.netty.channel.ChannelHandlerContext;
04import io.netty.util.CharsetUtil;
05import io.netty.util.ReferenceCountUtil;
06 
07/**
08 * 服务端处理通道.这里只是打印一下请求的内容,并不对请求进行任何的响应
09 * DiscardServerHandler 继承自 ChannelHandlerAdapter,
10 * 这个类实现了ChannelHandler接口,
11 * ChannelHandler提供了许多事件处理的接口方法,
12 * 然后你可以覆盖这些方法。
13 * 现在仅仅只需要继承ChannelHandlerAdapter类而不是你自己去实现接口方法。
14 *
15 */
16public class DiscardServerHandler extends ChannelHandlerAdapter {
17 
18    /***
19     * 这里我们覆盖了chanelRead()事件处理方法。
20     * 每当从客户端收到新的数据时,
21     * 这个方法会在收到消息时被调用,
22     * 这个例子中,收到的消息的类型是ByteBuf
23     * @param ctx 通道处理的上下文信息
24     * @param msg 接收的消息
25     */
26    @Override
27    public void channelRead(ChannelHandlerContext ctx, Object msg) {
28        try {
29            ByteBuf in = (ByteBuf) msg;
30          /*  while (in.isReadable()) {
31                System.out.print((char) in.readByte());
32                System.out.flush();
33            }*/
34            //这一句和上面注释的的效果都是打印输入的字符
35            System.out.println(in.toString(CharsetUtil.US_ASCII));
36        }finally {
37            /**
38             * ByteBuf是一个引用计数对象,这个对象必须显示地调用release()方法来释放。
39             * 请记住处理器的职责是释放所有传递到处理器的引用计数对象。
40             */
41            ReferenceCountUtil.release(msg);
42        }
43    }
44 
45    /***
46     * 这个方法会在发生异常时触发
47     * @param ctx
48     * @param cause
49     */
50    @Override
51    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
52        /***
53         * 发生异常后,关闭连接
54         */
55        cause.printStackTrace();
56        ctx.close();
57    }
58 
59}

以上是一个丢弃服务的处理方式,你可以运行后通过telnet来发送消息,来查看是否正常运行,注意console里会打印你的输入内容。

2.ECHO服务(响应式协议)

    到目前为止,我们虽然接收到了数据,但没有做任何的响应。然而一个服务端通常会对一个请求作出响应。让我们学习怎样在ECHO协议的实现下编写一个响应消息给客户端,这个协议针对任何接收的数据都会返回一个响应。

    和discard server唯一不同的是把在此之前我们实现的channelRead()方法,返回所有的数据替代打印接收数据到控制台上的逻辑。

说明NettyServer 还是用上面已经提供的类,只是把这段里的注销部分修改成如下。


1//ch.pipeline().addLast(new DiscardServerHandler());        
2ch.pipeline().addLast(new ResponseServerHandler());
3//ch.pipeline().addLast(new TimeServerHandler());

下面是处理类ResponseServerHandler的代码

01import io.netty.channel.ChannelHandlerAdapter;
02import io.netty.channel.ChannelHandlerContext;
03 
04/**
05 * 服务端处理通道.
06 * ResponseServerHandler 继承自 ChannelHandlerAdapter,
07 * 这个类实现了ChannelHandler接口,
08 * ChannelHandler提供了许多事件处理的接口方法,
09 * 然后你可以覆盖这些方法。
10 * 现在仅仅只需要继承ChannelHandlerAdapter类而不是你自己去实现接口方法。
11 * 用来对请求响应
12 */
13public class ResponseServerHandler extends ChannelHandlerAdapter {
14 
15    /**
16     * 这里我们覆盖了chanelRead()事件处理方法。
17     * 每当从客户端收到新的数据时,
18     * 这个方法会在收到消息时被调用,
19     *ChannelHandlerContext对象提供了许多操作,
20     * 使你能够触发各种各样的I/O事件和操作。
21     * 这里我们调用了write(Object)方法来逐字地把接受到的消息写入
22     * @param ctx 通道处理的上下文信息
23     * @param msg 接收的消息
24     */
25    @Override
26    public void channelRead(ChannelHandlerContext ctx, Object msg) {
27        ctx.write(msg);
28        //cxt.writeAndFlush(msg)
29 
30        //请注意,这里我并不需要显式的释放,因为在定入的时候netty已经自动释放
31        // ReferenceCountUtil.release(msg);
32    }
33 
34    /**
35     * ctx.write(Object)方法不会使消息写入到通道上,
36     * 他被缓冲在了内部,你需要调用ctx.flush()方法来把缓冲区中数据强行输出。
37     * 或者你可以在channelRead方法中用更简洁的cxt.writeAndFlush(msg)以达到同样的目的
38     * @param ctx
39     * @throws Exception
40     */
41    @Override
42    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
43        ctx.flush();
44    }
45 
46    /**
47     * 这个方法会在发生异常时触发
48     *
49     * @param ctx
50     * @param cause
51     */
52    @Override
53    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
54        /***
55         * 发生异常后,关闭连接
56         */
57        cause.printStackTrace();
58        ctx.close();
59    }
60 
61}

同样以上运行后,可以通过telnet发送数据,console里会打印出你发送的数据,同时你的命令行界面里应该也会接收到相同的数据。


3.TIME服务(时间协议的服务)

    在这个部分被实现的协议是TIME协议。和之前的例子不同的是在不接受任何请求时他会发送一个含32位的整数的消息,并且一旦消息发送就会立即关闭连接。在这个例子中,你会学习到如何构建和发送一个消息,然后在完成时主动关闭连接。


    因为我们将会忽略任何接收到的数据,而只是在连接被创建发送一个消息,所以这次我们不能使用channelRead()方法了,代替他的是,我们需要覆盖channelActive()方法,下面的就是实现的内容:


说明NettyServer 还是用上面已经提供的类,只是把这段里的注销部分修改成如下。


1//ch.pipeline().addLast(new DiscardServerHandler());        
2//ch.pipeline().addLast(new ResponseServerHandler());
3ch.pipeline().addLast(new TimeServerHandler());

TimeServerHandler类的如下:

01public class TimeServerHandler extends ChannelHandlerAdapter {
02 
03    /**
04     * channelActive()方法将会在连接被建立并且准备进行通信时被调用。
05     * 因此让我们在这个方法里完成一个代表当前时间的32位整数消息的构建工作。
06     *
07     * @param ctx
08     */
09    @Override
10    public void channelActive(final ChannelHandlerContext ctx) {
11        /**
12         * 为了发送一个新的消息,我们需要分配一个包含这个消息的新的缓冲。
13         * 因为我们需要写入一个32位的整数,因此我们需要一个至少有4个字节的ByteBuf。
14         * 通过ChannelHandlerContext.alloc()得到一个当前的ByteBufAllocator,
15         * 然后分配一个新的缓冲。
16         */
17        final ByteBuf time = ctx.alloc().buffer(4);
18        time.writeInt((int) (System.currentTimeMillis() / 1000L + 2208988800L));
19        /***
20         * 和往常一样我们需要编写一个构建好的消息
21         * 。但是等一等,flip在哪?难道我们使用NIO发送消息时不是调用java.nio.ByteBuffer.flip()吗?
22         * ByteBuf之所以没有这个方法因为有两个指针,
23         * 一个对应读操作一个对应写操作。
24         * 当你向ByteBuf里写入数据的时候写指针的索引就会增加,
25         * 同时读指针的索引没有变化。
26         * 读指针索引和写指针索引分别代表了消息的开始和结束。
27         * 比较起来,NIO缓冲并没有提供一种简洁的方式来计算出消息内容的开始和结尾,
28         * 除非你调用flip方法。
29         * 当你忘记调用flip方法而引起没有数据或者错误数据被发送时,
30         * 你会陷入困境。这样的一个错误不会发生在Netty上,
31         * 因为我们对于不同的操作类型有不同的指针。
32         * 你会发现这样的使用方法会让你过程变得更加的容易,
33         * 因为你已经习惯一种没有使用flip的方式。
34         * 另外一个点需要注意的是ChannelHandlerContext.write()(和writeAndFlush())方法会返回一个ChannelFuture对象,
35         * 一个ChannelFuture代表了一个还没有发生的I/O操作。
36         * 这意味着任何一个请求操作都不会马上被执行,
37         * 因为在Netty里所有的操作都是异步的。
38         * 因此你需要在write()方法返回的ChannelFuture完成后调用close()方法,
39         * 然后当他的写操作已经完成他会通知他的监听者。
40         */
41        final ChannelFuture f = ctx.writeAndFlush(time); // (3)
42        /**
43         * 当一个写请求已经完成是如何通知到我们?
44         * 这个只需要简单地在返回的ChannelFuture上增加一个ChannelFutureListener。
45         * 这里我们构建了一个匿名的ChannelFutureListener类用来在操作完成时关闭Channel。
46         */
47        f.addListener(new ChannelFutureListener() {
48            @Override
49            public void operationComplete(ChannelFuture future) {
50                assert f == future;
51                /***
52                 * 请注意,close()方法也可能不会立马关闭,他也会返回一个ChannelFuture。
53                 */
54                ctx.close();
55            }
56        });
57    }
58    @Override
59    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
60        cause.printStackTrace();
61        ctx.close();
62    }
63}

4.Time客户端

    不像DISCARD和ECHO的服务端,对于TIME协议我们需要一个客户端因为人们不能把一个32位的二进制数据翻译成一个日期或者日历。在这一部分,我们将会讨论如何确保服务端是正常工作的,并且学习怎样用Netty编写一个客户端。

    在Netty中,编写服务端和客户端最大的并且唯一不同的使用了不同的BootStrap和Channel的实现。


01public class TimeClient {
02    public static void main(String[] args) throws Exception {
03        String host = "127.0.0.1";
04        int port =8000;
05        EventLoopGroup workerGroup = new NioEventLoopGroup();
06        try {
07            /**
08             * 如果你只指定了一个EventLoopGroup,
09             * 那他就会即作为一个‘boss’线程,
10             * 也会作为一个‘workder’线程,
11             * 尽管客户端不需要使用到‘boss’线程。
12             */
13            Bootstrap b = new Bootstrap(); // (1)
14            b.group(workerGroup); // (2)
15            /**
16             * 代替NioServerSocketChannel的是NioSocketChannel,这个类在客户端channel被创建时使用
17             */
18            b.channel(NioSocketChannel.class); // (3)
19            /**
20             * 不像在使用ServerBootstrap时需要用childOption()方法,
21             * 因为客户端的SocketChannel没有父channel的概念。
22             */
23            b.option(ChannelOption.SO_KEEPALIVE, true); // (4)
24            b.handler(new ChannelInitializer<SocketChannel>() {
25                @Override
26                public void initChannel(SocketChannel ch) throws Exception {
27                    ch.pipeline().addLast(new TimeClientHandler());
28                }
29            });
30            //用connect()方法代替了bind()方法
31            ChannelFuture f = b.connect(host, port).sync();
32            //等到运行结束,关闭
33            f.channel().closeFuture().sync();
34        finally {
35            workerGroup.shutdownGracefully();
36        }
37    }
38 
39}
01/**
02*客户端处理类
03*/
04public class TimeClientHandler extends ChannelHandlerAdapter {
05    private ByteBuf buf;
06    /**
07     * 开始处理的时候触发
08     *
09     * @param ctx
10     */
11    @Override
12    public void handlerAdded(ChannelHandlerContext ctx) {
13        buf = ctx.alloc().buffer(4); // 分配4个字节的空间给ByteBuf
14    }
15 
16    /**
17     * 处理结束的时候触发
18     *
19     * @param ctx
20     */
21    @Override
22    public void handlerRemoved(ChannelHandlerContext ctx) {
23        buf.release();//释放ByteBuf的空间
24        buf = null;
25    }
26 
27    @Override
28    public void channelRead(ChannelHandlerContext ctx, Object msg) {
29        ByteBuf m = (ByteBuf) msg;
30        /**
31         * 所有接收的数据都应该被累积在buf变量里
32         */
33        buf.writeBytes(m);
34        m.release();
35        /**
36         * 处理器必须检查buf变量是否有足够的数据,在这个例子中是4个字节,
37         * 然后处理实际的业务逻辑。
38         * 否则,Netty会重复调用channelRead()当有更多数据到达直到4个字节的数据被积累。
39         */
40        if (buf.readableBytes() >= 4) {
41            long currentTimeMillis = (buf.readInt() - 2208988800L) * 1000L;
42            System.out.println(new Date(currentTimeMillis));
43            ctx.close();
44        }
45    }
46 
47    @Override
48    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
49        cause.printStackTrace();
50        ctx.close();
51    }
52}

总结

  这里通过三个例子说明一下netty的用法,更多例子可以去官方下载io.netty.example

来源:自成e家 出处:3个netty5的例子,简单介绍netty的用法
本文由 自成e家 翻译 ,转载请注明出处,你的支持是我继续写作、分享的最大动力!

0 0
原创粉丝点击