通過一個簡單的例子看服務端的啟動過程以及客戶端與服務端的連接過程(Netty 4.0.18)


Netty版本:4.0.18

這個例子接受socket連接,並將客戶端發送來的數據輸出到控制台,不做任何響應。代碼來自Netty的Example。

首先看源代碼。運行這個例子,然后執行命令telnet localhost 8080 在命令行的任何輸入,都將被服務端輸出到控制台。

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.ReferenceCountUtil;

public class DiscardServerHandler extends SimpleChannelInboundHandler<Object> {

public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf in = (ByteBuf) msg;
try {
while (in.isReadable()) { // (1)
System.out.print((char) in.readByte());
System.out.flush();
}
} finally {
//ReferenceCountUtil.release(msg); // (2)
}
}

public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) throws Exception {
// Close the connection when an exception is raised.
logger.log(
Level.WARNING,
"Unexpected exception from downstream.",
cause);
ctx.close();
}
}
 
import io.netty.bootstrap.ServerBootstrap;import io.netty.channel.ChannelFuture;import io.netty.channel.ChannelInitializer;import io.netty.channel.EventLoopGroup;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NioServerSocketChannel;/** * Discards any incoming data. */public class DiscardServer {    private final int port;    public DiscardServer(int port) {        this.port = port;    }    public void run() throws Exception {        EventLoopGroup bossGroup = new NioEventLoopGroup(1);        //bossGroup只用來監聽客戶端的連接,可用阻塞的方式,所以只需要一個線程,故傳入參數1        EventLoopGroup workerGroup = new NioEventLoopGroup();        //workerGroup用來處理客戶端與服務端的各種IO操作        try {            ServerBootstrap b = new ServerBootstrap();//服務端的引導程序            b.group(bossGroup, workerGroup) //boss將賦給b的父類實例,worker賦給b             .channel(NioServerSocketChannel.class)//將通過反射機制創建該實例             .childHandler(new ChannelInitializer<SocketChannel>() {                 @Override                 public void initChannel(SocketChannel ch) throws Exception {                     ch.pipeline().addLast(new DiscardServerHandler());                 }             }); //這里的匿名類的實例將加到每個socketchannel的pipeline中。(每個客戶端與服務端連接成功都將產生一個socketchannel)            // Bind and start to accept incoming connections.            ChannelFuture f = b.bind(port).sync();            //netty中所有的IO操作都是異步的,也就是說IO操作會立即返回,返回時不保證操作已經完成            //通過返回一個ChannelFuture來告訴你IO操作的結果和狀態            //調用channelFuture的sync方法,為阻塞線程,等待結果的出現,該方法被打斷時會拋出InterruptedException            // 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 DiscardServer(port).run();    }}

  • 服務端的啟動過程:
程序首先實例化了一個ServerBootstrap,可以看出它的構造器並沒有參數,因為參數太多,所以在后面調用了一串鏈式方法,給它的實例b設置了一系列參數,參數的意義在注釋中已經說明。但目前為止並沒有實質性的動作,當調用bind方法時,服務端就開始啟動了。


調用bind后,首先了調用AbstractBootstrap中的initAndRegister方法,通過反射機制實例化一個NioServerSocketChannel(調用ServerBootstrap.channel方法傳入了它的class),代碼中變量名為channel,為了防止迷惑,稱之為serverSocketChannel。然后初始化serverSocketChannel,設置它的參數(可設置的參數見ChannelOption類),獲取它的ChannelPipeline,並添加了一個ChannelHandler(實際上為ServerBootstrapAcceptor的實例,該實例包含了workerGroup,它的handler,以及一些參數),最后向bossGroup注冊。返回一個ChannelFuture。到這里啟動就結束了。ChannelFuture的解釋在注釋中。

  • 客戶端與服務端的連接過程

當我們使用telnet localhost 8080連接服務端時,NioServerSocketChannel通過調用accept方法完成連接,獲得socketChannel。觸發channelRead事件,通過回調從serverSocketChannel的ChannelPipeline中選擇第一個ChannelHandler(也即head,默認情況下serverSocketChannel也只有這么一個handler,貌似也沒有添加多個的必要,畢竟serverSocketChannel只是用來處理連接而已,上文說過,這個head也就是ServerBootstrapAcceptor的實例,這個類是ServerBootstrap的內部類)。然后調用head這個handler的channelRead(ChannelHandlerContext ctx, Object msg)方法。(事實上,這個msg對象就是這個socketChannel),然后獲取socketChannel的ChannelPipeline,現在這個管道中是沒有任何handler的,隨后將我們啟動服務端時,調用ServerBootstrap.childChannel()方法傳入的handler加入到這個socketChannel的管道中。隨后將這個socketChannel向workerGroup注冊,到此為止,客戶端和服務端連接成功。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com