iOS 蓝牙初章

本文主要以蓝牙4.0做介绍,因为现在iOS能用的蓝牙也就是只仅仅4.0的设备

用的库就是core bluetooth

这里是关于蓝牙的开发者官方文档,感兴趣的童鞋可以看一下点击进入 https://developer.apple.com/library/prerelease/content/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetoothOverview/CoreBluetoothOverview.html

其次就是了解一下蓝牙的一些概念吧.

core bluetooth就两个东西,peripheral和central 也就是外设和中心

如下图所示


20140523195339187.jpeg

图为Core Bluetooth 对象类模型
一般情况下蓝牙设备为外设,手机为中心,也就是在APP里面建立CBCentralManager获取到设备的peripheral这个类, 每个peripheral又会有若干个CBService,每个CBservice里面也会有若干个CBCharateristic,每个CBCharateristic里面又会有CBDescriptor.
看图

1442301743869760.png

这句话说的真长.真费劲...!! 所以从网上盗了图.

蓝牙外设模型
了解的差不多了,下面就是手机作为中心连接外设的步骤了:

1.第一步当然是先建立中心管理者的角色了,成为代理,实现必须实现的代理方法也就是蓝牙更新状态的方法

2.确认蓝牙是打开的时候,我们就可以扫描周边设备了.扫描设备的时候要同时把设备保存的数组中,以便接下来连接的时候使用,

3.扫描到我们的设备之后就可以连接了.可以根据蓝牙的一些信息进行指令连接.例如:根据蓝牙名字连接等等.

4.连接上蓝牙之后就会生成一个peripheral对象,这时候我们就可以拿着这个对象进行操作了,例如查看外设信息,向这个外设读写信息等等.嗯,回归正题,拿到这个peripheral对象之后

获取peripheral的services,在获取services里的characteristics,拿到可以读写的characteristic并把这个characteristic记录下来

5.现在我们就可以进行数据交互了.可以向characteristic里面写数据 以及从characteristic读数据.嗯,读数据也就是订阅通知

6.完成后就可以断开连接了.

手机作为中心连接外设的步骤也就到这里结束了.下面就开始贴代码了.

我觉得这个代码贴的不太好,留下github地址,下载可运行.这样方便多了.

https://github.com/907064772/iOS----BluetoothCentral

---------****--------开始贴代码

import "ViewController.h"

import <CoreBluetooth/CoreBluetooth.h>

import "NSString+Emoji.h"

pragma step1 导入coreBluetooth头文件,建立主设备管理类,设置主设备委托

@interface ViewController ()<CBCentralManagerDelegate,CBPeripheralDelegate,UITableViewDelegate,UITableViewDataSource>{
CBCentralManager * manager;
CBPeripheral *per;

// 记录下写的通道
CBCharacteristic * WriteCharacteristic;
// 记录下读的通道
CBCharacteristic * ReadCharacteristic;
}
//扫描到的蓝牙外设
@property (nonatomic,strong) NSMutableArray *discoverPeripherals;

@property (nonatomic,strong) UITableView *tableview;
//通知显示label
@property (weak, nonatomic) IBOutlet UILabel *notifyLabel;
//发送按钮的方法

  • (IBAction)notifyBtnClick:(UIButton *)sender;

@property (weak, nonatomic) IBOutlet UITextView *textView;

@property (weak, nonatomic) IBOutlet UIButton *sendBtn;

/**

  • 点击选择是否以十六进制发送显示
    */
  • (IBAction)isHexBtnClick:(UIButton *)sender;

/**

  • 是否以十六进制输入输出标记
    /
    @property (nonatomic,assign) BOOL isHex;
    /
    *
  • 发送方法
    */
  • (IBAction)sendAction:(UIButton )sender;
    /
    *
  • 从蓝牙串口监听到的所有消息字符串
    */
    @property (nonatomic,copy) NSMutableString *notifyString;

/**

  • 写入的字符串
    */
    @property (nonatomic,copy) NSMutableString *writeString;

/**

  • 监听蓝牙串口广播按钮
    */
    @property (weak, nonatomic) IBOutlet UIButton *listenBtn;

@property (weak, nonatomic) IBOutlet UIButton *cleanBtnClick;

  • (IBAction)cleanBtnClick:(id)sender;

@end

@implementation ViewController

  • (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    _discoverPeripherals = [[NSMutableArray alloc]init];
    // 初始化一个蓝牙中心管理者并设置代理和线程,默认线程为住线程
    manager = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue()];

// [self createTableView];

}
/**

  • 创建表格视图
    */
    -(void)createTableView{
    CGRect frame = self.view.frame;
    frame.origin.y += 20;
    self.tableview = [[UITableView alloc]initWithFrame:frame style:UITableViewStylePlain];
    self.tableview.delegate = self;
    self.tableview.dataSource = self;
    [self.view addSubview:self.tableview];
    }

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.discoverPeripherals.count;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
CBPeripheral * peripheral = self.discoverPeripherals[indexPath.row];
cell.textLabel.text = peripheral.name;
return cell;
}

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath )indexPath{
CBPeripheral peripheral = self.discoverPeripherals[indexPath.row];
// 连接设备 如果蓝牙串口名字前缀有 这个就连接
if ([peripheral.name hasPrefix:@"SerialCom"]) {
/

* 一个设备最多能连7个外设,每个外设最多只能给一个住设备连接,连接成功,失败,断开会进入各自的委托.
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//连接外设成功的委托
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外设连接失败的委托
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//断开外设的委托
*/
//找到的设备必须持有它,否则CBCentralManager中也不会保存peripheral,那么CBPeripheralDelegate中的方法也不会被调用!!

    [manager connectPeripheral:peripheral options:nil];
}

}

pragma step2 扫描外设(discover),扫描外设的方法我们放在centrallManager成功打开的委托中,因为只有设备成功打开,才能开始扫描,否则会报错

/**

  • 蓝牙中心管理者必须实现的代理方法.更新状态

  • @param central
    */
    -(void)centralManagerDidUpdateState:(CBCentralManager *)central{

    /*!

    • @enum CBCentralManagerState
    • @discussion Represents the current state of a CBCentralManager.
    • @constant CBCentralManagerStateUnknown 未知状态,更新迫在眉睫
    • @constant CBCentralManagerStateResetting 与系统连接服务暂时丢失,
    • @constant CBCentralManagerStateUnsupported 这个平台不支持蓝牙 中心/客户端 角色
    • @constant CBCentralManagerStateUnauthorized 此应用未被授权
    • @constant CBCentralManagerStatePoweredOff 蓝牙当前状态为关闭
    • @constant CBCentralManagerStatePoweredOn 蓝牙当前状态为打开
      /
      switch (central.state) {
      case CBCentralManagerStateUnknown:
      NSLog(@"CBCentralManagerStateUnknown");
      break;
      case CBCentralManagerStateResetting:
      NSLog(@"CBCentralManagerStateResetting");
      break;
      case CBCentralManagerStateUnsupported:
      NSLog(@"CBCentralManagerStateUnsupported");
      break;
      case CBCentralManagerStateUnauthorized:
      NSLog(@"CBCentralManagerStateUnauthorized");
      break;
      case CBCentralManagerStatePoweredOff:
      NSLog(@"CBCentralManagerStatePoweredOff");
      break;
      case CBCentralManagerStatePoweredOn:
      NSLog(@"CBCentralMnagerStatePoweredOn");
      /
      *
      * 第一个参数nil就是扫描周围所有的外设,扫描到外设后会进入
      */
      // [manager scanForPeripheralsWithServices:nil options:nil];
      [manager scanForPeripheralsWithServices:@[] options:nil];
      break;
      default:
      break;
      }

}

pragma step3 连接外设(connect)

/**

  • 扫描到设备会进入方法

  • @param central <#central description#>

  • @param peripheral <#peripheral description#>

  • @param advertisementData <#advertisementData description#>

  • @param RSSI <#RSSI description#>
    */
    -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI{

    NSLog(@"当前扫描到设备:%@",peripheral);
    [_discoverPeripherals addObject:peripheral];
    // 连接设备
    if ([peripheral.name hasPrefix:@"SerialCom"]) {
    /**
    * 一个设备最多能连7个外设,每个外设最多只能给一个住设备连接,连接成功,失败,断开会进入各自的委托.
    - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//连接外设成功的委托
    - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外设连接失败的委托
    - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//断开外设的委托
    */
    //找到的设备必须持有它,否则CBCentralManager中也不会保存peripheral,那么CBPeripheralDelegate中的方法也不会被调用!!
    [self.tableview reloadData];
    [manager connectPeripheral:peripheral options:nil];
    }

}

pragma step4 扫描外设中的服务和特征(discover)

/**

  • 设备连接成功后,就可以扫描设备的服务了.同样是通过委托形式,扫描到结果后进入委托方法,但是这个委托已经不再是主设备的委托(CBCentralManagerDelegate),而是外设的委托(CBPeripheralDelegate),这个委托包含了主设备与外设交互的许多回调方法,包括获取services,获取characteristics ,获取characteristics的值,获取characteristics的descirptor和descriptor的只,写数据,读rssi,用通知的方式订阅数据等等.
    */

pragma mark - centralManagerDelegate

/**

  • 连接外设成功委托
  • @param central <#central description#>
  • @param peripheral <#peripheral description#>
    */
    -(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
    NSLog(@"连接到名称为(%@)的设备-成功",peripheral.name);
    // 设置peripheral的委托
    peripheral.delegate = self;
    // 记录下当前连接上的外设
    per = peripheral;
    // 扫描外设servers,成功后会进入 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error;
    [peripheral discoverServices:nil];
    // 连接成功后停止扫描
    [central stopScan];
    }

/**

  • 连接外设失败的委托
  • @param central
  • @param peripheral
  • @param error
    */
    -(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral )peripheral error:(NSError )error{
    NSLog(@"连接到名称为(%@)的设备-失败,原因:%@",peripheral.name,[error localizedDescription]);
    }
    /
  • Peripheral断开连接
  • @param central
  • @param peripheral
  • @param error
    */
    -(void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
    NSLog(@"外设连接断开连接设备:%@,原因:%@",peripheral.name,[error localizedDescription]);
    }

pragma mark - peripheralDelegate

/**

  • 扫描到services,获取外设的services
  • @param peripheral
  • @param error
    */
    -(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
    if (error) {
    NSLog(@">>>discovered services for %@ with error: %@",peripheral.name,[error localizedDescription]);
    return;
    }
    for (CBService *service in peripheral.services) {
    NSLog(@"service.UUID:%@",service.UUID);
    // 扫描每个service的characteristics,扫描到会进入 -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error 代理方法中
    [peripheral discoverCharacteristics:nil forService:service];
    }

}
/**

  • 获取外设的characteristics,获取characteristics的值,获取characteristics的descriptor和descriptor的值

  • @param peripheral

  • @param service

  • @param error
    */
    -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
    if (error) {
    NSLog(@"error discovered characteristics for %@ with error: %@",service.UUID,[error localizedDescription]);
    return;
    }
    // 获取characteristics的值.读到数据会进入 -peripheral:didUpdateValueForCharacteristic:error:方法
    for (CBCharacteristic *characteristic in service.characteristics) {
    [peripheral readValueForCharacteristic:characteristic];
    }

    // 获取characteristics的descriptor值,读取到数据会进入到
    // - peripheral:didDiscoverDescriptorsForCharacteristic:error:

    for (CBCharacteristic *charateristic in service.characteristics) {
    [peripheral discoverDescriptorsForCharacteristic:charateristic];
    }

}
/**

  • 获取characteristic的值
  • @param peripheral
  • @param characteristic
  • @param error
    */
    -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError )error{
    NSString hexString =[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
    if (self.isHex) {
    hexString = [NSString hexStringFromString:hexString];
    }
    [_notifyString appendFormat:@"%@ ",hexString];
    // 打印charateristic的uuid值和value值
    _notifyLabel.text = [NSString stringWithFormat:@"%@",_notifyString];
    NSLog(@"characteristic.uuid:%@ value:%@",characteristic.UUID,characteristic.value);
    }
    /
  • 搜索到characteristic的descriptors
  • @param peripheral
  • @param descriptor
  • @param error
    */
    -(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError )error{
    NSLog(@"characteristic:%@ properties:%ld",characteristic.UUID,characteristic.properties);
    // 记录下当前通道.在我的蓝牙设备里面,FFF6的权限是可读写的就是RW,但是实际应用中应该判断 characteristic.properties然后在进行保存
    if ([[characteristic.UUID UUIDString] isEqual:@"FFF6"]) {
    WriteCharacteristic = characteristic;
    }
    // 读通道
    if ([[characteristic.UUID UUIDString] isEqual:@"FFF7"]) {
    ReadCharacteristic = characteristic;
    }
    for (CBDescriptor descriptor in characteristic.descriptors) {
    NSLog(@"descriptor.uuid:%@",descriptor.UUID);
    }
    _listenBtn.enabled = YES;
    }
    /
  • 获取到descriptor的值
  • @param peripheral
  • @param descriptor
  • @param error
    */
    -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error{
    // 打印出descriptorUUID和value
    // 这个descriptor都是对于charateristic的描述,一般都是字符串.所以这里我们用了字符串拼接
    NSLog(@"characteristic.uuid:%@ value:%@",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value);
    }
    //波特率115200

pragma step5 把数据写到characteristic中

/**

  • 写数据
    */
    -(void)writeCharacteristic:(CBPeripheral *)peripheral characteristic:(CBCharacteristic *)characteristic value:(NSData )value{
    //打印出 characteristic 的权限,可以看到有很多种,这是一个NS_OPTIONS,就是可以同时用于好几个值,常见的有read,write,notify,indicate,知知道这几个基本就够用了,前连个是读写权限,后两个都是通知,两种不同的通知方式。
    /

    typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) {
    CBCharacteristicPropertyBroadcast = 0x01,
    CBCharacteristicPropertyRead = 0x02,
    CBCharacteristicPropertyWriteWithoutResponse = 0x04,
    CBCharacteristicPropertyWrite = 0x08,
    CBCharacteristicPropertyNotify = 0x10,
    CBCharacteristicPropertyIndicate = 0x20,
    CBCharacteristicPropertyAuthenticatedSignedWrites = 0x40,
    CBCharacteristicPropertyExtendedProperties = 0x80,
    CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x100,
    CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x200
    };

    */

    NSLog(@"%lu",(unsigned long)characteristic.properties);
    // 只有characteristic.properties 有write 的权限才可以写
    if (characteristic.properties & CBCharacteristicPropertyWrite) {
    // 最好一个type参数可以为CBCharacteristicWriteWithResponse或者type:CBCharacteristicWrite,区别是是否会有反馈
    [peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
    }else{
    NSLog(@"改字段不可写");
    }
    }
    /**

  • 设置通知
    */
    -(void)notifyCharacteristic:(CBPeripheral )peripheral characteristic:(CBCharacteristic )characteristic{
    // 设置通知.数据通知会进入didUpdateValueForCharacteristic方法
    [peripheral setNotifyValue:YES forCharacteristic:characteristic];
    }
    /

  • 取消通知
    */
    -(void)cancelNotifuCharacteristic:(CBPeripheral )perpheral characteristic:(CBCharacteristic )characteristic{
    // 取消通知
    [perpheral setNotifyValue:NO forCharacteristic:characteristic];
    }
    /

  • 停止扫描并断开连接
    */
    -(void)disconnectPeripheral:(CBCentralManager *)centralManager peripheral:(CBPeripheral *)periphearl{
    // 停止扫描
    [centralManager stopScan];
    // 断开连接
    [centralManager cancelPeripheralConnection:periphearl];
    }

-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
NSLog(@"peripheral:%@ characteristic:%@ error:%@",peripheral,characteristic,[error localizedDescription]);
}

  • (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
    }

  • (IBAction)sendAction:(UIButton *)sender {
    NSString *tmpString = _textView.text;
    // 判断十六进制是否打开,如果要发送十六进制数据就转换字符串
    if (self.isHex) {
    tmpString = [NSString hexStringFromString:tmpString];
    }
    // 想制定特征写入字符
    [self writeCharacteristic:per characteristic:WriteCharacteristic value:[tmpString dataUsingEncoding:NSUTF8StringEncoding]];
    }
    //是否开启蓝牙串口读取字符串

  • (IBAction)notifyBtnClick:(UIButton *)sender {
    sender.selected = !sender.isSelected;
    if (sender.selected) {
    _notifyString = [[NSMutableString alloc]init];
    [self notifyCharacteristic:per characteristic:ReadCharacteristic];
    }else{
    _notifyString = nil;
    [self cancelNotifuCharacteristic:per characteristic:ReadCharacteristic];
    }

}
//取消可编辑
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.view endEditing:NO];
}
//点击切换编码

  • (IBAction)isHexBtnClick:(UIButton *)sender {
    sender.selected = !sender.selected;
    if (sender.selected) {
    _isHex = NO;
    [sender setTitle:@"UTF-8" forState:UIControlStateSelected];
    }else{
    _isHex = YES;
    [sender setTitle:@"Hex" forState:UIControlStateNormal];
    }

}
//清空消息

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

推荐阅读更多精彩内容