iOS中tcp+protobuf

iOS Protobuf和TCP与服务器交互 文章概要:本篇是我在实际项目开发中遇到的有关Protobuf相关知识,主要包括两方面:一是protobuf的安装和使用;二是protobuf和tcp结合与服务器交互的使用。内容并不是很完整无误,但是可以分享给遇到过相同问题的朋友们,又或者即将面对这个问题的你们,有问题的欢迎与我探讨!!! 一、Protobuf的安装和使用 什么是Protobuf?         Protobuf是谷歌公 阅读详情

简介:

tcp+protobuf概念介绍

关于tcp和Google Protocol Buffer其它博客一搜一大堆,在这里我就不再搬抄了。

Google Protocol Buffer

tcp

CocoaAsyncSocket

准备:

Protobuf安装:

  • 检查是否安装Homebrew:
    brew -v
    如果没有安装:
    ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  • 安装Protobuf Compiler
    brew install automake 
    brew install libtool 
    brew install protobuf
  • 安装Objective-C扩展
    git clone https://github.com/alexeyxo/protobuf-objc.git
    cd protobuf-objc
    ./scripts/build.sh
  • 使用CocoaPods集成到项目(这个不做过多介绍)
    pod "Protobuf"  # 注意不是 pod "ProtocolBuffers"

Protobuf使用:

创建 .proto 文件根据前后台项目需求制定协议例如:
syntax = "proto3";


// 1-15之间的标识码应给常用的字段使用
// 约定如下
// 1 -> command
// 2 -> uid
// 3 -> openid
// 4 -> kuocode
// 5 -> status
// 5 -> status
// 6 -> create_at
// 7 -> update_at

enum Commands {
    Request = 0;
    Reply = 1;
    Update = 2;
}

message GetUser {
    Commands command = 1;
    string openid = 3;
}

message UserProfile {
    Commands command = 1;
    string nickname = 18;
    int32 uid = 2;
    string openid = 3;
    int32 status = 5;
    float refresh_at = 19;
    repeated UserProfile friends = 20;
}

具体protobuf协议格式可以参照官网 (ps:protobuf  3.0语法格式和3.0以前有很大改动,参照网上搜来的 .proto 文件时需要注意不同点,然后修改)

将生成的 .pbobjc 文件拖入项目中(但是这样不好,不利于管理。个人认为最好的方式是管理一个git并集成到项目中,里面存放项目所用到的.proto文件,前后台共同维护,在xcode项目编译前添加一个脚本使用脚本语言自动生成 .pbobjc 文件在项目中。这个具体看后面如果有时间,然后大家都有需求的话再写一篇专门介绍这部分,这里就不细说了)

使用protobuf好处:

Protobuf和XML相比同是数据交换协议,不过Protobuf更小、更快、也更简单。可以通过定义自己的数据结构,然后使用Protobuf的代码生成器生成代码,用生成的代码来读写这个数据结构。Protobuf具有如下几个优点:
  • “向后”兼容性好。不必担心因为消息结构的改变而造成的大规模的代码重构或者迁移的问题,因为添加新的消息中的字段并不会引起已经发布的程序的任何改变。

  • 语义更清晰。Protobuf使用.proto文件描述数据交换的格式,然后Protobuf编译器会将.proto文件编译生成对应的数据访问类以对Protobuf数据进行序列化、反序列化操作),无需解释器之类的东西。

  • 简单易学。使用Protobuf无需学习复杂的文档对象模型,它拥有良好的文档和示例,对于喜欢简单事物的人们而言,Protobuf比其他的技术更加有吸引力。

CocoaAsyncSocket添加到项目中

下载 CocoaAsyncSocket后将GCDAsyncSocket文件及GCDAsyncUdpSocket(如果用到udp)拖入项目中
然后自己根据需求写一个管理类,例如:KLSocketManager
.h文件如下:
#import <Foundation/Foundation.h>

/** socket通讯数据接收 */
@protocol KLSocketManagerDelegate <NSObject>

/** 接收数据成功 */
- (void)socketRequstBackResultData:(NSData *)resultData;

/** 连接失败 */
- (void)socketRequstFail;

/** 获取data */
- (NSData *)getData;

@end
@interface KLSocketManager : NSObject

+ (KLSocketManager *)shareInstance;

/**
 socket连接
 */
- (void)socketConnectHost;

/**
 socket断开 (被视为用户主动断开socket,不会自动重连)
 */
- (void)cutOffSocket;

/**
 向服务器发送数据 并指定代理
 */
-(void)socketWriteData:(NSData *)data andDelegate:(id <KLSocketManagerDelegate>)delegate;

@end
.m文件如下:
#import "KLSocketManager.h"
#import "GCDAsyncSocket.h"
#import "GCDAsyncUdpSocket.h"
#import "Test.pbobjc.h"

#define KLSocketHost @"你的socket地址"
#define KLSocketPort 8080
#define KLSocketTimerTime 60
#define KLSocketTimerTag 200

/** socket断开状态 */
typedef enum : NSUInteger {
    /** 超时 */
    KLSocketOffLineOutTime,
    KLSocketOffLineByUser,
    KLSocketOffLineHome,
} KLSocketetOffLineType;

@interface KLSocketManager () <GCDAsyncSocketDelegate>

/** socket */
@property (nonatomic, strong) GCDAsyncSocket *clientSocket;

/** 服务器地址 */
@property (nonatomic, strong) NSString *socketHost;

/** 端口号 */
@property (nonatomic, assign) uint16_t socketPort;

/** 心跳计时器 */
@property (nonatomic, strong) NSTimer *socketTimer;

/** socket状态 */
@property (nonatomic, assign) KLSocketetOffLineType offlineType;

/** socket回调标识 */
@property (nonatomic, assign) NSInteger socketTag;

/** socket重连次数限定 */
@property (nonatomic, assign) NSInteger reconnectCount;

/** socket回调存储 */
@property (nonatomic, strong) NSMutableDictionary *socketDic;

/**
 心跳连接
 */
- (void)socketTimerConnectSocket;

- (void)startTimer;

@end

@implementation KLSocketManager

+ (KLSocketManager *)shareInstance{
    static KLSocketManager *manager = nil;
    static dispatch_once_t onceSocketToken;
    dispatch_once(&onceSocketToken, ^{
        manager = [[KLSocketManager alloc] init];
    });
    return manager;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.socketHost = KLSocketHost;
        self.socketPort = KLSocketPort;
        self.socketTag = 1;
        self.offlineType = KLSocketOffLineOutTime;
        self.reconnectCount = 1;
//        [self socketConnectHost];
    }
    return self;
}

// 连接
- (void)socketConnectHost{
    if (self.clientSocket.isConnected) {
        return;
    }
    [self cutOffSocket];
    self.offlineType = KLSocketOffLineOutTime;
    // 连接ip 端口
    NSError *error;
    [self.clientSocket connectToHost:self.socketHost onPort:self.socketPort viaInterface:nil withTimeout:-1 error:&error];
    NSLog(@"%@", error);
}

// 断开
- (void)cutOffSocket{
    [self.clientSocket disconnectAfterReadingAndWriting];
    if (self.socketTimer) {
        [self.socketTimer invalidate];
        self.socketTimer = nil;
    }
}

// 心跳
- (void)socketTimerConnectSocket {
    GetUser *user = [[GetUser alloc] init];
    user.openid = @"1";
    [self.clientSocket writeData:[user data] withTimeout:-1 tag:KLSocketTimerTag];
}

// 开始心跳
- (void)startTimer {
    if (self.socketTimer) {
        [self.socketTimer invalidate];
        self.socketTimer = nil;
    }
    self.socketTimer = [NSTimer scheduledTimerWithTimeInterval:KLSocketTimerTime target:self selector:@selector(socketTimerConnectSocket) userInfo:nil repeats:YES];
    [self.socketTimer fire];//执行
    [[NSRunLoop currentRunLoop] addTimer:self.socketTimer forMode:NSRunLoopCommonModes];
    [[NSRunLoop currentRunLoop] run];
}

/**
 向服务器发送数据
 */
- (void)socketWriteData:(NSData *)data andDelegate:(id <KLSocketManagerDelegate>)delegate {
    if (!self.clientSocket.isConnected) {
        [delegate socketRequstFail];
        return;
    }
    self.socketTag = self.socketTag > 1000000 ? 1 : self.socketTag + 1;
    NSLog(@"%zd", data.length);
    [self.clientSocket writeData:data withTimeout:-1 tag:self.socketTag];
}

#pragma mark - GCDAsyncSocketDelegate 
/** 已经连接 */
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
    NSLog(@"已经连接  %@", host);
    self.reconnectCount = 1;
    if (self.socketDic.count > 0) {
        for (NSString *key in self.socketDic.allKeys) {
            id <KLSocketManagerDelegate>delegate = self.socketDic[key];
            if ([delegate respondsToSelector:@selector(getData)]) {
                NSData *data = [delegate getData];
                [self.clientSocket writeData:data withTimeout:-1 tag:key.integerValue];
            }
        }
    }
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(startTimer) object:nil];
    [self performSelector:@selector(startTimer) withObject:nil afterDelay:KLSocketTimerTime];
}

/** 连接断开 */
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(nullable NSError *)err {
    // 先判断网络,网络不好情况下不重连
    
    // 不是用户主动断开就重连
    if (self.offlineType != KLSocketOffLineByUser) {
        if (self.reconnectCount > 64) {
            self.offlineType = KLSocketOffLineOutTime;
            [self.socketDic removeAllObjects];
        } else {
            self.reconnectCount *= 2;
            [self socketConnectHost];
        }
    }
}

/** 读取服务器数据 */
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    NSError *error;
    UserProfile *pro = [[UserProfile alloc] initWithData:data error:&error];
    
    // 继续监听
    [self.clientSocket readDataWithTimeout:-1 tag:KLSocketTimerTag];
}

/** 写入完成的回调 */
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
    [self.clientSocket readDataWithTimeout:-1 tag:tag];
}

#pragma mark - 获取当前时间
- (NSData *)currentDatedata {
    NSDate *currentDate = [NSDate date];//获取当前时间,日期
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    //设定时间格式,这里可以设置成自己需要的格式
    [dateFormatter setDateFormat:@"YYYY/MM/dd hh:mm:ss"];
    NSString *dateString = [dateFormatter stringFromDate:currentDate];
    return [dateString dataUsingEncoding:NSUTF8StringEncoding];
}

- (GCDAsyncSocket *)clientSocket {
    if (!_clientSocket) {
        _clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    }
    return _clientSocket;
}

- (NSMutableDictionary *)socketDic {
    if (!_socketDic) {
        _socketDic = [[NSMutableDictionary alloc] init];
    }
    return _socketDic;
}
ps:这个管理类只是暂时写的,大家还是要根据自己项目需求写自己的管理类。
这里接收到的数据流转换为protobuf的model,但是还是要新建项目中实际用到的model,最好不要用protobuf的model用在项目中,只做网络传输协议就好。
UserProfile *pro = [[UserProfile alloc] initWithData:data error:&error];
关于协议制定还需和后台商议,如协议头怎么传,用不用界定符等等。
老规矩,git 小例子
完。
转载请标明出处,谢谢!

iOS:GCDAsyncSocket 的二次封装,粘包处理 因为要做实时数据所以简单的了解了下CocoaAsyncSocket这个库,其中的GCDAsyncSocket就是这个TCP协议通信的,看了一些网上demo之后基于目前使用的自定义协议的版本做了简单分装。 这是自定以的协议头,后面拼接了一个json包(目前改为了protobuf,因为这个确实比较好用,体积也小),后面的封装基于这个协议,可供大家做一个参考!!!! #import ty 阅读详情

相关推荐

iOS 使用Protobuf完成HTTP和Socket通信

Protobuf(Protocol Buffer)是一种数据通信协议,相比JSON,它的传输数据量更小,而且没有对应的proto文件根本无法看懂传输的二进制格式数据,所以传输安全性也更高。 Protobuf的安装就不多做介绍了,这是之前写的Protobuf安装踩坑记录 放一个批量编译.proto文件的命令: 批量编译Module目录下的所有proto文件...

夜阑微静的备忘录 928

iOS开发】之CocoaAsyncSocket使用

本文介绍了CocoaAsyncSocket库中GCDAsyncSocket类的使用、粘包处理以及时间延迟测试. 一.CocoaAsyncSocket介绍 CocoaAsyncSocket中主要包含两个类: 1.GCDAsyncSocket. 1 2 用GCD搭建的基于TCP/IP协议的socket网络库 GCDAsyncSocket is a TCP/IP socket net...

weixin_34021089的博客 334

社交交友源码解析:双端原生IM与音视频通话架构设计

在移动社交领域,即时通讯(IM)和音视频通话能力是产品体验的基石。从基础概念来看,IM系统需要解决长连接维护、消息可靠传输与服务端时序控制等核心问题,而语音视频通话则涉及信令协商、媒体传输与音频焦点管理等底层机制。理解这些技术原理,有助于评估一套双端原生APP源码的工程价值。在实际业务中,一套完整的陌生人社交解决方案,通常包含用户匹配、实时聊天、一对一通话、直播间互动等多个模块,其架构选型直接决定了后续的扩展成本与运营稳定性。同时,原生开发在音视频性能、Android后台保活及隐私合规方面具备显著优势。无论

weixin_34054931的博客 453

TCP/Protobuf之基础知识准备

最近项目里,引入了TCP,通过protobuf,在App里来获取司机的经纬度信息。还使用到了Netty,本篇先对涉及到的基础知识做了梳理。Socket套接字之间的连接过程分为三个步骤: 服务器监听 客户端请求 连接确认。 Java NIO的通道类似流,但又有些不同: 流只是在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类),而通道可以用于读、写或者同时用

sugaryaruan的博客 4530

NettyChat:基于Netty + TCP + Protobuf实现的Android IM库,包含Protobuf序列化,TCP拆包与粘包,长连接握手认证,心跳机制,断线重连机制,消息重发机制,读写超时机制,离线消息,螺纹池等功能

使用方式 1. 依赖im_lib库,implementation project(':im_lib') 2. 自定义IMSEventListener,实现OnEventListener,重写对应的方法配置参数 3. 自定义IMSConnectStatusListener,实现IMSConnectStatusCallback,重现对应的方法监听ims连接状态 4. 调用IMSClientInterface.init(Vector<String> serverUrlList, OnEventListener listener, IMSConnectStatusCallback callback)方法,把服务器地址列表、IMSEventListener、IMSConnectStatusListener三个参数传入即可。 5. 发送消息:调用IMSClientInterface.sendMsg(M

jmeter 5.1.1 tcp链接 发送protobuf消息到服务端,并返回结果的demo

jmeter 5.1.1 版本。maven项目。服务端 vertx+kotlin。 实现jmeter tcp链接 发送protobuf消息到服务端,并返回结果的demo。具体看里面的doc目录的说明。

序列化结构(protobuf)实现一个TCP服务器(C++)

1、首先建立proto文件,syntax如果不标明proto3,则会默认使用proto2版本,在后面的使用过程中需要加上包名,以防止命名空间冲突,消息体中的序号表明在序列化数据中该变量出现的顺序。当服务端两次使用同一个端口号,会出现Error on binding,这是同一时间在同一端口上启动两个TCP服务器,第二个服务器会收到端口已被占用的错误,并且无法绑定到该端口上。2、编写服务端,编辑 g++ -o server server.cpp message.pb.cc -lprotobuf。

qq_41452177的博客 1011

Unity5Google Protobuf解析工具,支持android/ios泛型列表参数正反序列化

Unity5Google Protobuf解析工具,支持android/ios泛型列表参数正反序列化,下载即可用

netty4 tcp与protobuf3的整合使用。

netty是一个非常棒的NIO框架就不介绍了。protobuf是google提供的一个开源序列化框架,也是一个非常棒的东西。这里不介绍这两个框架了,想要了解的朋友网上一搜一大把。本文主要介绍 protobuf3与netty4 在tcp协议里的整合使用。个人认为netty与protobuf是绝配的组合。配合使用非常棒,框架提供了粘包拆包等工具类。特别是你要实现如及时通讯功能的时候,能让你省很多功夫。...

把握自己。 1896

瑞芯微rk3568移植openbmc(一)----关于ob移植的优缺点

基于rock3a的单板,CPU为瑞芯微rk3568的环境,移植了openbmc

feixiang3839的专栏 5537

Linux安装部署PostgreSQL详细步骤

Linux安装部署PostgreSQL详细步骤

2301_77356579的博客 9322

推荐使用 Protobuf-iOS: 一种高效、易于使用的 iOS 通信协议库

推荐项目:protobuf-ios 项目简介 是一个针对 iOS 平台的 Protocol Buffers(protobuf)编译器和库。Protocol Buffers 是 Google 提出的一种数据序列化协议,它可以将结构化数据序列化为二进制流,方便在网络间传输或持久化存储。 功能与用途 protobuf-ios 主要提供了以下功能: 编译器:支持将定义在 .proto 文件中的消息类型编...

gitblog_00067的博客 663

基于STM32F407ZGT6与INMP441的I2S音频采集系统:从配置到实时波形显示

本文详细介绍了基于STM32F407ZGT6与INMP441麦克风模块的I2S音频采集系统设计与实现。从硬件选型、CubeMX配置到音频数据处理和上位机波形显示,全面解析了系统架构、DMA传输优化及常见问题解决方案,帮助开发者快速构建高精度音频采集系统。

weixin_33743661的博客 403

iOS中protobuf和tcp长链接(AsyncSocket)scoket配合使用

在ios开发中,客户端开发最重要的就是数据的获取和展示了,说到获取数据,AFN是目前用的最广的类库了吧,不过在实时数据tcp获取上面,我们还是需要用到AsyncSocket这个类库来进行开发。不过近日听过网上google的一个protobuf协议十分好用,就研究了一下。 AsyncSocket的下载和引用以及protobuf的安装和使用都在之前的博客中讲过,不懂的同学可以去稍稍看一下。 接着我们

凉城旧梦的博客 2098

【SAP PO】SAP PO 接口配置完整教程之一RFC服务发布

SAP RFC开发,PO接口配置

XLevon的博客 2万+

SGP4/SDP4详解

**SGP4** (Simplified General Perturbations Version 4) 和 **SDP4** (Simplified Deep-space Perturbations Version 4) 是一组**解析轨道预报模型**,用于预测地球轨道卫星的位置和速度。它们是北美防空司令部(NORAD)维护的**TLE**数据格式的标准配套算法。 简单来说:**TLE是卫星轨道的"身份证",而SGP4/SDP4是解读这张身份证的"说明书"**。

ScilogyHunter的博客 589

记:iOS使用GCDAsyncSocket外部如何设置keepalive

问题描述:最近在我的项目组中遇到一个问题,由于我们项目使用采用的是TCP+Protobuf来做主要通讯协议,心跳是使用udp。服务器分为多台,各服务器负责各自的任务,比如我们账号服务器只负责与用户数据相关的任务,推送服务器负责服务器向前端通知等等。当我们的TCP中间由于某种原因与服务器断开连接,但是服务器和客户端都没有接到断开通知,或者需要好长时间才能知道(大概10分钟,不确定的时间段),彼此之间...

_understand的博客 1480

Cadence 批量一次性修改title 页码标题

本文介绍如何在Cadence软件中对DSN文件进行标题栏的批量编辑操作,包括选择编辑对象、使用快捷键打开属性面板以及如何修改标题、页码等关键信息。

tianpu2320959696的博客 976

从零打造一套移动IM系统(一) 玩转二进制协议及protobuf

本文默认您已具备以下知识: iOS开发的基础知识,以及swift语法 node.js的基础语法 TCP基础及IM相关基础知识 通过本文您将能收获 在iOS上用底层socket,服务器建立tcp连接并通讯 如何设计一个二进制通讯协议 swift当中如何操作二进制网络数据流,会涉及一些unsafe类型及C指针的操作 node.js中如何操作网络数据流 protobuf 3.0在客户端及服务器的实...

weixin_33720452的博客 861

【YOLOv12改进trick】高效多尺度卷积注意力EMCAD_Block模块引入YOLOv12,实现涨点和效率提升,含创新点Python代码

EMCAD引入YOLOv12模块,实现高效多尺度注意力模块引入到YOLOv12中的快速涨点及效率提升

向上生长的程序媛的博客 852

【示波器专题】示波器探头的原理深入——有源探头之差分有源探头

差分有源探头是一种特殊的有源探头,与普通单端有源探头的区别在于其前端的放大器是差分放大器。差分放大器的好处是可以直接测试高速差分信号,同时其共模抑制比高,对共模噪声的抑制能力比较好。...

一点硬件 2625

iOS Socket编程入门指北

Socket收发的消息我们如何区分消息类型呢?如何知道每一个包的大小呢?Protocol Buffer(Protobuf的全称)是一种支持多平台、多语言、可扩展的的数据序列化机制,相较于XML来说,Protobuf更小更快更简单,支持自定义的数据结构,用Protobuf编译器生成特定语言的源代码,如C++、Java、Python,目前Protobuf对主流的编程语言都提供了支持,非常方便的进行序列化和反序列化。Protobuf官网链接。...

pwf2006的博客 1255

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

TeamTalk项目iOS端代码总结

前段时间加入一个团队,对TeamTalk进行二次开发。我负责iOS端的功能扩展。这个项目目前已少有人维护,iOS端在我接手后进行了一些底层改动,现介绍给大家。 TeamTalk是一个很有名气的即时通讯解决方案, github地址:https://github.com/meili/TeamTalk 但由于维护一个即时通讯项目工作量太大,内部人员分崩离析,导致项目无人维护了。iOS端最大的过时体现就...

694

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Pitaya游戏服务器框架:快速构建分布式多人游戏的终极指南

Pitaya是一个简单、快速且轻量级的游戏服务器框架,专为分布式多人游戏和服务器端应用设计。作为一款功能强大的Go语言框架,Pitaya提供了完整的集群支持、客户端库以及现代化的游戏服务器架构,让开发者能够快速构建可扩展的多人游戏后端系统。 ## 🚀 为什么选择Pitaya游戏服务器框架? 如果你正在寻找一个能够快速搭建多人游戏服务器的解决方案,Pitaya绝对是你的理想选择!这款框架拥有众

gitblog_01009的博客 799

用Unity实现远程桌面控制?手把手教你打造手机触摸板(含完整C#代码)

本文详细介绍了如何使用Unity将手机打造成高精度无线触摸板,实现远程桌面控制。通过分析Unity的Input System与EventSystem,结合完整的C#代码示例,讲解了从触摸数据处理、网络通信到延迟优化的全流程实战方案,并探讨了手势识别与跨平台适配等高级功能。

ii567的博客 821

深入浅出(七)序列化库 FlatBuffers、Protobuf、MessagePack

FlatBuffers、 Protobuf和MessagePack都是高效的序列化(serialization)库,用来把结构化数据转成二进制,以便存储或网络传输。但它们在设计理念、性能特征、内存模型和使用场景上有明显区别。

qq_43572400的博客 1603
上一篇: OpenGL详解
下一篇: 备忘录
_Understand
博客等级 码龄10年 10粉丝 16原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值