草庐IT

基于NS3仿真的的TCP性能分析

ascldads 2024-02-20 原文

  最近一直在学习ns3网络仿真,现在想做一下关于TCP协议的性能测试,也就专门做了记录文档,方便记录一下学习进度,以后有学习的进展也可以在放到这里。

  本次测试的性能指标是时延,时延简单来讲就是数据从发送到接收的时间差,这个指标能够反应网络的拥塞程度。

  在开始实验之前先构想一下需要做哪些准备,计算时延简单来讲需要获得两个参数,数据发送的时间,接收数据的时间,然后将两者相减就可以获得时延。从原理上讲感觉十分简单,但是在做实验的时候却困难重重。

  遇到的第一个问题就是如何获取数据的发送时间,获取数据到达时间很容易,直接Simulator::Now().GetSeconds(),但是在ns3中没有直接获取发送时间的方法,参考了网上的一些资料,在这里介绍一种方法,可以在发包的时候给包打上标签,这个标签不会改变宝的大小,等包到达的时候获取这个标签,之后就用到达时间减去时间戳就可以得到时延。

  但是之后就遇到了第二个问题,因为TCPS是以数据流的形式传输的,也就是说发送端发送一个包,但是TCP可能会把这一个包分成几部分传给接收端,也就是所谓的粘包问题。接收端收到的包中,只有一个包带有时间戳,这就给计算时延带来了很大的困难。查阅了一些资料,寻找解决粘包问题的解决方法,这里主要介绍两种,第一种,设置接收端必须要等待上一个包发送完成才能发包,第二个方案是调整包的大小。但是这两种方法都会影响传输效率。所以没有采用。因为没有解决粘包问题,所以这一方案就被否决了。

  之后又跑去继续学习TCP,发现上个方案时延的计算不合理,对于UDP协议来说,上边的计算方法是没有问题的。但是对于TCP来说就不是很合理,因为TCP协议是一种可靠的传输层协议,发送端发送完数据之后还要等待所有数据的确认才能够继续发送数据。所以时延的计算方法就要改变一下,在TCP中应该用发送端接收包的确认报文时间减去发送包的时间。所以根据这一思路,在官方文档TcpSorket类中果然找到了需要的trace source 即Tx,Rx和RTT

  由于TCP是一次性将窗口内的所有报文发出,所以所有报文都到达并被确认的时间,近似的等于一个RTT所以时延。可以近似等于一个RTT那么现在就有两个方案,第一个就是直接跟踪RTT,第二个就是分别求出Tx,Rx。

  对于第一个方案,有一个难点,就是想要跟踪RTT,必须把RTT和发送端的套接字相关联。一般地,我们都会选择ns3的helper类来创建应用程序来产生数据流量,比如OnOffApplication,但是这会带来许多问题。1. OnOffApplication的套接字在应用程序运行前是没有被创建的,因此无法在运行程序之前关联套接字。2.即使可以在开始之后通过函数调用来解决这些问题,但是由于套接字不是公共的,因此也无法访问。

  因此要想要跟踪套接字上的trace source,不能借助helper类来创建应用程序。所以就需要自定义一个应用程序在仿真运行之前关联套接字。

  对于第二种方案,因为要跟踪两个socket所以不仅仅要自定义发送端的应用程序,还要自定义接收端的应用程序,接收端应用程序工作还没有完成,完后之后会补上。因此本文只介绍第一个方案的程序。

1.网络拓扑结构

点对点网络(peer-to-peer, 简称P2P),又称对等式网络,是无中心服务器、依靠用户群(peers)交换信息的互联网体系,它的作用在于,减低以往网路传输中的节点,以降低资料遗失的风险。与有中心服务器的中央网络系统不同,对等网络的每个用户端既是一个节点,也有服务器的功能,任何一个节点无法直接找到其他节点,必须依靠其户群进行信息交流。

P2P的有线网络连接是两个终端直接建立有线连接,不经过中继设备直接交换数据或服务,连接方式如图(a)

 

2.网络仿真设计

本次仿真使用的是两个节点之间的通信连接,所以使用NS3中的NodeContainer创建两个节点,并为其分配IP地址,源节点的IP地址为10.1.1.1,接收节点IP地址为10.1.1.2 。

.

2.1链路创建

本次仿真实验采用的拓扑结构为PointToPoint,使用NS3的PointToPointHelper设置链路,且将设备的传输速率设置为5Mbps和信道的延时设置为2ms。最后将设置好的链路安装到先前创建的节。

2.2应用程序安装

本次仿真实验是两个终端之间的通信连接,接收端使用NS3中的PacketSink类为节点安装应用服务。PacketSink是专门部署在接收端的应用,对到达接收端的数据尽可能的接收。

对于发送端本次仿真使用的应用程序类似于BulkSendApplication即尽可能的发送数据。

在本次仿真中设置发送端发送速率为5Mbps,包的大小为500

2.3拥塞控制算法

在本次仿真中,引入了拥塞控制,设置控制TCP拥塞算法为TCP-Reno

TCP的一个关键部分就是拥塞控制机制,因为IP层不向端系统提供显式的网络拥塞反馈,因此TCP必须使用端到端拥塞控制而不是使用网络辅助的拥塞控制。TCP Reno 算法主要由三部分组成:

1.慢启动;2. 拥塞避免;3. 快重传,快恢复;

慢启动

当主机开始发送数据时,由于并不清楚网路的负荷情况,所以如果立即把大量数据字节注入到网络,那么就有可能引发网络发生拥塞。所以最好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是说,由小到达逐渐增大拥塞窗口值。

一开始发送方先设置cwnd=1,发送第一个报文段M1。发送方收到对M1的确认后,把cwnd从1 增大到2,于是发送方接着发送M2和M3两个报文段。发送方每收到一个新报文段的确认 (重传的不算在内) 就使发送方的拥塞窗口加1,因此发送方在收到两个确认后,cwnd就从2增大到4,并可发送M4~M7共4个报文段。因此使用慢开始算法后,每经过一个传输轮次(transmission round),拥塞窗口cwnd就加倍 (1 -> 2 -> 4 -> 8 -> n)

拥塞避免

为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量,sshthresh的用法如下。

  • 当cwnd < ssthresh时,使用上述的慢开始算法。
  • 当cwnd > ssthresh时,停止使用慢开始算法而改用拥塞避免算法。
  • 当cwnd = ssthresh时,既可使用慢开始算法,也可使用拥塞避免算法。

拥塞避免算法

让拥塞窗口cwnd缓慢的增大,每经过一个往返时间RTT就把发送方的拥塞窗口+1,而不是像慢开始那样成倍的增加。即加法增大

快重传与快恢复

为什么需要快重传。有时,个别报文段会在网络中丢失,但实际上网络并未发生拥塞,如果发送方迟迟收不到确认,就会产生超时,就会误认为网络发生了拥塞,就导致了重新开始慢开始,将拥塞窗口cwnd又设置为1,因而降低了传输效率。

采用快重传算法可以让发送方尽早知道发生了个别报文段的丢失。

快重传算法首先要求接收方不要等待自己发送数据时才进行捎带确认,而是要立即发送确认,即使收到了失序的报文段也要立即发出对已收到的报文段的重复确认。快重传算法规定,发送方只要一连收到3个重复确认,就知道接收方确实没有收到报文段M3,因而应该立即进行重传(即“快重传”),这样就不会出现超时。

2.4其它配置

如果大家创建的模没有任何错误,那么根据拥塞窗口的原理,拥塞窗口就会一直增大,这和我们的要求和实际不符,因此引入了错误模型,来造成分组丢失,最终会影响拥塞窗口的改变。

本次仿真中设置接收端的丢包率为0.0001,设置发包起点时间为1s,结束时间为20s,记录仿真过程中数据传输的数据,并生成trace文件。

3.仿真结果与分析

 

仿真结果显示了RTT的变化情况。

将数据导入Excel中绘图。

上图中横坐标代表着仿真时间,纵坐标代表着往返时延。

4.总结

学习ns3这段时间,第一次实际上手做性能仿真的实验,实话说还是很困难的,但是经过这段时间的反复,对ns3的理解又有了不少的增长,对ns3的内核机制有了新的理解。

对于TCP的拥塞控制理论,经过了这次实验,也不在是仅仅停留在理论知识上了。通过仿真,能够更直观的感受TCP协议的工作原理,为以后的深入实验打下了基础。

在本次仿真中虽然只是简单的添加了错误模型,输出了往返时延,并未加入其他的方案,但是这是一个很好的开始,本次实验的模型具有很强的延展性,将来如果有好的想法,可以很方便的在这上边添加。

对于上边提到对的第二个方案,需要自定义接收端应用程序,现在还未完成,完成之后会补上。

代码

//自定义发送端程序
class MyApp : public Application 
{
public:

  MyApp ();
  virtual ~MyApp();

  void Setup (Ptr<Socket> socket, Address address, uint32_t packetSize, uint32_t nPackets, DataRate dataRate);//设置构造函数

private:
  virtual void StartApplication (void);
  virtual void StopApplication (void);

  void ScheduleTx (void);
  void SendPacket (void);

  Ptr<Socket>     m_socket;
  Address         m_peer;
  uint32_t        m_packetSize;
  uint32_t        m_nPackets;
  DataRate        m_dataRate;
  EventId         m_sendEvent;
  bool            m_running;
  uint32_t        m_packetsSent;
};

MyApp::MyApp ()
  : m_socket (0), 
    m_peer (), 
    m_packetSize (0), 
    m_nPackets (0), 
    m_dataRate (0), 
    m_sendEvent (), 
    m_running (false), 
    m_packetsSent (0)
{
}

MyApp::~MyApp()
{
  m_socket = 0;
}

void
MyApp::Setup (Ptr<Socket> socket, Address address, uint32_t packetSize, uint32_t nPackets, DataRate dataRate)
{
  m_socket = socket;
  m_peer = address;
  m_packetSize = packetSize;
  m_nPackets = nPackets;
  m_dataRate = dataRate;
}

void
MyApp::StartApplication (void)
{
  m_running = true;
  m_packetsSent = 0;
  m_socket->Bind ();
  m_socket->Connect (m_peer);
  SendPacket ();
}

void 
MyApp::StopApplication (void)
{
  m_running = false;

  if (m_sendEvent.IsRunning ())
    {
      Simulator::Cancel (m_sendEvent);
    }

  if (m_socket)
    {
      m_socket->Close ();
    }
}

void 
MyApp::SendPacket (void)
{
  Ptr<Packet> packet = Create<Packet> (m_packetSize);
  m_socket->Send (packet);

  if (++m_packetsSent < m_nPackets)
    {
      ScheduleTx ();
    }
}

void 
MyApp::ScheduleTx (void)
{
  if (m_running)
    {
      Time tNext (Seconds (m_packetSize * 8 / static_cast<double> (m_dataRate.GetBitRate ())));//设置发送间隔
      m_sendEvent = Simulator::Schedule (tNext, &MyApp::SendPacket, this);
    }
}
//显示丢包发生的时间
static void
RxDrop (Ptr<const Packet> p)
{
  // NS_LOG_UNCOND ("RxDrop at " << Simulator::Now ().GetSeconds ());
}
//显示rtt
void Rtt(Time oldValue, Time newValue)
{

  NS_LOG_UNCOND (Simulator::Now ().GetSeconds () << "\t" << newValue.GetSeconds());
}

int 
main (int argc, char *argv[])
{
  CommandLine cmd;
  cmd.Parse (argc, argv);
  
  NodeContainer nodes;
  nodes.Create (2);

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

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

  Ptr<RateErrorModel> em = CreateObject<RateErrorModel> ();
  em->SetAttribute ("ErrorRate", DoubleValue (0.0001));
  devices.Get (1)->SetAttribute ("ReceiveErrorModel", PointerValue (em));//设置丢包率

  InternetStackHelper stack;
  stack.Install (nodes);

  Ipv4AddressHelper address;
  address.SetBase ("10.1.1.0", "255.255.255.252");
  Ipv4InterfaceContainer interfaces = address.Assign (devices);

  uint16_t sinkPort = 8080;
  Address sinkAddress (InetSocketAddress (interfaces.GetAddress (1), sinkPort));
  PacketSinkHelper packetSinkHelper ("ns3::TcpSocketFactory", InetSocketAddress (Ipv4Address::GetAny (), sinkPort));
  ApplicationContainer sinkApps = packetSinkHelper.Install (nodes.Get (1));
  sinkApps.Start (Seconds (0.));
  sinkApps.Stop (Seconds (20.));

  Ptr<Socket> ns3TcpSocket = Socket::CreateSocket (nodes.Get (0), TcpSocketFactory::GetTypeId ());//关联套接字
  ns3TcpSocket->TraceConnectWithoutContext ("CongestionWindow", MakeCallback (&CwndChange));//关联回调函数
ns3TcpSocket->TraceConnectWithoutContext ("RTT", MakeCallback (&Rtt));
  Ptr<MyApp> app = CreateObject<MyApp> ();

  app->Setup (ns3TcpSocket, sinkAddress, 500, 20000, DataRate ("5Mbps"));


  nodes.Get (0)->AddApplication (app);
  app->SetStartTime (Seconds (1.));
  app->SetStopTime (Seconds (20.));

  Simulator::Stop (Seconds (20));

  Simulator::Run ();
  Simulator::Destroy ();

  return 0;
}

 

有关基于NS3仿真的的TCP性能分析的更多相关文章

  1. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  2. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  3. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  4. Ruby 的数字方法性能 - 2

    我正在使用Ruby解决一些ProjectEuler问题,特别是这里我要讨论的问题25(Fibonacci数列中包含1000位数字的第一项的索引是多少?)。起初,我使用的是Ruby2.2.3,我将问题编码为:number=3a=1b=2whileb.to_s.length但后来我发现2.4.2版本有一个名为digits的方法,这正是我需要的。我转换为代码:whileb.digits.length当我比较这两种方法时,digits慢得多。时间./025/problem025.rb0.13s用户0.02s系统80%cpu0.190总计./025/problem025.rb2.19s用户0.0

  5. ruby-on-rails - 我真的需要在 Rails 中使用 csv gem 吗? - 2

    我的问题很简单:我是否必须在使用RubyonRails的类上require'csv'?如果我打开一个railsconsole并尝试使用CSVgem它可以工作,但我必须在文件中这样做吗? 最佳答案 CSVlibrary是ruby​​标准库的一部分;它不是gem(即第三方库)。与所有标准库(与核心库不同)一样,csv不会由ruby​​解释器自动加载。所以是的,在您的应用程序中某处您确实需要要求它:irb(main):001:0>CSVNameError:uninitializedconstantCSVfrom(irb):1from/Us

  6. ruby - Ruby 性能中的计时器 - 2

    我正在寻找一个用ruby​​演示计时器的在线示例,并发现了下面的代码。它按预期工作,但这个简单的程序使用30Mo内存(如Windows任务管理器中所示)和太多CPU有意义吗?非常感谢deftime_blockstart_time=Time.nowThread.new{yield}Time.now-start_timeenddefrepeat_every(seconds)whiletruedotime_spent=time_block{yield}#Tohandle-vesleepinteravalsleep(seconds-time_spent)iftime_spent

  7. ruby-on-rails - 如果条件与 &&,是否有任何性能提升 - 2

    如果用户是所有者,我有一个条件来检查说删除和文章。delete_articleifuser.owner?另一种方式是user.owner?&&delete_article选择它有什么好处还是它只是一种写作风格 最佳答案 性能不太可能成为该声明的问题。第一个要好得多-它更容易阅读。您future的自己和其他将开始编写代码的人会为此感谢您。 关于ruby-on-rails-如果条件与&&,是否有任何性能提升,我们在StackOverflow上找到一个类似的问题:

  8. ruby-on-rails - (Ruby,Rails) 基于角色的身份验证和用户管理...? - 2

    我正在寻找用于Rails的优质管理插件。似乎大多数现有的插件/gem(例如“restful_authentication”、“acts_as_authenticated”)都围绕着self注册等展开。但是,我正在寻找一种功能齐全的基于管理/管理角色的解决方案——但不是简单地附加到另一个非基于角色的解决方案。如果我找不到,我想我会自己动手......只是不想重新发明轮子。 最佳答案 RyanBates最近做了两个关于授权的railscast(注意身份验证和授权之间的区别;身份验证检查用户是否如她所说的那样,授权检查用户是否有权访问资源

  9. ruby - 在 Rakefile 中动态生成 Rake 测试任务(基于现有的测试文件) - 2

    我正在根据Rakefile中的现有测试文件动态生成测试任务。假设您有各种以模式命名的单元测试文件test_.rb.所以我正在做的是创建一个以“测试”命名空间内的文件名命名的任务。使用下面的代码,我可以用raketest:调用所有测试require'rake/testtask'task:default=>'test:all'namespace:testdodesc"Runalltests"Rake::TestTask.new(:all)do|t|t.test_files=FileList['test_*.rb']endFileList['test_*.rb'].eachdo|task|n

  10. ruby - 如何找到我的 Ruby 应用程序中的性能瓶颈? - 2

    我编写了一个Ruby应用程序,它可以解析来自不同格式html、xml和csv文件的源中的大量数据。我如何找出代码的哪些区域花费的时间最长?有没有关于如何提高Ruby应用程序性能的好资源?或者您是否有任何始终遵循的性能编码标准?例如,你总是用加入你的字符串吗?output=String.newoutput或者你会使用output="#{part_one}#{part_two}\n" 最佳答案 好吧,有一些众所周知的做法,例如字符串连接比“#{value}”慢得多,但是为了找出您的脚本在哪里消耗了大部分时间或比所需时间更多,您需要进行分

随机推荐