2014年7月28日

Netty 介紹

如果我們想要開發client-server應用程式,大部份的programmer會使用較熟悉的http協定來開發web service,以慣用java的programmer來說最常用的http container就是tomcat。但隨著project需求變化,我們會發現這一類的協定或實作有時不易擴展(scale)的很好,我們會想要高度客製化來滿足我們的特殊需求。

這時我們可能會需要Netty,以下是取自Netty官網對於Netty的介紹

The Netty project is an effort to provide an asynchronous event-driven network application framework and tooling for the rapid development of maintainable high-performance · high-scalability protocol servers and clients.

也就是說Netty是一個framework,一個socket框架,並非僅是web server如tomcat,我們可以拿Netty來開發http, ftp, snmp一類的協定, 或者是自定的協定。

以下介紹使用Netty來實作簡單的Echo Server。從名稱可知此範例的server接收到來自clent的訊息後就回應相同的訊息回去給client。 執行時server會listen一個port。client可使用telnet來連此server的port,連上後client便可開始送訊息,server收到後也會回應相同的訊息回去。

首先直接撰寫handler部份,handler負責處理Netty產生的I/O event. 此範例的重點就是將收到的訊息再回傳回去。

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class EchoServerHandler extends ChannelInboundHandlerAdapter {//(1)

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {//(2)

        // echo
        ctx.write(msg); // (3)
        ctx.flush(); // (4)

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}
  1. handler需extend ChannelInboundHandlerAdapter
  2. override channelRead method,當server收到訊息後此method會被呼叫
  3. ChannelHandlerContext有提供許多的method來觸發一些io event,在這個範例中使用write來回傳資料至client
  4. ctx.write()被呼叫時並未立即將資料送至client,僅是先buffer起來,當ctx.flush()被呼叫時才是送到client

主要的處理邏輯寫完了,接下來就是寫main來啟動server並使用EchoServerHandler

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;

public class EchoServer {

    private int port;

    public EchoServer(int port) {
        this.port = port;
    }

    public void run() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap(); // (2)
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class) // (3)
                    .childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                                @Override
                                public void initChannel(SocketChannel ch)
                                        throws Exception {
                                    ch.pipeline().addLast(
                                            new EchoServerHandler());
                                }
                            }).option(ChannelOption.SO_BACKLOG, 128) 
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            // Bind and start to accept incoming connections.
            ChannelFuture f = b.bind(port).sync(); // (5)

            // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to
            // gracefully
            // shut down your server.
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        } else {
            port = 8080;
        }
        new EchoServer(port).run();
    }
}
  1. NioEventLoopGroup is a multithreaded event loop that handles I/O operation.
  2. ServerBootstrap 是個helper class用來設定server相關的參數.
  3. 此範例指定NioServerSocketChannel來處理進來的連線
  4. 指定channel要使用剛撰寫的的EchoServerHandler
  5. bind指定的port並啟動server

測試

首先先將server run起來,(直接用java run EchoServer main())。接著開終端機來下指令telnet localhost 8080,等連上後就可以開始送訊息了!畫面如下,其中(1)(3)是向erver送出的訊息,(2)(4)是server回應的訊息

jamestekiMacBook-Pro:~ james$ telnet localhost 8080
Trying ::1...
Connected to localhost.
Escape character is '^]'.
hello  (1)
hello  (2)
world  (3)
world  (4)

Summary

在study Netty過程中常會看到Jetty, Tomcat, Mina。這邊做個簡單的比較。Jetty是http容器,和Tomcat是同样的概念,但是具體實現不一樣。 Netty是socket框架,和MINA是相同的概念,但是具體實現不一樣。 據說Mina跟Netty是來自同一個作者,但Mina好像較少在更新了比較沒有Netty來的活躍。

參考來源 http://netty.io/wiki/user-guide-for-4.x.html#wiki-h3-15