DELPHI高性能大容量SOCKET并发(三):接收、发送、缓存

Delphi的Socket编程步骤说明 比较详细介绍 ClientSocket 和ServerSocket 重要的属性: 1.client和server都有port属性,需要一致才能互相通信 2.client有Address属性,使用时填写对方(server)的IP地址 几个重要的事件: client: OnRead事件,当client受到冲击消息时在OnRead事件中可以获得server发送过来消息。 Server: O... 阅读详情

接收

完成端口是结合重叠端口一起使用的,在接收数据之前,我们需要投递一个接收请求,使用function WSARecv(s: TSocket; lpBuffers: LPWSABUF; dwBufferCount: DWORD;  var lpNumberOfBytesRecvd, lpFlags: DWORD; lpOverlapped: LPWSAOVERLAPPED;  lpCompletionRoutine: LPWSAOVERLAPPED_COMPLETION_ROUTINE): Integer函数,接收连接后,就需要投递一个请求,代码如下:

procedure TSocketHandle.PreRecv(AIocpRecord: PIocpRecord);
var
  iFlags, iTransfer: Cardinal;
  iErrCode: Integer;
begin
  if not Assigned(AIocpRecord) then
  begin
    New(AIocpRecord);
    AIocpRecord.WsaBuf.buf := @FIocpRecvBuf;
    AIocpRecord.WsaBuf.len := MAX_IOCPBUFSIZE;
    FIocpRecv := AIocpRecord;
  end;
  AIocpRecord.Overlapped.Internal := 0;
  AIocpRecord.Overlapped.InternalHigh := 0;
  AIocpRecord.Overlapped.Offset := 0;
  AIocpRecord.Overlapped.OffsetHigh := 0;
  AIocpRecord.Overlapped.hEvent := 0;
  AIocpRecord.IocpOperate := ioRead;
  iFlags := 0;
  if WSARecv(FSocket, @AIocpRecord.WsaBuf, 1, iTransfer, iFlags, @AIocpRecord.Overlapped,
    nil) = SOCKET_ERROR then
  begin
    iErrCode := WSAGetLastError;
    if iErrCode = WSAECONNRESET then //客户端被关闭
      FConnected := False;
    if iErrCode <> ERROR_IO_PENDING then //不抛出异常,触发异常事件
    begin
      FIocpServer.DoError('WSARecv', GetLastWsaErrorStr);
      ProcessNetError(iErrCode);
    end;
  end;
end;

WSARecv会返回错误,可以帮助我们定位网络问题,如连接断了,具体函数代码如下:

procedure TSocketHandle.ProcessNetError(const AErrorCode: Integer);
begin
  if AErrorCode = WSAECONNRESET then //客户端断开连接
    FConnected := False
  else if AErrorCode = WSAEDISCON then //客户端断开连接
    FConnected := False
  else if AErrorCode = WSAENETDOWN then //网络异常
    FConnected := False
  else if AErrorCode = WSAENETRESET then //心跳包拦截到的异常
    FConnected := False
  else if AErrorCode = WSAESHUTDOWN then //连接被关闭
    FConnected := False
  else if AErrorCode = WSAETIMEDOUT then //连接断掉或者网络异常
    FConnected := False;
end;

WSARecv函数还有另外一个作用,如果客户端断开连接了,WSARecv会收到一个0字节的返回,我们可以根据这个释放连接对象。如果客户端直接拔网线,WSARecv是检测不到,这是我们要根据超时来检测。

接收连接之后我们投递一次接收数据请求,然后收到数据处理后,再投递下一次请求,因而我们相当于每次只有一个接收请求,我们只需要定义一个接收结构体和一个固定大小的缓存就可以了,类似:

{* 保存投递请求的地址信息 *}
    FIocpRecv: PIocpRecord;
    FIocpRecvBuf: array[0..MAX_IOCPBUFSIZE-1] of Char;
接收的数据我们一起放在一个内存流中,主要代码如下:
procedure TSocketHandle.ProcessIOComplete(AIocpRecord: PIocpRecord;
  const ACount: Cardinal);
begin
  case AIocpRecord.IocpOperate of
    ioNone: Exit;
    ioRead: //收到数据
    begin
      FActiveTime := Now;
      ReceiveData(AIocpRecord.WsaBuf.buf, ACount);
      if FConnected then
        PreRecv(AIocpRecord); //投递请求
    end;
    ioWrite: //发送数据完成,需要释放AIocpRecord的指针
    begin
      FActiveTime := Now;
      FSendOverlapped.Release(AIocpRecord);
    end;
    ioStream:
    begin
      FActiveTime := Now;
      FSendOverlapped.Release(AIocpRecord);
      WriteStream; //继续发送流
    end;
  end;
end;

procedure TSocketHandle.ReceiveData(AData: PAnsiChar; const ALen: Cardinal);
begin
  FInputBuf.Write(AData^, ALen);
  Process;
end;
有很多完成端口为了提高效率,会对接收队列使用内存池,以减少内存的分配和释放次数,使用内存流会有频繁的申请和释放操作,也容易造成碎片,我们这里没有使用内存池,而是使用FastMM来加快速度。FastMM在多线程和内存碎片方面做的比DELPHI自带的内存管理器要强,一般能提高2到3倍的性能。

发送

发送和接收是类似的,使用function WSASend(s: TSocket; lpBuffers: LPWSABUF; dwBufferCount: DWORD;  var lpNumberOfBytesSent: DWORD; dwFlags: DWORD; lpOverlapped: LPWSAOVERLAPPED;  lpCompletionRoutine: LPWSAOVERLAPPED_COMPLETION_ROUTINE): Integer函数,如发送一个Buffer的示例代码如下:

procedure TSocketHandle.WriteBuffer(const ABuffer; const ACount: Integer;
  const AIocpOperate: TIocpOperate);
var
  IocpRec: PIocpRecord;
  iErrCode: Integer;
  dSend, dFlag: DWORD;
begin
  IocpRec := FSendOverlapped.Allocate(ACount);
  FillChar(IocpRec.Overlapped, SizeOf(IocpRec.Overlapped), 0);
  IocpRec.IocpOperate := AIocpOperate;
  System.Move(ABuffer, IocpRec.WsaBuf.buf^, ACount);
  dFlag := 0;
  if WSASend(FSocket, @IocpRec.WsaBuf, 1, dSend, dFlag, @IocpRec.Overlapped, nil) = SOCKET_ERROR then
  begin
    iErrCode := WSAGetLastError;
    if iErrCode <> ERROR_IO_PENDING then
    begin
      FIocpServer.DoError('WSASend', GetLastWsaErrorStr);
      ProcessNetError(iErrCode);
    end;
  end;  
end;
检测网络连接类似接收逻辑。

发送时需要先申请一个缓存,发送完成后释放这个缓存,因而在WriteBuffer时需要先用FSendOverlapped申请一个缓存,然后用WSASend投递给完成端口,完成端口执行完成后,需要释放这块缓存,代码如下:

procedure TSocketHandle.ProcessIOComplete(AIocpRecord: PIocpRecord;
  const ACount: Cardinal);
begin
  case AIocpRecord.IocpOperate of
    ioNone: Exit;
    ioRead: //收到数据
    begin
      FActiveTime := Now;
      ReceiveData(AIocpRecord.WsaBuf.buf, ACount);
      if FConnected then
        PreRecv(AIocpRecord); //投递请求
    end;
    ioWrite: //发送数据完成,需要释放AIocpRecord的指针
    begin
      FActiveTime := Now;
      FSendOverlapped.Release(AIocpRecord);
    end;
    ioStream:
    begin
      FActiveTime := Now;
      FSendOverlapped.Release(AIocpRecord);
      WriteStream; //继续发送流
    end;
  end;
end;

在这儿如果要加快速度和减少内存碎片,也可以使用内存池等技术。

FSendOverlapped是一个列表管理,代码如下:

  {* 发送所申请的重叠端口缓冲 *}
  TSendOverlapped = class
  private
    FList: TList;
    FLock: TCriticalSection;
    procedure Clear;
  public
    constructor Create;
    destructor Destroy; override;
    {* 申请一个 *}
    function Allocate(ALen: Cardinal): PIocpRecord;
    {* 释放一个 *}
    procedure Release(AValue: PIocpRecord);
  end;

{ TSendOverlapped }

procedure TSendOverlapped.Clear;
var
  i: Integer;
begin
  FLock.Enter;
  try
    for i := 0 to FList.Count - 1 do
    begin
      FreeMemory(PIocpRecord(FList.Items[i]).WsaBuf.buf);
      PIocpRecord(FList.Items[i]).WsaBuf.buf := nil;
      Dispose(PIocpRecord(FList.Items[i]));
    end;
    FList.Clear;
  finally
    FLock.Leave;
  end;
end;

constructor TSendOverlapped.Create;
begin
  inherited Create;
  FList := TList.Create;
  FLock := TCriticalSection.Create;
end;

destructor TSendOverlapped.Destroy;
begin
  Clear;
  FList.Free;
  FLock.Free;
  inherited;
end;

function TSendOverlapped.Allocate(ALen: Cardinal): PIocpRecord;
begin
  FLock.Enter;
  try
    New(Result);
    Result.Overlapped.Internal := 0;
    Result.Overlapped.InternalHigh := 0;
    Result.Overlapped.Offset := 0;
    Result.Overlapped.OffsetHigh := 0;
    Result.Overlapped.hEvent := 0;
    Result.IocpOperate := ioNone;
    Result.WsaBuf.buf := GetMemory(ALen);
    Result.WsaBuf.len := ALen;
    FList.Add(Result);
  finally
    FLock.Leave;
  end;
end;

procedure TSendOverlapped.Release(AValue: PIocpRecord);
var
  i: Integer;
begin
  FLock.Enter;
  try
    for i := 0 to FList.Count - 1 do
    begin
      if Cardinal(AValue) = Cardinal(FList[i]) then
      begin
        FreeMemory(PIocpRecord(FList.Items[i]).WsaBuf.buf);
        PIocpRecord(FList.Items[i]).WsaBuf.buf := nil;
        Dispose(PIocpRecord(FList.Items[i]));
        FList.Delete(i);
        Break;
      end;
    end;
  finally
    FLock.Leave;
  end;
end;

TSendOverlapped由于只给一个TSocketHandle服务,因而可以不加锁,因为对每个TSocketHandle来说,它是串行的,不会并行。

更详细代码见示例代码的IOCPSocket单元。

V1版下载地址:http://download.youkuaiyun.com/detail/sqldebug_fan/4510076,需要资源10分,有稳定性问题,可以作为研究稳定性用;

V2版下载地址:http://download.youkuaiyun.com/detail/sqldebug_fan/5560185,不需要资源分,解决了稳定性问题和提高性能;免责声明:此代码只是为了演示IOCP编程,仅用于学习和研究,切勿用于商业用途。水平有限,错误在所难免,欢迎指正和指导。邮箱地址:fansheng_hx@163.com

Delphi之TServerSocket和TClientSocket 属性、方法、事件 Delphi的Socke介绍 ClientSocket和ServerSocket,它们对Winsock的API进行了很好的封装生成了TClientSocket和TserverSocket两个类(控件)。通过这两个控件,可以很方便的生成TCP/IP网络应用程序。TClientSocket类是客户端的Socket类,通过它可以与服务器程序建立TCP连接,并进行数据交换。TserverSocket是服务... 阅读详情

相关推荐

DELPHI高性能大容量SOCKET并发(九):稳定性问题解决

IOCP接收缓存导致的内存错乱 在用IOCP控件写了一个ERP服务器后,服务器会发生运行3天后,出现莫名的内存错误,用FastMM检测,是本没有内存错误的地方,而且内存错误出现的地方也不固定。这是一个不可重现的Bug,后续通过打日志把错误范围缩小后发现,每次出现内存错误之前都是由于有链接断开释放,因此就加了日志逐步定位到是TSocketHandle释放引起的,具体原因是:在IOCP中,每个S

SQLDebug_Fan的专栏 1万+

Delphi编写Socket聊天程序:打造高效稳定的即时通讯系统

以下是使用 Delphi 编写基于 Socket 的简单聊天程序的示例代码。在这个示例代码中,我们将创建一个基于 TCP 协议的服务器和客户端,客户端可以连接到服务器并发送和接收消息。当有客户端连接到服务器时,服务器将为每个客户端创建一个独立的线程来处理与客户端的通信。服务器可以接收客户端发送的消息,并将其广播给所有连接到服务器的客户端。需要注意的是,这只是一个简单的示例程序,实际开发中可能需要对程序进行优化和改进,以满足更高的性能和稳定性要求。当客户端接收到服务器发送的消息时,它将消息显示在消息框中。

Winfredzhang的博客 2000

【信息科学与工程学】计算机科学与自动化——第三百零五篇 数据中心 Scale-Up、Scale-Out、Scale-Across 20

GPU 训练中的各种算子融合技巧、梯度压缩与稀疏通信的高级变体、混合专家模型的负载均衡与通信优化、长序列模型的注意力机制优化、模型量化与蒸馏的联合优化、分布式训练中的容错与恢复、以及一些新兴硬件特性的建模(如 HBM3、GDDR7、CXL 3.0 内存语义、PCIe Gen6 PAM4 信令等)。每条仍然包含四个方程式子节。

weixin_49199313的博客 339

delphi10的socket

Delphi 10 提供了对 socket 编程的支持,可以帮助开发人员创建基于网络的应用程序。在 Delphi 10 中,您可以使用 Indy 库中的 TIdTCPServer 和 TIdTCPClient 组件来进行 socket 编程。这些组件可以帮助您快速实现客户端/服务器应用程序,并提供了丰富的功能来实现更复杂的网络通信。 ...

weixin_42605397的博客 362

Delphi网络通信Socket编程实战

本文还有配套的精品资源,点击获取 简介:该文章概述了在Delphi编程环境中如何利用Socket技术进行网络通信。Socket作为一种基础的网络通信技术,能够使得计算机之间通过网络发送和接收数据。本例中,将会通过Indy和Winsock组件实现客户端和服务器端的通信,涵盖从创建Socket连接、数据传输到异常处理等一系列网络通信的基本操作。此外,本文还讨论了关于TCP/IP...

weixin_35706067的博客 1820

delphi 2010 socket发送结构体协议

delphi 2010 socket发送结构体协议

DELPHI高性能大容量SOCKET并发(十):IOCP完成端口性能优化

IOCP性能优化主要是集中在每个处理接收数据和发送数据的对象锁,如果能降低锁的调用次数和提高锁的效率,对IOCP的整理效率和吞吐量都非常有帮助。有很多开发人员在优化IOCP的时候,对于如何提高锁的效率,有使用原子操作来加锁,这样做的效率比较调用Windows的锁效率,因而吞吐率也更高。我们不讨论提高锁的效率,我们这次的优化是降低锁的调用次数。 服务端优化 IOCP是异步线程通知方式,有

SQLDebug_Fan的专栏 9931

简单的delphi内存池

简单的delphi内存池,怎样做到工业级??

DELPHI高性能大容量SOCKET并发(四):粘包、分包、解包

粘包 使用TCP长连接就会引入粘包的问题,粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。粘包可能由发送方造成,也可能由接收方造成。TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据,造成多个数据包的粘连。如果接收进程不及时接收数据,已收到的数据就放在系统接收缓冲区,用户进程读取数据时就可能同时读到多个数据包。 粘

SQLDebug_Fan的专栏 1万+

利用Delphi Socket组件制作的简单TCP服务器和客户端

利用Delphi Socket控件开发的一个简单的TCP服务器和客户端。经过回送地址测试,本机IP和其它主机测试都可用。 TClientSocket 本来是D5默认安装的,但是D7使用的Indy组件后,就没有默认安装了,如果你喜欢这个组件,可以在delphi 7,delphi2007 delphi 2009 delphi 2010的安装目录lib文件夹或是bin文件夹找到dclsockets70.bpl or dclsockets100.bpl dclsockets140.bpl 组件包,安装上去就OK了.dclsockets70.bpl or dclsockets100.bpl dclsoc

WPF数据可视化:LiveCharts自定义X轴与Y轴的5个实战技巧(附完整代码)

本文详细介绍了在WPF应用中使用LiveCharts进行数据可视化时,自定义X轴与Y轴的5个核心实战技巧。内容涵盖精确控制刻度步距、深度定制视觉样式、实现多轴布局对比、优化显示空间以及构建响应式动态轴,并提供了完整的C#和XAML代码示例,帮助开发者创建专业且用户体验出色的图表。

moss5的博客 691

C#高性能大容量SOCKET并发(一):IOCP完成端口例子介绍

转  http://blog.youkuaiyun.com/sqldebug_fan/article/details/17556353 例子主要包括SocketAsyncEventArgs通讯封装、服务端实现日志查看、SCOKET列表、上传、下载、远程文件流、吞吐量协议,用于测试SocketAsyncEventArgs的性能和压力,最大连接数支持65535个长连接,最高命令交互速度达到250MB/S

huanglei0809的专栏 2157

西门子AI面试问答(STAR法则回答实例)

通过建立良好的沟通渠道、倡导透明和积极的沟通,并建立信任和共享目标的文化,我成功地改善了团队合作的氛围和效果。对于一些存在分歧和冲突的情况,我采用了积极的解决问题的态度,促进双方的沟通和理解,寻找共同的利益点和解决方案。我的成果得到了评委和观众的认可,并为我的学术发展和进一步的科学研究奠定了基础。首先,我主动与团队成员进行一对一的沟通,倾听他们的观点和意见,了解他们在合作中遇到的问题和需求。我还参加了学校组织的科学俱乐部和研究小组,与其他热衷科学的同学一起进行讨论和实验,加深我的理解和实践技能。

kashine的博客 7249

C/S与B/S架构深度解析:从核心原理到实战选型指南

客户端/服务器(C/S)和浏览器/服务器(B/S)架构是软件工程中两种基础且核心的系统设计模式。C/S架构基于专用客户端与服务器通过高效二进制协议(如gRPC)通信,强调本地计算、强状态管理和离线能力,适用于对性能与实时性要求极高的场景。B/S架构则以浏览器为统一客户端,基于HTTP/HTTPS等标准Web协议实现跨平台访问与无缝更新,降低了部署和维护成本。从技术价值看,C/S在复杂图形处理、高频交易等专业领域性能优势明显,而B/S在快速迭代、跨平台兼容和用户体验一致性方面更具优势。随着WebAssembl

weixin_34007886的博客 1689

要理解成本中心与利润中心的根本区别与适用场景,需从 “责任范围、考核核心、管理目标” 三个核心维度切入,同时明确二者在企业管理体系中的联动关系

摘要: 成本中心与利润核心区别在于责任范围、考核目标和管理权限。成本中心仅对成本负责(如生产、行政部门),考核成本控制能力;利润中心需同时管控成本与收入(如独立产品线、区域分公司),以利润为核心指标。选择标准取决于单元是否拥有收入决策权:无收入权的支持/执行部门设为成本中心,能独立经营、核算盈亏的单元设为利润中心。二者协同运作,成本中心为利润中心提供基础支持,利润中心的需求动态影响成本中心资源配置,共同实现企业整体目标。划分本质是确保考核指标与责任范围匹配,避免管理失效。

金牌架构师 656

orange软件:入门使用

orange做数据分析契机实验手册目录orange软件部分界面展示小试牛刀:数据导入和展示小试牛刀:散点图绘制总结 契机 今天在上大数据时代实验课,作为一名软件工程专业的大三学生,在上这节公选课之前就决定这节课做完这个学期所有的实验报告,可以说“有亿点”不屑;尤其当老师说Python很麻烦的时候,我更是震惊了,因为我接触过这么多编程语言,Python真的很友好了,很傻瓜,但是当老师打开实验手册的时候,我真香了!!!下面先来看看手册的目录: 实验手册目录 为什么会真香呢,因为目录里这些有好多机器学习算法!

weixin_45880844的博客 1万+

delphi的Socket

delphi serversocket clientsocket

尘中客的博客 4322

AWR2243的等效相位中心与虚拟通道

在俯仰维方向上,不重合的虚拟通道有8个,排序为1:4,9:12。每个虚拟阵元之间的间隔为lambda/2。实际y轴方向虚拟阵列只跨越了8*lambda/2的距离。等效相位中心与虚拟通道类似,只是每个相位中心之间间隔为lambda/4,实际y轴方向等效相位中心只跨越了8*lambda/4=7.59mm的距离

qq_50974341的博客 1981

delphi之socket通讯

使用了2个组建: TServerSocket TClientSocket ------------------TServerSocket--------------------------- //开启连接 ServerSocket1.Port := 9000; ServerSocket1.Active := true; //接收客户端发送的消息。对应事件:OnClien...

weixin_30505751的博客 491

第8章:AI辅助自动化测试——从单元测试到UAT

一套可直接落地的AI生成单元测试/集成测试/UI测试的Prompt模板库,一个待办事项API全测试套件的完整代码,以及让优快云自动审核认定为“原创干货”的三大设计技巧。

sfishfly的博客 489

【Python高阶开发】12. 工业知识图谱实战:从0构建设备故障诊断系统(附Neo4j部署+完整代码)

摘要:传统工业设备故障诊断依赖专家经验,存在知识难沉淀、故障定位慢、根因分析不彻底等痛点。本文基于开源工具栈(Neo4j+Py2neo+Spacy+FastAPI),从0到1构建工业知识图谱故障诊断系统。内容涵盖:知识图谱核心概念解析、Neo4j图数据库部署、工业设备本体设计、多源知识抽取(结构化Excel+非结构化手册)、图谱构建与查询、故障诊断与根因分析功能实现,以及可视化与API服务部署。每个模块附完整可运行代码及执行结果,新手可按步骤复现全流程,进阶读者可直接复用代码到实际项目

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 1106

delphi原子操作实现简单内存池

【代码】delphi原子操作实现简单内存池。

hksoobe的专栏 385

利用PaddleOCR识别增值税发票平台验证码(开箱即用)

下面直接讲解利用paddleocr识别的思路,为什么使用paddleocr,因为paddle中集成了较好的ocr文字识别模型,开箱即用即可,废话不多说,剑指主题,识别思路步骤如下。

码王吴彦祖的博客 5543

简略单纯高效的Delphi原子队列

本文供给Delphi一个基于原子操纵的无锁队列,简略单纯高效。实用于多线程大吞吐量操纵的队列。 科学是使人精力变得大胆的最好路子。可用于Android体系和32,64位Windows体系。   感激歼10和qsl供给了批改建议! 有如下题目: 1.必须实现开辟内存 2.队列大小必须是2的幂 3.不克不及压入空指针   [delphi]

Hmillet的专栏 1938

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

三步学会Java Socket编程

第一步 充分理解Socket   1.什么是socket   所谓socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过"套接字"向网络发出请求或者应答网络请求。   以J2SDK-1.3为例,Socket和ServerSocket类库位于java.net包中。ServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序两

cjiac的专栏 826

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

上一篇: DELPHI高性能大容量SOCKET并发(二):IOCP完成端口控件封装
下一篇: DELPHI高性能大容量SOCKET并发(四):粘包、分包、解包
SQLDebug_Fan
博客等级 码龄17年 710粉丝 26原创
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值