概述
最近一直在学习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性能分析在本次仿真中,引入了拥塞控制,设置控制TCP拥塞算法为TCP-Reno。的全部内容,希望文章能够帮你解决基于NS3仿真的的TCP性能分析在本次仿真中,引入了拥塞控制,设置控制TCP拥塞算法为TCP-Reno。所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复