本文是对 http://www.omnetpp.org/doc/INET/tcp-tutorial/index.html 的翻译。
注意:此教程中的 OMNeT++ 版本为 3.4 ,和现在的 4.0 版本有不少区别,特别是绘图部分。
INET 框架包含了一个详细的可靠的 TCP 模型。这个教程解释了在 TCP 仿真中可以跟踪什么信息、怎样在你的模型中使用 TCP ,最后,我们将看看如何扩展 TCP 。
对使用 TCP 的模型跟踪和分析
开始
启动任何含有 TCP 应用的网络模型。两个比较好的模型是INET/NClients 和INET/REDTest仿真模型例子。如果你使用NClients 网络模型的话,我们建议你将客户端主机的数目设置为 1 或者 2 ——这样有利于跟踪事件。(在 omnetpp.ini 中查找并修改“ *.n=4 ”)
开始的时候,启动仿真动画并将速度设慢一点(可以通过调节滑动块来设置这点)。你应该可以看到熟悉的 SYN 、 SYN+ACK 报文, TCP 三次握手连接的建立,接着你将看到数据报文和 ACK 确认。如果你观察到了连接的断开,你应该可以看到双向的 FIN 和 ACK 报文分组(如果你观察到了此事件发生的时间,在下次运行的时候可以使用“Run until..”对话框来直接跳到这里)。如果上面的字句你没有感觉的话,则建议你在进行仿真之前对 TCP 协议进一步的熟悉。
看看 TCP 报文内部
重启仿真,通过重复的按 F4 来单步执行仿真。可以通过双击红色的图标打开一个报文的检查器。
即使是这样,有些时候也不容易:在你点击那些运动的报文之前,可能就已经消失了。有两种方法来解决这个问题:一种使降低动画的速度,使得你有时间点击报文头标(当然速度也还是要快的);另一种方法是在路由器和主机中找到此报文:双击图标打开内部构成,并检查ppp[0], ppp[1]内部,你可以看到类似下面的图:
当找到此报文后,可以双击此图标来打开检查器窗口。
你将可以看到表示 PPP 帧的报文。为了可以看到 IP 数据报文头标,点击“Encapsulated message”来打开 IPDatagram 的检查器窗口。为了获得 TCP 头标,你可以点击数据报文检查器的“Encapsulated message”按钮,并选择“ Fields ”标签。
跟踪应用的 listen() 和 connect() 调用
应用层可以通过发送消息到 TCP 上来打开一个连接。此消息被称为 ActiveOPEN 或者 PassiveOPEN (至少如果你在应用中使用 TCPSocket 工具类的时候是如此),你可以在应用开始之前通过打开主机模块(双击)来获取此消息。然后单步的执行仿真(单步, F4 )。你应该可以看到下面的图像:
ActiveOEN 消息和 connect() 调用是关联的,而 PassiveOPEN 和 listen() 调用是关联的。 Open 消息本身并不包含数据:所有的连接参数都是在“ Control Info ”结构中。你可以通过打开检查器(双击报文对象)并选择“ Control Info ”标签来查看。
connId 域从应用层的角度标志了此连接,仅仅用于应用层和 TCP 层之间,而在此范围之外无效,举个例子,在任何的 TCP 报文中都不会包含此信息。 connId 必须在每个应用层发送给 TCP 层的消息中存在(作为 Control Info 对象的一部分)(数据和控制信息),使得 TCP 可以知道是在和什么应用通信。同样的, TCP 在每个发送给应用层的消息中的 Control Info 中包含 connId 域。
其他的域则是 OPEN 调用的参数。 Remote address , remote port , local address 和 local port 不需要在这里进行解释。 Fork 只有在被动 OPEN ( listen() )的时候才使用:如果设置为 true ,一个连接请求( SYN 报文)将总是引起连接进行 Fork ,从而保证总是有一个侦听用来处理新请求;当设置为 false 的时候,不会 Fork 一个新连接, TCP 在处理完此连接之前将会拒绝其他的连接请求。剩下的参数则和 TCP 模型有关,将在后面进行讨论。
自身的命令类型(主动打开、被动打开、发送、关闭等)是包含在数字的 message field 域中,将显示在检查器的第一个标签页中。同样的,当 TCP 发送消息到上层应用的时候,将设置 message field 域用来指示操作类型:连接建立的通知,被远程 TCP 关闭连接的通知,重置,超时等等;数据到达了 TCP 建立的连接上,并在 Control Info 和 message kind 来指示此报文是数据还是紧要数据。 C++ API 中定义了 message field 域的符号常数:位于 TcpCommandCode 中的 TCP_C_OPEN_ACTIVE 等和 TcpStatusInd 中的TCP_I_ESTABLISHED等。
怎样看到自己的 TCP 连接
TCP 连接列表保存在 TCP 中的tcpConnMap 和tcpAppConnMap数据结构中。这两个数据结构包含了相同的连接,只是有着不同的索引方式:tcpConnMap可以为进入的 TCP 报文快速的查找连接进行了优化,而tcpAppConnMap则从应用层方面来通过 connID 来标志连接。
为了查看此数据结构,双击 TCP ,选择“ Contents ”的标签页,并双击tcpConnMap或者tcpAppConnMap项。如tcpConnMap:
现在,关于每个连接的信息在非常长的一行中打印出来。以后的发行版中将此连接信息展示为更结构化的形式。
在哪里查找开放式 socket 的信息呢?
在 INET 的 TCP 中并没有类似于 socket 的数据结构—— TCPSocket 类仅仅只是保存了 connID ,并提供了连接、发送、关闭等操作的少量工具函数。
绘制序列图
记录数据到输出矢量中
可视化观察 TCP 行为的一种方法是绘制序列图。 TCP 模块将记录序列号和其他的信息到 OMNeT++ 的输出矢量中。
OMNeT++ 的输出矢量是一种基本的时间序列数据:每个输出矢量包含有排序好的( time , value )对,其中 time 为 value 记录时的仿真时间。一个仿真的所有输出矢量将会输出到一个单一的矢量文件,经常被称为 omnetpp.vec 或者是 something.vec 。输出矢量文件的内容可以通过 OMNeT++ 自带的 Plove 文件来查看,由于内容是文本的,所以也可以通过其他你喜欢的工具来处理,如awk, perl, octave, matlab, gnuplot, xmgrace, R, spreadsheet programs, 等。
记录发送和接收 TCP 保温的输出矢量包含下面的内容:
大部分的模拟器都是用 segment 来计数,而在 OMNeT++ 中的 TCP 中,为了能够更忠于 RFC ,使用字节数来计数。
更多的,下面的信息也将会记录:
有些时候少就是多,所以你需要对写入矢量文件的项进行限制,同时限制记录区间。为了你需要在 omnetpp.ini 中加入一些选项。通过查看 OMNeT++ manual 看看如何设置输出矢量。
一个例子: RTT 绘图
让我们来看一个例子。运行一段时间的 REDTest 仿真,然后停止并退出。结果在当前目录中将会有一个 omnetpp.vec 文件。你可以通过 Plove 来打开:
$ plove omnetpp.vec
Plove 窗口将会出现,在左边窗口中将会显示已有的输出矢量。假设你需要知道 RTT 时间。我们可以在右边栏中绘制输出矢量图。可以通过双击左边栏中的矢量放置到右边栏中。
然后可以通过工具栏按钮创建一个图片。经过缩放和自定义(在图表上右键点击可以看到对线条样式、轴标签等的设置),可以看到下面的内容:
可以讲此图保存为 postscript 或者 GIF ,或者是拷贝到剪贴板中用于报告中。
通过序列图来看看 TCP 操作
下面我们将看看相同仿真中的序列图:
这张图可以看到 TCP 的所有内容。横轴表示时间,纵轴表示 TCP 的序列号。这里显示了 s1 发送 TCP 数据到 s2 中。刚开始的时候,发送的报文(蓝色)中序列号稳定的增长。这些报文将在一定的延时后到达 s2 (在蓝色点后大概 0.1s=100ms 的红色点)。这些报文在 s2 上得到确认(绿色的三角形——大概在红色点的 MSS=1024 字节之上,因为红色点表示到达的全 1024 字节的第一个字节,同时 ACK 中包含了下一个期望的序列号)。这些 ACK 报文将在 30ms 之后到达 s1 (黄色的十字)。不对称的延时( 100ms vs 30ms )是因为 ACK 报文要小一些,从而在瓶颈链路上需要更小的排队时延。
一个报文丢失了:大概在 t=44.81s 出的一个红色点丢失了,这也意味着一个报文在 s2 处没有被收到——有可能是在路由器被丢弃。发送者 s1 并不知道这点,从而保持发送状态直到窗口溢出。但是 s2 还是持续的发送相同的 ACK 数字(绿色的三角形变得水平),指示接收者需要丢失的报文。当这些 ACK 到达 s1 后,在第四个 ACK (前三个都是重复的)的地方意识到发生了错误,并重传了丢失的报文(单独的蓝色点)。最终,在 100ms 的延时后此报文到达了 s2 节点(单独的红色点),在 s2 节点上其他报文的 ACK 确认都缓存在队列中。
当丢失的报文到达 s2 后, s2 发送的 ACK 报文跳跃了( t=44.93s ,单独的绿色三角形),表示丢失的报文已经收到(这也表示在 gap 上收到的报文没有被 s2 丢弃,而是为以后的处理保存了起来)。
当 ACK 报文到达 s1 后( t=44.95s ,黄色的十字), s1 知道所有的报文已经收到,并且同一时间发送了好几个报文(垂直线的蓝色点)。这是因为这些重复的 ACK 报文影响了竞争窗口(表示以前的序列号已经被 s2 节点正确接收)——这是快速恢复算法。当然,这些报文在 s2 上是一个接一个的( t=44.98s 的红色点序列是倾斜的),因为都是单独的排队和传输。 s1 上的 TCP 在收到 ACK 后将发送更多的报文( t=45s 后的黄色十字)。
这就是 TCP 运行的所有过程内容。
画图的其他提示
当使用 Plove 来绘制序列图的时候,有用的过滤器是“ divide ”过滤器(可以将字节数转换为报文数)和“ modulo ”过滤器(可以将报文数据按照同样的区间排列,从而方便显示)。为了应用过滤器,在边栏上右键点击矢量,并在上下文菜单中选择“Pre-plot filtering...”。