Netty源碼分析(一) ServerBootStrap

先來說說為什么要寫netty源碼分析的文章傻谁,一個方面是自己看了一些源碼孝治,卻找不到了解原理的方式,一個方面是萬一bat哪個大派蟠牛看到我寫的文章谈飒,給我一個5k的工作呢。不開玩笑了态蒂,在學(xué)習(xí)netty之前杭措,需要先學(xué)習(xí)java nio的知識,如果有不了解java nio的钾恢,可以先學(xué)習(xí)一下java nio方面的知識手素。下面我先介紹一個netty:
Netty是一個高性能、異步事件驅(qū)動的NIO框架赘那,提供了對TCP刑桑、UDP和文件傳輸?shù)闹С郑鳛橐粋€異步NIO框架募舟,Netty的所有IO操作都是異步非阻塞的祠斧,通過Future-Listener機制,用戶可以方便的主動獲取或者通過通知機制獲得IO操作結(jié)果拱礁。

作為當(dāng)前最流行的NIO框架琢锋,Netty在互聯(lián)網(wǎng)領(lǐng)域、大數(shù)據(jù)分布式計算領(lǐng)域呢灶、游戲行業(yè)吴超、通信行業(yè)等獲得了廣泛的應(yīng)用,一些業(yè)界著名的開源組件也基于Netty構(gòu)建鸯乃,比如RPC框架鲸阻、zookeeper等跋涣。
netty性能高的原因是netty的reactor線程模型


Reactor線程模型

先來一個netty的基本實現(xiàn):
server 代碼實現(xiàn)

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

public void run() throws Exception {
    // Configure the server.
    EventLoopGroup bossGroup = new NioEventLoopGroup();  // (1)
    EventLoopGroup workerGroup = new NioEventLoopGroup();
    try {
        ServerBootstrap b = new ServerBootstrap(); // (2)
        b.group(bossGroup, workerGroup)
         .channel(NioServerSocketChannel.class) // (3)
         .option(ChannelOption.SO_BACKLOG, 100)
         .handler(new LoggingHandler(LogLevel.INFO))
         .childHandler(new ChannelInitializer<SocketChannel>() { // (4)
             @Override
             public void initChannel(SocketChannel ch) throws Exception {
                 ch.pipeline().addLast(
                         //new LoggingHandler(LogLevel.INFO),
                         new EchoServerHandler());
             }
         });

        // Start the server.
        ChannelFuture f = b.bind(port).sync(); // (5)

        // Wait until the server socket is closed.
        f.channel().closeFuture().sync();
    } finally {
        // Shut down all event loops to terminate all threads.
        bossGroup.shutdownGracefully();
        workerGroup.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();
}
}

EchoServerHandler 實現(xiàn)
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

private static final Logger logger = Logger.getLogger(
        EchoServerHandler.class.getName());

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    ctx.write(msg);
}

@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
    ctx.flush();
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
    // Close the connection when an exception is raised.
    logger.log(Level.WARNING, "Unexpected exception from downstream.", cause);
    ctx.close();
}
}

client 代碼實現(xiàn)

public class EchoClient {

private final String host;
private final int port;
private final int firstMessageSize;

public EchoClient(String host, int port, int firstMessageSize) {
    this.host = host;
    this.port = port;
    this.firstMessageSize = firstMessageSize;
}

public void run() throws Exception {
    // Configure the client.
    EventLoopGroup group = new NioEventLoopGroup();
    try {
        Bootstrap b = new Bootstrap();
        b.group(group)
         .channel(NioSocketChannel.class)
         .option(ChannelOption.TCP_NODELAY, true)
         .handler(new ChannelInitializer<SocketChannel>() {
             @Override
             public void initChannel(SocketChannel ch) throws Exception {
                 ch.pipeline().addLast(
                         //new LoggingHandler(LogLevel.INFO),
                         new EchoClientHandler(firstMessageSize));
             }
         });

        // Start the client.
        ChannelFuture f = b.connect(host, port).sync();

        // Wait until the connection is closed.
        f.channel().closeFuture().sync();
    } finally {
        // Shut down the event loop to terminate all threads.
        group.shutdownGracefully();
    }
}

public static void main(String[] args) throws Exception {
    final String host = args[0];
    final int port = Integer.parseInt(args[1]);
    final int firstMessageSize;
    if (args.length == 3) {
        firstMessageSize = Integer.parseInt(args[2]);
    } else {
        firstMessageSize = 256;
    }

    new EchoClient(host, port, firstMessageSize).run();
}
}

EchoClientHandler 實現(xiàn)
public class EchoClientHandler extends ChannelInboundHandlerAdapter {

private static final Logger logger = Logger.getLogger(
        EchoClientHandler.class.getName());

private final ByteBuf firstMessage;

/**
 * Creates a client-side handler.
 */
public EchoClientHandler(int firstMessageSize) {
    if (firstMessageSize <= 0) {
        throw new IllegalArgumentException("firstMessageSize: " + firstMessageSize);
    }
    firstMessage = Unpooled.buffer(firstMessageSize);
    for (int i = 0; i < firstMessage.capacity(); i ++) {
        firstMessage.writeByte((byte) i);
    }
}

@Override
public void channelActive(ChannelHandlerContext ctx) {
    ctx.writeAndFlush(firstMessage);
}

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    ctx.write(msg);
}

@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
   ctx.flush();
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
    // Close the connection when an exception is raised.
    logger.log(Level.WARNING, "Unexpected exception from downstream.", cause);
    ctx.close();
}
}

1、NioEventLoopGroup是用來處理I/O操作的線程池鸟悴,Netty對 EventLoopGroup 接口針對不同的傳輸協(xié)議提供了不同的實現(xiàn)陈辱。在本例子中,需要實例化兩個NioEventLoopGroup细诸,通常第一個稱為“boss”沛贪,用來accept客戶端連接,另一個稱為“worker”震贵,處理客戶端數(shù)據(jù)的讀寫操作利赋。 2、ServerBootstrap是啟動服務(wù)的輔助類猩系,有關(guān)socket的參數(shù)可以通過ServerBootstrap進行設(shè)置媚送。 3、這里指定NioServerSocketChannel類初始化channel用來接受客戶端請求寇甸。 4季希、通常會為新SocketChannel通過添加一些handler,來設(shè)置ChannelPipeline幽纷。ChannelInitializer 是一個特殊的handler,其中initChannel方法可以為SocketChannel 的pipeline添加指定handler博敬。 5友浸、通過綁定端口8080,就可以對外提供服務(wù)了偏窝。

下面我們要說的就是netty的啟動輔助類ServerBootStrap收恢。
ServerBootStrap主要就是包含兩個NioEventLoopGroup,每個NioEventLoopGroup由一個或多個NioEventLoop組成,我們來看看ServerBootStrap的結(jié)構(gòu):

ServerBootStrap

其實很簡單祭往,就是繼承自AbstractBootstrap伦意,這個類有一些通用的屬性,比如NioEventLoopGroup等硼补。
我們主要來看doBind方法:

private ChannelFuture doBind(final SocketAddress localAddress) {
    // 初始化并注冊一個 Channel 對象驮肉,因為注冊是異步的過程,所以返回一個 ChannelFuture 對象已骇。
    final ChannelFuture regFuture = initAndRegister();
    final Channel channel = regFuture.channel();
    if (regFuture.cause() != null) { // 若發(fā)生異常离钝,直接進行返回。
        return regFuture;
    }

    // 綁定 Channel 的端口褪储,并注冊 Channel 到 SelectionKey 中卵渴。
    if (regFuture.isDone()) { // 未
        // At this point we know that the registration was complete and successful.
        ChannelPromise promise = channel.newPromise();
        doBind0(regFuture, channel, localAddress, promise); // 綁定
        return promise;
    } else {
        // Registration future is almost always fulfilled already, but just in case it's not.
        final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
        regFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                System.out.println(Thread.currentThread() + ": PendingRegistrationPromise");
                Throwable cause = future.cause();
                if (cause != null) {
                    // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
                    // IllegalStateException once we try to access the EventLoop of the Channel.
                    promise.setFailure(cause);
                } else {
                    // Registration was successful, so set the correct executor to use.
                    // See https://github.com/netty/netty/issues/2586
                    promise.registered();

                    doBind0(regFuture, channel, localAddress, promise); // 綁定
                }
            }
        });
        return promise;
    }
}

通過doBind方法綁定好端口后,調(diào)用initAndRegister方法鲤竹,1浪读、方法initAndRegister返回一個ChannelFuture實例regFuture,通過regFuture可以判斷initAndRegister執(zhí)行結(jié)果。 2碘橘、如果regFuture.isDone()為true互订,說明initAndRegister已經(jīng)執(zhí)行完,則直接執(zhí)行doBind0進行socket綁定蛹屿。 3屁奏、否則regFuture添加一個ChannelFutureListener監(jiān)聽,當(dāng)initAndRegister執(zhí)行完成時错负,調(diào)用operationComplete方法并執(zhí)行doBind0進行socket綁定坟瓢。

Channel channel = null;
    try {
        // 創(chuàng)建 Channel 對象
        channel = channelFactory.newChannel();
        // 初始化 Channel 配置
        init(channel);
    } catch (Throwable t) {
        if (channel != null) { // 已創(chuàng)建 Channel 對象
            // channel can be null if newChannel crashed (eg SocketException("too many open files"))
            channel.unsafe().closeForcibly(); // 強制關(guān)閉 Channel
            // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
            return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
        }
        // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
        return new DefaultChannelPromise(new FailedChannel(), 
       GlobalEventExecutor.INSTANCE).setFailure(t);
    }

    // 注冊 Channel 到 EventLoopGroup 中
    ChannelFuture regFuture = config().group().register(channel);
    if (regFuture.cause() != null) {
        if (channel.isRegistered()) {
            channel.close();
        } else {
            channel.unsafe().closeForcibly(); // 強制關(guān)閉 Channel
        }
    }

    // If we are here and the promise is not failed, it's one of the following cases:
    // 1) If we attempted registration from the event loop, the registration has been completed at this point.
    //    i.e. It's safe to attempt bind() or connect() now because the channel has been registered.
    // 2) If we attempted registration from the other thread, the registration request has been successfully
    //    added to the event loop's task queue for later execution.
    //    i.e. It's safe to attempt bind() or connect() now:
    //         because bind() or connect() will be executed *after* the scheduled registration task is executed
    //         because register(), bind(), and connect() are all bound to the same thread.

    return regFuture;

initAndRegister創(chuàng)建了NioServerSocketChannel實例,并為NioServerSocketChannel的pipeline添加handler,再將NioServerSocketChannel注冊到Selector上犹撒。
在NioServerSockerChannel創(chuàng)建完成后折联,調(diào)用pipeline的Head結(jié)點的read方法。服務(wù)端啟動的流程就分析完了识颊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末诚镰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子祥款,更是在濱河造成了極大的恐慌清笨,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刃跛,死亡現(xiàn)場離奇詭異抠艾,居然都是意外死亡,警方通過查閱死者的電腦和手機桨昙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進店門检号,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蛙酪,你說我怎么就攤上這事齐苛。” “怎么了桂塞?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵凹蜂,是天一觀的道長。 經(jīng)常有香客問我阁危,道長炊甲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任欲芹,我火速辦了婚禮卿啡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘菱父。我一直安慰自己颈娜,他們只是感情好剑逃,可當(dāng)我...
    茶點故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著官辽,像睡著了一般蛹磺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上同仆,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天萤捆,我揣著相機與錄音,去河邊找鬼俗批。 笑死俗或,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的岁忘。 我是一名探鬼主播笼恰,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼猫妙,長吁一口氣:“原來是場噩夢啊……” “哼九巡!你這毒婦竟也來了倦畅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤麻汰,失蹤者是張志新(化名)和其女友劉穎速客,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體五鲫,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡挽封,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了臣镣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,643評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡智亮,死狀恐怖忆某,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情阔蛉,我是刑警寧澤弃舒,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站状原,受9級特大地震影響聋呢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜颠区,卻給世界環(huán)境...
    茶點故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一削锰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧毕莱,春花似錦器贩、人聲如沸颅夺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吧黄。三九已至,卻和暖如春唆姐,著一層夾襖步出監(jiān)牢的瞬間拗慨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工奉芦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留赵抢,地道東北人。 一個月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓仗阅,卻偏偏與公主長得像昌讲,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子减噪,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,509評論 2 348

推薦閱讀更多精彩內(nèi)容