Simulation of a P2P Application Using Oversim

本文介绍了P2P网络仿真的工具Oversim及其特点,并对比了其他几种P2P仿真工具的不足之处。文章详细阐述了Oversim的层次结构与开发流程,并对P2P网络进行了分类讨论,最后提出了结构化P2P网络的API设计方法。

1、Munoz-Gea 发表在2007年,本文的目的在于向P2P 领域的工作者介绍这个工具。

2、其它P2P 仿真工具的缺点:

1、P2PSim:不易扩展,统计存在局限;

2、PeerSim:底层的TCP/IP网络不是模块化的;

3、PlanetSim:在仿真底层TCP/IP网络时候存在局限性。

3、Oversim的层次结构:

image

1、构建在OMNET++上,开发的基本步骤是:1)用NED 语言描述系统结构;2)用C++实现简单模块;3)配置和仿真。

2、Application 被分为两层:1)一些简单的引用可以在Tier1实现;2)一些复杂的需要Tier层提供服务的应用则在Tier2实现。

3、Overlay层的程序需继承于BaseOverlay类,而应用层的则需继承于BaseApp类。

4、P2P的分类:

1、Centralized directory model: 存在单点故障;

2、Unstructured Model:采用flooding +TTL 进行查找,traffic非常大,不能够确定所需要数据是否存在;

3、Structured Model: 不支持模糊查询;

5、结构化P2P 的API设计:

1、void route(key k,msg m) 实现在Overlay层;

2、void forward(key k,msg m,nodehandle nexthopnode) 实现在Applicaiton层;

3、void delivery(key k,msg m)实现在Applicaiton层。

6、基于超级节点的P2P网络API:

Common Operations:

1.Super-peer joint 就是普通节点加入到Super-peer节点;

2.Publication 就是发布内容;

3.Lookup 就是查询内容;

4.Download 就是下载所请求的内容。

相互交互的操作:

1、Super-peer 通信:有 SuperPeerRequestMessage 和 SuperPeerResponseMessage,以应对 Super-peer joint、publicaiton of contents、lookup of contents。

2、Peer 之间的通信:有 PeerRequestMessage 和 PeerResponseMessage,用于下载所请求的内容。

3、为了使节点能够接受消息,需实现 bindToPort() 函数,为了使节点能够发送消息,需实现sendToUDP()函数,处理消息函数为handleMessage()。

7、P2P的建模:

1、有必要对queries, the node connection inter-arrival time, session length, the file-time, files arrival rate, the number of peers receiving them进行特征提取和建模.

2、the time until the first query 是通过 weibull (for body)  分布和  log-normal (for tail) 分布进行建模;

3、the query inter-arrival time (for body)  是通过log-normal 分布和 Pareto (for tail ) 分布来建模的;

4、the query popularity 服从zipf-like 分布;

5、inter-arrival time 服从 weibull 分布;

6、 the session length 服从weibull 分布。(注:同一种分布,其参数各不相同)

/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation; * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "ns3/applications-module.h" #include "ns3/internet-module.h" #include "ns3/mobility-module.h" #include "ns3/network-module.h" #include "ns3/point-to-point-module.h" #include "ns3/yans-wifi-helper.h" #include "ns3/command-line.h" #include "ns3/config.h" #include "ns3/internet-stack-helper.h" #include "ns3/ipv4-address-helper.h" #include "ns3/ipv4-global-routing-helper.h" #include "ns3/mobility-helper.h" #include "ns3/multi-model-spectrum-channel.h" #include "ns3/non-communicating-net-device.h" #include "ns3/on-off-helper.h" #include "ns3/packet-sink-helper.h" #include "ns3/packet-sink.h" #include "ns3/propagation-loss-model.h" #include "ns3/spectrum-wifi-helper.h" #include "ns3/ssid.h" #include "ns3/string.h" #include "ns3/udp-client-server-helper.h" #include "ns3/waveform-generator-helper.h" #include "ns3/waveform-generator.h" #include "ns3/wifi-net-device.h" #include "ns3/yans-wifi-channel.h" #include "ns3/yans-wifi-helper.h" #include "ns3/core-module.h" #include "ns3/spectrum-module.h" #include <ns3/spectrum-value.h> #include "ns3/log.h" #include "ns3/network-module.h" #include "ns3/applications-module.h" #include "ns3/global-router-interface.h" #include <iomanip> #include <cstdlib> #include <ctime> #include <iostream> #include <fstream> #include <sstream> #include <string> #include <vector> #include <map> #include <cmath> #include <cstdlib> #include <algorithm> #include <iterator> #include "ns3/olsr-helper.h" using namespace ns3; NS_LOG_COMPONENT_DEFINE("multinodetest"); int waveformPower = 1e-3; std::vector<uint16_t> monitoredChannels = {36, 40, 44, 48, 52, 56, 60, 64}; std::map<uint16_t, double> channelFreqMap = { {36, 5180e9}, {40, 5200e9}, {44, 5220e9}, {48, 5240e9}, {52, 5260e9}, {56, 5280e9}, {60, 5300e9}, {64, 5320e9} }; std::map<uint16_t, uint32_t> channelToBandIndex; Ptr<SpectrumModel> CreateMultiChannelModel() { std::vector<BandInfo> bands; uint32_t index = 0; for (auto& channel : monitoredChannels) { NS_ABORT_MSG_IF(channelFreqMap.find(channel) == channelFreqMap.end(), "Undefined frequency for channel " << channel); BandInfo band; double centerFreq = channelFreqMap[channel]; band.fc = centerFreq; band.fl = centerFreq - 10e6; band.fh = centerFreq + 10e6; bands.push_back(band); channelToBandIndex[channel] = index++; } return Create<SpectrumModel>(bands); } void ProcessSpectrumData(Ptr<const SpectrumValue> spectrumData) { double time = Simulator::Now().GetSeconds(); std::cout << "\nSpectrum 1 at " << time << "s | "; // 遍历所有频段 for (auto it = spectrumData->ConstValuesBegin(); it != spectrumData->ConstValuesEnd(); ++it) { double powerWatts = (*it) * 20e6; // 计算总功率(W) double powerDbm = 10 * log10(powerWatts * 1000); // 转换为 dBm // 频段功率 std::cout << "band" << std::distance(spectrumData->ConstValuesBegin(), it) << ":" << std::fixed << std::setprecision(1) << powerDbm << "dBm | "; } std::cout << std::endl; } void ProcessSpectrumData_2(Ptr<const SpectrumValue> spectrumData) { double time = Simulator::Now().GetSeconds(); std::cout << "\nSpectrum 2 at " << time << "s | "; for (auto it = spectrumData->ConstValuesBegin(); it != spectrumData->ConstValuesEnd(); ++it) { double powerWatts = (*it) * 20e6; // 计算总功率(W) double powerDbm = 10 * log10(powerWatts * 1000); std::cout << "band" << std::distance(spectrumData->ConstValuesBegin(), it) << ":" << std::fixed << std::setprecision(1) << powerDbm << "dBm | "; } std::cout << std::endl; } void Signal(Ptr<SpectrumValue> jammingPsd, const std::string& jammingType) { static uint32_t currentBand = 0; static const std::vector<uint16_t> LChannel = {4,3,6,7,0,1,2,5}; int interval = 1; uint32_t targetBand = 0; for (uint32_t i = 0; i < monitoredChannels.size(); ++i) { (*jammingPsd)[i] = 0; } if (jammingType == "SWEEP") { targetBand = (currentBand + 1) % monitoredChannels.size(); currentBand = targetBand; } else if (jammingType == "SINGLE") { targetBand = 0; // } else if (jammingType == "NLSWEEP") { currentBand = (currentBand + 1) % LChannel.size(); targetBand = LChannel[currentBand]; } else if (jammingType == "RANDOM") { targetBand = rand() % monitoredChannels.size(); } else { NS_FATAL_ERROR("Unknown jamming type: " << jammingType); } (*jammingPsd)[targetBand] = 1e-6; NS_LOG_LOGIC("Jamming type: " << jammingType << " | Band: " << targetBand << " | Time: " << Simulator::Now().GetSeconds() << "s"); Simulator::Schedule(Seconds(interval), &Signal, jammingPsd, jammingType); } WifiPhy::ChannelTuple getChannelSetting(WifiStandard wifiStandard, WifiPhyBand phyband, int channelWidth, uint32_t channelId) { uint16_t frequency = 0; switch (wifiStandard) { case WIFI_STANDARD_80211ac: case WIFI_STANDARD_80211ax: default: // 不同wifi标准、不同频段和不同带宽都会导致base频率不同,11ac+5GHz: 5180/5190/5210/5250 for 20/40/80/160Mhz u_int16_t basefreq = 0; if(WIFI_PHY_BAND_2_4GHZ == phyband) { basefreq = 2402; } else if(WIFI_PHY_BAND_5GHZ == phyband) { basefreq = 5180; } else if(WIFI_PHY_BAND_6GHZ == phyband) { basefreq = 5180;//todo } else { std::cout<< "******phyband is not found! " << phyband << std::endl; //return ; } frequency = basefreq + channelWidth * channelId; if (channelWidth != 20 && channelWidth != 40 && channelWidth != 80 && channelWidth != 160) { std::cout<< "******Bandwidth is not compatible with standard" << std::endl; //return ; } break; } auto channelNum = std::get<0>(*WifiPhyOperatingChannel::FindFirst(0, frequency, channelWidth, wifiStandard, phyband)); uint32_t chnum = channelNum; std::cout<< "******channelId: " << channelId << " frequency: " << frequency << " channelnum: " << chnum << " channelwidth: " << channelWidth << " phyband: " << phyband << std::endl; return WifiPhy::ChannelTuple{chnum, channelWidth, (int)(phyband), (int)(0)}; } void FreChange(NodeContainer nodes,NodeContainer apnode,const std::string& ChangeType,const u_int16_t& plot){ uint16_t Channel = 0; if (ChangeType == "RANDOM") { Channel = std::rand() % monitoredChannels.size(); std::cout<<Channel<<std::endl; } else if (ChangeType == "FIXED") { Channel = 0; std::cout<<Channel<<std::endl; } else if (ChangeType == "Q") { std::cout << "Unknown type: " << std::endl; } else { std::cout << "Unknown type: " << std::endl; } for (auto end = nodes.End(), iter = nodes.Begin(); iter != end; ++iter) { Ptr<WifiNetDevice> netDevice = DynamicCast<WifiNetDevice>((*iter)->GetDevice(0)); Ptr<WifiPhy> wifiPhy = netDevice->GetPhy(); WifiPhy::ChannelTuple channelSetting = getChannelSetting(WIFI_STANDARD_80211ac, WIFI_PHY_BAND_5GHZ, 20, Channel); wifiPhy->SetOperatingChannel(channelSetting); } auto iter = apnode.Begin(); Ptr<WifiPhy> apPhy = DynamicCast<WifiNetDevice>((*iter)->GetDevice(1))->GetPhy(); WifiPhy::ChannelTuple channelSetting = getChannelSetting( WIFI_STANDARD_80211ac, WIFI_PHY_BAND_5GHZ, 20, Channel ); apPhy->SetOperatingChannel(channelSetting); Ptr<Node> Rxnode= nodes.Get(0); Ptr<WifiNetDevice> netDevice = DynamicCast<WifiNetDevice>(Rxnode->GetDevice(0)); Ptr<WifiPhy> wifiPhy_ptr = netDevice->GetPhy(); std::cout<< "******wifi[0]:" << Rxnode->GetId() << "频率: " << wifiPhy_ptr->GetFrequency() << "Mhz" << std::endl; Ptr<WifiNetDevice> apSampleDevice = DynamicCast<WifiNetDevice>(apnode.Get(0)->GetDevice(1)); if (apSampleDevice) { std::cout << "AP[0] Frequency: " << apSampleDevice->GetPhy()->GetFrequency() << " MHz" << std::endl; } Simulator::Schedule(Seconds(plot), &FreChange,nodes,apnode,ChangeType,plot); } int main() { u_int16_t wifinode_num1 = 32; u_int16_t wifinode_num2 = 32; u_int16_t jammernode_num = 1; u_int16_t plot = 3; LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO); LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO); LogComponentEnable("OnOffApplication", LOG_LEVEL_INFO); LogComponentEnable("PacketSink", LOG_LEVEL_INFO); //**********----network Topology----************// /*******----p2pnode----*******/ NodeContainer P2P_1; P2P_1.Create(2); PointToPointHelper P2Phelper; P2Phelper.SetDeviceAttribute("DataRate",StringValue("5Mbps")); P2Phelper.SetChannelAttribute("Delay",StringValue("2ms")); NetDeviceContainer P2P_1_1; P2P_1_1 = P2Phelper.Install(P2P_1); /*******----wifinode----*******/ NodeContainer wifi_1; wifi_1.Create(wifinode_num1); NodeContainer wifi_1_ap; wifi_1_ap.Add(P2P_1.Get(0)); NodeContainer wifi_2; wifi_2.Create(wifinode_num2); NodeContainer wifi_2_ap; wifi_2_ap.Add(P2P_1.Get(1)); Ptr<SpectrumModel>multiChannelModel = CreateMultiChannelModel(); Ptr<MultiModelSpectrumChannel> spectrumChannel = CreateObject<MultiModelSpectrumChannel>(); Ptr<FriisPropagationLossModel> lossModel = CreateObject<FriisPropagationLossModel> (); Ptr<NakagamiPropagationLossModel> fadingModel = CreateObject<NakagamiPropagationLossModel> (); lossModel->SetNext (fadingModel); spectrumChannel->AddPropagationLossModel (lossModel); Ptr<ConstantSpeedPropagationDelayModel> delayModel = CreateObject<ConstantSpeedPropagationDelayModel> (); spectrumChannel->SetPropagationDelayModel (delayModel); SpectrumWifiPhyHelper wifiPhy; wifiPhy.SetChannel(spectrumChannel); wifiPhy.Set("ChannelSettings", StringValue("{36, 20, BAND_5GHZ, 0}")); wifiPhy.Set("TxPowerStart", DoubleValue(20)); // dBm wifiPhy.Set("TxPowerEnd", DoubleValue(20)); // 20dBm SpectrumWifiPhyHelper wifiPhy1; wifiPhy1.SetChannel(spectrumChannel); wifiPhy1.Set("ChannelSettings", StringValue("{36, 20, BAND_5GHZ, 0}")); wifiPhy1.Set("TxPowerStart", DoubleValue(20.0)); // dBm wifiPhy1.Set("TxPowerEnd", DoubleValue(20.0)); // dBm WifiMacHelper mac; Ssid zs = Ssid("zs"); Ssid sz = Ssid("sz"); WifiHelper wifi; NetDeviceContainer stawifi; mac.SetType("ns3::StaWifiMac", "Ssid", SsidValue(zs), "ActiveProbing", BooleanValue(false)); stawifi = wifi.Install(wifiPhy,mac,wifi_1); NetDeviceContainer apwifi; mac.SetType("ns3::ApWifiMac","Ssid",SsidValue(zs)); apwifi = wifi.Install(wifiPhy,mac,wifi_1_ap); NetDeviceContainer stawifi_2; mac.SetType("ns3::StaWifiMac", "Ssid", SsidValue(sz), "ActiveProbing", BooleanValue(false)); stawifi_2 = wifi.Install(wifiPhy1,mac,wifi_2); NetDeviceContainer apwifi_2; mac.SetType("ns3::ApWifiMac","Ssid",SsidValue(sz)); apwifi_2 = wifi.Install(wifiPhy1,mac,wifi_2_ap); MobilityHelper mobility; mobility.SetPositionAllocator("ns3::GridPositionAllocator", "MinX", DoubleValue(-7.0), "MinY", DoubleValue(-7.0), "DeltaX", DoubleValue(2.0), "DeltaY", DoubleValue(2.0), "GridWidth", UintegerValue(4), "LayoutType", StringValue("RowFirst")); mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel"); mobility.Install(wifi_1); Ptr<ListPositionAllocator> apPositionAlloc = CreateObject<ListPositionAllocator>(); apPositionAlloc->Add(Vector(-4, 0.0, 0.0)); mobility.SetPositionAllocator(apPositionAlloc); mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel"); mobility.Install(wifi_1_ap); mobility.SetPositionAllocator("ns3::GridPositionAllocator", "MinX", DoubleValue(1.0), "MinY", DoubleValue(-7.0), "DeltaX", DoubleValue(2.0), "DeltaY", DoubleValue(2.0), "GridWidth", UintegerValue(4), "LayoutType", StringValue("RowFirst")); mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel"); mobility.Install(wifi_2); Ptr<ListPositionAllocator> ap_2_PositionAlloc = CreateObject<ListPositionAllocator>(); ap_2_PositionAlloc->Add(Vector(4, 0.0, 0.0)); mobility.SetPositionAllocator(ap_2_PositionAlloc); mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel"); mobility.Install(wifi_2_ap); /*******----jamnode----*******/ NodeContainer jammernodes; jammernodes.Create(jammernode_num); Ptr<ListPositionAllocator> jamnode_PositionAlloc = CreateObject<ListPositionAllocator>(); jamnode_PositionAlloc->Add(Vector(0, 0.0, -3)); mobility.SetPositionAllocator(jamnode_PositionAlloc); mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel"); mobility.Install(jammernodes); Ptr<SpectrumValue> jammingPsd = Create<SpectrumValue>(multiChannelModel); WaveformGeneratorHelper jammerHelper; jammerHelper.SetChannel(spectrumChannel); jammerHelper.SetTxPowerSpectralDensity(jammingPsd);//w/hz jammerHelper.SetPhyAttribute("Period", TimeValue(Seconds(1))); jammerHelper.SetPhyAttribute("DutyCycle", DoubleValue(1)); NetDeviceContainer waveformGeneratorDevices =jammerHelper.Install(jammernodes); Ptr<WaveformGenerator> waveformGenerator = waveformGeneratorDevices.Get(0) ->GetObject<NonCommunicatingNetDevice>() ->GetPhy() ->GetObject<WaveformGenerator>(); waveformGenerator->Start(); /*******----spectrumanalyzerhelper----*******/ SpectrumAnalyzerHelper spectrumAnalyzerHelper; spectrumAnalyzerHelper.SetChannel(spectrumChannel); spectrumAnalyzerHelper.SetRxSpectrumModel(multiChannelModel); spectrumAnalyzerHelper.SetPhyAttribute("Resolution", TimeValue(MilliSeconds(10))); spectrumAnalyzerHelper.SetPhyAttribute("NoisePowerSpectralDensity", DoubleValue(1e-15)); NetDeviceContainer spectrumAnalyzerDevices = spectrumAnalyzerHelper.Install(wifi_1_ap); Ptr<SpectrumAnalyzer> analyzer = DynamicCast<SpectrumAnalyzer>( spectrumAnalyzerDevices.Get(0)->GetObject<NonCommunicatingNetDevice>()->GetPhy() ); Simulator::Schedule(Seconds(9.0), &SpectrumAnalyzer::Start, analyzer); analyzer->TraceConnectWithoutContext("AveragePowerSpectralDensityReport", MakeCallback(&ProcessSpectrumData)); NetDeviceContainer spectrumAnalyzerDevices_2 = spectrumAnalyzerHelper.Install(wifi_2_ap); Ptr<SpectrumAnalyzer> analyzer_2 = DynamicCast<SpectrumAnalyzer>( spectrumAnalyzerDevices_2.Get(0)->GetObject<NonCommunicatingNetDevice>()->GetPhy() ); //Simulator::Schedule(Seconds(9.0), &SpectrumAnalyzer::Start, analyzer_2); //analyzer_2->TraceConnectWithoutContext("AveragePowerSpectralDensityReport", MakeCallback(&ProcessSpectrumData_2)); //**********----Internet layer----************// InternetStackHelper stack; stack.SetRoutingHelper(OlsrHelper()); stack.Install(wifi_1); stack.Install(wifi_2); stack.Install(P2P_1); Ipv4AddressHelper address; address.SetBase("192.168.1.0", "255.255.255.252"); address.Assign(P2P_1_1); address.SetBase("10.1.6.0","255.255.255.0"); Ipv4InterfaceContainer wifi_interface_1; wifi_interface_1 = address.Assign(stawifi); address.Assign(apwifi); address.SetBase("10.1.7.0","255.255.255.0"); Ipv4InterfaceContainer wifi_interface; wifi_interface = address.Assign(stawifi_2); address.Assign(apwifi_2); //**********----APP layer----************// //********** cluster A to B communication UdpEchoServerHelper echoServer(9); ApplicationContainer serverApps = echoServer.Install(wifi_2.Get(3)); UdpEchoClientHelper echoClient(wifi_interface.GetAddress(3), 9); echoClient.SetAttribute("MaxPackets", UintegerValue(1)); echoClient.SetAttribute("Interval", TimeValue(Seconds(5))); echoClient.SetAttribute("PacketSize", UintegerValue(1024)); ApplicationContainer clientApps = echoClient.Install(wifi_1.Get(4)); /*serverApps.Start(Seconds(11.0)); serverApps.Stop(Seconds(14.0)); clientApps.Start(Seconds(12.5)); clientApps.Stop(Seconds(14.0));*/ //********** cluster A inner communication ApplicationContainer serverApp; uint16_t port = 5000; Address localAddress(InetSocketAddress(Ipv4Address::GetAny(), port)); PacketSinkHelper packetSinkHelper("ns3::UdpSocketFactory", localAddress); serverApp = packetSinkHelper.Install(wifi_1.Get(0)); serverApp.Start(Seconds(0.0)); serverApp.Stop(Seconds(100)); OnOffHelper onoff("ns3::UdpSocketFactory", Ipv4Address::GetAny()); onoff.SetAttribute("OnTime", StringValue("ns3::ConstantRandomVariable[Constant=1]")); onoff.SetAttribute("OffTime", StringValue("ns3::ConstantRandomVariable[Constant=0]")); onoff.SetAttribute("PacketSize", UintegerValue(1024)); onoff.SetAttribute("DataRate", DataRateValue(1024 * 8 * 3)); // bit/s AddressValue remoteAddress(InetSocketAddress(wifi_interface_1.GetAddress(0), port)); onoff.SetAttribute("Remote", remoteAddress); std::vector<ApplicationContainer> clientApp(wifinode_num1); for(int i = 1; i < wifinode_num1;++i){ clientApp[i] = onoff.Install(wifi_1.Get(i)); } for (uint32_t i = 1; i < wifinode_num1; ++i) { // fre_change 0~1 s // data send 1~plot s clientApp[i].Start(Seconds(i * plot + (11 - plot))); clientApp[i].Stop(Seconds(i * plot + 9.99)); } std::ofstream logFile("simulation_AtoA.txt"); std::clog.rdbuf(logFile.rdbuf()); // ns-3 默认用 clog 输出日志 P2Phelper.EnablePcap("p2p_1",P2P_1_1.Get(0)); P2Phelper.EnablePcap("p2p_1",P2P_1_1.Get(1)); wifiPhy.EnablePcap("apwifi",apwifi_2.Get(0)); wifiPhy.EnablePcap("wifi_1",stawifi_2.Get(3)); //**********----simulation layer----************// Simulator::Schedule(Seconds(9),&Signal,jammingPsd,"SINGLE"); Simulator::Schedule(Seconds(9),&FreChange,wifi_1,wifi_1_ap,"FIXED",plot); Simulator::Stop(Seconds(20)); Simulator::Run(); Simulator::Destroy(); return 0; } 看看哪里的问题
最新发布
07-29
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值