国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Netty 源碼分析之 一 揭開 Bootstrap 神秘的紅蓋頭 (服務器端)

張金寶 / 808人閱讀

摘要:目錄源碼分析之番外篇的前生今世的前生今世之一簡介的前生今世之二小結的前生今世之三詳解的前生今世之四詳解源碼分析之零磨刀不誤砍柴工源碼分析環境搭建源碼分析之一揭開神秘的紅蓋頭源碼分析之一揭開神秘的紅蓋頭客戶端源碼分析之一揭開神秘的紅蓋頭服務器

目錄

Netty 源碼分析之 番外篇 Java NIO 的前生今世

Java NIO 的前生今世 之一 簡介

Java NIO 的前生今世 之二 NIO Channel 小結

Java NIO 的前生今世 之三 NIO Buffer 詳解

Java NIO 的前生今世 之四 NIO Selector 詳解

Netty 源碼分析之 零 磨刀不誤砍柴工 源碼分析環境搭建

Netty 源碼分析之 一 揭開 Bootstrap 神秘的紅蓋頭

Netty 源碼分析之 一 揭開 Bootstrap 神秘的紅蓋頭 (客戶端)

Netty 源碼分析之 一 揭開 Bootstrap 神秘的紅蓋頭 (服務器端)

服務器端

在分析客戶端的代碼時, 我們已經對 Bootstrap 啟動 Netty 有了一個大致的認識, 那么接下來分析服務器端時, 就會相對簡單一些了.
首先還是來看一下服務器端的啟動代碼:

public final class EchoServer {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));

    public static void main(String[] args) throws Exception {
        // Configure SSL.
        final SslContext sslCtx;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        } else {
            sslCtx = null;
        }

        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc()));
                     }
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(new EchoServerHandler());
                 }
             });

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

            // 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();
        }
    }
}

和客戶端的代碼相比, 沒有很大的差別, 基本上也是進行了如下幾個部分的初始化:

EventLoopGroup: 不論是服務器端還是客戶端, 都必須指定 EventLoopGroup. 在這個例子中, 指定了 NioEventLoopGroup, 表示一個 NIO 的EventLoopGroup, 不過服務器端需要指定兩個 EventLoopGroup, 一個是 bossGroup, 用于處理客戶端的連接請求; 另一個是 workerGroup, 用于處理與各個客戶端連接的 IO 操作.

ChannelType: 指定 Channel 的類型. 因為是服務器端, 因此使用了 NioServerSocketChannel.

Handler: 設置數據的處理器.

Channel 的初始化過程

我們在分析客戶端的 Channel 初始化過程時, 已經提到, Channel 是對 Java 底層 Socket 連接的抽象, 并且知道了客戶端的 Channel 的具體類型是 NioSocketChannel, 那么自然的, 服務器端的 Channel 類型就是 NioServerSocketChannel 了.
那么接下來我們按照分析客戶端的流程對服務器端的代碼也同樣地分析一遍, 這樣也方便我們對比一下服務器端和客戶端有哪些不一樣的地方.

Channel 類型的確定

同樣的分析套路, 我們已經知道了, 在客戶端中, Channel 的類型其實是在初始化時, 通過 Bootstrap.channel() 方法設置的, 服務器端自然也不例外.
在服務器端, 我們調用了 ServerBootstarap.channel(NioServerSocketChannel.class), 傳遞了一個 NioServerSocketChannel Class 對象. 這樣的話, 按照和分析客戶端代碼一樣的流程, 我們就可以確定, NioServerSocketChannel 的實例化是通過 BootstrapChannelFactory 工廠類來完成的, 而 BootstrapChannelFactory 中的 clazz 字段被設置為了 NioServerSocketChannel.class, 因此當調用 BootstrapChannelFactory.newChannel() 時:

@Override
public T newChannel() {
    // 刪除 try 塊
    return clazz.newInstance();
}

就獲取到了一個 NioServerSocketChannel 的實例.

最后我們也來總結一下:

ServerBootstrap 中的 ChannelFactory 的實現是 BootstrapChannelFactory

生成的 Channel 的具體類型是 NioServerSocketChannel.
Channel 的實例化過程, 其實就是調用的 ChannelFactory.newChannel 方法, 而實例化的 Channel 的具體的類型又是和在初始化 ServerBootstrap 時傳入的 channel() 方法的參數相關. 因此對于我們這個例子中的服務器端的 ServerBootstrap 而言, 生成的的 Channel 實例就是 NioServerSocketChannel.

NioServerSocketChannel 的實例化過程

首先還是來看一下 NioServerSocketChannel 的實例化過程.
下面是 NioServerSocketChannel 的類層次結構圖:

首先, 我們來看一下它的默認的構造器. 和 NioSocketChannel 類似, 構造器都是調用了 newSocket 來打開一個 Java 的 NIO Socket, 不過需要注意的是, 客戶端的 newSocket 調用的是 openSocketChannel, 而服務器端的 newSocket 調用的是 openServerSocketChannel. 顧名思義, 一個是客戶端的 Java SocketChannel, 一個是服務器端的 Java ServerSocketChannel.

private static ServerSocketChannel newSocket(SelectorProvider provider) {
    return provider.openServerSocketChannel();
}

public NioServerSocketChannel() {
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}

接下來會調用重載的構造器:

public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

這個構造其中, 調用父類構造器時, 傳入的參數是 SelectionKey.OP_ACCEPT. 作為對比, 我們回想一下, 在客戶端的 Channel 初始化時, 傳入的參數是 SelectionKey.OP_READ. 有 Java NIO Socket 開發經驗的朋友就知道了, Java NIO 是一種 Reactor 模式, 我們通過 selector 來實現 I/O 的多路復用復用. 在一開始時, 服務器端需要監聽客戶端的連接請求, 因此在這里我們設置了 SelectionKey.OP_ACCEPT, 即通知 selector 我們對客戶端的連接請求感興趣.

接著和客戶端的分析一下, 會逐級地調用父類的構造器 NioServerSocketChannel <- AbstractNioMessageChannel <- AbstractNioChannel <- AbstractChannel.
同樣的, 在 AbstractChannel 中會實例化一個 unsafe 和 pipeline:

protected AbstractChannel(Channel parent) {
    this.parent = parent;
    unsafe = newUnsafe();
    pipeline = new DefaultChannelPipeline(this);
}

不過, 這里有一點需要注意的是, 客戶端的 unsafe 是一個 AbstractNioByteChannel#NioByteUnsafe 的實例, 而在服務器端時, 因為 AbstractNioMessageChannel 重寫了newUnsafe 方法:

@Override
protected AbstractNioUnsafe newUnsafe() {
    return new NioMessageUnsafe();
}

因此在服務器端, unsafe 字段其實是一個 AbstractNioMessageChannel#AbstractNioUnsafe 的實例.
我們來總結一下, 在 NioServerSocketChannsl 實例化過程中, 所需要做的工作:

調用 NioServerSocketChannel.newSocket(DEFAULT_SELECTOR_PROVIDER) 打開一個新的 Java NIO ServerSocketChannel

AbstractChannel(Channel parent) 中初始化 AbstractChannel 的屬性:

parent 屬性置為 null

unsafe 通過newUnsafe() 實例化一個 unsafe 對象, 它的類型是 AbstractNioMessageChannel#AbstractNioUnsafe 內部類

pipeline 是 new DefaultChannelPipeline(this) 新創建的實例.

AbstractNioChannel 中的屬性:

SelectableChannel ch 被設置為 Java ServerSocketChannel, 即 NioServerSocketChannel#newSocket 返回的 Java NIO ServerSocketChannel.

readInterestOp 被設置為 SelectionKey.OP_ACCEPT

SelectableChannel ch 被配置為非阻塞的 ch.configureBlocking(false)

NioServerSocketChannel 中的屬性:

ServerSocketChannelConfig config = new NioServerSocketChannelConfig(this, javaChannel().socket())

ChannelPipeline 初始化

服務器端和客戶端的 ChannelPipeline 的初始化一致, 因此就不再多帶帶分析了.

Channel 的注冊

服務器端和客戶端的 Channel 的注冊過程一致, 因此就不再多帶帶分析了.

關于 bossGroup 與 workerGroup

在客戶端的時候, 我們只提供了一個 EventLoopGroup 對象, 而在服務器端的初始化時, 我們設置了兩個 EventLoopGroup, 一個是 bossGroup, 另一個是 workerGroup. 那么這兩個 EventLoopGroup 都是干什么用的呢? 其實呢, bossGroup 是用于服務端 的 accept 的, 即用于處理客戶端的連接請求. 我們可以把 Netty 比作一個飯店, bossGroup 就像一個像一個前臺接待, 當客戶來到飯店吃時, 接待員就會引導顧客就坐, 為顧客端茶送水等. 而 workerGroup, 其實就是實際上干活的啦, 它們負責客戶端連接通道的 IO 操作: 當接待員 招待好顧客后, 就可以稍做休息, 而此時后廚里的廚師們(workerGroup)就開始忙碌地準備飯菜了.
關于 bossGroup 與 workerGroup 的關系, 我們可以用如下圖來展示:

首先, 服務器端 bossGroup 不斷地監聽是否有客戶端的連接, 當發現有一個新的客戶端連接到來時, bossGroup 就會為此連接初始化各項資源, 然后從 workerGroup 中選出一個 EventLoop 綁定到此客戶端連接中. 那么接下來的服務器與客戶端的交互過程就全部在此分配的 EventLoop 中了.

口說無憑, 我們還是以源碼說話吧.
首先在ServerBootstrap 初始化時, 調用了 b.group(bossGroup, workerGroup) 設置了兩個 EventLoopGroup, 我們跟蹤進去看一下:

public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
    super.group(parentGroup);
    ...
    this.childGroup = childGroup;
    return this;
}

顯然, 這個方法初始化了兩個字段, 一個是 group = parentGroup, 它是在 super.group(parentGroup) 中初始化的, 另一個是 childGroup = childGroup. 接著我們啟動程序調用了 b.bind 方法來監聽一個本地端口. bind 方法會觸發如下的調用鏈:

AbstractBootstrap.bind -> AbstractBootstrap.doBind -> AbstractBootstrap.initAndRegister

AbstractBootstrap.initAndRegister 是我們的老朋友了, 我們在分析客戶端程序時, 和它打過很多交到了, 我們再來回顧一下這個方法吧:

final ChannelFuture initAndRegister() {
    final Channel channel = channelFactory().newChannel();
    ... 省略異常判斷
    init(channel);
    ChannelFuture regFuture = group().register(channel);
    return regFuture;
}

這里 group() 方法返回的是上面我們提到的 bossGroup, 而這里的 channel 我們也已經分析過了, 它是一個是一個 NioServerSocketChannsl 實例, 因此我們可以知道, group().register(channel) 將 bossGroup 和 NioServerSocketChannsl 關聯起來了.
那么 workerGroup 是在哪里與 NioSocketChannel 關聯的呢?
我們繼續看 init(channel) 方法:

@Override
void init(Channel channel) throws Exception {
    ...
    ChannelPipeline p = channel.pipeline();

    final EventLoopGroup currentChildGroup = childGroup;
    final ChannelHandler currentChildHandler = childHandler;
    final Entry, Object>[] currentChildOptions;
    final Entry, Object>[] currentChildAttrs;

    p.addLast(new ChannelInitializer() {
        @Override
        public void initChannel(Channel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            ChannelHandler handler = handler();
            if (handler != null) {
                pipeline.addLast(handler);
            }
            pipeline.addLast(new ServerBootstrapAcceptor(
                    currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
        }
    });
}

init 方法在 ServerBootstrap 中重寫了, 從上面的代碼片段中我們看到, 它為 pipeline 中添加了一個 ChannelInitializer, 而這個 ChannelInitializer 中添加了一個關鍵的 ServerBootstrapAcceptor handler. 關于 handler 的添加與初始化的過程, 我們留待下一小節中分析, 我們現在關注一下 ServerBootstrapAcceptor 類.
ServerBootstrapAcceptor 中重寫了 channelRead 方法, 其主要代碼如下:

@Override
@SuppressWarnings("unchecked")
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    final Channel child = (Channel) msg;
    child.pipeline().addLast(childHandler);
    ...
    childGroup.register(child).addListener(...);
}

ServerBootstrapAcceptor 中的 childGroup 是構造此對象是傳入的 currentChildGroup, 即我們的 workerGroup, 而 Channel 是一個 NioSocketChannel 的實例, 因此這里的 childGroup.register 就是將 workerGroup 中的摸個 EventLoop 和 NioSocketChannel 關聯了. 既然這樣, 那么現在的問題是, ServerBootstrapAcceptor.channelRead 方法是怎么被調用的呢? 其實當一個 client 連接到 server 時, Java 底層的 NIO ServerSocketChannel 會有一個 SelectionKey.OP_ACCEPT 就緒事件, 接著就會調用到 NioServerSocketChannel.doReadMessages:

@Override
protected int doReadMessages(List buf) throws Exception {
    SocketChannel ch = javaChannel().accept();
    ... 省略異常處理
    buf.add(new NioSocketChannel(this, ch));
    return 1;
}

在 doReadMessages 中, 通過 javaChannel().accept() 獲取到客戶端新連接的 SocketChannel, 接著就實例化一個 NioSocketChannel, 并且傳入 NioServerSocketChannel 對象(即 this), 由此可知, 我們創建的這個 NioSocketChannel 的父 Channel 就是 NioServerSocketChannel 實例 .
接下來就經由 Netty 的 ChannelPipeline 機制, 將讀取事件逐級發送到各個 handler 中, 于是就會觸發前面我們提到的 ServerBootstrapAcceptor.channelRead 方法啦.

handler 的添加過程

服務器端的 handler 的添加過程和客戶端的有點區別, 和 EventLoopGroup 一樣, 服務器端的 handler 也有兩個, 一個是通過 handler() 方法設置 handler 字段, 另一個是通過 childHandler() 設置 childHandler 字段. 通過前面的 bossGroup 和 workerGroup 的分析, 其實我們在這里可以大膽地猜測: handler 字段與 accept 過程有關, 即這個 handler 負責處理客戶端的連接請求; 而 childHandler 就是負責和客戶端的連接的 IO 交互.
那么實際上是不是這樣的呢? 來, 我們繼續通過代碼證明.

關于 bossGroup 與 workerGroup 小節中, 我們提到, ServerBootstrap 重寫了 init 方法, 在這個方法中添加了 handler:

@Override
void init(Channel channel) throws Exception {
    ...
    ChannelPipeline p = channel.pipeline();

    final EventLoopGroup currentChildGroup = childGroup;
    final ChannelHandler currentChildHandler = childHandler;
    final Entry, Object>[] currentChildOptions;
    final Entry, Object>[] currentChildAttrs;

    p.addLast(new ChannelInitializer() {
        @Override
        public void initChannel(Channel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            ChannelHandler handler = handler();
            if (handler != null) {
                pipeline.addLast(handler);
            }
            pipeline.addLast(new ServerBootstrapAcceptor(
                    currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
        }
    });
}

上面代碼的 initChannel 方法中, 首先通過 handler() 方法獲取一個 handler, 如果獲取的 handler 不為空,則添加到 pipeline 中. 然后接著, 添加了一個 ServerBootstrapAcceptor 實例. 那么這里 handler() 方法返回的是哪個對象呢? 其實它返回的是 handler 字段, 而這個字段就是我們在服務器端的啟動代碼中設置的:

b.group(bossGroup, workerGroup)
 ...
 .handler(new LoggingHandler(LogLevel.INFO))

那么這個時候, pipeline 中的 handler 情況如下:

根據我們原來分析客戶端的經驗, 我們指定, 當 channel 綁定到 eventLoop 后(在這里是 NioServerSocketChannel 綁定到 bossGroup)中時, 會在 pipeline 中發出 fireChannelRegistered 事件, 接著就會觸發 ChannelInitializer.initChannel 方法的調用.
因此在綁定完成后, 此時的 pipeline 的內如如下:

前面我們在分析 bossGroup 和 workerGroup 時, 已經知道了在 ServerBootstrapAcceptor.channelRead 中會為新建的 Channel 設置 handler 并注冊到一個 eventLoop 中, 即:

@Override
@SuppressWarnings("unchecked")
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    final Channel child = (Channel) msg;
    child.pipeline().addLast(childHandler);
    ...
    childGroup.register(child).addListener(...);
}

而這里的 childHandler 就是我們在服務器端啟動代碼中設置的 handler:

b.group(bossGroup, workerGroup)
 ...
 .childHandler(new ChannelInitializer() {
     @Override
     public void initChannel(SocketChannel ch) throws Exception {
         ChannelPipeline p = ch.pipeline();
         if (sslCtx != null) {
             p.addLast(sslCtx.newHandler(ch.alloc()));
         }
         //p.addLast(new LoggingHandler(LogLevel.INFO));
         p.addLast(new EchoServerHandler());
     }
 });

后續的步驟就沒有什么好說的了, 當這個客戶端連接 Channel 注冊后, 就會觸發 ChannelInitializer.initChannel 方法的調用, 此后的客戶端連接的 ChannelPipeline 狀態如下:

最后我們來總結一下服務器端的 handler 與 childHandler 的區別與聯系:

在服務器 NioServerSocketChannel 的 pipeline 中添加的是 handler 與 ServerBootstrapAcceptor.

當有新的客戶端連接請求時, ServerBootstrapAcceptor.channelRead 中負責新建此連接的 NioSocketChannel 并添加 childHandler 到 NioSocketChannel 對應的 pipeline 中, 并將此 channel 綁定到 workerGroup 中的某個 eventLoop 中.

handler 是在 accept 階段起作用, 它處理客戶端的連接請求.

childHandler 是在客戶端連接建立以后起作用, 它負責客戶端連接的 IO 交互.

下面我們用一幅圖來總結一下服務器端的 handler 添加流程:

后記

這是 Netty 源碼分析 系列教程的第一篇, 按我的計劃, 這一篇文章是一個簡述性質的, 即這里會涉及到 Netty 各個功能模塊, 但是我只是簡單地提了一下, 而沒有深入地探索它們內部的實現機理. 之所以這樣做, 第一, 是因為如果一上來就從細節分析, 那么未免會陷入各種瑣碎的細節中難以自拔; 第二, 我想給讀者展示一個一個完整的 Netty 的運行流程, 讓讀者從一個整體上對 Netty 有一個感性的認識.
此篇文章涉及的模塊比較多, 面比較廣, 因此寫起來難免有一點跳躍, 并且我感覺寫著寫著見見有點不知所云, 邏輯混亂了, 汗. 唉, 還是感覺自己功力不夠, hold 不住.
接下來的幾篇文章, 我會根據 Netty 的各個模塊深入分析一下, 希望以后的文章能夠組織的調理更加清晰一些.

本文由 yongshun 發表于個人博客, 采用 署名-相同方式共享 3.0 中國大陸許可協議.
Email: yongshun1228@gmail.com
本文標題為: Netty 源碼分析之 一 揭開 Bootstrap 神秘的紅蓋頭 (服務器端)
本文鏈接為: https://segmentfault.com/a/1190000007283053

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66228.html

相關文章

  • Netty 源碼分析 揭開 Bootstrap 神秘的紅蓋頭 (客戶)

    摘要:目錄源碼分析之番外篇的前生今世的前生今世之一簡介的前生今世之二小結的前生今世之三詳解的前生今世之四詳解源碼分析之零磨刀不誤砍柴工源碼分析環境搭建源碼分析之一揭開神秘的紅蓋頭源碼分析之一揭開神秘的紅蓋頭客戶端源碼分析之一揭開神秘的紅蓋頭服務器 目錄 Netty 源碼分析之 番外篇 Java NIO 的前生今世 Java NIO 的前生今世 之一 簡介 Java NIO 的前生今世 ...

    zhaot 評論0 收藏0
  • 源碼下無秘密 ── 做最好的 Netty 源碼分析教程

    摘要:背景在工作中雖然我經常使用到庫但是很多時候對的一些概念還是處于知其然不知其所以然的狀態因此就萌生了學習源碼的想法剛開始看源碼的時候自然是比較痛苦的主要原因有兩個第一網上沒有找到讓我滿意的詳盡的源碼分析的教程第二我也是第一次系統地學習這么大代 背景 在工作中, 雖然我經常使用到 Netty 庫, 但是很多時候對 Netty 的一些概念還是處于知其然, 不知其所以然的狀態, 因此就萌生了學...

    shenhualong 評論0 收藏0
  • Netty 源碼分析 三 我就是大名鼎鼎的 EventLoop()

    摘要:目錄源碼之下無秘密做最好的源碼分析教程源碼分析之番外篇的前生今世的前生今世之一簡介的前生今世之二小結的前生今世之三詳解的前生今世之四詳解源碼分析之零磨刀不誤砍柴工源碼分析環境搭建源碼分析之一揭開神秘的紅蓋頭源碼分析之一揭開神秘的紅蓋頭客戶端 目錄 源碼之下無秘密 ── 做最好的 Netty 源碼分析教程 Netty 源碼分析之 番外篇 Java NIO 的前生今世 Java NI...

    livem 評論0 收藏0
  • Netty 源碼分析 二 貫穿Netty 的大動脈 ── ChannelPipeline ()

    摘要:目錄源碼之下無秘密做最好的源碼分析教程源碼分析之番外篇的前生今世的前生今世之一簡介的前生今世之二小結的前生今世之三詳解的前生今世之四詳解源碼分析之零磨刀不誤砍柴工源碼分析環境搭建源碼分析之一揭開神秘的紅蓋頭源碼分析之一揭開神秘的紅蓋頭客戶端 目錄 源碼之下無秘密 ── 做最好的 Netty 源碼分析教程 Netty 源碼分析之 番外篇 Java NIO 的前生今世 Java NI...

    tunny 評論0 收藏0
  • Netty 源碼分析 二 貫穿Netty 的大動脈 ── ChannelPipeline (二)

    摘要:目錄源碼之下無秘密做最好的源碼分析教程源碼分析之番外篇的前生今世的前生今世之一簡介的前生今世之二小結的前生今世之三詳解的前生今世之四詳解源碼分析之零磨刀不誤砍柴工源碼分析環境搭建源碼分析之一揭開神秘的紅蓋頭源碼分析之一揭開神秘的紅蓋頭客戶端 目錄 源碼之下無秘密 ── 做最好的 Netty 源碼分析教程 Netty 源碼分析之 番外篇 Java NIO 的前生今世 Java NI...

    kamushin233 評論0 收藏0

發表評論

0條評論

張金寶

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<