NS3快速入門(使用VScode查看、編譯代碼)


前言

工欲善其工,必先利其器。

最近在學習ns3,於是折騰了好幾天的環境。終於可以自己動手了。如果你也是剛准備學習ns3,你可以借鑒我博客的文章,
先把應該有的環境配置好,才能更好的去學習ns3。
Ubuntu的安裝與配置
Ubuntu的優化
NS3環境搭建
搞定了環境之后,還沒有進入正題去入門我們的NS3。
在我們學習ns3的時候,大部分時候我們都是在終端中輸入命令./waf和./war --run的方式來編譯和運行項目,對於代碼文件的更改也只是稱“使用你喜歡的IDE將代碼修改”。雖然說理論上我們可以通過vim和終端命令完成所需要的所有代碼編輯功能,但是作為2020年的程序員,我們希望有IDE。
這時候大部分人會想到用eclipse去編譯代碼,但是書本或者官網給出的配置實在是太多了。
並且我也可以接受去使用終端編譯項目,我想要的只是更方便的代碼編寫以及代碼高亮,自動補全。
所以這時候,就輪到VScode登場了。

配置VScode

1.安裝vscode,安裝好擴展C/C++。
在這里插入圖片描述
2.創建一個目錄,使用vscode打開,按F1調出控制台,配置相關設置。
在這里插入圖片描述
修改includepath的參數
在這里插入圖片描述
至此大功告成,只要我們打開相關的代碼進行編譯就好了。當然編譯運行還是要在終端中輸入命令。

開始入門NS3

通過閱讀分析一個例子程序(first.cc)的源代碼,並通過運行該例子程序,快速理解ns3中的幾個概念。

NS3中的幾個關鍵概念

1.節點Node

在網絡術語中,任何一台連接到網絡的計算設備被稱為主機,亦稱為終端。NS3是一個網絡模擬器,而非一個專門的因特網模擬器,為此我們避開術語“主機”,因為這個詞太容易讓人聯想到因特網和及其相關協議。因此,我們選用了一個來源於圖論,在其他網絡模擬器中亦廣泛使用的術語:節點。

NS3中基本計算設備被抽象為節點。節點由用C++編寫的Node類來描述。Node類提供了用於管理計算設備的各種方法。

可以將節點設想為一台可以添加各種功能的計算機。為了使一台計算機有效地工作,我們可以給它添加應用程序,協議棧,外設卡及驅動程序等。NS3采用了與此相同的模型。

2.信道

在現實世界中,人們可以把計算機連接到網絡上。通常我們把網絡中數據流流過的媒介稱為信道。當你把以太網線插入到牆壁上的插孔時,你正通過信道將計算機與以太網連接。在NS3中,可以把節點連接到代表數據交換信道的對象上。在這里,基本的通信子網這一抽象概念被稱為信道,用C++編寫的Channel類來描述。

Channel類提供了管理通信子網對象和把節點連接至信道的各種方法。信道類同樣可以由開發者以面向對象的方法自定義。一個信道實例可以模擬一條簡單的線纜(wire),也可以模擬一個復雜的巨型以太網交換機,甚至無線網絡中充滿障礙物的三維空間。

在本章中我們將使用幾個信道模型的實例,包括:CsmaChannel,PointToPointChannel和WifiChannel。舉例來說,CsmaChannel信道模擬了用於一個可以實現載波偵聽多路訪問的信道,這個信道具有和以太網相似的功能。

3.網絡設備

如果想把一台計算機連接到網絡上,必須在計算機上安裝有網卡。一張網卡如果缺少控制硬件的軟件驅動是不能工作的。在Unix/Linux系統中,外圍硬件被划為“設備”。設備通過驅動程序來控制,而網卡通過網卡驅動程序來控制。在Unix/Linux系統中,網卡被稱為像eth0這樣的名字。

在NS3中,網絡設備這一抽象概念相當於硬件設備和軟件驅動的總和。NS3仿真環境中,網絡設備相當於安裝在節點上,使得節點通過信道和其他節點通信。像真實的計算機一樣,一個節點可以通過多個網絡設備同時連接到多條信道上。

網絡設備由用C++編寫的NetDevice類來描述。NetDevice類提供了管理連接其他節點和信道對象的各種方法,並且允許開發者以面向對象的方法來自定義。我們在本教程中將使用幾個特定的網絡設備的實例,它們分別是CsmaNetDevice,PointToPointNetDevice, 和WifiNetDevice。正如以太網卡被設計成在以太網中工作一樣,CsmaNetDevice被設計成在csma信道中工作,而PointToPointNetDevice在PointToPoint信道中工作,WifiNetNevice在wifi信道中工作。

4.應用程序

計算機軟件通常可分為兩大類:系統軟件和應用軟件。系統軟件根據計算模型配置,並管理計算機中的各種資源,如內存,處理器周期,硬盤,網絡等。系統軟件通常並不直接使用這些資源來完成用戶任務。用戶往往需要運行應用程序來完成一些特定的任務,而應用程序需要使用由系統軟件控制的資源。

通常,系統軟件和應用軟件的界線表現為特權級別的變化,而這種變化是通過操作系統的自陷功能(operating systemtraps)來實現的。在NS3中並沒有真正的操作系統的概念,更沒有特權級別或者系統調用的概念。然而,我們有應用程序的概念。正如“現實世界”中在計算機上運行應用程序以執行各種任務一樣,NS3仿真環境中的應用程序在節點上運行來驅動模擬過程。

在NS3中,需要被仿真的用戶程序被抽象為應用。用Application類來描述。這個類提供了管理仿真過程中用戶層應用的各種方法。開發者應當用面向對象的方法自定義和創建新的應用。在本教程中,我們會使用Application類的兩個實例:UdpEchoClientApplication和UdpEchoServerApplication。這些應用程序包含了一個client應用和一個server應用來發送和回應仿真網絡中的數據包。

分析例子程序first.cc的源代碼

進入ns-3.15/examples/tutorial目錄。你會發現一個叫first.cc的文件。這一個腳本會在兩個節點間創建一個簡單的點到點的連接,並且在這兩個節點之間傳送一個數據包。為方便后續分許,先將first.cc的源代碼粘貼如下:

/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * 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/core-module.h"
#include "ns3/network-module.h"
#include "ns3/internet-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/applications-module.h"

using namespace ns3;

NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");

int
main (int argc, char *argv[])
{
  CommandLine cmd;
  cmd.Parse (argc, argv);
  
  Time::SetResolution (Time::NS);
  LogComponentEnable ("UdpEchoClientApplication", LOG_LEVEL_INFO);
  LogComponentEnable ("UdpEchoServerApplication", LOG_LEVEL_INFO);

  NodeContainer nodes;
  nodes.Create (2);

  PointToPointHelper pointToPoint;
  pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
  pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));

  NetDeviceContainer devices;
  devices = pointToPoint.Install (nodes);

  InternetStackHelper stack;
  stack.Install (nodes);

  Ipv4AddressHelper address;
  address.SetBase ("10.1.1.0", "255.255.255.0");

  Ipv4InterfaceContainer interfaces = address.Assign (devices);

  UdpEchoServerHelper echoServer (9);

  ApplicationContainer serverApps = echoServer.Install (nodes.Get (1));
  serverApps.Start (Seconds (1.0));
  serverApps.Stop (Seconds (10.0));

  UdpEchoClientHelper echoClient (interfaces.GetAddress (1), 9);
  echoClient.SetAttribute ("MaxPackets", UintegerValue (1));
  echoClient.SetAttribute ("Interval", TimeValue (Seconds (1.0)));
  echoClient.SetAttribute ("PacketSize", UintegerValue (1024));

  ApplicationContainer clientApps = echoClient.Install (nodes.Get (0));
  clientApps.Start (Seconds (2.0));
  clientApps.Stop (Seconds (10.0));

  Simulator::Run ();
  Simulator::Destroy ();
  return 0;
}

模塊包含

代碼一般是以一系列的include聲明開始的:

#include "ns3/core-module.h"
#include "ns3/simulator-module.h"
#include "ns3/node-module.h"
#include "ns3/helper-module.h"

為了幫助高層的腳本用戶處理大量的系統中的include文件,我們把所有的包含文件,根據模塊功能,進行了大致的分類。我們提供了一個單獨的include文件,這個文件會遞歸加載所有會在每個模塊中會被使用的include文件。NS3提供了按大致功能分類的一組include文件,在使用時只需選擇包含這幾個包含文件(include文件),而不用考慮復雜的依賴關系,省去在尋找所需要的頭文件上花費的不必要的時間。這不是最有效地方法但很明顯讓編寫腳本文件容易多了。

在編譯的過程中,每一個ns-3的include文件被放在build目錄下一個叫ns3的目錄中,這樣做可以避免include文件名的沖突。ns3/core-module.h與src/core目錄下的模塊相對應。查看ns3目錄會發現大量的頭文件。當你編譯時,Waf會根據配置把在ns3目錄下的公共的頭文件放到build/debug或者build/optimized目錄下。Waf也會自動產生一個模塊include文件來加載所有的公共頭文件。
當然,如果遵循着這個手冊走的話,你可能已經使用過如下命令:
./waf -d debug --enable-examples --enable-tests configure來配置工程以完成調試工作。
你可能同樣使用了如下命令:./waf來編譯ns-3。現在如果你進入…/…/build/debug/ns3目錄的話你會發現本節開頭提到的四個頭文件。仔細看一下這些文件的內容,會發現它們包含了相關模塊中的所有的include文件。

命名空間

在first.cc腳本的下一行是namespace的聲明。
using namespace ns3;
NS3工程是在一個叫做ns3的C++命名空間中實現的。這把所有與ns3相關的聲明,集中在一個與全局命名空間相區別的命名空間中。我們希望這樣會給ns3與其他代碼的集成帶來好處。C++用“using”語句用來把ns-3namespace引入到當前的(全局的)作用域中。這個聲明就是說,你不用為了使用ns-3的代碼而必須在所有的ns-3代碼前打上ns3::作用域操作符。如果對命名空間並不熟悉,可以查閱任何的C++手冊並比較ns3命名空間和標准”std”命名空間的使用。

日志

下一句腳本如下:

NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");

這一行聲明了一個叫FirstScriptExample的日志組件,通過引用FirstScriptExample這個名字的操作,可以實現打開或者關閉控制台日志的輸出。

Main函數

下面的腳本是:
int main (int argc, char *argv[]) {

這就是你的腳本程序的主函數的聲明。正如任何其它C++程序一樣,你需要定義一個會被第一個執行的主函數。你的ns-3腳本沒有什么特別的,就和一個普通的C++程序一樣。

再接下來兩行腳本是用來使兩個日志組件生效的。它們被內建在Echo Client 和EchoServer 應用中:

LogComponentEnable("UdpEchoClientApplication",LOG_LEVEL_INFO);
LogComponentEnable("UdpEchoServerApplication",LOG_LEVEL_INFO);

使用基本對象模型搭建仿真拓撲

使用NodeContainer類
在first.cc腳本中的下面兩行將會創建ns-3節點對象。

NodeContainer nodes;
nodes.Create (2);

上面的第一行只是聲明了一個名為”nodes”的NodeContainer。第二行調用了nodes對象的Create()方法創建了兩個節點。

使用PointToPointHelper類

接下來,我們將會用到被稱為拓撲輔助工具的helper類。這些helper類里面封裝了低級的方法,有助於我們高效的建立仿真拓撲。

回憶一下我們的兩個關鍵抽象概念:網絡設備、信道。在真實的世界中,這些東西大致相當於網卡和網線。需要說明的是這兩樣東西緊密的聯系在一起而不能夠把它們交互地使用(比如以太網設備和無線信道就不能一起使用)。在這個腳本中使用了PointToPointHelper來配置和連接網絡設備PointToPointNetDevice和信道PointToPointChannel對象。

在腳本中下面的三句話是:

PointToPointHelper pointToPoint;
pointToPoint.SetDeviceAttribute ("DataRate", StringValue("5Mbps"));
pointToPoint.SetChannelAttribute ("Delay", StringValue("2ms"));

其中第一行,

PointToPointHelper pointToPoint;

在棧中初始化了一個PointToPointHelper的對象pointToPoint。而緊接着的下一行,

pointToPoint.SetDeviceAttribute ("DataRate", StringValue("5Mbps"));

從上層的角度告訴PointToPointHelper對象當創建一個PointToPointNetDevice對象時使用“5Mbps"來作為數據速率。
從細節方面講,字符串“DataRate”與PointToPointNetDevice的一個屬性相對應。如果查看ns3::PointToPointNetDevice類,並閱讀GetTypeId方法的文檔,你會發現設備定義了一系列屬性,在這些屬性中就有“DataRate”。大部分用戶可見的ns-3對象都有類似的屬性列表。正如你在下面的部分會看到的一樣,我們使用了這個機制以方便地配置仿真器,而不用重新對源代碼進行編譯。
與PointToPointNetDevice上的“DataRate”類似,PointToPointChannel也有一個Delay屬性:

pointToPoint.SetChannelAttribute ("Delay", StringValue("2ms"));

告訴PointToPointHelper使用"2ms"(2毫秒)作為每一個被創建的點到點信道傳輸延時值。

使用NetDeviceContainer類

現在我們有一個包含兩個節點的NodeContainer對象。我們有一個准備在兩個節點之間創建PointToPointNetDevices和wirePointToPointChannel對象的PointToPointHelper對象。正如我們使用NodeContainer對象來為我們創建節點,我們會讓pointToPointHelper來做關於創建,配置和安裝設備的工作。
我們使用一個NetDeviceContainer對象來存放需要所有被創建的NetDevice對象,就像我們使用一個NodeContainer對象來存放我們所創建節點。下面兩行代碼:

NetDeviceContainer devices;
devices = pointToPoint.Install (nodes);

會完成設備和信道的配置。第一行聲明了上面提到的設備容器,第二行完成了主要工作。PointToPointHelper的Install()方法以一個NodeContainer對象作為一個參數。在Install()方法內,一個NetDeviceContainer被創建了。對於在NodeContainer對象中的每一個節點(對於一個點到點鏈路必須明確有兩個節點),都將有一個PointToPointNetDevice被創建和保存在設備容器內,有一個PointToPointChannel對象被創建,兩個PointToPointNetDevices與之連接。當PointToPointHelper對象創建時,那些在helper中被預先設置的屬性被用來初始化對象對應的屬性值。
當調用了pointToPoint.Install(nodes)后,我們會有兩個節點,每一個節點安裝了點到點網絡設備,在它們之間是一個點到點信道。兩個設備會被配置在一個有2ms傳輸延時的信道上以5Mbps的速率傳輸數據。

使用InternetStackHelper類

我們現在已經配置了節點和設備,但是我們還沒有在節點上安裝任何協議棧。下面兩行代碼完成這個任務:

InternetStackHelper stack;
stack.Install (nodes);

類InternetStackHelper是一個輔助安裝網絡協議棧的helper類。其中Install()方法以NodeContainer對象作為參數,當它被執行后,它會為節點容器中的每一個節點安裝一個網絡協議棧(TCP,UDP,IP等)。

使用Ipv4AddressHelper類

下面我們需要為節點上的設備設置IP地址。我們也提供了一個helper類來管理IP地址的分配。當執行實際的地址分配時唯一用戶可見的API是設置IP地址和子網掩碼。

在我們的范例腳本文件first.cc的下兩行代碼

Ipv4AddressHelper address;

address.SetBase ("10.1.1.0", "255.255.255.0");

聲明了一個helper對象,並且告訴它應該開始從10.1.1.0開始以子網掩碼為255.255.255.0分配地址。地址分配默認是從1開始並單調的增長,所以在這個基礎上第一個分配的地址會是10.1.1.1,緊跟着是10.1.1.2等等。底層NS3系統事實上會記住所有分配的IP地址,如果你無意導致了相同IP地址的產生,這將是一個致命的錯誤(順便說一下,這是個很難調試正確的錯誤)。

下面一行代碼,

Ipv4InterfaceContainer interfaces = address.Assign(devices);

完成了真正的地址配置。在NS3中我們使用Ipv4Interface對象將一個IP地址同一個網絡設備關聯起來。正如我們有時候需要一個網絡設備列表一樣,我們有時候需要一個Ipv4Interface對象的列表。Ipv4InterfaceContainer提供了這樣的功能。

現在我們有了一個安裝了協議棧,配置了IP地址的點到點的網絡。剩下的所要做的事情是運用它來產生數據通信。

Applications類

NS3系統的另一個核心抽象是Application類。在這個腳本中我們用兩個特定的Application類:UdpEchoServerApplication和UdpEchoClientApplication。正如我們先前聲明過的一樣,我們使用helper對象來幫助配置和管理潛在的對象。在這里,我們用UdpEchoServerHelper和UdpEchoClientHelper對象來使我們的工作更加容易點。

首先來看UdpEchoServerHelper類:

下面的代碼用來在我們之前創建的節點上設置一個UDP 回顯服務應用。

UdpEchoServerHelper echoServer (9);
ApplicationContainer serverApps = echoServer.Install (nodes.Get(1));
serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));

上面一片代碼中的第一行聲明了UdpEchoServerHelper。像往常一樣,這個並非應用本身,這是一個用來幫助創建真正應用的helper對象。我們約定在helper類的對象中放置必需的屬性。本例中,除非我們告知helper對象服務器和客戶端所共知的一個端口號,否則這個helper對象是不會起任何作用的。

同其它helper對象類似,UdpEchoServerHelper對象有一個Install()方法。實際上是這個方法的執行,才初始化回顯服務器的應用,並將應用連接到一個節點上去。有趣的是,install()方法把NodeContainter參數,正如我們看到的其他安裝方法一樣。這里有一個C++隱式轉換,此轉換以nodes.Get(1)的結果作為輸入,並把它作為一個NodeContainer的構造函數的參數,最終這個新構造的NodeContainer被送入Install方法中去。

我們現在會看到echoServer.Install將會在管理節點的NodeContainer容器索引號為1的機節點上安裝一個UdpEchoServerApplication。安裝會返回一個容器,這個容器中包含了指向所有被helper對象創建的應用指針。

應用程序對象需要一個時間參數來“開始”產生數據通信並且可能在一個可選的時間點“停止”。我們提供了開始和停止的兩個參數。這些時間點是用ApplicationContainer的方法Start和Stop來設置的。這些方法以”Time”對象為參數。本例中,我們傳遞了double類型對象1.0到Seconds的一個方法,通過seconds()方法,把它轉換到ns-3的Time對象。需要注意的是,這里的轉換規則是模型的作者所控制的,並且C++也有它自己的標准,所以你不能總是假定參數會按照你的意願順利地轉換。下面兩行,

serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));

會使echo服務應用在1s時開始(生效)並在10s時停止(失效)。既然我們已經聲明了一個模擬事件(就是應用的停止事件)在10s時被執行,模擬至少會持續10s。

然后再來看UdpEchoClientHelper類:

echo客戶端應用的設置與回顯服務器端類似。也有一個UdpEchoClientHelper來管理UdpEchoClientApplication。

UdpEchoClientHelper echoClient (interfaces.GetAddress (1),9);
echoClient.SetAttribute ("MaxPackets", UintegerValue (1));
echoClient.SetAttribute ("Interval", TimeValue (Seconds(1.)));
echoClient.SetAttribute ("PacketSize", UintegerValue(1024));
ApplicationContainer clientApps = echoClient.Install (nodes.Get(0));
clientApps.Start (Seconds (2.0));
clientApps.Stop (Seconds (10.0));

然而,對於echo客戶端,我們需要設置五個不同的屬性。首先兩個屬性是在UdpEchoClientHelper的構建過程中被設置的。按照UdpEchoClientHelper構造函數的格式,我們傳遞了”RemoteAdress”和”RemotePort”屬性,來實例化對象。

在上面的第一行代碼中,我們創建了一個UdpEchoClientHelper的對象,並告訴它設置客戶端的遠端地址為服務器節點的IP地址。我們同樣告訴它准備發送數據包到端口9。

“MaxPackets”屬性告訴客戶端我們所允許它在模擬期間所能發送的最大數據包個數。

“Interval”屬性告訴客戶端在兩個數據包之間要等待多長時間。

“PacketSize”屬性告訴客戶端它的數據包應該承載多少數據。本例中,我們讓客戶端發送一個1024字節的數據包。

正如echo服務端一樣,我們告訴echo客戶端何時來開始和停止,這里我們使客戶端在模擬器中時間為2s的時候開始(即服務端生效1s后才開始)。

Simulator類

下面我們所需要做的就是運行模擬器,這是用全局函數Simulator::Run.來做到的:

Simulator::Run ();

當我們調用了如下方法時:

serverApps.Start (Seconds (1.0));

serverApps.Stop (Seconds (10.0));

...

clientApps.Start (Seconds (2.0));

clientApps.Stop (Seconds (10.0));

實際上我們是在模擬器中1.0s,2.0s,和10.0s時預設了時間的發生。當Simulator::Run被調用時,系統會開始遍歷預設事件的列表並執行。首先它會在1.0s時運行事件,這個事件會使echo服務端應用生效(這個事件會預設更多的其他事件)。接下來仿真器會運行在t=2.0s時的事件,即讓echo客戶端應用開始。同樣的,這個事件可能會預定更多的其他事件。在echo客戶端應用中的開始事件的執行會通過給服務端傳送一個數據包來開始仿真的數據傳送階段。

發送一個數據包給服務端會引發一系列更多的事件。這些事件會被預設在此事件之后,並根據我們已經在腳本中設定的時間參數來執行數據包的應答。

其實,我們只發送了一個數據包(回憶一MaxPackets屬性被設置為1),在此之后,那個被單獨的客戶端應答請求所引發的連鎖反應會停止,並且模擬器會進入空閑狀態。當這發生時,生下來的事件就是服務端和客戶端的Stop事件。當這些事件被執行后,就沒有將來的事件來執行了,函數Simulator::Run會返回。整個模擬過程就結束了。

下面剩下的事情就是清理了。這個通過調用全局函數Simulator::Destroy來完成。當該方法被執行后,模擬器中所有創建的對象將被銷毀。你自己並不需要追蹤任何對象,你所需要做的僅僅是調用Simulator::Destroy並且退出。ns-3系統會幫你料理這些繁雜的任務。在first.cc腳本中對應的代碼如下:

Simulator::Destroy ();
return 0;
}

運行first.cc腳本程序

要運行自己的腳本,你所需要做的僅僅是把你的腳本放到scratch目錄下,並運行waf,這樣你的腳本就會被編譯。
你會發現,使用vscode可以讓這一部分如同絲滑一般的操作。
接下來我們來運行上面分析過的first.cc腳本。

在這里插入圖片描述
注意:我們要使用root模式去打開vscode,才能在vscode的終端中直接編譯程序。不然無法獲取權限。
打開的命令為:
sudo code --user-data-dir="~/.vscode-root"

在終端中輸入命令:

./waf --run scratch/fitst

結果如圖所示:
在這里插入圖片描述

這里可以看到編譯系統先檢查文件被編譯了,接着運行了它。你看到在echo日志構件顯示了它已經發送了1024字節到在10.1.1.2的echo服務端。還可以看到回顯服務器端的日志構件顯示他從10.1.1.1接收到了1024字節。接下來echo服務端應答了數據包,你能看到echo客戶端記錄了它已經接收到了從服務端發送過來的回顯數據包。

后續

至此vscode我們只是快速上手了,一定要自己去下載、安裝ns3,並在真實的ns3環境下,熟悉其目錄結構,了解開發流程,然后再系統里閱讀代碼、調試程序,才能真真的理解ns3。否則“紙上得來終覺淺”,加油!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM