高性能Server---Reactor模型

無處不在的C/S架構(gòu)

在這個充斥著云的時代,我們使用的軟件可以說99%都是C/S架構(gòu)的维费!

  • 你發(fā)郵件用的Outlook,Foxmail等
  • 你看視頻用的優(yōu)酷,土豆等
  • 你寫文檔用的Office365,googleDoc捏悬,Evernote等
  • 你瀏覽網(wǎng)頁用的IE,Chrome等(B/S是特殊的C/S)
  • ......

C/S架構(gòu)的軟件帶來的一個明顯的好處就是:只要有網(wǎng)絡(luò)捌归,你可以在任何地方干同一件事非区。

例如:你在家里使用Office365編寫了文檔亲茅。到了公司娘纷,只要打開編輯地址就可以看到在家里編寫的文檔嫁审,進行展示或者繼續(xù)編輯。甚至在手機上進行閱讀與編輯赖晶。不再需要U盤拷來拷去了律适。

C/S架構(gòu)可以抽象為如下模型:

  • C就是Client(客戶端),上面的B是Browser(瀏覽器)
  • S就是Server(服務(wù)器):服務(wù)器管理某種資源,并且通過操作這種資源來為它的客戶端提供某種服務(wù)

C/S架構(gòu)之所以能夠流行的一個主要原因就是網(wǎng)速的提高以及費用的降低遏插,特別是無線網(wǎng)絡(luò)速度的提高捂贿。試想在2G時代,大家最多就是看看文字網(wǎng)頁胳嘲,小說什么的厂僧。看圖片了牛,那簡直就是奢侈颜屠!更別說看視頻了!

網(wǎng)速的提高鹰祸,使得越來越多的人使用網(wǎng)絡(luò)甫窟,例如:優(yōu)酷,微信都是上億用戶量蛙婴,更別說天貓雙11的瞬間訪問量了粗井!這就對服務(wù)器有很高的要求!能夠快速處理海量的用戶請求街图!那服務(wù)器如何能快速的處理用戶的請求呢浇衬?

高性能服務(wù)器

高性能服務(wù)器至少要滿足如下幾個需求:

  • 效率高:既然是高性能,那處理客戶端請求的效率當然要很高了
  • 高可用:不能隨便就掛掉了
  • 編程簡單:基于此服務(wù)器進行業(yè)務(wù)開發(fā)需要足夠簡單
  • 可擴展:可方便的擴展功能
  • 可伸縮:可簡單的通過部署的方式進行容量的伸縮餐济,也就是服務(wù)需要無狀態(tài)

而滿足如上需求的一個基礎(chǔ)就是高性能的IO!

Socket

無論你是發(fā)郵件耘擂,瀏覽網(wǎng)頁,還是看視頻~實際底層都是使用的TCP/IP颤介,而TCP/IP的編程抽象就是Socket!

我一直對Socket的中文翻譯很困惑梳星,個人覺得是我所接觸的技術(shù)名詞翻譯里最莫名其妙的,沒有之一滚朵!

Socket中文翻譯為"套接字"冤灾!什么鬼?在很長的時間里我都無法將其和網(wǎng)絡(luò)編程關(guān)聯(lián)上辕近!后來專門找了一些資料韵吨,最后在知乎上找到了一個還算滿意的答案(具體鏈接,請見文末的參考資料鏈接)移宅!

Socket的原意是插口归粉,想表達的意思是插口與插槽的關(guān)系椿疗!"send socket"插到"receive socket"里,建立了鏈接糠悼,然后就可以通信了届榄!

套接字的翻譯,應(yīng)該是參考了套接管(如下圖)倔喂!從這個層面上來看铝条,是有那么點意思!

套接字這個翻譯已經(jīng)是標準了席噩,不糾結(jié)這個了班缰!

我們看一下Socket之間建立鏈接及通信的過程!實際上就是對TCP/IP連接與通信過程的抽象:

  • 服務(wù)端Socket會bind到指定的端口上悼枢,Listen客戶端的"插入"
  • 客戶端Socket會Connect到服務(wù)端
  • 當服務(wù)端Accept到客戶端連接后
  • 就可以進行發(fā)送與接收消息了
  • 通信完成后即可Close

對于IO來說埠忘,我們聽得比較多的是:

  • BIO:阻塞IO
  • NIO:非阻塞IO
  • 同步IO
  • 異步IO

以及其組合:

  • 同步阻塞IO
  • 同步非阻塞IO
  • 異步阻塞IO
  • 異步非阻塞IO

那么什么是阻塞IO、非阻塞IO馒索、同步IO莹妒、異步IO呢蒜哀?

  • 一個IO操作其實分成了兩個步驟:發(fā)起IO請求和實際的IO操作
  • 阻塞IO和非阻塞IO的區(qū)別在于第一步:發(fā)起IO請求是否會被阻塞岩喷,如果阻塞直到完成那么就是傳統(tǒng)的阻塞IO;如果不阻塞,那么就是非阻塞IO
  • 同步IO和異步IO的區(qū)別就在于第二個步驟是否阻塞,如果實際的IO讀寫阻塞請求進程渔期,那么就是同步IO,因此阻塞IO渴邦、非阻塞IO疯趟、IO復(fù)用、信號驅(qū)動IO都是同步IO;如果不阻塞谋梭,而是操作系統(tǒng)幫你做完IO操作再將結(jié)果返回給你信峻,那么就是異步IO

舉個不太恰當?shù)睦?:比如你家網(wǎng)絡(luò)斷了,你打電話去中國電信報修瓮床!

  • 你撥號---客戶端連接服務(wù)器
  • 電話通了---連接建立
  • 你說:“我家網(wǎng)斷了,幫我修下”---發(fā)送消息
  • 說完你就在那里等盹舞,那么就是阻塞IO
  • 如果正好你有事,你放下帶電話隘庄,然后處理其他事情了踢步,過一會你來問下,修好了沒---那就是非阻塞IO
  • 如果客服說:“馬上幫你處理丑掺,你稍等”---同步IO
  • 如果客服說:“馬上幫你處理获印,好了通知你”,然后掛了電話---異步IO

本文只討論BIO和NIO,AIO使用度沒有前兩者普及街州,暫不討論兼丰!

下面從代碼層面看看BIO與NIO的流程!

BIO

  • 客戶端代碼
//Bind,Connect
Socket client = new Socket("127.0.0.1",7777);    
//讀寫
PrintWriter pw = new PrintWriter(client.getOutputStream());
BufferedReader br=
        new BufferedReader(new InputStreamReader(System.in));  
pw.write(br.readLine());  
//Close
pw.close();  
br.close();
  • 服務(wù)端代碼
Socket socket;  
//Bind,Listen
ServerSocket ss = new ServerSocket(7777);  
while (true) {  
    //Accept
    socket = ss.accept();  
    //一般新建一個線程執(zhí)行讀寫
    BufferedReader br = new BufferedReader(
            new InputStreamReader(socket  .getInputStream()));  
    System.out.println("you input is : " + br.readLine());  
}
  • 上面的代碼可以說是學習Java的Socket的入門級代碼了
  • 代碼流程和前面的圖可以一一對上

模型圖如下所示:

BIO優(yōu)缺點

  • 優(yōu)點
    • 模型簡單
    • 編碼簡單
  • 缺點
    • 性能瓶頸低

優(yōu)缺點很明顯玻孟。這里主要說下缺點:主要瓶頸在線程上。每個連接都會建立一個線程鳍征。雖然線程消耗比進程小黍翎,但是一臺機器實際上能建立的有效線程有限,以Java來說艳丛,1.5以后玩敏,一個線程大致消耗1M內(nèi)存!且隨著線程數(shù)量的增加质礼,CPU切換線程上下文的消耗也隨之增加旺聚,在高過某個閥值后,繼續(xù)增加線程眶蕉,性能不增反降砰粹!而同樣因為一個連接就新建一個線程,所以編碼模型很簡單造挽!

就性能瓶頸這一點碱璃,就確定了BIO并不適合進行高性能服務(wù)器的開發(fā)!像Tomcat這樣的Web服務(wù)器饭入,從7開始就從BIO改成了NIO嵌器,來提高服務(wù)器性能!

NIO

  • NIO客戶端代碼(連接)
//獲取socket通道
SocketChannel channel = SocketChannel.open();        
channel.configureBlocking(false);
//獲得通道管理器
selector=Selector.open();        
channel.connect(new InetSocketAddress(serverIp, port));
//為該通道注冊SelectionKey.OP_CONNECT事件
channel.register(selector, SelectionKey.OP_CONNECT);
  • NIO客戶端代碼(監(jiān)聽)
while(true){
    //選擇注冊過的io操作的事件(第一次為SelectionKey.OP_CONNECT)
   selector.select();
   while(SelectionKey key : selector.selectedKeys()){
       if(key.isConnectable()){
           SocketChannel channel=(SocketChannel)key.channel();
           if(channel.isConnectionPending()){
               channel.finishConnect();//如果正在連接谐丢,則完成連接
           }
           channel.register(selector, SelectionKey.OP_READ);
       }else if(key.isReadable()){ //有可讀數(shù)據(jù)事件爽航。
           SocketChannel channel = (SocketChannel)key.channel();
           ByteBuffer buffer = ByteBuffer.allocate(10);
           channel.read(buffer);
           byte[] data = buffer.array();
           String message = new String(data);
           System.out.println("recevie message from server:, size:"
               + buffer.position() + " msg: " + message);
       }
   }
}
  • NIO服務(wù)端代碼(連接)
//獲取一個ServerSocket通道
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(port));
//獲取通道管理器
selector = Selector.open();
//將通道管理器與通道綁定,并為該通道注冊SelectionKey.OP_ACCEPT事件乾忱,
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
  • NIO服務(wù)端代碼(監(jiān)聽)
while(true){
    //當有注冊的事件到達時讥珍,方法返回,否則阻塞窄瘟。
   selector.select();
   for(SelectionKey key : selector.selectedKeys()){
       if(key.isAcceptable()){
           ServerSocketChannel server =
                (ServerSocketChannel)key.channel();
           SocketChannel channel = server.accept();
           channel.write(ByteBuffer.wrap(
            new String("send message to client").getBytes()));
           //在與客戶端連接成功后衷佃,為客戶端通道注冊SelectionKey.OP_READ事件。
           channel.register(selector, SelectionKey.OP_READ);
       }else if(key.isReadable()){//有可讀數(shù)據(jù)事件
           SocketChannel channel = (SocketChannel)key.channel();
           ByteBuffer buffer = ByteBuffer.allocate(10);
           int read = channel.read(buffer);
           byte[] data = buffer.array();
           String message = new String(data);
           System.out.println("receive message from client, size:"
               + buffer.position() + " msg: " + message);
       }
   }
}

NIO模型示例如下:

  • Acceptor注冊Selector蹄葱,監(jiān)聽accept事件
  • 當客戶端連接后氏义,觸發(fā)accept事件
  • 服務(wù)器構(gòu)建對應(yīng)的Channel,并在其上注冊Selector图云,監(jiān)聽讀寫事件
  • 當發(fā)生讀寫事件后惯悠,進行相應(yīng)的讀寫處理

NIO優(yōu)缺點

  • 優(yōu)點
    • 性能瓶頸高
  • 缺點
    • 模型復(fù)雜
    • 編碼復(fù)雜
    • 需處理半包問題

NIO的優(yōu)缺點和BIO就完全相反了!性能高,不用一個連接就建一個線程琼稻,可以一個線程處理所有的連接吮螺!相應(yīng)的,編碼就復(fù)雜很多,從上面的代碼就可以明顯體會到了鸠补。還有一個問題萝风,由于是非阻塞的,應(yīng)用無法知道什么時候消息讀完了紫岩,就存在了半包問題规惰!

半包問題

簡單看一下下面的圖就能理解半包問題了!

我們知道TCP/IP在發(fā)送消息的時候泉蝌,可能會拆包(如上圖1)歇万!這就導(dǎo)致接收端無法知道什么時候收到的數(shù)據(jù)是一個完整的數(shù)據(jù)。例如:發(fā)送端分別發(fā)送了ABC,DEF,GHI三條信息勋陪,發(fā)送時被拆成了AB,CDRFG,H,I這四個包進行發(fā)送贪磺,接受端如何將其進行還原呢?在BIO模型中诅愚,當讀不到數(shù)據(jù)后會阻塞寒锚,而NIO中不會!所以需要自行進行處理!例如,以換行符作為判斷依據(jù)违孝,或者定長消息發(fā)生刹前,或者自定義協(xié)議!

NIO雖然性能高雌桑,但是編碼復(fù)雜喇喉,且需要處理半包問題!為了方便的進行NIO開發(fā)校坑,就有了Reactor模型!

Reactor模型

  • AWT Events

Reactor模型和AWT事件模型很像拣技,就是將消息放到了一個隊列中,通過異步線程池對其進行消費撒踪!

Reactor中的組件

  • Reactor:Reactor是IO事件的派發(fā)者过咬。
  • Acceptor:Acceptor接受client連接,建立對應(yīng)client的Handler制妄,并向Reactor注冊此Handler。
  • Handler:和一個client通訊的實體泵三,按這樣的過程實現(xiàn)業(yè)務(wù)的處理耕捞。一般在基本的Handler基礎(chǔ)上還會有更進一步的層次劃分, 用來抽象諸如decode烫幕,process和encoder這些過程俺抽。比如對Web Server而言,decode通常是HTTP請求的解析较曼, process的過程會進一步涉及到Listener和Servlet的調(diào)用磷斧。業(yè)務(wù)邏輯的處理在Reactor模式里被分散的IO事件所打破, 所以Handler需要有適當?shù)臋C制在所需的信息還不全(讀到一半)的時候保存上下文,并在下一次IO事件到來的時候(另一半可讀了)能繼續(xù)中斷的處理弛饭。為了簡化設(shè)計冕末,Handler通常被設(shè)計成狀態(tài)機,按GoF的state pattern來實現(xiàn)侣颂。

對應(yīng)上面的NIO代碼來看:

  • Reactor:相當于有分發(fā)功能的Selector
  • Acceptor:NIO中建立連接的那個判斷分支
  • Handler:消息讀寫處理等操作類

Reactor從線程池和Reactor的選擇上可以細分為如下幾種:

Reactor單線程模型

這個模型和上面的NIO流程很類似档桃,只是將消息相關(guān)處理獨立到了Handler中去了!

雖然上面說到NIO一個線程就可以支持所有的IO處理憔晒。但是瓶頸也是顯而易見的藻肄!我們看一個客戶端的情況,如果這個客戶端多次進行請求拒担,如果在Handler中的處理速度較慢嘹屯,那么后續(xù)的客戶端請求都會被積壓,導(dǎo)致響應(yīng)變慢从撼!所以引入了Reactor多線程模型!

Reactor多線程模型

Reactor多線程模型就是將Handler中的IO操作和非IO操作分開抚垄,操作IO的線程稱為IO線程,非IO操作的線程稱為工作線程!這樣的話谋逻,客戶端的請求會直接被丟到線程池中呆馁,客戶端發(fā)送請求就不會堵塞!

但是當用戶進一步增加的時候毁兆,Reactor會出現(xiàn)瓶頸浙滤!因為Reactor既要處理IO操作請求,又要響應(yīng)連接請求气堕!為了分擔Reactor的負擔纺腊,所以引入了主從Reactor模型!

主從Reactor模型

主Reactor用于響應(yīng)連接請求,從Reactor用于處理IO操作請求茎芭!

Netty

Netty是一個高性能NIO框架揖膜,其是對Reactor模型的一個實現(xiàn)!

  • Netty客戶端代碼
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
    Bootstrap b = new Bootstrap();
    b.group(workerGroup);
    b.channel(NioSocketChannel.class);
    b.option(ChannelOption.SO_KEEPALIVE, true);
    b.handler(new ChannelInitializer<SocketChannel>() {
        @Override
        public void initChannel(SocketChannel ch) throws Exception {
            ch.pipeline().addLast(new TimeClientHandler());
        }
    });

    ChannelFuture f = b.connect(host, port).sync();

    f.channel().closeFuture().sync();
} finally {
    workerGroup.shutdownGracefully();
}
  • Netty Client Handler
public class TimeClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf m = (ByteBuf) msg;
        try {
            long currentTimeMillis =
                (m.readUnsignedInt() - 2208988800L) * 1000L;
            System.out.println(new Date(currentTimeMillis));
            ctx.close();
        } finally {
            m.release();
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx,
                Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}
  • Netty服務(wù)端代碼
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
    ServerBootstrap b = new ServerBootstrap();
    b.group(bossGroup, workerGroup)
     .channel(NioServerSocketChannel.class)
     .childHandler(new ChannelInitializer<SocketChannel>() {
         @Override
         public void initChannel(SocketChannel ch) throws Exception {
             ch.pipeline().addLast(new TimeServerHandler());
         }
     })
     .option(ChannelOption.SO_BACKLOG, 128)  
     .childOption(ChannelOption.SO_KEEPALIVE, true);
    // Bind and start to accept incoming connections.
    ChannelFuture f = b.bind(port).sync();
    f.channel().closeFuture().sync();
} finally {
    workerGroup.shutdownGracefully();
    bossGroup.shutdownGracefully();
}
  • Netty Server Handler
public class TimeServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(final ChannelHandlerContext ctx) {
        final ByteBuf time = ctx.alloc().buffer(4);
        time.writeInt((int)
            (System.currentTimeMillis() / 1000L + 2208988800L));

        final ChannelFuture f = ctx.writeAndFlush(time);
        f.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) {
                assert f == future;
                ctx.close();
            }
        });
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx,
        Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

我們從Netty服務(wù)器代碼來看梅桩,與Reactor模型進行對應(yīng)壹粟!

  • EventLoopGroup就相當于是Reactor,bossGroup對應(yīng)主Reactor,workerGroup對應(yīng)從Reactor
  • TimeServerHandler就是Handler
  • child開頭的方法配置的是客戶端channel宿百,非child開頭的方法配置的是服務(wù)端channel

具體Netty內(nèi)容趁仙,請訪問Netty官網(wǎng)

Netty的問題

Netty開發(fā)中一個很明顯的問題就是回調(diào)垦页,一是打破了線性編碼習慣雀费,
二就是Callback Hell!

看下面這個例子:

a.doing1();  //1
a.doing2();  //2
a.doing3();  //3

1,2,3處代碼如果是同步的痊焊,那么將按順序執(zhí)行盏袄!但是如果不是同步的呢忿峻?我還是希望2在1之后執(zhí)行,3在2之后執(zhí)行辕羽!怎么辦呢逛尚?想想AJAX!我們需要寫類似如下這樣的代碼!

a.doing1(new Callback(){
    public void callback(){
        a.doing2(new Callback(){
            public void callback(){
                a.doing3();
            }
        })
    }
});

那有沒有辦法解決這個問題呢逛漫?其實不難黑低,實現(xiàn)一個類似Future的功能!當Client獲取結(jié)果時酌毡,進行阻塞克握,當?shù)玫浇Y(jié)果后再繼續(xù)往下走!實現(xiàn)方案枷踏,一個就是使用鎖了菩暗,還有一個就是使用RingBuffer。經(jīng)測試旭蠕,使用RingBuffer比使用鎖TPS有2000左右的提高停团!

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市旗芬,隨后出現(xiàn)的幾起案子舌胶,更是在濱河造成了極大的恐慌,老刑警劉巖疮丛,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幔嫂,死亡現(xiàn)場離奇詭異,居然都是意外死亡誊薄,警方通過查閱死者的電腦和手機履恩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呢蔫,“玉大人切心,你說我怎么就攤上這事「琅伲” “怎么了昙衅?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長定鸟。 經(jīng)常有香客問我,道長著瓶,這世上最難降的妖魔是什么联予? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上沸久,老公的妹妹穿的比我還像新娘季眷。我一直安慰自己,他們只是感情好卷胯,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布子刮。 她就那樣靜靜地躺著,像睡著了一般窑睁。 火紅的嫁衣襯著肌膚如雪挺峡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天担钮,我揣著相機與錄音橱赠,去河邊找鬼。 笑死箫津,一個胖子當著我的面吹牛狭姨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播苏遥,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼饼拍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了田炭?” 一聲冷哼從身側(cè)響起师抄,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎诫肠,沒想到半個月后司澎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡栋豫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年挤安,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丧鸯。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡蛤铜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出丛肢,到底是詐尸還是另有隱情围肥,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布蜂怎,位于F島的核電站穆刻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏杠步。R本人自食惡果不足惜氢伟,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一榜轿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧朵锣,春花似錦谬盐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至诬烹,卻和暖如春砸烦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背椅您。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工外冀, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人掀泳。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓雪隧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親员舵。 傳聞我的和親對象是個殘疾皇子脑沿,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

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

  • 1、Netty基礎(chǔ)入門 Netty是由JBOSS提供的一個java開源框架马僻。Netty提供異步的庄拇、事件驅(qū)動的網(wǎng)絡(luò)應(yīng)...
    我是嘻哈大哥閱讀 4,687評論 0 31
  • NIO(Non-blocking I/O,在Java領(lǐng)域韭邓,也稱為New I/O)措近,是一種同步非阻塞的I/O模型,也...
    閃電是只貓閱讀 3,086評論 0 7
  • 本文是Netty文集中“Netty 那些事兒”系列的文章女淑。主要結(jié)合在開發(fā)實戰(zhàn)中瞭郑,我們遇到的一些“奇奇怪怪”的問題,...
    tomas家的小撥浪鼓閱讀 15,440評論 3 35
  • 人,生而值得被愛袱巨。 生命不可逆轉(zhuǎn)阁谆,不可往復(fù),所以可愛愉老。何況人场绿,更可愛。 一個人嫉入,必有美與丑裳凸,長與短贱鄙。愛人就要愛他的...
    DEAR陳可閱讀 323評論 0 0
  • 1劝贸、練字一小時姨谷。 2、喜馬拉雅映九、得到泛聽3小時梦湘。 3、跑步件甥、做操半小時捌议。 4、今日以照顧媽媽為主引有,主要的時間都奉獻...
    了茶閱讀 272評論 0 0