freeSwitch入门

阅读《FreeSWITCH 权威指南》笔记。

简单使用

windows 安装 FreeSwitch

自己的电脑系统是 xubuntu 22.10 Ubuntu Kinetic Kudu (development branch) 版本,linx 安装 FreeSwitch 需要自己编译安装,里面安装编译会出错,所以采用虚拟机 windows 安装。windows 安装很方便,不再赘述。

freeswitch 默认开启了 1000~1019 的号码,默认密码为 1234。

可通过配置文件 安装目录下\conf\directory\default\ 内查看各个号码的配置信息,其中的变量如 $${default_password}安装目录\conf\vas.xml 中定义。

启动 freeswitch 在开始菜单的列表中 右键程序-以管理员身份运行 进行启动,当出现如下界面时即启动完成。

image.png

sip客户端安装、登陆、呼叫

在 Windows 中用的是 MicroSip,安装和使用都很方便(仅使用于 Windoes)。下载链接

手机端用的是 SipDroid,在手机浏览器中找的下载链接。配置登陆号码见下方截图(域名即 FreeSwitch 所安装机器的 IP 地址)。

手机端和 MicroSip 的配置几乎是一样的,不过需要在同一个局域网中。我是用手机分出了 Wifi 让主机连接,然后 VirtualBox 使用桥接的方式连上主机网络。多个终端也可以通过多开几个虚拟机安装 MircoSip 的方式实现。

image.png

不同客户端通过登陆不同的号码,就可以通过电话进行呼叫和通话了。

image.png

启动 FS 后,在安装包中还有一个 fs-cli 是可以连接到 FS 的客户端,和 FS 一样的输入输出,但关闭不影响服务程序。

FreeSwitch 基本命令

sofia status profile internal reg # 查看已注册(登陆)设备(号码)
originate user/1000 &park # 通过 1000 拨打电话到 park 程序
# 程序(APP)其实为 freeswitch 内置的函数(注意使用时加上 & 符号):
  # park 挂起(听不到任何声音)
  # hold 挂起(能听到声音,Music On Hold, MOH)
  # playback(/root/welcome.wav) 播放特定的声音文件
  # record(/tmp/rec.wav) 录音文件
  # bridge(user/1001)  转接到 1001
show channels # 显示通话中的一些信息,包含 UUID
uuid_bridge <uuid1> <uuid2> # 将两个 channel 桥接起来
help # 帮助
sofia help # 模块帮助
sofia global siptrace on # 开启 sip 信息的显示,用 off 可以关闭。

FreeSWITCH 架构

以下 FS 表示 FreeSWITCH 的缩写。

总体架构

总体来说包括 核心外围模块 组成。核心短小精悍高稳定高安全,外围模块通过调用核心提供的 API 与核心进行通信,核心通过让外围模块注册回调函数执行外围模块代码。

核心主要有四部分:DB、公共接口(Public API)、抽象接口和事件(Event)。

  • DB 默认使用 SQLite,SQLite 是一种嵌入式数据库。FS 使用核心数据库(在 安装目录/db/core.db)来记录系统接口、任务(tasks)及当前的通道(channels)、通话(calls)等实时数据。模块也有自己的数据库(表)在 db 目录下。

  • 公共接口(Public API)可以被外围模块调用。如创建或释放媒体流、Json处理函数等等。

  • 抽象接口,抽象接口是核心没有实现的接口,一般由外围模块负责实现并向核心层 注册,核心通过 回调 的方式调用具体的实现。

  • 事件(Event),FS 在内部使用消息和 事件机制 进行进程间和模块间通信。事件的产生和消费是异步的,事件可以在 FS 中通过 绑定(Bind)回调函数 进行捕获,即 FS 在时间发生时会依次回调这些函数。

外围接口实现

  • Endpoint,是 FS 的最外围(再向外就不是 FS 了),主要 包含了不同 呼叫控制协议的接口,实现与不同电话系统的通信。
  • Dialplan,拨号计划,提供电话路由功能。系统默认是由 mod_dialplan_xml 提供。
  • Chatplan,聊天计划,提供对文本消息的路由,由 mod_sms 实现。
  • APP,应用程序,FS 提供了一些内置的 APP。如 mod_voicemail 实现语音留言;mod_conference 实现多方会议。
  • FSAPI,命令接口,是对外的命令接口。
  • XML 接口,支持多种 XML 的读取,如 本地文件、DB、远程 HTTP 请求等。但对于应用和扩展在外围模块中完成,如 mod_xml_rpc、mod_xml_curl 等。
  • Codec,编解码器(是 COde 与 DECode 的组合)。FS 的实现可以桥接不同采样频率的电话或会议电话。
  • 语音识别及语音合成(ASR/TTS)。
  • 格式、文件接口(Format, File Interface),支持不同格式文件的回放、录音。
  • 日志(Logger),控制日志写到 控制台、文件、系统日志(syslog)、远程日志服务器等。
  • 定时器(Timer),FS 最理想的工作始终频率是 1000Hz,许多默认 Linux 发行版内核默认是 100Hz 或 250 Hz,在这种情况下可以重新编译内核调整始终频率。
  • 嵌入式语言(Embeded Language),支持 Lua、Javascript、Perl 等控制呼叫流程。
  • 事件套接字(Event Socket),可以使用任何其他语言通过事件套接字控制呼叫流程、扩展 FS 功能。

目录结构

在安装目录下

sounds 提供各种声音文件,sounds/music 提供 MOH(Music On Hold,保持音乐)
storage 存放从其他 HTTP 服务器下载下来的语音文件缓存及录音留言文件
conf 存放配置文件

着重介绍下配置文件,配置文件由众多 XML 组成系统装载时,会将 XML 组织在一起 Load 到内存,成为 XML 注册表。

  • conf/freeswitch.xml 是主入口,是所有 XML 文件的黏合剂。标签 X-PRE-PROCESS 是预处理命令,是在加载阶段只进行简单的替换不会被解析,所以对它进行注释仍然会发生替换,需要注意一些影响。
  • vars.xml 是通过 X-PRE-PROCESS 定义的一些全局变量,在后续以 $${var} 的方式进行引用。可以通过 global_getvar 命令来查看变量值。
  • autoload_configs 目录下是模块级的配置文件,命令方式 模块名.conf.xml(无前缀 mod_),会在系统启动的时候 Load。各模块查找是通过 configuration 标签的 name 属性查找的。如 mod_sofia 在启动时会向 XML 注册表中查找 configuration 标签为 sofia.conf 的配置。
    • autoload_configs 下的 modules.conf.xml 定义了启动时自动加载哪些模块。
    • autoload_configs 下的 post_load_modules.conf.xml 中定义的模块是最后加载的。
  • conf/dialplan 目录中的 XML 是路由计划
  • conf/ivr_menues 中存放了默认的 IVR 菜单
  • conf/directory 中存放了用户配置目录(用户目录)。FS 的 用户目录 支持多个域(Domain)。

XML 用户目录

SIP 不要求一定要注册才可以打电话,但通话前仍需用户认证,认证参数即中用户目录中进行配置。即用户目录决定了哪些用户能注册到 FS 中。

<include>
    <domain name="$${domain}">
        <params>
            <param name="dial-string" value="..."/>
        </params>
        <variables>
            <variable name="record_stereo" value="true"/>
        </variables>
        <groups>
            <group name="sales">
                <users>
                    <user id="1000" type="pointer"/>
                </users>
            </group>
        </groups>
    </domain>
</include>

default.xml 是自带默认的配置文件。其中 $${domain} 默认变量值是主机 IP 地址,可以将他修改为一个域名。

  • params 标签中定义 domain 下所有用户的公共参数。
  • params 定义的 dial-string 变量很重要,FS 会使用 user/username 或 sofia/internal/username@domain 呼叫时会根据 username 等信息找到 dial-string 最终扩展成用户实际 SIP 地址。
  • variables 标签定义了一些 Channel 级别的公共变量,在通话中会绑定到相应的 Channel 上形成 Channel Variables。
  • groupsgroup 组标签,是不必要的,但可以方便地进行群呼、代接之类的业务。
  • usersuser 标签可以是完整的 XML,也可以是指向已存在用户的“指针”(type="pointer",通过 <user id="xxx"> 来找到)。
  • 注:params 和 variables 可以出现在 user、group 或 domain 中,优先级按作用域的减小而增大。

呼叫相关概念

两种典型流程:

  • Bob -> FS -> Alice
  • FS -> Bob && FS -> Alice

市场上有对方式二的变种,流程为 a) B -> FS 随即 FS 挂掉电话;b) FS -> B && FS -> A。好处:接电话不会被收费。(华为中 Welink 呼叫就是这个流程)。

  • 来话,针对与 FS 是到达 FS 的呼叫
  • 去话,针对于 FS 是从 FS 出去的呼叫
  • Session,无论来话去话 FS 都会启动一个 Session(会话)用于控制整个呼叫
  • Channel,每个 Session 控制这一个 Channel(信道、通道),是一对 UA 间通信的实体,相当于 FS 的一条腿。每个 Channel 都用一个唯一的 UUID 来标识,称为 Channel UUID;每个 Channel 上可以绑定一些呼叫参数,称为 Channel Variables(通道变量)。
  • Call,FS 的作用是将两个 Channel 桥接 到一起组成一个通话,称为一个 Call。
  • 回铃音和 Early Media,假设A、B 不在同一交换机(服务器)上通话,中间会经过两台交换机 a、b:A <-> a <-> b <-> B。在早期,A 呼 B 在 B 开始振铃时,A 能听到单一的回铃音(Ring Back Tone),这里 b 只向 a 传送了个信令传达到 B 的信号,由 a 交换机生成来铃流;后来,为了支持让 A 听 B 端定制的铃流,必须由交换机 b 返回铃流,这就是 Early Media(早期媒体)。在 SIP 通信中是由 183 消息(带有 SDP)描述的。

Early Media 的流量不包含在通信费中,一般是在月租或套餐中的收费的,所以可以将真正的话音数据伪装成 Early Media 实现“免费通话”。但这种应用有一定的限制,大多数交换机允许的 Early Media 不会太常,如 1 分钟,以避免这种免费通话。

  • 全局变量和局部变量,全局变量在服务加载时只求值一次,用 $${var} 形式引用;局部变量即 Channel Variables,在每次创建 Channel 时求值(生命周期),用 ${var} 引用。部分变量在显示时有 variable_ 前缀,但在使用时不需要此前缀。

拨号计划 DialPlan

找到了本章的在线读书笔记可参考。

从配置文件看工作流程

配置文件的拨号计划又叫 XML DialPlan,下文还会降到 内联拨号计划。

拨号计划默认用 XML 格式配置。DailPlan 的完整结构的配置是这样的嵌套结构(简写了 xml 文件):

- document
  - section(name: dialplan)
    - context 
      - extension # extension 与 extension 之间在逻辑上是隔离的
        - condition(field="xxx" expression="^echo|1234$") # 测试条件,指定表达式
          - action(application="info" data="xxx") # 执行动作
  • 修改配置文件后要用 reloadxml 命令或 F6键 重载配置文件
  • DailPlan 的匹配顺序是按先后顺序进行匹配的,所以注意(不清楚整体情况时)自己修改的放到前面
  • 注意修改权限,我在 sublime 中修改保存成功但实际 reloadxml 不成功,在整个文件夹的上加修改权限后成功
  • 按 F8 或 console loglevel debug 设置 console 的 log 级别为 debug
  • extension 标签有个 continue="true" 属性,表示匹配完当前 extension 后还可以匹配后方的 extension,默认 continue 为 false。
  • condition 标签,为空表示匹配所有条件
    • 验证用户信息的 field(可以是内置变量,用户目录中的外置变量要用 ${})与 expression(正则)是否匹配
    • condition 标签可叠加,表示与关系
    • condition 标签中 break 参数可以做逻辑判断,如 break="on-true" 表示 在执行完本 cond 中的 action 后 if true break,另外有 on-false(默认),always,never
  • action 标签是执行动作,application 表示设置执行的应用,data 为传进去的字段,现知的应用有:
    • info:输出所有变量到日志中
    • answer:接听电话,相当于 FS 响应应答消息,后面可以设置一些媒体流如放音、转到语音信箱等。
    • echo(数字号码 9196):回声,可以听到自己的声音,另外还有个延迟回声 delay_echo(9195)
    • log:输出日志,
    • hangup:挂断电话
    • set:设置一个当前 leg 的变量绑定到 Channel 上。如设置 greeting 变量的 data:data="greeting=good-morning.wav"
    • export:设置一个双方 leg 的变量,同时还设置一个 export_vars=greeting,当 data 开头为 nolocal: 时表示只设置到对方 leg 上。
    • hash:设置变量到内存哈希表中,data 的格式为 operator/key/scope/value
    • bridge:作用,将两条腿桥接起来(在这里创建 b-leg),data 是标准的呼叫字符串(Dial string),内部 {c=d} 可以设置 b-leg 的通道变量 c = d 。birdge 会一直阻塞等待 b-leg 接听或挂机等操作。
    • sleep:表示暂停的 ms 数
    • conference,会议,可以配置 condition 将呼叫某一(类)号码的用户都转到会议中(会议分组看 data 格式)。
    • bind_meta_app,根据用户输入的 number(加星号)执行操作。
    • transfer,将当前通话重新转移到 Routing 阶段。
    • playback(9664),播放一个声音文件
    • record,开启录音,data 中传保存位置
<extension name="My Echo Test"> <!-- 测试时名字没改,忽略其意义就好 -->
    <condition expression="^1234(\d+)$" field="destination_number">
        <action application="log" data="INFO you called ${destination_number}"/> <!-- log 数据首单词可设置级别 -->
        <action application="log" data="NOTICE the suffix is $1"/>
        <action application="hangup"/>
    </condition>
</extension>
image.png
  • 注意执行阶段,解析阶段,执行阶段
  • 日志级别:CONSOLE、ALERT、CRIT、ERR、WARNING、NOTICE、INFO、DEBUG
  • 反动作标签 anti-action,当 condition 不匹配时可以执行 内部anti-action 标签(神奇的设定)。

工作机制

Channel 状态机转换工程: NEW - INIT - ROUTING/HUNTING - EXECUTE - HANGUP - REPORTING - DESTROY
新建 Channel - 初始化 - 路由(查找解析 Dialplan) - 执行动作 - 挂机(某一方执行)- 包好(统计计费) - 销毁(释放资源)
在 执行 阶段,也可以发生转移(Transfer),转移到同一个 Context 下不同 Extension,转移后会重新进入 Routing 阶段。

注意:

  • 默认情况下,Routing 阶段会查到到 执行计划 中的所有 Extension,并把 action 放到一个队列中,然后才进入 Execute 阶段执行。所以在 action 标签中改变某值去影响路由的逻辑是不对的(除非用 inline 属性)。
  • action 标签上 inline="true" 属性可以让 action 在 Routing 阶段执行
  • 可用 inline 属性的 app 不多,一般都是很快地存取变量的操作。

内联拨号计划

可以把前面讲到的拨号计划成为“XML 拨号计划”。内联拨号计划(Inline Dialplan),用于快速测试不同的 action,可以直接在命令行中写出对应的命令:

originate user/1000 answer,playback:/tmp/a.wav,record:/tmp/b.wav inline
# 解释:使用 1000 拨打,首先 answer,然后放音 a.wav,然后录音到 b.wav

如上注释中的解释,不再重复了,其中:

  • 不同于之前用 & 指定的 APP,用内联形式不需加 & 且可以指定多个 APP(是个流程)
  • 带参数的 APP 用 APP:args 格式来书写
  • 多个 APP 间默认用 逗号 分隔
  • 当参数中有空格时,用单引号括住参数
  • 当参数中有逗号时,可以用 m 语法修改分隔符为其他字符,如 m:^:xxx 表示 xxx 中用 ^ 表示分隔符

调用 API

在拨号计划中可以调用一些 API,使用方法和引用变量一样,不过变量为函数,如 ${version()} 获取版本。 ${expr(1+1)} 计算一个表达式。

调试技巧

一般流程:发现问题 - 定位问题 - 分析问题 - 解决问题。

  • 拨打内置的 APP 看现象缩小定位范围,如 echo(9196)、playback(9664)
  • 通过 uuid_debug_media <uuid> both on 打开媒体调试开关(uuid 通过 show channels 查看)
  • 通过 console loglevel debug 打开 FS debug 日志,检查消息的到达
  • 检查日志中挂机原因(Hangup Cause),一般 CALL_REJECTED 表示呼叫拒绝,可能是认证错误,USER_NOT_REGISTERED 说明对方未注册。
  • 经过网关通过 bgapi originate sofia/gateway/gw1/Bob &echo “分段”查看网关后半段通信,bgapi 在后台执行线程,不阻塞控制台
  • sofia profile external siptrace on 打开 external 的 profile siptrace,sofia global siptrace on 打开所有 Profile 的。
  • sofia loglevel all 9 打开 Sofia 底层协议栈的调试,换为 0 为关闭
  • 使用 fs_cli 进行如上操作,可以随时关闭打开调试信息、固定消息内容等操作,而不影响主进程
  • 抓包工具:tcpdump、wireshark、tshark(wireshark 的命令行版,参考)、ngrep(类似于 grep 在文本界面中方便),pcapsipdump(能将不同通话 IP 包存到不同的文件中,在通话量大时很好用)
  • tcpdump -nq -s 0 -A -vvv -i eth0 -w abc.pcap port 5060,-n、-q 表示不进行域名翻译及减少输出内容,-s 0 表示不限制包长,-A 表示以易读的 ASCII 方式输出,-v 表示详细程度,v 越多越详细,-i eth0 表示指定网卡 etho,-w 为写出到指定文件。对于搜索条件 udp 指定抓 udp 包,可以分析 RTP 流;host 1.2.3.4 过滤 IP 地址;与用 and,或用 or。

originate 命令详解

originate # 使用 FS 发起呼叫(默认主叫号码是 000000000)
-USAGE: <call url> <exten>|&<application_name>(<app_args>) [<dialplan>] [<context>] [<cid_name>] [<cid_num>] [<timeout_sec>]
  • 同振,同时呼叫多个用户,某个接听另一个自动挂断:originate user/1000,user/1001 &echo
  • 顺振,呼叫某一个号码,如果失败呼叫下一个:originate user/1000|user/1001 &echo
  • call url 即呼叫字符串,格式 类型/参数/参数,如 user/1000,类型表示 Channel 的类型,不存在的类型会报错 ERR CHAN_NOT_IMPLEMENTED
  • 第二个参数是分机号(exten)或者 &app。若是分机号时,会转入 Dialplan 去路由,路由的目的是查找到 enten
  • <dialplan> 第三个参数是 Dialplan 的类型,如果不设置默认是 XML
  • <context> 是 Dialplan 的 Context,对于 inlineDialplan 可忽略
  • <cid_name> <cid_num> 是主叫名称 和 主叫号码(CallID Number),用于界面及 FROM 头中显示
  • <timeout_sec> 是不回 100 Trying 的超时时间。
  • originate 命令是阻塞的,可以在前方加上使用 bgapi 转为后台执行。若已经发生阻塞:
    • 可以用 fs_cli 执行 show channels 查到 uuid,然后用 uuid_kill <uuid> 结束此呼叫。
    • 或用 hupup 挂断所有电话。
  • 命令中使用通道变量 originate {var1=1}{var2=2}user/1000 &echo,细节略。
  • 忽略早期媒体的影响用 originate {ignore_early_media=true}sofia/gateway/gw/13800000000 &playback(/a.wav),因为 originate 命令是受到媒体指令就返回,如 183 或 200。由于软电话会回复 180 而不是 183,183 相当于携带媒体的 180,而在 PSTN 场景下一般都是回复 183 的。加入此参数后可以忽略 Early Media 对我们呼叫的影响。
  • originate user/1000 &bridge(user/1001) 流程:建立 channel 然后呼叫 user/1000,1000 接听后执行 bridge,bridge 再建立一个 channel 并呼叫 user/1001。此时双方在信令上建立了桥接关系;在 1001 接听后,媒体也会被桥接起来,进入正常通话。

实际上,bridge 和 oiginate 底层用的同一个函数实现,伪代码是 originate(session, new_session, dial_str) 差别在于 originate 调用函数是 session 字段为 null。

  • 上方用 bridge 的逻辑中,是先拨通 a-leg 后再建立 b-leg 的,如果 b-leg 回的是 183 则媒体流正常转发到 a-leg 中,若为 180 则由于无媒体流 a-leg 听不到任何声音,为了解决这个问题,可以让 FS 回收一个假的回铃音,方法:1)设置 {transfer_ringback=local_stream://moh} 变量,此变量控制在 b-leg 回复 180 时开始播放声音。2)在 1 的基础上再加 {instant_ringback=true} 变量,可以让 bridge 立即播放回铃音,而不等待 180。

呼叫的完整逻辑

假设 1000 呼叫 1001

  • 1000 发送 INVITE 到达 mod_sofia 的 inernal Profile(conf/sip_profiles/internal.xml,通过 5060 都是先到这里)
  • FS 收到后立即返回 100,由于 internal.xml 配置了 auth-calls=true 所以会进行鉴权(使用 Digest Auth),一般首次会鉴权失败,所以回复 401
  • UAC(即1000)重新发送带鉴权信息的 INVITE 到 internal Profile(UAS)
  • UAS 收到后,将鉴权信息提交到上层 FS 代码,FS 通过 Directory(用户目录)找到相应用户信息,并根据配置的密码鉴权(失败 403)
  • 鉴权通过,FS 通过 user_context 配置项找到应该进入哪个路由。如 1000.xml 中配的 user_context 为 default,则进入 conf/dialplan/default.xml。(已经进入路由阶段)
  • Dialplan 会查到 1001 用户,找到匹配的 Extension 执行里面的 action,action 有 bridge 命令及 data,所以执行 bridge <data>,此时会再次查 Directory(用户目录)找到 1001 的配置信息
  • 找到 1001 的 dial-string 配置项,此项会配置在 conf/directory/default.xml 中(由于此域下所有用户的规则一样,所以放在这里),其中 sofia_contact 这个 API 会查找数据库,找到 1001 的实际注册 Contact 地址,返回真正的呼叫字符串。(如通过 sofia_contact 1000 可快速查看 sofia/internal/sip:1000@10.0.2.15:63757;ob
  • 当找到 dial-string 后,FS 会另外启动一个会话给 1001 发送 INVITE 请求
  • 如果 1001 摘机(接听),则 1001 向 FS 回送 200 OK,FS 再向 1000 回送 200 OK,通话开始。

总结主流程:1000 Invite -> sofia profile -> FS context -> dialplan -> action (bridge 1001) -> invite 1001。

external.xml 配置(5080 会走这里)
auth-calls 为 false,所以不进行鉴权
context=public 其中也没有每一个 user 上配置的 user_context(internal 中也有 context=public 但走 user_context)

FS GUI

图形化界面实现一般有两种方式:

  • 通过界面提供的操作方式修改 FS XML 配置文件,及 reloadxml 等操作使之生效
  • 通过 http 服务器给 FS 提供 XML

如:

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

推荐阅读更多精彩内容