Reachability类的学习

Reachability类的学习

简介

Reachability类是Apple官方出的判断当前网络状况的工具类,这个库一直在随着iOS的版本在更新,目前iOS10对应的最新版本是5.0

Reachability_xcodeproj.png

官方说明文档分为四部分

简介

The Reachability sample application demonstrates how to use the System Configuration framework to monitor the network state of an iOS device. In particular, it demonstrates how to know when IP can be routed and when traffic will be routed through a Wireless Wide Area Network (WWAN) interface such as EDGE or 3G.

Note: Reachability cannot tell your application if you can connect to a particular host, only that an interface is available that might allow a connection, and whether that interface is the WWAN. To understand when and how to use Reachability, read [Networking Overview][1].[1]: http://developer.apple.com/library/ios/#documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/

  • 总结
    Reachability能判断网路路由状态,包括2G/3G等状态。

  • 这里有两个问题

  1. 什么是路由?

这是TCP网络层寻址的一个概念,通俗的讲就是是否有对应的IP或者host(多一层DNS解析)是否在网络上存在。而然也仅仅是只能判断出是否存在,无法知道更多信息。例如:丢包率(ping 100% lost照样算可达状态)。更不可能判断当前需要的服务是否可用,例如:login接口是否可用?

  1. 可以判断出有网状态下是否是2G/3G/4G

可以判断出,然而本工具并未集成.

IPV6支持

Reachability fully supports IPv6. More specifically, each of the APIs handles IPv6 in the following way:

  • reachabilityWithHostName and SCNetworkReachabilityCreateWithName: Internally, this API works be resolving the host name to a set of IP addresses (this can be any combination of IPv4 and IPv6 addresses) and establishing separate monitors on all available addresses.
  • reachabilityWithAddress and SCNetworkReachabilityCreateWithAddress: To monitor an IPv6 address, simply pass in an IPv6 sockaddr_in6 struct instead of the IPv4 sockaddr_in struct.
  • reachabilityForInternetConnection: This monitors the address 0.0.0.0, which reachability treats as a special token that causes it to actually monitor the general routing status of the device, both IPv4 and IPv6.
  • 总结
    reachabilityWithHostNameSCNetworkReachabilityCreateWithName网址/IP均可以直接支持。
    reachabilityWithAddressSCNetworkReachabilityCreateWithAddress要吃支持IPv6需要替换对应数据结构,用sockaddr_in6 struct替换sockaddr_in struct
    reachabilityForInternetConnection监控的是0.0.0.0地址,IPv4,IPv6一起支持

  • 问题

  1. 支持IPv6,但是工具类里边没有对应代码,要自己实现
  2. reachabilityForInternetConnection监控的0.0.0.0是什么意思?
    看解释

Checks whether the default route is available. Should be used by applications that do not connect to a particular host.

在没有特定网址需要探测的情况下,判断路由是否可达。通俗的说就是没开飞行模式,网络模块没坏。

移除reachabilityForLocalWiFi

Older versions of this sample included the method reachabilityForLocalWiFi. As originally designed, this method allowed apps using Bonjour to check the status of "local only" Wi-Fi (Wi-Fi without a connection to the larger internet) to determine whether or not they should advertise or browse.

However, the additional peer-to-peer APIs that have since been added to iOS and OS X have rendered it largely obsolete. Because of the narrow use case for this API and the large potential for misuse, reachabilityForLocalWiFi has been removed from Reachability.

Apps that have a specific requirement can use reachabilityWithAddress to monitor IN_LINKLOCALNETNUM (that is, 169.254.0.0).

Note: ONLY apps that have a specific requirement should be monitoring IN_LINKLOCALNETNUM. For the overwhelming majority of apps, monitoring this address is unnecessary and potentially harmful.

  • 总结
    讲了reachabilityForLocalWiFi的原理,监控一个特殊IP链路本地地址(169.254.0.0)来判断WIFI没有连上互联网的时候WIFI状态。Apple认为新出来的点对点啥的,这个函数判断不出来,还容易误导大家。干脆给删除了,干得漂亮。这个API只能探测到是否连接上WIFI(不包括AP),没WIFI用WLan也返回没网。

用法

Build and run the sample using Xcode. When running the iPhone Simulator, you can exercise the application by disconnecting the Ethernet cable, turning off AirPort, or by joining an ad-hoc local Wi-Fi network.

By default, the application uses www.apple.com for its remote host. You can change the host it uses in APLViewController.m by modifying the value of the remoteHostName variable in -viewDidLoad.

IMPORTANT: Reachability must use DNS to resolve the host name before it can determine the Reachability of that host, and this may take time on certain network connections. Because of this, the API will return NotReachable until name resolution has completed. This delay may be visible in the interface on some networks.

The Reachability sample demonstrates the asynchronous use of the SCNetworkReachability API. You can use the API synchronously, but do not issue a synchronous check by hostName on the main thread. If the device cannot reach a DNS server or is on a slow network, a synchronous call to the SCNetworkReachabilityGetFlags function can block for up to 30 seconds trying to resolve the hostName. If this happens on the main thread, the application watchdog will kill the application after 20 seconds of inactivity.

SCNetworkReachability API's do not currently provide a means to detect support for device level peer-to-peer networking, including Multipeer Connectivity, GameKit, Game Center, or peer-to-peer NSNetService.

  • 总结
  1. Reachability提供了同步,异步两个版本的判断当前网络状态的方法,同步会有坑,如果在主线程调用SCNetworkReachabilityGetFlags相关的API(其实就是connectionRequiredcurrentReachabilityStatus)DNS或者网络状况比较差的话,超过30秒没反应,看门狗程序会早程序20秒未响应的情况下,会杀死本应用。
  2. Reachability判断不了P2P,包括Multipeer Connectivity, GameKit, Game Center, or peer-to-peer NSNetService.

扩展

  1. 增加2G/3G/4G判断
    第一种方法

  • (void)getNetworkType
    {
    UIApplication *app = [UIApplication sharedApplication];
    NSArray *subviews = [[[app valueForKeyPath:@"statusBar"] valueForKeyPath:@"foregroundView"] subviews];
    for (id subview in subviews) {
    if ([subview isKindOfClass:NSClassFromString(@"UIStatusBarDataNetworkItemView")]) {
    int networkType = [[subview valueForKeyPath:@"dataNetworkType"] intValue];
    switch (networkType) {
    case 0:
    NSLog(@"NONE");
    break;
    case 1:
    NSLog(@"2G");
    break;
    case 2:
    NSLog(@"3G");
    break;
    case 3:
    NSLog(@"4G");
    break;
    case 5:
    {
    NSLog(@"WIFI");
    }
    break;
    default:
    break;
    }
    }
    }
    }

    **第二种**
    

    if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN)
    {
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0)
    {

         CTTelephonyNetworkInfo * info = [[CTTelephonyNetworkInfo alloc] init];
         NSString *currentRadioAccessTechnology = info.currentRadioAccessTechnology;
         if (currentRadioAccessTechnology)
         {
             if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyLTE])
             {
                 returnValue =  kReachableVia4G;
             }
             else if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyEdge] || [currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyGPRS])
             {
                 returnValue =  kReachableVia2G;
             }
             else
             {
                 returnValue =  kReachableVia3G;
             }
             return returnValue;
    
         }
     }
    
     if ((flags & kSCNetworkReachabilityFlagsTransientConnection) == kSCNetworkReachabilityFlagsTransientConnection)
     {
         if((flags & kSCNetworkReachabilityFlagsConnectionRequired) == kSCNetworkReachabilityFlagsConnectionRequired)
         {
             returnValue =  kReachableVia2G;
             return returnValue;
         }
         returnValue =  kReachableVia3G;
         return returnValue;
     }
    
     returnValue = ReachableViaWWAN;
    

    }

  1. 支持IPv6
  2. 防止应用被杀死
  3. 关于Reachability的使用
  4. 网络信号强度判断
    黑魔法,使用状态栏来判断
- (void)getSignalStrength{
    UIApplication *app = [UIApplication sharedApplication];
    NSArray *subviews = [[[app valueForKey:@"statusBar"] valueForKey:@"foregroundView"] subviews];
    NSString *dataNetworkItemView = nil;
    
    for (id subview in subviews) {
        if([subview isKindOfClass:[NSClassFromString(@"UIStatusBarDataNetworkItemView") class]]) {
            dataNetworkItemView = subview;
            break;
        }
    }
    
    int signalStrength = [[dataNetworkItemView valueForKey:@"_wifiStrengthBars"] intValue];
    
    NSLog(@"signal %d", signalStrength);
}

测试

适配支持验证方法

测试验证方式就是通过Mac的共享网络共享一个IPv6的无线网,跟已往创建方式不同的是进入共享时需要按住Option键,不然Create NAT64 Network的选项不会出现

1475116819.png

然后开启无线共享,使iPhone连接上分享出来的热点即可 注:需要将iPhone的蜂窝网络数据关掉,以保证只有通过WiFi在连接网络。

使用误区

  • 以下是一段错误,用法
        self.reachAbility = [Reachability reachabilityForLocalWiFi];
        [self.reachAbility startNotifier];
        NetworkStatus netStatus = [self.reachAbility currentReachabilityStatus];
        if (ReachableViaWiFi == netStatus)
        {   DEBUGLOG(@"wifi statu");
            [[UploadLogManager shareManager] startUploadLog];
        }

解析:

  1. 初始化不合理

相关代码段

 self.reachAbility = [Reachability reachabilityForLocalWiFi];

分析

reachabilityForLocalWiFi已经在最新的版本被废止,原因就是他的局限性,只能判断出本地WIFI是否可用,重点是本地也就是说无法判断出WIFI是否联网。一般情况下,不实用此API,应该用reachabilityForInternetConnection

  1. 同步获取状态不合理
    相关代码段
 NetworkStatus netStatus = [self.reachAbility currentReachabilityStatus];

Apple Documents解释

The SCNetworkReachability programming interface allows an application to determine the status of a system's current network configuration and the reachability of a target host. A remote host is considered reachable when a data packet, sent by an application into the network stack, can leave the local device. Reachability does not guarantee that the data packet will actually be received by the host.

The SCNetworkReachability programming interface supports a synchronous and an asynchronous model. In the synchronous model, you get the reachability status by calling the SCNetworkReachabilityGetFlags function. In the asynchronous model, you can schedule the SCNetworkReachability object on the run loop of a client object’s thread. The client implements a callback function to receive notifications when the reachability status of a given remote host changes. Note that these functions follow Core Foundation naming conventions. A function that has "Create" or "Copy" in its name returns a reference you must release with the CFRelease function.


分析

获取网络状态,有同步异步两种。使用startNotifier代表使用异步方式,获取状态应该先注册通知kReachabilityChangedNotification在回调之后,调用currentReachabilityStatus才能正确获取,在这里直接调用第一次获取只能是默认状态NotReachable,其实实在获取中,这样用不够准确。

正确做法

  1. 使用异步方式
  • 初始化

       [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil];
    
  • 注册通知

    NSString *remoteHostName = @"https://twitter.com";
    self.hostReachability = [Reachability reachabilityWithHostName:remoteHostName];
  • 开始监控
[self.hostReachability startNotifier];
  • 回调处理
- (void) reachabilityChanged:(NSNotification *)note
{
    Reachability* curReach = [note object];
    NSParameterAssert([curReach isKindOfClass:[Reachability class]]);
    [self updateInterfaceWithReachability:curReach];
}
  1. 使用同步方式
  • 初始化
 self.internetReachability = [Reachability reachabilityForInternetConnection];
  • 获取状态
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NetworkStatus netStatus = [self.hostReachability currentReachabilityStatus];
        NSString *tips = [self syncGetNetStatus:netStatus];
        dispatch_async(dispatch_get_main_queue(), ^{
            syncLabel.text = [NSString stringWithFormat:@"同步探测结果:%@",tips];
        });
    });

封装

代码

  • TSReachabilityManager.h
#import <Foundation/Foundation.h>
#import "Reachability.h"

@(原创整理)interface TSReachabilityManager : NSObject

@property (nonatomic, strong) NSString *remoteHostName;

+ (instancetype)shareManager;

- (NetworkStatus)currentReachabilityStatus;

- (NSString*)netStatusDescription;

@end
  • TSReachabilityManager.m
#import "TSReachabilityManager.h"


static TSReachabilityManager *gTSReachabilityManager = nil;

@interface TSReachabilityManager ()

@property (nonatomic, strong) NSThread *reachabilityDaemonThread;
@property (nonatomic) Reachability *internetReachability;
@property (nonatomic, assign) BOOL isGotStatus;
@end

@implementation TSReachabilityManager

+ (instancetype)shareManager;
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        gTSReachabilityManager = [[TSReachabilityManager alloc] init];
        [gTSReachabilityManager _setup];
    });
    
    return gTSReachabilityManager;
}

- (void)_setup
{
    _isGotStatus = NO;
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(reachabilityChanged:)
                                                 name:kReachabilityChangedNotification
                                               object:nil];
    
    //能够快速判断出本机的连接状况,未连接、WIFI、WLan
    self.internetReachability = [Reachability reachabilityForInternetConnection];
    [self.internetReachability startNotifier];
}


/*!
 * Called by Reachability whenever status changes.
 */
- (void) reachabilityChanged:(NSNotification *)note
{
    _isGotStatus = YES;
    Reachability* curReach = [note object];
    NSParameterAssert([curReach isKindOfClass:[Reachability class]]);
    [self updateInterfaceWithReachability:curReach];
    
}

- (NetworkStatus)currentReachabilityStatus;
{
    if(_isGotStatus)
    {
        return [self.internetReachability currentReachabilityStatus];
    }
    return kReachableUnkown;
}

- (NSString*)netStatusDescription
{
    NetworkStatus netStatus = [self currentReachabilityStatus];
    NSString *tips = @"";
    switch (netStatus)
    {
        case NotReachable:
            tips = @"无网络连接";
            break;
            
        case ReachableViaWiFi:
            tips = @"Wifi";
            break;
            
        case ReachableViaWWAN:
            NSLog(@"移动流量");
        case kReachableVia2G:
            tips = @"2G";
            break;
            
        case kReachableVia3G:
            tips = @"3G";
            break;
            
        case kReachableVia4G:
            tips = @"4G";
            break;
        default:
            tips = @"正在获取状态";
    }
    return tips;
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil];
}

@end

用法举例

- (void)startRequest
{
    NSString *URLString = @"http://192.168.43.1:8080/cateye/Status";
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
    
    NSURLRequest *request =
    [[AFHTTPRequestSerializer serializer] requestWithMethod:@"GET" URLString:URLString parameters:nil error:nil];;
    
    NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
        if (error) {
            NSLog(@"Error: %@", error);
            //获取当前网络状态
            NetworkStatus status = [[TSReachabilityManager shareManager] currentReachabilityStatus];
            //弹出断网alert/tips/toast等等
            if(status == NotReachable)
            {
                
            }
        } else {
            NSLog(@"%@ %@", response, responseObject);
        }
    }];
    [dataTask resume];
}

彩蛋

  • 使用AP模式,注意左上角显示4G,但是reachabilityWithHostName显示WIFI,reachabilityForInternetConnection显示网络连接状态4G,跟左上角保持一致,reachabilityForLocalWiFi认为无网络,因为没用WIFI连接。
ScreenShot_20161011093447.png

设置里能看出连着AP

ScreenShot_20161011093514.png
  • 更新说明

    经过讨论,使用后台线程可完全避免被看门狗杀死APP,方案以及用法如下:

#import <Foundation/Foundation.h>
#import "Reachability.h"

extern NSString *kTSReachabilityChangedNotification;

@interface TSReachabilityManager : NSObject

@property (nonatomic, strong) NSString *remoteHostName;

+ (instancetype)shareManager;

- (void)startMonitor;

@end
#import "TSReachabilityManager.h"


NSString *kTSReachabilityChangedNotification = @"kTSNetworkReachabilityChangedNotification";


static TSReachabilityManager *gTSReachabilityManager = nil;

@interface TSReachabilityManager ()

@property (nonatomic, strong) NSThread *reachabilityDaemonThread;
@property (nonatomic) Reachability *internetReachability;
@property (nonatomic, assign) NetworkStatus currentReachabilityStatus;
@end

@implementation TSReachabilityManager

+ (instancetype)shareManager;
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        gTSReachabilityManager = [[TSReachabilityManager alloc] init];
        [gTSReachabilityManager _setup];
    });
    
    return gTSReachabilityManager;
}

- (void)_setup
{
    _remoteHostName = @"www.baidu.com";
    [self startMonitor];
}

- (void)startMonitor
{
    //启动守护线程,防止服务器在长时间无响应导致的看门狗杀死app的情形发生
    _reachabilityDaemonThread = [[NSThread alloc] initWithTarget:self selector:@selector(monitorNetReachability) object:nil];
    [_reachabilityDaemonThread start];
    
}

- (void)monitorNetReachability
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil];
    
    //Change the host name here to change the server you want to monitor.

    //能够判断出远程服务器时候可达,ping不通的时候,还是wifi连接状态。服务器状态还是用接口返回情况来判断准确,只有需要判断connectionRequired的时候才用host
    self.internetReachability = [Reachability reachabilityWithHostName:_remoteHostName];
    [self.internetReachability startNotifier];
    
    
    //保持线程不退出
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSRunLoopCommonModes];
    [[NSRunLoop currentRunLoop] run];
    
    _currentReachabilityStatus = [self currentReachabilityStatus];
    dispatch_async(dispatch_get_main_queue(), ^{
        NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:_currentReachabilityStatus],@"status",[self netStatusDescription],@"description", nil];
         [[NSNotificationCenter defaultCenter] postNotificationName:kTSReachabilityChangedNotification object:self userInfo:dic];
    });

}


/*!
 * Called by Reachability whenever status changes.
 */
- (void) reachabilityChanged:(NSNotification *)note
{
    Reachability* curReach = [note object];
    NSParameterAssert([curReach isKindOfClass:[Reachability class]]);
    _currentReachabilityStatus = [curReach currentReachabilityStatus];
    dispatch_async(dispatch_get_main_queue(), ^{
        NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:_currentReachabilityStatus],@"status",[self netStatusDescription],@"description", nil];
        [[NSNotificationCenter defaultCenter] postNotificationName:kTSReachabilityChangedNotification object:self userInfo:dic];
    });
}



- (NSString*)netStatusDescription
{
    NetworkStatus netStatus = [self currentReachabilityStatus];
    NSString *tips = @"";
    switch (netStatus)
    {
        case NotReachable:
            tips = @"无网络连接";
            break;
            
        case ReachableViaWiFi:
            tips = @"Wifi";
            break;
            
        case ReachableViaWWAN:
            NSLog(@"移动流量");
        case kReachableVia2G:
            tips = @"2G";
            break;
            
        case kReachableVia3G:
            tips = @"3G";
            break;
            
        case kReachableVia4G:
            tips = @"4G";
            break;
        default:
            tips = @"正在获取状态";
    }
    return tips;
}




- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil];
}

@end

调用方法:

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_showNetStatus:) name:kTSReachabilityChangedNotification object:nil];
    [[TSReachabilityManager shareManager] startMonitor];
}

- (void)_showNetStatus:(NSNotification*)aNo
{
    NSLog(@"userInfo:%@",[aNo.userInfo objectForKey:@"description"]);
}

代码:

代码附件在印象笔记,后边有需要再传。

代码放在git上了。

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

推荐阅读更多精彩内容