冯翠丽,刘波涛,王青海,陈宪超
(1.长江大学 计算机科学学院,湖北 荆州 434023;2.胜利油田钻井工艺研究院信息中心;3. 长沙华德科技开发有限公司)
当前,研究得如火如荼的物联网技术中,首先要解决的一个问题是将嵌入式设备接入网络[1].其中,比较常见的一种实现方案是在嵌入式设备中集成精简后的TCP/IP协议栈而将该设备接入Internet[2-3].在此过程中,如果需要实现基于TCP协议的高层应用,就必须要根据嵌入式设备的具体功能来简化实现TCP协议,因此如何针对该设备的具体应用来有效地简化实现TCP协议就是一个技术难点.
TCP(Transmission Control Protocol)传输控制协议是TCP/IP协议簇的核心协议, 也是TCP/IP协议簇中最复杂的协议.它是一种面向连接的、可靠的、基于字节流的运输层通信协议[4].标准的TCP协议会实现流量控制、滑动窗口协议、拥塞控制、TCP各种计时器、TCP重传、TCP有限状态机及TCP连接管理等等功能[5].
通用计算机系统有足够的资源支持系统在内核中实现复杂的TCP重传机制,然而对于嵌入式Web服务器有限的资源及比较低的处理速度来说,要想实现那些复杂的TCP协议既不现实也没有必要.在研究嵌入式系统TCP协议的实现过程中,需要解决的几个关键问题是:①如何精简传输控制块(TCB);②如何精简TCP协议的几个计时器;③如何简化TCP连接管理,裁剪TCP有限状态机;④如何进行流量控制,简化滑动窗口协议,并实现TCP的重传机制.
TCP首部格式的定义需要遵循RFC 793的规定,可定义如下:
typedef struct
{ WORD SrcePort; //源端口号
WORD DestPort; //目的端口号
LWORD SeqNum; //系列号
LWORD AckNum; //确认号
WORD LenFlags; //首部长度及标识,首部=LenFlags&0xf000>>10
WORD WndSize; //窗口大小
WORD CkSum; //TCP校验和
WORD UrgPtr; //紧急指针
} _TCP_HDR;
标准的TCP选项有三种:最大报文长度选项、窗口扩大因子选项和时间戳选项.最大报文长度选项用于交互的双方协商TCP数据的最大长度.窗口扩大因子选项用于提高TCP的吞吐量,如果其值为n,则2的n次方与WndSize的积即是新的窗口大小.时间戳选项用于记录往返时间,便于动态地定义超时时间.在嵌入式TCP中,可以使用固定大小的窗口和简单的确认机制来简化程序以节约RAM空间,因此它不需要窗口扩大因子和最大报文长度选项.嵌入式TCP不需要动态定义超时时间,也就是说,它不需要时间戳选项.
为了实现TCP面向连接的、可靠的服务,需要使用一个结构来维持每条连接的相关信息,该结构被称为TCB(传输控制块).针对标准的TCB,每种剪裁的实现都不一样,笔者实现的TCB如图1所示.
图1 标准TCB及笔者实现的嵌入式TCB对比图
笔者裁剪TCB的原则如下:①在标准的TCP服务中,客户端在申请建立连接时和在与服务器建立连接后所使用的目的端口是不同的,前者使用的是HTTP协议的熟知端口80,而后者使用的是一个临时端口,这种方式提高了系统的吞吐量.因此,标准的TCP服务需要记录本地的临时端口,考虑到嵌入式Web服务器的吞吐量不大,因此嵌入式TCB中也不需要记录临时端口;②标准的TCP协议支持多穴功能,因此需要记录本地的IP地址.而嵌入式Web服务器没有必要实现多穴功能,因此可以将该IP地址设置成一个全局变量,从而使得每个连接的本地IP都可以使用此变量.也就是说,嵌入式TCB不需要定义本地IP字段;③由于笔者没有使用多任务OS,所以标准TCB中的进程号没有意义;④协议栈没有进行Socket封装,故不需要接口号;⑤因为采用了零拷贝的封包解包技巧[6],因此没有必要定义缓存指针及缓存大小;⑥笔者使用了简单的确认机制[6],而避免了实现复杂的滑动窗口协议,因此本地窗口及远程窗口没有意义;⑦笔者没有实现标准TCB中的坚持计时器[5],因此不需要定义往返时间.
在标准的TCP服务中,实现保活定时器是为了防止两个TCP之间的连接长时期的空闲.假如一个客户端打开了一个到服务器的连接,传送一些数据后就出了故障,那么这个连接将永远的处于打开状态,这对服务器来说是一个资源的浪费.并且,从安全性的角度考虑,这种服务器容易受到类似的攻击从而无法完成正常的服务.为了避免这种情况的发生,通常在服务器端设置一个保活定时器,每当服务器收到客户端信息时都将该定时器复位.超时时间通常设置为2小时,若2小时后服务器还没有收到客户端的信息,它就发送10个间隔为75秒的探测报文.在此期间,如若服务器仍然没有收到客户端的响应,它就会认为客户端出现故障而主动终止该连接.
嵌入式TCP服务中也会出现上述问题,因此,笔者在嵌入式TCP中也需要实现简化的保活定时器:①2个小时的超时时间对嵌入式Web服务器来说太长,因为它是一种检测、控制的工具,连接时间越长,其安全性越差,这个时间应根据具体的应用通过实验的方法来获取比较合理.笔者经过大量的实验表明,该值取20分钟比较合理;②嵌入式Web服务器没有必要发送探测报文,当保活定时器超时后可直接复位客户端来关闭该连接.
标准的TCP使用了慢启动的滑动窗口机制,它允许发送方在等待一个确认之前发送多个报文.对于使用了滑动窗口的TCP连接,其确认是一种批量报文的确认.考虑到嵌入式处理器要对多个数据报连续传输进行维护和处理,困难较大.仔细考察滑动窗口协议可以发现,滑动窗口的一个极限情况就是对每个报文都对应发一个确认,使用这个方法后,所有的处理只是针对单个数据报的发送进行确认.这样一来,既节约了系统的资源,又便于维护连接.当然了,为了协议的兼容性,需要在通信的客户端也使用简单确认的方法.因为如果客户端使用了较大的窗口,就可能造成服务器被淹没.
遵循以上思路,笔者采取了如下方式来实现TCP的简单确认机制:①在连接建立初期,服务器通过TCP的最大报文长度选项来通知客户端,它以后的每个报文的最大长度都是一个定值M;②不允许交互双方的任何一方使用窗口扩大因子;③在三次握手[7]时的ACK+SYN报文中,将WndSize字段的大小取固定值W,通知客户端其滑动窗口的大小是W;④让W≤M,使得客户端在每收到一个报文后就给服务器发送一个对应的确认.W和M的值理论上都可以取到65536,考虑到底层网络是以太网,其最大传输单元MTU等于1518,为了避免IP包被分片传输,发送的TCP包大小不能超过1518字节.而最小以太首部、IP首部和TCP首部的长度和为54,还考虑到在建立连接初期要用到四字节的TCP最大报文选项,故发送TCP包的有效载荷数据长度最大只能取1518-54-4=1460字节,笔者建议取W=M=1400.
TCP是一种面向连接的服务.“面向连接”就意味着[8]:①客户端和服务器彼此交换TCP数据之前,必须先建立一个TCP连接;②建立连接后进行数据传输;③数据传输完毕后必须终止连接.其中,连接的建立是通过三向握手来建立的;连接的终止由四向握手而正常终止,也有可能由异常复位而带来异常终止;为了清楚地跟踪这三个阶段中所发生的不同事件,TCP使用了TCP有限状态机.
三向握手是客户端主动打开而服务器被动打开连接的情况,还有一种情况是双方同时主动打开.为了减少程序的复杂度,笔者实现的服务器不支持这种主动打开,也就是说,它只被动的接收客户端请求.
针对嵌入式Web应用,笔者简化了四向握手的过程,当服务器收到客户端的FIN报文后,直接将ACK报文和FIN报文合为一个ACK+FIN报文发送给客户端.也就是说,笔者设计的服务器不支持连接的半关闭和主动关闭.
在标准的TCP服务中,服务器在连接发生以下情况之一时会向客户端发送RST报文,使之能够异常地终止一个不正常的连接:①客户端TCP请求了一条到服务器并不存在的端口的连接;②服务器发现客户端TCP已经空闲很长一段时间;③服务器侦测到异常事件,并愿意异常终止该连接.考虑到嵌入式Web服务器的需求,针对上述标准的要求,笔者简化如下:①由于嵌入式Web服务器没有使用临时端口,因此,目的端口不是80的数据包就不可能是TCP包,服务器可以简单丢弃,不作任何处理;②由于嵌入式Web服务器的保活计时器超时时间设置得比较短,服务器能够及时发现客户端TCP已经空闲,从而发出RST报文来关闭该连接;③服务器的异常事件很多,限于嵌入式Web服务器的应用需求,只需要处理必要的异常事件:收到的报文没有合法的ACK号;重发计时器超时并且重发次数超过规定值.
图2 笔者裁减后的TCP有限状态机
TCP有限状态机越复杂,维护的开销就越大,对系统的存储能力和运算能力要求就越高.标准的TCP有限状态机太过复杂,笔者精简如图2所示.当发生以下事件之一时,发生如图2中的异常事件:收到RST报文、收到的报文没有合法的ACK及保活计时器超时.
TCP的重传机制是TCP成为一种可靠协议的基础.在标准的TCP协议实现中,重传机制的核心是计算RTO(Retransmission TimeOut),具体计算过程参见参考文献[4].而对于嵌入式Web服务器有限的资源及比较低的处理速度来说,花费巨大的代价来计算RTO并不值得.因此,精简TCP的重传首要任务是需要寻找一种简化的方法以便于很容易地得到RTO.这里,笔者采用了最简化的方式,即取固定的大小的RTO.
图3 笔者设计的TCP重传模块顺序链表结构示意图
RTO确定之后,剩下的就是当重传计时器超时后进行重发TCP包的操作.可以有很多方法来实现该重发操作.笔者的设计思路是利用顺序链表记录每个已发送的TCP数据包,而当收到TCP确认报文时就从该顺序链表中删除相应的TCP数据包.同时,每隔一个RTO时间就触发一个TCP重传事件,以发送那些已经超时但没有收到确认的TCP数据包.其中,顺序链表的结构如图3所示.顺序链表的头指针、链表长度、该TCP数据包中TCP层数据部分的长度分别由TCB中的MemPoolHeader、MemPkgNum、TcpDataLen字段给出,如图2所示.图3中,BuffPtr和TotalPkgLen字段定义了可能需要重发的TCP报文的首地址和总长度,这便于直接调用发送驱动函数进行重发操作,也便于在ARP解析失败后直接进行ARP重传操作的处理.Next字段将该连接中所有可能需要重发的TCP报文都挂在一个顺序链表上.ExpectAck是该结点中的关键字,便于在收到TCP确认后进行相应的删除操作.
根据嵌入式TCP协议的特点,笔者进行了三种测试:①跟踪三向握手及对应的状态图变迁;②测试TCP保活计时器;③跟踪TCP重传.这里采用测试方法是将嵌入式Web服务与PC机在RJ-45口及串口分别对接[9].利用串口精灵接收嵌入式Web服务器的输出并显示,利用Sniffer Pro抓取PC机发送和接收的数据包.
图4 服务器收到的SYN报文
如图4所示,在5.058s时,服务器TCP收到了客户端TCP发起SYN请求,其源端口是1507,包序号是291941000,这个包首部长度是28字节,由于笔者设计的系统不支持接收包的TCP选项,故该包的此选项被忽略.在收到这个包后,服务端TCP有限状态机就变成了SYN_RCVE;接着由服务器TCP产生一个SYN+ACK报文,其序号字段值是640001,确认号是291941001,如图5所示.
图5 服务器发送SYN+ACK报文
图6 服务器收到ACK报文
图7 客户端发送的HTTP请求
然后是客户端给服务器发送ACK报文以确认服务器到客户端的连接,其序号是291941001,确认号是640002,服务器在收到该报文后,其TCP有限状态机变迁为ESTABLISHED,如图6所示.当建立连接后,客户端紧接着就发送了HTTP请求,如图7所示.
由上述测试可以得出结论:笔者设计的TCP连接管理模块工作正常,并且在此期间其TCP有限状态机按照笔者设计的方式进行变迁.
为了方便测试,笔者做了如下设置:让保活计时器每隔5s轮询一次,并设置其超时时间值为15s.这时输出的信息如图8所示.从该图的第3、第4及第7个报文后输出的信息(即“TCP ActiveTimer update!”)可以看出,在建立连接后,每当TCP收到一个包,它都要更新该连接的保活计时器.在收到客户端最后一个包(即图中的第7个包)后,每隔5s轮询一次Inactivity_Tcp()函数,在该函数中Conn[0].ActTimer字段被减一,当其减至0时,说明保活计时器超时,于是发送RST报文(对于图中的第8个包)到客户端.接着关闭服务器端的该TCP连接,并使TCP状态变成LISTEN态.此后,由于此时没有TCP连接,故没有输出相关的信息.
由上述测试可知,TCP保活计时器工作正常,并且当保活超时时,TCP有限状态机按预期地方式变迁到LISTEN态.
图8 测试TCP保活计时器输出信息
图9 测试TCP顺序链表时的输出信息
图10 测试TCP重传计时器输出信息
由于笔者设计的系统中,TCP重传的实现依赖于顺序链表,因此,为了使服务器的重发模块运行稳定,测试顺序链表的操作就必不可少.测试顺序链表时输出的信息如图9所示.每当发送一个非RST型的TCP报文都被插入到顺序链表中,如图9中信息“Insert_Mem(): firstly insert PkgNum=6”表明:第2个TCP报文被插入到顺序链表的第一个位置();而每当收到报文时,就用该报文的ACK号在顺序链表中找到该报文,如“Search_Mem(): find PkgNum=6 Pkg”;接着删掉顺序链表中该报文以前的TCP报文,如“Delete_Mem():the first Pkg of PkgNum=5 deleted”和“Delete_BeforSeqMem(): total 2 pkgs are deleted”表明服务器利用收到的报文9的ACK号删掉了顺序链表中对应的报文5和报文6;信息“Free_Mem(): free the PkgNum=5 success”则表明该包的空间被成功地释放.
由于将系统直接接到PC上时,网络环境比较稳定,为了迫使TCP启动重传模块,可以在服务器TCP层发送数据时人为将系统与PC机的网络断开一会儿后迅速连上.此时,调试信息如图10所示.由图10可以看出,由于没有及时收到客户端的ACK而导致重发了报文5和报文6,并且在收到的报文9后删掉了顺序链表中对应的报文5和报文6.
通过上述跟踪TCP收发包的过程及重传操作的测试可知,顺序链表的操作无误、TCP重传模块工作正常.
笔者详细讨论了如何设计并实现精简的TCP协议,并做了相关测试,验证了该设计思路的可行性.这为嵌入式设备中顺利实现嵌入式TCP/IP协议栈,进而实现物联网技术打下了坚实的基础.
参考文献:
[1]International Telecommunication Union UIT[R].ITU Internet Reports 2005: The Internet of Things. 2005.
[2]冯翠丽,刘波涛.一种嵌入式TCP/IP协议栈的设计与实现[J].长江大学学报(自然科学版)理工卷,2008,5(4):331~333.
[3]李金梁,景博.嵌入式Internet中TCP协议的设计与实现[J].微计算机信息,2005,21(7):40~138.
[4]RFC793 - Transmission Control Protocol [S]. IETF,1981.
[5]谢希仁.计算机网络[M] .北京:电子工业出版社,2008:187~219.
[6]刘波涛.物联网中嵌入式TCP/IP协议栈的设计技巧[J].通化师范学院学报,2011,32(2):40~42.
[7]夏春涛,杜学绘,郝耀辉,等.基于.NET 平台的SYN Flood攻击测试的实现[J].计算机工程与设计,2011,32(6):1918~1921.
[8]李立清,路海.应用于嵌入式系统的TCP简化实现方法[J].计算机工程与应用,2004(7):142~145.
[9]刘波涛,冯翠丽,王青海,等.应用RTL8019AS的嵌入式Web服务器硬件实现[J].长江大学学报(自然科学版),2008,5(1),75~78.