从 NSURLConnection 到 NSURLSession

前言

现如今的移动应用开发,网络模块几乎成了标配。如果你是早期 iOS 开发者的话,那么你对 NSURLConnection一定不会陌生。但其操作起来有许多不便,这也使得大家更愿意使用第三方库的解决方案,比如大名鼎鼎的 AFNetworking 你一定有所耳闻。正是因为这一点,苹果随着 iOS 7 的发布,也为开发者带来了改进后的原生网络库支持,那就是 NSURLSession。

今天,就让我来给你道一道从 NSURLConnectionNSURLSession 那些你知道和不知道的事。


NSURLConnection

NSURLConnection 作为 Core Foundation / CFNetwork 框架的 API 之上的一个抽象,在 2003 年,随着第一版的 Safari 的发布就发布了。NSURLConnection 这个名字,实际上是指代的 Foundation 框架的 URL 加载系统中一系列相关联的组件:NSURLRequestNSURLResponseNSURLProtocolNSURLCacheNSHTTPCookieStorageNSURLCredentialStorage 以及同名类 NSURLConnection

NSURLRequest 被传递给 NSURLConnection。被委托对象(遵守以前的非正式协议 <NSURLConnectionDelegate><NSURLConnectionDataDelegate>)异步地返回一个 NSURLResponse 以及包含服务器返回信息的 NSData

在一个请求被发送到服务器之前,系统会先查询共享的缓存信息,然后根据策略(policy)以及可用性(availability)的不同,一个已经被缓存的响应可能会被立即返回。如果没有缓存的响应可用,则这个请求将根据我们指定的策略来缓存它的响应以便将来的请求可以使用。

在把请求发送给服务器的过程中,服务器可能会发出鉴权查询(authentication challenge),这可以由共享的 cookie 或机密存储(credential storage)来自动响应,或者由被委托对象来响应。发送中的请求也可以被注册的 NSURLProtocol 对象所拦截,以便在必要的时候无缝地改变其加载行为。

使用步骤

概览

NSURL

创建一个 NSURL 对象,设置请求路径:

NSURL *url = [NSURL URLWithString:@"协议://主机地址/路径?参数&参数"];

解释如下:

  • 协议:不同的协议,代表着不同的资源查找方式、资源传输方式,比如常用的 HTTP、FTP 等
  • 主机地址:存放资源的主机的 IP 地址(域名)
  • 路径:资源在主机中的具体位置
  • 参数:参数可有可无,也可以多个。如果带参数的话,用 “?” 号后面接参数,多个参数的话之间用 “&” 隔开

NSURLRequest

创建一个 NSURLRequest 对象并传入 NSURL,设置请求头和请求体:

NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15.0];

参数解释如下:

  • requestWithURL:资源路径
  • cachePolicy:缓存策略(无论使用哪种缓存策略,都会在本地缓存数据),类型为枚举类型,取值如下:
    • NSURLRequestUseProtocolCachePolicy = 0 // 默认的缓存策略,使用协议的缓存策略
    • NSURLRequestReloadIgnoringLocalCacheData = 1 // 每次都从网络加载
    • NSURLRequestReturnCacheDataElseLoad = 2 // 返回缓存否则加载,很少使用
    • NSURLRequestReturnCacheDataDontLoad = 3 // 只返回缓存,没有也不加载,很少使用
  • timeoutInterval:超时时长,默认 60s

另外,还可以设置其它一些信息,比如请求头,请求体等等,如下:

// 告诉服务器数据为 JSON 类型
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; 
// 设置请求体(JSON类型)
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:@{@"userid":@"123456"} options:NSJSONWritingPrettyPrinted error:nil];
request.HTTPBody = jsonData;

注意,上面的 request 应为 NSMutableURLRequest,即可变类型。(使用 POST 请求的话,那么就必须的使用 NSMutableURLRequest

NSURLConnection

使用 NSURLConnection 发送请求,通过返回 NSURLResponse 实例和 NSError 实例分析结果,接受服务器返回数据。

NSURLConnection 默认的请求类型为 GET,下面分异步和同步介绍 GET 的使用(POST 会在 NSURLSession 中介绍)。

异步请求

异步是指:在发送请求之后,一边在子线程中接收返回数据,一边执行之后的代码,当返回数据接收完毕后,采用回调的方式通知主线程做处理。同时,异步请求有两种实现方式。

1.使用 block:

[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
    // 有的时候,服务器访问正常,但是会没有数据
    // 以下的 if 是比较标准的错误处理代码
    if (connectionError != nil || data == nil) {
        //给用户的提示信息
        NSLog(@"网络不给力");
        return;
    }
}];

参数说明如下:

  • completionHandler:请求响应后(或者请求超时)执行的代码,queue 为代码添加到的队列,即 block执行的线程
    • NSURLResponse 为服务器的响应,真实类型为 NSHTTPURLResponse,通常只在「下载」功能时,才会使用;下面是协议头的参数:
      • URL:响应的 URL,有的时候,访问一个 URL 地址,服务器可能会出现重定向,会定位到新的地址
      • MIMEType(Content-Type):服务器告诉客户端,可以用什么软件打开二进制数据。网络之所以丰富多采,是因为有丰富的客户端软件。栗子:Windows 上提示安装 Flash 插件
      • expectedContentLength:预期的内容长度,要下载的文件长度,下载文件时非常有用
      • suggestedFilename:「建议」的文件名,方便用户直接保存,很多时候,用户并不关心要保存成什么名字
      • textEncodingName:文本的编码名称 @”UTF8”,大多数都是 UTF8
      • statusCode:状态码,在做下载操作的时候,需要判断一下
      • allHeaderFields:所有的响应头字典时候,用户并不关心要保存成什么名字
  • NSData:服务器返回的数据,例如:JSON、XML
  • NSError:网络访问错误码

2.使用代理(Delegate):

@interface ViewController ()<NSURLConnectionDataDelegate>
@end


NSURL * url = [NSURL URLWithString:@"http://itangqi.me"];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
[NSURLConnection connectionWithRequest:request delegate:self];

使用代理可以监测下载过程:

-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    // 开始接收数据
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    // 正在接收数据(会调用多次)
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    // 接收数据失败
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
    // 接收数据完成(成功|失败)
}

同步

同步是指:数据的请求在主线程来执行,一旦发送同步请求,直至服务器返回数据完成,才可以进行下一步操作,而网络数据加载需要一个时间过程,这样的话就会堵塞主线程,当然这种使用场景很少。

// 同步请求,代码会阻塞在这里一直等待服务器返回,如果 data 为 nil 则请求失败,当获取少量数据时可以使用此方法。
// request 参数同上。
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];

NSURLSession

不管怎样,NSURLConnection 作为网络基础架构,已经服务了成千上万的 iOS 和 Mac OS 程序,并且做的还算相当不错。但是这些年,一些用例——尤其是在 iPhone 和 iPad 上面——已经对 NSURLConnection 的几个核心概念提出了挑战,让苹果有理由对它进行重构。

在 2013 的 WWDC 上,苹果推出了 NSURLConnection 的继任者:NSURLSession

NSURLConnection 一样,NSURLSession 指的也不仅是同名类 NSURLSession,还包括一系列相互关联的类。NSURLSession 包括了与之前相同的组件,NSURLRequestNSURLCache,但是把 NSURLConnection 替换成了 NSURLSessionNSURLSessionConfiguration 以及 NSURLSessionTask 的 3 个子类:NSURLSessionDataTaskNSURLSessionUploadTaskNSURLSessionDownloadTask

NSURLConnection 相比,NSURLsession 最直接的改进就是可以配置每个 session 的缓存,协议,cookie,以及证书策略(credential policy),甚至跨程序共享这些信息。这将允许程序和网络基础框架之间相互独立,不会发生干扰。每个 NSURLSession 对象都由一个 NSURLSessionConfiguration 对象来进行初始化,后者指定了刚才提到的那些策略以及一些用来增强移动设备上性能的新选项。

NSURLSession 中另一大块就是 session task。它负责处理数据的加载以及文件和数据在客户端与服务端之间的上传和下载。NSURLSessionTaskNSURLConnection 最大的相似之处在于它也负责数据的加载,最大的不同之处在于所有的 task 共享其创造者 NSURLSession 这一公共委托者(common delegate)。

我们先来深入探讨 task,过后再来讨论 NSURLSessionConfiguration

概览

Sessions

// 使用静态的 sharedSession 的方法,该类使用共享的 seesion,该 seesion 使用全局的 Cache,Cookie 和证书
+ (NSURLSession *)sharedSession;  

// 根据 NSURLSessionConfiguration 创建对应配置的 seesion
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration; 

// 也是根据 NSURLSessionConfiguration 创建对应配置的 seesion,并且可以指定 seesion 的委托和委托所处的队列
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(id <NSURLSessionDelegate>)delegate delegateQueue:(NSOperationQueue *)queue;

NSURLSessionTask

NSURLSession 本身是不会进行请求的,而是通过创建 task 的形式进行网络请求,同一个 NSURLSession 可以创建多个 task,并且这些 task 之间的 cache 和 cookie 是共享的。那么我们就来看看 NSURLSession 都能创建哪些 task 吧。

Task 可以翻译为任务,那么在和网络请求相关的任务中,我们可以理解为:数据请求任务、下载任务、上传任务等。

其实从类名就能猜出它们各自的用途:

  1. NSURLSessionDataTask:使用这个 task 来调用 HTTP GET 方式请求,从服务器获取数据到内存。返回的数据格式是 NSData,可根据需要自行转换成 XML、JSON 等数据格式

  2. NSURLSessionUploadTask:使用这个 task 来上传磁盘文件到 web 服务器,典型地通过 HTTP POST 或者 PUT 方式

  3. NSURLSessionDownloadTask:使用这个 task 来从远程服务器下载文件到临时文件地址

  4. NSURLSessionStreamTask:使用这个 task 来执行异步的读和写

你也能暂停,恢复(开始)和取消 tasks。

NSURLSessionDataTask POST

/**
 *  简单 Post 请求,POST 和 GET 请求在于对 request 的处理不同,其余和 GET 相同
 */
- (void)postWithSharedSession {
  // 获取默认 Session
  NSURLSession *session = [NSURLSession sharedSession];
  // 创建 URL
  NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
  // 创建 request
  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
  // 请求方法
  request.HTTPMethod = @"POST";
  // 请求体
  request.HTTPBody = [@"username=1234&pwd=4321" dataUsingEncoding:NSUTF8StringEncoding];
  // 创建任务 task
  NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
  // 获取数据后解析并输出
  NSLog(@"%@",[NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]);
  }];
  // 启动任务
  [task resume];
}

NSURLSessionDataDelegate 代理方法

NSURLSession 提供了 block 的方式处理返回的数据,但是如果我们想要在接收数据的过程中处理数据,我们可以使用 NSURLSessionDataDelegate 的代理方法,分为接收响应、接收数据和请求完成三个阶段。

 - (void)sessionDataDelegate {
 // 创建带有代理方法的自定义 session
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
 
 // 创建任务
NSURLSessionDataTask *task = [session dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/login?username=1234&pwd=4321"]]];
 
  // 启动任务
[task resume];
}
 
// 1. 接受到服务器的响应
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
  NSLog(@"任务完成");
  // 必须设置对响应进行允许处理才会执行后面两个操作。
  completionHandler(NSURLSessionResponseAllow);
}
 
// 2. 接收到服务器的数据(可能调用多次)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
  // 处理每次接收的数据
  NSLog(@"%s",__func__);
}
 
// 3. 请求成功或者失败(如果失败,error有值)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    // 请求完成,成功或者失败的处理
    NSLog(@"SessionTask %s",__func__);
}

NSURLSessionConfiguration

NSURLSessionConfiguration 对象用于对 NSURLSession 对象进行初始化。从指定可用网络,到 cookie,安全性,缓存策略,再到使用自定义协议,启动事件的设置,以及用于移动设备优化的几个新属性,你会发现使用 NSURLSessionConfiguration 可以找到几乎任何你想要进行配置的选项。其有三个类工厂方法:

  1. 默认模式(+defaultSessionConfiguration):返回一个标准的 configuration,工作模式类似于 NSURLConnection,可以使用缓存的 Cache,Cookie,证书(credential)

  2. 及时模式(+ephemeralSessionConfiguration): 临时 session 配置,与默认配置相比,这个配置不会使用缓存的 Cache,Cookie,证书,只会存在内存里,所以当程序退出时,所有的数据都会消失,这对于实现像秘密浏览这种功能来说是很理想的

  3. 后台模式(+backgroundSessionConfiguration):它会创建一个后台 session。后台 session 不同于常规的,普通的 session,它甚至可以在应用程序挂起,退出或者崩溃的情况下运行上传和下载任务。初始化时指定的标识符,被用于向任何可能在进程外恢复后台传输的守护进程(daemon)提供上下文

除了这三种预设的模式之外 NSURLSessionConfiguration 还可以进行很多的配置。 timeoutIntervalForRequesttimeoutIntervalForResource 可以控制网络操作的超时时间。 allowsCellularAccess 属性可以控制是否允许使用无线网络。HTTPAdditionalHeaders 可以指定 HTTP 请求头。

NSURLSessionConfiguration 几乎可以完成网络操作的大多数配置功能,并且这些配置都绑定到当前的 Session 中,我们一旦用配置好的 NSURLSessionConfiguration 初始化 NSURLSession 实例后,就不能修改这个 NSURLSession 相关的配置了。所以,一切的配置操作都放在初始化 NSURLSession 之前。


iOS 9

  1. 在 iOS 7 后,苹果推荐使用 NSURLSession,并在 iOS 9 中废弃了 NSURLConnection

  2. iOS 9 所有网络连接默认为 HTTPS 服务,为此推出 App Transport Security,详情可参见:Networking with NSURLSession


NSURLConnection vs. NSURLSession

  1. 后台上传和下载:只需在创建 NSURLSession 的时候配置一个选项,就能得到后台网络的所有好处。这样可以延长电池寿命,并且还支持 UIKit 的多 task,在进程间使用相同的委托模型

  2. 能够暂停和恢复网络操作:使用 NSURLSession API 能够暂停,停止,恢复所有的网络任务,再也完全不需要子类化 NSOperation

  3. 可配置的容器:对于 NSURLSession 里面的 requests 来说,每个NSURLSession 都是可配置的容器。举个例来说,假如你需要设置 HTTP header 选项,你只用做一次,session 里面的每个 request 就会有同样的配置

  4. 提高认证处理:认证是在一个指定的连接基础上完成的。在使用 NSURLConnection 时,如果发出一个访问,会返回一个任意的 request。此时,你就不能确切的知道哪个 request 收到了访问。而在 NSURLSession 中,就能用代理处理认证

  5. 丰富的代理模式:在处理认证的时候,NSURLConnection 有一些基于异步的 block 方法,但是它的代理方法就不能处理认证,不管请求是成功或是失败。在 NSURLSession 中,可以混合使用代理和 block 方法处理认证

  6. 上传和下载通过文件系统:它鼓励将数据(文件内容)从元数据(URL 和 settings)中分离出来

要点

  1. 请求的缓存策略使用 NSURLRequestReloadIgnoringCacheData,忽略本地缓存

  2. 服务器响应结束后,要记录 Etag,服务器内容和本地缓存对比是否变化的重要依据

  3. 在发送请求时,设置 If-None-Match,并且传入 etag

  4. 连接结束后,要判断响应头的状态码,如果是 304,说明本地缓存内容没有发生变化,此时可以使用本地缓存来加载数据,如果缓存文件被意外删除,程序依然运行但会报错,加载数据也失败

  5. GET 缓存的数据会保存在 Cache 目录中 /bundleId 下,Cache.db 中


本文转自:wakice的博客


其它资料
NSURLSession与NSURLConnection区别
NSURLSession与NSURLConnection区别


两者的区别主要在以下几个方面:

1. 使用现状

NSURLSession是NSURLConnection 的替代者,在2013年苹果全球开发者大会(WWDC2013)随ios7一起发布,是对NSURLConnection进行了重构优化后的新的网络访问接口。从iOS9.0开始, NSURLConnection中发送请求的两个方法已过期(同步请求,异步请求),初始化网络连接(initWithRequest: delegate:)的方法也被设置为过期,系统不再推荐使用,建议使用NSURLSession发送网络请求。

2. 普通任务和上传

NSURLSession针对下载/上传等复杂的网络操作提供了专门的解决方案,针对普通、上传和下载分别对应三种不同的网络请求任务:NSURLSessionDataTask, NSURLSessionUploadTask和NSURLSessionDownloadTask.。创建的task都是挂起状态,需要resume才能执行。

当服务器返回的数据较小时,NSURLSession与NSURLConnection执行普通任务的操作步骤没有区别。

执行上传任务时,NSURLSession与NSURLConnection一样同样需要设置POST请求的请求体进行上传。

3. 下载任务方式

NSURLConnection下载文件时,先将整个文件下载到内存,然后再写入沙盒,如果文件比较大,就会出现内存暴涨的情况。而使用NSURLSessionUploadTask下载文件,会默认下载到沙盒中的tem文件夹中,不会出现内存暴涨的情况,但在下载完成后会将tem中的临时文件删除,需要在初始化任务方法时,在completionHandler回调中增加保存文件的代码。

以下代码是实例化网络下载任务时将下载的文件保存到沙盒的caches文件夹中:

[NSURLSessionDownloadTask [NSURLSessionDownloadTask *task = [session downloadTaskWithURL:[NSURL URLWithString:@"http://127.0.0.1/dawenjian.zip"] completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {

   //获取沙盒的caches路径

   NSString *path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)lastObject]stringByAppendingPathComponent:@"kkk.dmg"];

   //生成URL路径

   NSURL *DCurl = [NSURL fileURLWithPath:path];

   //将文件保存到指定文件目录下

   [[NSFileManager defaultManager]moveItemAtURL:location toURL:DCurl error:nil];   }]resume];

4. 请求方法的控制

NSURLConnection实例化对象,实例化开始,默认请求就发送(同步发送),不需要调用start方法。而cancel 可以停止请求的发送,停止后不能继续访问,需要创建新的请求。

NSURLSession有三个控制方法,取消(cancel),暂停(suspend),继续(resume),暂停后可以通过继续恢复当前的请求任务。

5. 断点续传的方式

NSURLConnection进行断点下载,通过设置访问请求的HTTPHeaderField的Range属性,开启运行循环,NSURLConnection的代理方法作为运行循环的事件源,接收到下载数据时代理方法就会持续调用,并使用NSOutputStream管道流进行数据保存。

NSURLSession进行断点下载,当暂停下载任务后,如果 downloadTask (下载任务)为非空,调用 cancelByProducingResumeData:(void (^)(NSData *resumeData))*completionHandler* 这个方法,这个方法接收一个参数,完成处理代码块,这个代码块有一个 NSData 参数 resumeData,如果 resumeData 非空,我们就保存这个对象到视图控制器的 resumeData 属性中。在点击再次下载时,通过调用 [ [self.session downloadTaskWithResumeData:self.resumeData]resume]方法进行继续下载操作。

经过以上比较可以发现,使用NSURLSession进行断点下载更加便捷。

6. 配置信息

NSURLSession的构造方法(sessionWithConfiguration:delegate:delegateQueue)中有一个 NSURLSessionConfiguration类的参数可以设置配置信息,其决定了cookie,安全和高速缓存策略,最大主机连接数,资源管理,网络超时等配置。NSURLConnection不能进行这个配置,相比于 NSURLConnection 依赖于一个全局的配置对象,缺乏灵活性而言,NSURLSession 有很大的改进了。

NSURLSession可以设置三种配置信息,分别通过调用三个累方法返回配置对象:

+ (NSURLSessionConfiguration *)defaultSessionConfiguration,配置信息使用基于硬盘的持久话Cache,保存用户的证书到钥匙串,使用共享cookie存储;

+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration ,配置信息和default大致相同。除了,不会把cache,证书,或者任何和Session相关的数据存储到硬盘,而是存储在内存中,生命周期和Session一致。比如浏览器无痕浏览等功能就可以基于这个来做;

+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier,配置信息可以创建一个可以在后台甚至APP已经关闭的时候仍然在传输数据的session。注意,后台Session一定要在创建的时候赋予一个唯一的identifier,这样在APP下次运行的时候,能够根据identifier来进行相关的区分。如果用户关闭了APP,IOS 系统会关闭所有的background Session。而且,被用户强制关闭了以后,IOS系统不会主动唤醒APP,只有用户下次启动了APP,数据传输才会继续。


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

推荐阅读更多精彩内容