Dubbo深入分析之Transport層

Transporter類分析

dubbo為通訊框架提供了統(tǒng)一的bind和connet接口犁钟,方便進(jìn)行管理和擴(kuò)展脓规,封裝在接口類:Transporter中:

@SPI("netty")

public interface Transporter {

@Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})

Server bind(URL url, ChannelHandler handler) throws RemotingException;

@Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})

Client connect(URL url, ChannelHandler handler) throws RemotingException;

}

提供了bind和connect接口士败,分別對應(yīng)這服務(wù)器端和客戶端送讲,具體有哪些實(shí)現(xiàn)類瞻想,如下圖所示:

以默認(rèn)使用的netty框架為例砾省,代碼如下:

?public class NettyTransporter implements Transporter {

? ? public static final String NAME = "netty";

?@Override

public Server bind(URL url, ChannelHandler listener) throws RemotingException {

return new NettyServer(url, listener);

}

?@Override

public Client connect(URL url, ChannelHandler listener) throws RemotingException {

return new NettyClient(url, listener);

}

}

具體的服務(wù)器端封裝在NettyServer中把跨,客戶端封裝在NettyClient谁鳍;url參數(shù)是包含了xml配置的信息(包括:對外的接口们拙,使用的協(xié)議稍途,使用的序列化方式,使用的通訊框架等)砚婆,listener是一個Handler械拍,在解碼之后將數(shù)據(jù)交給它做后續(xù)的業(yè)務(wù)處理;對應(yīng)以上的幾種通訊開源框架装盯,分別提供了對應(yīng)的Transporter包括:NettyTransporter坷虑,NettyTransporter(netty4),MinaTransporter以及GrizzlyTransporter埂奈,具體使用哪種類型的Transporter迄损,在Transporters類中提供了getTransporter方法:

public static Transporter getTransporter() {

return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();

}

這里并沒有像在獲取具體serialization類一樣,通過在url指定transporter參數(shù)账磺,然后加載具體的transporter類芹敌,而是生成了一個動態(tài)的transporter,由此動態(tài)transporter去加載具體的類垮抗;

因?yàn)镾erver端和Client可以分別設(shè)置成不同的通訊框架氏捞,一次獲取唯一的Transporter不能滿足此需求;具體的生成動態(tài)代碼的方法在ExtensionLoader的createAdaptiveExtensionClassCode方法中冒版,此處不在列出源碼液茎,在此展示一下默認(rèn)生成的動態(tài)代碼擴(kuò)展類:

?package com.alibaba.dubbo.remoting;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Transporter$Adaptive implements com.alibaba.dubbo.remoting.Transporter {

public com.alibaba.dubbo.remoting.Server bind(

com.alibaba.dubbo.common.URL arg0,

com.alibaba.dubbo.remoting.ChannelHandler arg1)

throws com.alibaba.dubbo.remoting.RemotingException {

if (arg0 == null) {

throw new IllegalArgumentException("url == null");

}

com.alibaba.dubbo.common.URL url = arg0;

String extName = url.getParameter("server",

url.getParameter("transporter", "netty"));

if (extName == null) {

throw new IllegalStateException(

"Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" +

url.toString() + ") use keys([server, transporter])");

}

com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class)

.getExtension(extName);

return extension.bind(arg0, arg1);

}

public com.alibaba.dubbo.remoting.Client connect(

com.alibaba.dubbo.common.URL arg0,

com.alibaba.dubbo.remoting.ChannelHandler arg1)

throws com.alibaba.dubbo.remoting.RemotingException {

if (arg0 == null) {

throw new IllegalArgumentException("url == null");

}

com.alibaba.dubbo.common.URL url = arg0;

String extName = url.getParameter("client",

url.getParameter("transporter", "netty"));

if (extName == null) {

throw new IllegalStateException(

"Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" +

url.toString() + ") use keys([client, transporter])");

}

com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class)

.getExtension(extName);

return extension.connect(arg0, arg1);

}

}

可以發(fā)現(xiàn)Server端可以通過transporter和server兩個參數(shù)來設(shè)置擴(kuò)展類,而且server參數(shù)設(shè)置的值是可以覆蓋transporter參數(shù)的值辞嗡,同理Client也類似捆等;最后不管是bind()還是connet()都是通過ExtensionLoader的getExtension方法來獲取具體的transporter類;同serialize層欲间,相關(guān)的transporter也同樣定義在META-INF/dubbo/internal/com.alibaba.dubbo.remoting.Transporter文件中:

netty=com.alibaba.dubbo.remoting.transport.netty.NettyTransporter

netty4=com.alibaba.dubbo.remoting.transport.netty4.NettyTransporter

mina=com.alibaba.dubbo.remoting.transport.mina.MinaTransporter

grizzly=com.alibaba.dubbo.remoting.transport.grizzly.GrizzlyTransporter

Server端和Client分析

1.Server端

在實(shí)例化具體的Server類時楚里,會首先調(diào)用父類的構(gòu)造器,進(jìn)行參數(shù)初始化猎贴,同時調(diào)用bind()方法,啟動服務(wù)器蝴光;父類AbstractServer構(gòu)造器如下:

public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {

super(url, handler);

localAddress = getUrl().toInetSocketAddress();

String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());

int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());

if (url.getParameter(Constants.ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) {

bindIp = NetUtils.ANYHOST;

}

bindAddress = new InetSocketAddress(bindIp, bindPort);

this.accepts = url.getParameter(Constants.ACCEPTS_KEY, Constants.DEFAULT_ACCEPTS);

this.idleTimeout = url.getParameter(Constants.IDLE_TIMEOUT_KEY, Constants.DEFAULT_IDLE_TIMEOUT);

try {

doOpen();

if (logger.isInfoEnabled()) {

logger.info("Start " + getClass().getSimpleName() + " bind " + getBindAddress() + ", export " + getLocalAddress());

}

} catch (Throwable t) {

throw new RemotingException(url.toInetSocketAddress(), null, "Failed to bind " + getClass().getSimpleName()

+ " on " + getLocalAddress() + ", cause: " + t.getMessage(), t);

}

//fixme replace this with better method

DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();

executor = (ExecutorService) dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY, Integer.toString(url.getPort()));

}

主要從url獲取啟動參數(shù)包括:ip她渴,port,accepts(可接受的連接數(shù)蔑祟,0表示不受限制數(shù)量趁耗,默認(rèn)為0),idleTimeout等疆虚;然后調(diào)用doOpen方法通過具體的通訊框架綁定端口啟動服務(wù)苛败;已默認(rèn)使用的Netty為例满葛,查看doOpen()方法如下:

protected void doOpen() throws Throwable {

NettyHelper.setNettyLoggerFactory();

ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));

ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));

ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));

bootstrap = new ServerBootstrap(channelFactory);

final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);

channels = nettyHandler.getChannels();

// https://issues.jboss.org/browse/NETTY-365

// https://issues.jboss.org/browse/NETTY-379

// final Timer timer = new HashedWheelTimer(new NamedThreadFactory("NettyIdleTimer", true));

bootstrap.setOption("child.tcpNoDelay", true);

bootstrap.setPipelineFactory(new ChannelPipelineFactory() {

@Override

public ChannelPipeline getPipeline() {

NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);

ChannelPipeline pipeline = Channels.pipeline();

/*int idleTimeout = getIdleTimeout();

if (idleTimeout > 10000) {

pipeline.addLast("timer", new IdleStateHandler(timer, idleTimeout / 1000, 0, 0));

}*/

pipeline.addLast("decoder", adapter.getDecoder());

pipeline.addLast("encoder", adapter.getEncoder());

pipeline.addLast("handler", nettyHandler);

return pipeline;

}

});

// bind

channel = bootstrap.bind(getBindAddress());

}

以上是常規(guī)的啟動netty程序,需要指定編解碼器罢屈,nettyHandler嘀韧;編解碼已經(jīng)在上文中介紹過了,此處不在詳細(xì)介紹缠捌,重點(diǎn)介紹nettyHandler锄贷;server端在數(shù)據(jù)經(jīng)過解碼之后就交給NettyHandler來處理,NettyHandler繼承于Netty的SimpleChannelHandler類曼月,重寫了channelConnected谊却,channelDisconnected,messageReceived哑芹,writeRequested以及exceptionCaught方法炎辨,基本上就是常規(guī)的幾種操作:建立連接,斷開連接聪姿,接收消息碴萧,發(fā)送消息,異常處理咳燕;看一下部分源碼:

@Override

public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {

NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);

try {

if (channel != null) {

channels.put(NetUtils.toAddressString((InetSocketAddress) ctx.getChannel().getRemoteAddress()), channel);

}

handler.connected(channel);

} finally {

NettyChannel.removeChannelIfDisconnected(ctx.getChannel());

}

}

@Override

public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {

NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);

try {

channels.remove(NetUtils.toAddressString((InetSocketAddress) ctx.getChannel().getRemoteAddress()));

handler.disconnected(channel);

} finally {

NettyChannel.removeChannelIfDisconnected(ctx.getChannel());

}

}

@Override

public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {

NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);

try {

handler.received(channel, e.getMessage());

} finally {

NettyChannel.removeChannelIfDisconnected(ctx.getChannel());

}

}

@Override

public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) throws Exception {

super.writeRequested(ctx, e);

NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);

try {

handler.sent(channel, e.getMessage());

} finally {

NettyChannel.removeChannelIfDisconnected(ctx.getChannel());

}

}

@Override

public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {

NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);

try {

handler.caught(channel, e.getCause());

} finally {

NettyChannel.removeChannelIfDisconnected(ctx.getChannel());

}

}

將netty原生的channel包裝成dubbo的NettyChannel勿决,同時將NettyChannel保存在NettyChannel的內(nèi)部靜態(tài)變量channelMap中;這里的方法都統(tǒng)一調(diào)用了getOrAddChannel方法招盲,先添加進(jìn)去低缩,最后在finally中判定channel是否已經(jīng)關(guān)閉,如果關(guān)閉從channelMap中移除曹货;中間部分調(diào)用了handler對應(yīng)的方法咆繁,此處的handler就是在實(shí)例化時傳入的NettyServer,NettyServer本身也是一個ChannelHandler顶籽,可以看一下channelHandler接口類:

?public interface ChannelHandler {

? ? void connected(Channel channel) throws RemotingException;

? ? void disconnected(Channel channel) throws RemotingException;

? ? void sent(Channel channel, Object message) throws RemotingException;

? ? void received(Channel channel, Object message) throws RemotingException;

void caught(Channel channel, Throwable exception) throws RemotingException;

}

具體的server類中也可以做一些處理玩般,比如connected時判段是否超過accepts,如果超過拒絕連接礼饱;處理完之后交給實(shí)例化Server時傳入的ChannelHandler處理坏为,此類具體是在HeaderExchanger中被初始化的:

?public class HeaderExchanger implements Exchanger {

? ? public static final String NAME = "header";

@Override

public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {

return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);

}

@Override

public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {

return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));

}

}

可以發(fā)現(xiàn)這里具體的ChannelHandler是DecodeHandler,注這里的Decode和Netty本身的decode不一樣镊绪,Netty本身的decode在執(zhí)行NettyHandler之前就執(zhí)行解碼了匀伏;后續(xù)的操作在Exchange層進(jìn)行處理,本文暫時先不做介紹蝴韭;

2.Client端

同樣查看父類AbstractClient够颠,構(gòu)造方法如下:

public AbstractClient(URL url, ChannelHandler handler) throws RemotingException {

super(url, handler);

? ? ? ? send_reconnect = url.getParameter(Constants.SEND_RECONNECT_KEY, false);

? ? ? ? shutdown_timeout = url.getParameter(Constants.SHUTDOWN_TIMEOUT_KEY, Constants.DEFAULT_SHUTDOWN_TIMEOUT);

// The default reconnection interval is 2s, 1800 means warning interval is 1 hour.

reconnect_warning_period = url.getParameter("reconnect.waring.period", 1800);

try {

doOpen();

} catch (Throwable t) {

close();

throw new RemotingException(url.toInetSocketAddress(), null,

"Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress()

+ " connect to the server " + getRemoteAddress() + ", cause: " + t.getMessage(), t);

}

try {

// connect.

connect();

if (logger.isInfoEnabled()) {

logger.info("Start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress() + " connect to the server " + getRemoteAddress());

}

} catch (RemotingException t) {

if (url.getParameter(Constants.CHECK_KEY, true)) {

close();

throw t;

} else {

logger.warn("Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress()

+ " connect to the server " + getRemoteAddress() + " (check == false, ignore and retry later!), cause: " + t.getMessage(), t);

}

} catch (Throwable t) {

close();

throw new RemotingException(url.toInetSocketAddress(), null,

"Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress()

+ " connect to the server " + getRemoteAddress() + ", cause: " + t.getMessage(), t);

}

executor = (ExecutorService) ExtensionLoader.getExtensionLoader(DataStore.class)

.getDefaultExtension().get(Constants.CONSUMER_SIDE, Integer.toString(url.getPort()));

ExtensionLoader.getExtensionLoader(DataStore.class)

.getDefaultExtension().remove(Constants.CONSUMER_SIDE, Integer.toString(url.getPort()));

}

客戶端需要提供重連機(jī)制,所以初始化的幾個參數(shù)都和重連有關(guān)榄鉴,send_reconnect表示在發(fā)送消息時發(fā)現(xiàn)連接已經(jīng)斷開是否發(fā)起重連履磨,reconnect_warning_period表示多久報一次重連警告蛉抓,shutdown_timeout表示連接服務(wù)器一直連接不上的超時時間;接下來就是調(diào)用doOpen()方法剃诅,同樣已Netty為例:

protected void doOpen() throws Throwable {

NettyHelper.setNettyLoggerFactory();

bootstrap = new ClientBootstrap(channelFactory);

// config

// @see org.jboss.netty.channel.socket.SocketChannelConfig

bootstrap.setOption("keepAlive", true);

bootstrap.setOption("tcpNoDelay", true);

bootstrap.setOption("connectTimeoutMillis", getTimeout());

final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);

bootstrap.setPipelineFactory(new ChannelPipelineFactory() {

@Override

public ChannelPipeline getPipeline() {

NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);

ChannelPipeline pipeline = Channels.pipeline();

pipeline.addLast("decoder", adapter.getDecoder());

pipeline.addLast("encoder", adapter.getEncoder());

pipeline.addLast("handler", nettyHandler);

return pipeline;

}

});

}

Netty客戶端的常規(guī)代碼巷送,設(shè)置了和Server端相同的NettyHandler,decoder和encoder综苔;下面重點(diǎn)看看connect方法:

protected void connect() throws RemotingException {

connectLock.lock();

try {

if (isConnected()) {

return;

}

initConnectStatusCheckCommand();

doConnect();

if (!isConnected()) {

throw new RemotingException(this, "Failed connect to server " + getRemoteAddress() + " from " + getClass().getSimpleName() + " "

+ NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion()

+ ", cause: Connect wait timeout: " + getTimeout() + "ms.");

} else {

if (logger.isInfoEnabled()) {

logger.info("Successed connect to server " + getRemoteAddress() + " from " + getClass().getSimpleName() + " "

+ NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion()

+ ", channel is " + this.getChannel());

}

}

reconnect_count.set(0);

reconnect_error_log_flag.set(false);

} catch (RemotingException e) {

throw e;

} catch (Throwable e) {

throw new RemotingException(this, "Failed connect to server " + getRemoteAddress() + " from " + getClass().getSimpleName() + " "

+ NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion()

+ ", cause: " + e.getMessage(), e);

} finally {

connectLock.unlock();

}

}

首先判定是否已經(jīng)連接惩系,如果連接直接return;接下來初始化連接狀態(tài)檢查器如筛,定期檢查channel是否連接堡牡,連接斷開會進(jìn)行重連操作,具體代碼如下:在此我向大家推薦一個架構(gòu)學(xué)習(xí)交流群杨刨。交流學(xué)習(xí)群號:821169538 ?里面會分享一些資深架構(gòu)師錄制的視頻錄像:有Spring晤柄,MyBatis,Netty源碼分析妖胀,高并發(fā)芥颈、高性能、分布式赚抡、微服務(wù)架構(gòu)的原理爬坑,JVM性能優(yōu)化、分布式架構(gòu)等這些成為架構(gòu)師必備的知識體系涂臣。還能領(lǐng)取免費(fèi)的學(xué)習(xí)資源盾计,目前受益良多。

private synchronized void initConnectStatusCheckCommand() {

//reconnect=false to close reconnect

int reconnect = getReconnectParam(getUrl());

if (reconnect > 0 && (reconnectExecutorFuture == null || reconnectExecutorFuture.isCancelled())) {

Runnable connectStatusCheckCommand = new Runnable() {

@Override

public void run() {

try {

if (!isConnected()) {

connect();

} else {

lastConnectedTime = System.currentTimeMillis();

}

} catch (Throwable t) {

String errorMsg = "client reconnect to " + getUrl().getAddress() + " find error . url: " + getUrl();

// wait registry sync provider list

if (System.currentTimeMillis() - lastConnectedTime > shutdown_timeout) {

if (!reconnect_error_log_flag.get()) {

reconnect_error_log_flag.set(true);

logger.error(errorMsg, t);

return;

}

}

if (reconnect_count.getAndIncrement() % reconnect_warning_period == 0) {

logger.warn(errorMsg, t);

}

}

}

};

reconnectExecutorFuture = reconnectExecutorService.scheduleWithFixedDelay(connectStatusCheckCommand, reconnect, reconnect, TimeUnit.MILLISECONDS);

}

}

創(chuàng)建了一個Runnable赁遗,用來檢測是否連接署辉,如果連接斷開,調(diào)用connect方法岩四;定時調(diào)度交給ScheduledThreadPoolExecutor來執(zhí)行哭尝;初始化之后就調(diào)用具體Client的doConnect操作,也是通訊框架的一些常規(guī)代碼剖煌,此處不列出了材鹦;后續(xù)關(guān)于NettyChannel的介紹和Server端類似,不過多進(jìn)行介紹耕姊;

總結(jié)

本文重點(diǎn)分析了dubbo架構(gòu)中的transport層侠姑,具體圍繞Transporter, Client, Server,ChannelHandler幾個類展開箩做,關(guān)于后續(xù)的處理將在exchange信息交換層;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末妥畏,一起剝皮案震驚了整個濱河市邦邦,隨后出現(xiàn)的幾起案子安吁,更是在濱河造成了極大的恐慌,老刑警劉巖燃辖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鬼店,死亡現(xiàn)場離奇詭異,居然都是意外死亡黔龟,警方通過查閱死者的電腦和手機(jī)妇智,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來氏身,“玉大人巍棱,你說我怎么就攤上這事〉靶溃” “怎么了航徙?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長陷虎。 經(jīng)常有香客問我到踏,道長,這世上最難降的妖魔是什么尚猿? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任窝稿,我火速辦了婚禮,結(jié)果婚禮上凿掂,老公的妹妹穿的比我還像新娘伴榔。我一直安慰自己,他們只是感情好缠劝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布潮梯。 她就那樣靜靜地躺著,像睡著了一般惨恭。 火紅的嫁衣襯著肌膚如雪秉馏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天脱羡,我揣著相機(jī)與錄音萝究,去河邊找鬼。 笑死锉罐,一個胖子當(dāng)著我的面吹牛帆竹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播脓规,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼栽连,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起秒紧,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤绢陌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后熔恢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脐湾,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年叙淌,在試婚紗的時候發(fā)現(xiàn)自己被綠了秤掌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡鹰霍,死狀恐怖闻鉴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情衅谷,我是刑警寧澤椒拗,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站获黔,受9級特大地震影響蚀苛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜玷氏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一堵未、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧盏触,春花似錦渗蟹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辨嗽,卻和暖如春世落,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背糟需。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工屉佳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人洲押。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓武花,卻偏偏與公主長得像,于是被迫代替她去往敵國和親杈帐。 傳聞我的和親對象是個殘疾皇子体箕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評論 2 353

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