NS3 Attribute和Config内容翻译

以下是ns3手册内容翻译


在 ns-3 的模拟中,主要有两个方面的设置:

  • 模拟拓扑和对象是如何连接的。
  • 在拓扑中实例化的模型所使用的值。

本章的重点在于上述的第二项:ns-3 中使用的大量的值是如何组织的、记录的
以及 ns-3 的用户如何来修改这些值。对于跟踪和统计信息是如何在模拟器中收
集的,ns-3 的属性系统也起到了支柱作用。

在深入研究属性值系统之前,回顾 class ns3::Object 的一些基本属性会很有帮
助。


3.1 对象概述

ns-3 本质上是一个基于 C++对象的系统。这意味着新的 C++类(类型)可以像
往常一样被声明、定义以及子类化。
许多 ns-3 的对象继承基类 ns3::Object。这些对象有很多附加的属性,这些属
性是我们为了对系统进行组织和以及改进对象的内存管理而开发的:
“metadata” 系统将类名与大量的 meta-information 进行链接,这些
meta-informatino 是与对象有关的,包括子类的基类、子类中可访问的构造函数集以及子类的 “attributes” 集。
a reference counting smart pointer implementation, for memory management.
使用属性系统的 ns-3 对象是从 ns3::Object 或 ns3::ObjectBase 派生的。我们
将要讨论的大多数 ns-3 对象派生自 ns3::Object,但 一些在 smart pointer 内
存管理框架之外的对象派生自 ns3::ObjectBase。
让我们回顾一下这些对象的一些属性。


3.1.1 Smart pointers

正如 ns-3 tutorial 中介绍的,
ns-3 的对象的内存管理师通过 reference counting
smart pointer implementation,class ns3::Ptr 实现的。
Smart pointers 在 ns-3 的 API 被广泛的使用,来避免 将引用传递给堆分配的
对象而可能引起内存泄漏。对于大多数基本的用法(语法),将 smart pointer
看待为常规指针:

Ptr<WifiNetDevice> nd = ...;
nd->CallSomeFunction ();
// etc.

3.1.2 创建对象

正如我们在@ref{Object Creation}中讨论过的,在最底层的 API 中,具有
ns3::Object 类型的对象不是像通常一样用 operator new 实例化的,而是通过
一个叫做 CreateObject() 的模板函数实例化的。
创建这样的对象的典型方法如下:

Ptr<WifiNetDevice> nd = CreateObject<WifiNetDevice> ();

你可以认为这在功能上等价于:

WifiNetDevice* nd = new WifiNetDevice ();

由 ns3::Object 派生的对象一定是通过 CreateObject()被分配在堆上。而由
ns3::ObjectBase 派生的对象,
比如 ns-3 的 helper functions and packet headers
and trailers, 可以被分配在栈上。在一些脚本中,你可能看不到大量的 CreateObject()调用,那时因为存在很多
helper objects,他们为你执行了 CreateObject()s。


3.1.3 TypeId

ns-3 中由类 ns3::Object 派生出的类可以包含一个叫做 TypeId 的元数据类,
该类记录关于类的元信息,以便在对象聚合以及构件管理系统中使用:
识别该类的一个独一无二的字符串。a unique string identifying the class
在元数据系统中,子类的基类。
子类中 可访问的构造函数的集合。


3.1.4 对象小结

将所有这些概念综合在一起,让我们研究一个特定的例子:class ns3::Node。
公共头文件 node.h 中的一个声明包含了一个静态的 GetTypeId 函数调用:

class Node : public Object
{
public:
static TypeId GetTypeId (void);
...
该函数在文件 node.cc 中定义如下:
TypeId
Node::GetTypeId (void)
{
static TypeId tid = TypeId (“ns3::Node”)
.SetParent<Object> ()
;
return tid;
}

最终,当用户要创建节点时,可以这样调用:

Ptr<Node> n = CreateObject<Node> ();

下边我们会讨论属性(与类的成员变量以及成员函数相关联的值)是如何被加入上述 TypeId 的。


3.2 属性概述

属性系统的目标是组织对模拟的内部成员对象的访问。这个目标的产生式因为:
通常在模拟中,用户将剪切、粘贴或修改现存的模拟脚本,或者将使用更高层的
模拟构造,但通常对研究或跟踪某些特别的内部变量很有兴趣。例如:
“我只想跟踪第一个接入点的无线接口上的包。”
“我想跟踪某个特定 TCP 套接字上 TCP 拥塞窗口的值(每次当它发生变化时)”
“我想获取并记录模拟中所有被使用到的值。”
类似地,用户可能想对模拟中的内部变量进行细致的访问,或者可能想广泛地改
变某个特定参数的初始值,以便涉及到所有随后创建的对象。用户还可能希望知
道在模拟配置中哪些变量是可以设置的和可以获得的。这不仅仅是为了命令行下
直接交互,还考虑到(将来的)图形用户界面,该界面可能提供让用户在节点上
右击鼠标就能获得信息的功能,这些信息可能是一个层次性组织的参数列表,显
示该节点上可以设置的参数以及构成节点的成员对象,还有帮助信息和每个参数
的默认值。


3.2.1 功能概述 Functional overview

我们给用户提供可以访问系统深处的值的方法,而不用在系统中加入存取器(指
针)并通过指针链来获取想要的值。考虑类 DropTailQueue,该类有一个叫做
m_maxPackets 的无符号整型成员变量,该成员变量控制队列的大小。
查看 DropTailQueue 的声明,我们看到:

class DropTailQueue : public Queue {
public:
static TypeId GetTypeId (void);
...
private:
std::queue<Ptr<Packet> > m_packets;
uint32_t m_maxPackets;
};

考虑用户可能对 m_maxPackets 的值想要做的事情:为系统设置一个默认值,以便无论何时一个新的 DropTailQueue 被创建时,这
个成员变量都被初始化成该默认值。
对一个已经被实例化过的队列,设置或获取队列的该值。
上述情况通常需要提供 Set()和 Get()函数,以及某些类型的全局默认值。
在 ns-3 的属性系统中,这些值定义和存取器函数被移入类 TypeId,例如:

TypeId DropTailQueue::GetTypeId (void)
{
static TypeId tid = TypeId (“ns3::DropTailQueue”)
.SetParent<Queue> ()
.AddConstructor<DropTailQueue> ()
.AddAttribute (“MaxPackets”,
“The maximum number of packets accepted by this DropTailQueue.”,
UintegerValue (100),
MakeUintegerAccessor (&DropTailQueue::m_maxPackets),
MakeUintegerChecker<uint32_t> ())
;
return tid;
}

方法 AddAttribute()对该值进行一系列处理:
将变量 m_maxPackets 绑定到一个字符串”MaxPackets”。
提供默认值(100 packets)
提供为该值下定义的帮助信息。
提供”checker”(本例中未使用),可以用来设置所允许的上下界。
关键的一点是,现在该变量的值以及它的默认值在属性名字空间中是可访问的,
基于字符串”MaxPackets”和 TypeId 字符串。在下一节,我们将提供一个例子
来说明用户如何来操纵这些值。


3.2.2 基本用法

我们研究一下用户脚本如何访问这些值,基于文件
samples/main-attribute-value.cc,略去了一些细节。

//
// This is a basic example of how to use the attribute system to// set and get a value in the underlying system; namely, an unsigned
// integer of the maximum number of packets in a queue
//
int
main (int argc, char *argv[])
{
// By default, the MaxPackets attribute has a value of 100 packets
// (this default can be observed in the function DropTailQueue::GetTypeId)
//
// Here, we set it to 80 packets. We could use one of two value types:
// a string-based value or a Uinteger value
Config::SetDefault (“ns3::DropTailQueue::MaxPackets”, StringValue (“80′′));

// The below function call is redundant
Config::SetDefault (“ns3::DropTailQueue::MaxPackets”, UintegerValue (80));

// Allow the user to override any of the defaults and the above
// SetDefaults() at run-time, via command-line arguments
CommandLine cmd;
cmd.Parse (argc, argv);

需要注意的是对 Config::SetDefault 的两次调用。这表明了我们如何设置随后
被实例化的 DropTailQueues 的默认值。我们举例说明了两种类型的 Value 类:
类 StringValue 和类 UintegerValue,这两个类可以被用来将值赋给叫做
“ns3::DropTailQueue::MaxPackets” 的属性。
现在我们使用底层 API 来创建一些对象。由于上述对默认值的操作,新建的队
列的 m_maxPackets 值将被初始化为 80 而不是 100。

Ptr<Node> n0 = CreateObject<Node> ();
Ptr<PointToPointNetDevice> net0 = CreateObject<PointToPointNetDevice>
();
n0->AddDevice (net0);
Ptr<Queue> q = CreateObject<DropTailQueue> ();
net0->AddQueue(q);

这里我们创建了一个单独的节点(节点 0)和一个单独的PointToPointNetDevice(NetDevice 0),并将一个 DropTailQueue 加入到了
该 PointToPointNetDevice 上。
现在,我们可以操纵已经实例化过的 DropTailQueue 的 MaxPackets 值了,有
多种不同的方法来达到这个目的。


3.2.2.1 基于指针的存取 Pointer-based access

假定存在一个指向相关网络设备的智能指针(Ptr),例如这里 net0 的指针。
改变该值的一种方法是 通过存取指向底层的队列的指针,并修改该队列的属性。
首先,我们能够通过 PointToPointNetDevice 的属性获得指向队列(基类)的指
针,该属性叫做 TxQueue。

PointerValue tmp;
net0->GetAttribute (“TxQueue”, tmp);
Ptr<Object> txQueue = tmp.GetObject ();

使用函数 GetObject ,我们能够执行到 DropTailQueue 的安全的 downcast,
MaxPackets 是 DropTailQueue 的成员。

Ptr<DropTailQueue> dtq = txQueue->GetObject <DropTailQueue> ();
NS_ASSERT (dtq != 0);

现在我们能够获取该队列上属性的值。由于属性系统存储的是值,而不是类型,
所以与 Java 类似地,我们为底层数据类型引入了封装的 “Value” 类。这里,属
性值被赋值给了一个 UintegerValue ,对这个值应用 Get()方法会得到

(unwrapped)uint32_t。
UintegerValue limit;
dtq->GetAttribute (“MaxPackets”, limit);
NS_LOG_INFO (“1. dtq limit: ” << limit.Get () << ” packets”);

注意上述的 downcast 不是必须的。尽管该属性是该子类的成员,我们依然能够
使用 Ptr<Queue> 来做同样的事情。

txQueue->GetAttribute (“MaxPackets”, limit);
NS_LOG_INFO (“2. txQueue limit: ” << limit.Get () << ” packets”);
现在,让我们将他设置为另一个值(60)。txQueue->SetAttribute(“MaxPackets”, UintegerValue (60));
txQueue->GetAttribute (“MaxPackets”, limit);
NS_LOG_INFO (“3. txQueue limit changed: ” << limit.Get () << ” packets”);

3.2.2.2 基于名字空间的存取 Namespace-based access

另一种获取属性的方法是使用 configuration namespace。属性位于这个名字空
间的已经路径上。如果用户无法访问底层指针但又想要使用一条语句来配置某个
特定的属性时,这个方法很有用。

Config::Set (“/NodeList/0/DeviceList/0/TxQueue/MaxPackets”,
UintegerValue (25));
txQueue->GetAttribute (“MaxPackets”, limit);
NS_LOG_INFO (“4. txQueue limit changed through namespace: ” <<
limit.Get () << ” packets”);
我们还可以使用通配符来设置所有节点和所有网络设备的该值(例子如下)。
Config::Set (“/NodeList/*/DeviceList/*/TxQueue/MaxPackets”,
UintegerValue (15));
txQueue->GetAttribute (“MaxPackets”, limit);
NS_LOG_INFO (“5. txQueue limit changed through wildcarded namespace: ”
<<
limit.Get () << ” packets”);

3.2.3 通过构造函数和 helper classes 来设置 Setting through constructors

helper classes
任意的属性组合都可以由 helper 和底层 APIs 来设置和获得。通过构造函数本身:

Ptr<Object> p = CreateObject<MyNewObject> (“n1′′, v1, “n2′′, v2, ...);

通过高层 helper APIs,比如:

mobility.SetPositionAllocator (“GridPositionAllocator”,
“MinX”, DoubleValue (-100.0),
“MinY”, DoubleValue (-100.0),
“DeltaX”, DoubleValue (5.0),“DeltaY”, DoubleValue (20.0),
“GridWidth”, UintegerValue (20),
“LayoutType”, StringValue (“RowFirst”));

3.2.4 值类 Value classes

读者将注意到新的某 Value 类是 AttributeValue 基类的子类。这些类可以被看做
中间类,这些中间类可以被用来将 raw types 转换为可以被属性系统使用的值。
属性系统的数据库用一种一般类型来存储许多类型的对象,到该一般类型的转换
可以使用中间类(IntegerValue, DoubleValue for “floating point”)来完成,也
可以通过字符串来完成。从类型到值的直接隐式转换不是很可行,所以用户可以
选择使用字符串还是值:

p->Set (“cwnd”, StringValue (“100′′)); // string-based setter
p->Set (“cwnd”, IntegerValue (100)); // integer-based setter

对于用户想引入属性系统的新的类型,系统提供一些宏来帮助用户为新的类型声
明和定义新的 AttributeValue 子类。

ATTRIBUTE_HELPER_HEADER
ATTRIBUTE_HELPER_CPP

3.3 对属性进行扩展 Extending attributes

ns-3 系统在属性系统下边放置了许多内部值,但毫无疑问,用户将对系统不完
善的地方进行扩展以及加入用户自己的类。


3.3.1 将现存的内部变量加入元数据系统 Adding an existing internal variable to the metadata system

考虑类 TcpSocket 中的这个变量:

uint32_t m_cWnd; // Congestion window

假设 使用 Tcp 的某个人想要使用元数据系统获得或设置该变量的值。如果 ns-3
还没有提供这个,用户可以再元数据系统中添加如下声明

(在 TcpSocket 的
TypeId 声明中):.AddParameter (“Congestion window”,
“Tcp congestion window (bytes)”,
Uinteger (1),
MakeUintegerAccessor (&TcpSocket::m_cWnd),
MakeUintegerChecker<uint16_t> ());

现在,用户可以使用指向该 TcpSocket 的指针来执行设置和获取操作,而不用显
式添加这些函数。此外,访问控制可以被应用,比如使得该参数只读不可写和对
参数进行上下界检查。


3.3.2 添加新的 TypeId Adding a new TypeId

现在我们讨论用户如何往 ns-3 系统中添加新的类。
我们已经介绍过类似如下的 TypeId 定义:

TypeId
RandomWalk2dMobilityModel::GetTypeId (void)
{
static TypeId tid = TypeId (“ns3::RandomWalk2dMobilityModel”)
.SetParent<MobilityModel> ()
.SetGroupName (“Mobility”)
.AddConstructor<RandomWalk2dMobilityModel> ()
.AddAttribute (“Bounds”,
“Bounds of the area to cruise.”,
RectangleValue (Rectangle (0.0, 0.0, 100.0, 100.0)),
MakeRectangleAccessor (&RandomWalk2dMobilityModel::m_bounds),
MakeRectangleChecker ())
.AddAttribute (“Time”,
“Change current direction and speed after moving for this delay.”,
TimeValue (Seconds (1.0)),
MakeTimeAccessor (&RandomWalk2dMobilityModel::m_modeTime),
MakeTimeChecker ())
// etc (more parameters).
;
return tid;
}

类声明中与此相关的声明是一行公共成员方法:

public:
static TypeId GetTypeId (void);

典型的错误包括:
没有调用 SetParent 方法,或者使用了错误的类型来调用他。
没有调用 AddConstructor 方法,或者使用了错误的类型来调用他。
在 TypeId 的构造函数中对于 TypeId 的名字引入了印刷错误。
没有使用封装类的全限定 C++类型名作为 TypeId 的名字。
以上错误都无法被 ns-3 探测到,所以用户应当多次检查以确保正确性。


3.4 给属性系统中添加新的类

从用户的角度来看,编写新的类并将其加入属性系统主要是编写字符串与属性值
之间的转换。多数可以通过“宏化的“(macro-ized)代码来复制/粘贴。例如目
录 src/mobility/ 下的类 Rectangle:
类声明中加入一行:

/**
* brief a 2d rectangle
*/
class Rectangle
{
...
};

在类声明的下边加入一个宏调用和两个操作符:

std::ostream &operator << (std::ostream &os, const Rectangle &rectangle);
std::istream &operator >> (std::istream &is, Rectangle &rectangle);
ATTRIBUTE_HELPER_HEADER (Rectangle);

类定义的代码类似于:

ATTRIBUTE_HELPER_CPP (Rectangle);
std::ostream &
operator << (std::ostream &os, const Rectangle &rectangle){
    os << rectangle.xMin << “|” << rectangle.xMax << “|” << rectangle.yMin <<
    “|” << rectangle.yMax;
    return os;
}
std::istream &
operator >> (std::istream &is, Rectangle &rectangle)
{
    char c1, c2, c3;
    is >> rectangle.xMin >> c1 >> rectangle.xMax >> c2 >> rectangle.yMin >>
    c3 >> rectangle.yMax;
    if (c1 != ‘|’ ||c2 != ‘|’ ||c3 != ‘|’)
    {
        is.setstate (std::ios_base::failbit);
    }
    return is;
}

这些流操作符将字符串表示形式的 Rectangle (“xMin|xMax|yMin|yMax”)转
化为底层的 Rectangle,模块的编写者必须指定新类的这些操作符以及该类的实
例的字符串句法表示形式。


3.5 ConfigStore

请求反馈: 这是 ns-3 的一个试验性的特色。他不在主要的代码树中。如果您
喜欢该特色并愿意提供关于他的反馈,请给我们写电子邮件。
ns-3 的属性的值可以被存储在 ascii 文本文件中,并在将来的模拟中加载。这个
特色被认为是 ns-3 的 ConfigStore。 ConfigStore 的代码在 src/contrib/ 下。因
为我们还在寻求用户的反馈,所以目前还不在主要的代码树中。
我们用一个例子来探索这个系统。将文件 csma-bridge.cc 复制到 scratch 目录:
cp examples/csma-bridge.cc scratch/
./waf我们编辑该文件以加入 ConfigStore 特色。首先,添加一个 include 语句,然后
加入以下行:

#include “contrib-module.h”
...
int main (...)
{
// setup topology
// Invoke just before entering Simulator::Run ()
ConfigStore config;
config.Configure ();
Simulator::Run ();
}

存在一个控制 Configure()的属性,他决定 Configure()是将模拟的配置存储在文
件中并退出,还是加载模拟的配置文件并继续执行。首先,属性 LoadFilename
被检查,如果不为空,则程序从所提供的文件名来加载配置;如果为空,且属性
StoreFilename 被提供,则配置将被写入指定的输出文件。
虽然生成一个配置文件的样本并修改一些值是可能的,但有些情况这种方法是行
不通的,因为对于同一个自动生成的配置文件,同一个对象上的同一个值可能在
不同的配置路径上出现多次。
同样地,使用这个类的最好方法是用他生成一个初始的配置文件,仅从该文件中
提取严格必须得元素,并将这些元素移动一个新的配置文件。这个新的配置文件
在随后的模拟中可以被安全地编辑和加载。
以此为例运行一次程序来创建一个配置文件。如果你使用的是 bash shell,那么
下边的命令应该能够工作(阐明了如何从命令行设置属性):

./build/debug/scratch/csma-bridge –
ns3::ConfigStore::StoreFilename=test.config

如果上述命令不起作用(上述命令需要 rpath 的支持),试试如下:

./waf –command-template=”%s –
ns3::ConfigStore::StoreFilename=test.config” –run scratch/csma-bridge
运行该程序将产生一个叫做”test.config”的输出配置文件,类似于如下:
/$ns3::NodeListPriv/NodeList/0/$ns3::Node/DeviceList/0/$ns3::CsmaNet
Device/Addre
ss 00:00:00:00:00:01
/$ns3::NodeListPriv/NodeList/0/$ns3::Node/DeviceList/0/$ns3::CsmaNet
Device/Frame
Size 1518
/$ns3::NodeListPriv/NodeList/0/$ns3::Node/DeviceList/0/$ns3::CsmaNet
Device/SendE
nable true
/$ns3::NodeListPriv/NodeList/0/$ns3::Node/DeviceList/0/$ns3::CsmaNet
Device/Recei
veEnable true
/$ns3::NodeListPriv/NodeList/0/$ns3::Node/DeviceList/0/$ns3::CsmaNet
Device/TxQue
ue/$ns3::DropTailQueue/MaxPackets 100
/$ns3::NodeListPriv/NodeList/0/$ns3::Node/DeviceList/0/$ns3::CsmaNet
Device/Mtu 1
500
...

上边列出了拓扑脚本中的每一个对象以及每一个注册过的属性的值。此文件的语
法是每行都标明了属性独一无二的名字,名字后边是值。
该文件是某个给定模拟中的参数的一个方便的记录,可以使用模拟输出文件来存
储。此外,该文件还可以被用来将模拟参数化,而不是编辑脚本或传递命令行参
数。比如:检查并调整一个已经存在的配置文件中的值,然后将该文件传递给模
拟程序。相关的命令:

./build/debug/scratch/csma-bridge –
ns3::ConfigStore::LoadFilename=test.config
如果上述命令不起作用(上述命令需要 rpath 的支持),试试如下:
./waf –command-template=”%s –
ns3::ConfigStore::LoadFilename=test.config” –run scratch/csma-bridge

3.5.1 基于 GTK 的 ConfigStore GTK-based ConfigStore

对于 ConfigStore,存在一个基于 GTK 的前端。这使得用户可以使用 GUI 来存
取和修改变量。该特色的屏幕截图可以在 ns-3 Overview 找到。
要使用这个特色,必须安装 libgtk 和 libgtk-dev。Ubuntu 下安装命令的示例:
sudo apt-get install libgtk2.0-0 libgtk2.0-dev
通过 ./waf configure 阶段的输出来检验是否已经配置好:

—- Summary of optional NS-3 features:
Threading Primitives                : enabled
Real Time Simulator                 : enabled
GtkConfigStore                      : not enabled (library ‘gtk+-2.0 >= 2.12′ not found)

在上述例子中,GtkConfigStore 没有开启,要想使用他,必须安装合适的版本,
并且再次执行 ./waf configure; ./waf。
用法与 non-GTK-based 版本几乎一样:

// Invoke just before entering Simulator::Run ()
GtkConfigStore config;
config.Configure ();

现在,当你运行脚本时将弹出一个 GUI,使得你可以打开不同节点/对象上属性
的菜单,配置好之后启动模拟。


3.5.2 将来的工作 Future work

可能存在的改进:

  • 在文件起始处保存一个包含日期与时间的独一无二的版本号。
  • 在某处保存 rng 的初始种子。
  • 使每个 RandomVariable 都连续化(serialize)自己的初始种子并在后期重新读取。
  • 加入默认值。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容