转载地址:http://www.go-gddq.com/html/YuanMa-ChengXu/2013-03/1112798.htm

uIP是模块化设计的,头文件主要有uip.h、uipopt.h、uip_arp.h、uip_arch.h,核心文件主要包括uip_arp.c、uip.c、uip_arch.c等。另外在源码中给出了几个应用示例,实现了一个简单的http服务器,并且带有部分cgi功能。下面主要结合TCP/IP协议说明各核心文件实现的功能,对代码进行分析。
  
  (1)ARP协议的实现及uIP提供的相关函数。
  
  ARP协议本质是完成网络地址到物理地址的映射。物理地址本例中指以太网类型地址(MAC地址),网络地址特指IP地址。
  
  ARP的以太网封装格式和报文格式如下图所示。

  uIP使用一结构体arp_HDR来表示ARP数据帧。注意arp_hdr数据结构中还包括了以太网的头部,硬件类型和协议类型等,真正的ARP数据帧是从u8_thwlen开始的。

  ethhdr以太网头部。定义的结构体原型如下:

  ·dest,SRC:以太网目的地址和源地址,都是6字节,为FFFFFFFFFFFF时是广播地址,以太网上的所有节点都可以收到。
  
  ·type:以太网头部的类型一般有两种,如果type=0x0806,表明以太网封装的数据部分为ARP数据包,如果type=0x0800,表明以太网封装的数据部分为IP数据包。
  
  ·hwtype:硬件类型字段。指明了发送方想知道的硬件地址的类型,以太网的值为1。
  
  ·protocol:协议类型字段。表明要映射的协议地址类型,IP为0X0800。
  
  ·hwlen:硬件地址长度。指明了硬件地址和高层协议地址的长度。对于以太网上IP地址请求来讲,值为6。
  
  ·protolen:协议地址长度。指明了硬件地址和高层协议地址的长度,对于以太网上的IP地址来讲,值为4。
  
  ·opcode:操作字段,用来表示这个报文的类型,ARP请求为l,ARP响应为2,RARP请求为3,RARP响应为4。
  
  ·shwaddr:发送端的以太网地址(MAC地址),6字节。
  
  ·SIPaddr[2]:发送端的协议地址(IP地址),4字节。
  
  ·dhwaddr:接收端的以太网地址(MAC地址),6字节。
  
  ·DIPaddr[2]:接收端的协议地址(IP地址),4字节。
  
  uIP协议栈中与ARP协议有关的文件有uip_arp.c。文件中主要包含与arp实现相关的数据结构和相关函数等。
  
  uIP协议栈将IP地址和MAC地址的对应关系保存在一个表arp_table[]中。
  
  statICstructarp_entryxdataarp_table[UIP_ARPTAB_SIZE];表中的每一个元素笔者称之为表项,每一个表项保存的是一个IP地址和MAC地址的对应关系,另外还有一个标志时间的8位变量。显然表中每一个元素都是arp_entry型,arp_entry的定义如下:

  下表给出了一个arp_table[]的表格示例

  uip_arp.c又件中主要函数说明如下。

  ①voiduip_arp_init(void)。函数的作用是初始化arp_table,使其中的IP地址全部为0。
  
  ②voiduip_arp_timer(void)。这个函数应该每隔一段时间被调用一次,函数的作用是每调用一次,arp_table中的所有元素的老化时间增加1,然后与最大老化时间UIP-ARP_MAXAGE比较,如果大于这个数值,说明这个arptabl表项太老,于是表项被清0,以用来存放更新的数据。
  
  ③staticvoiduip_arp_update(u16_t*ipaddr,structuip_eth_addr*ethaddr)。函数是只被uip_arp.c文件中的其他函数调用,作用是更新arp_table中的表项。
  
  首先寻找未使用的表项,若找到则将新的arp对应关系加入其中;若未找到,则寻找arp_table中最老的表项,并用新的数据代替。
  
  ④voiduip_arp_iPIN(void)。函数功能是对收到的IP包进行ARP部分的处理。如果收到的IP包中的lP地址在arp_table中存在,则其中相应表项中的MAC地址将会被收到IP包的MAC地址代替。如果arp_table中不存在这个IP地址,则创建一个新的表项。实际上这个函数对收到IP包中的IP地址进行判断是否是本地网络中后,直接又调用uip_arp_update()函数更新arp_table表项。
  
  ⑤voiduip_arp_arpin(void)。函数的功能是对收到的ARP数据包进行处理。当收到一个ARP数据包时,应该调用这个函数处理。如果收到的ARP数据包是一个ARP请求的应答,则在这个函数将更新ARP表。如果收到的ARP包是其他主机发送的请求本机MAC地址的数据包,则这个函数生成一个ARP应答包,且生成的数据包存放在uip_buf[]缓冲中。
  
  当函数返回时,全局变量uip_len的值表明网络驱动是否应该发送一个数据包。如果uip_ler]
  
  不为0,则值就是uip_buf[]中存储的要发送的数据包的长度,数据包就存放在uip_buf[]中。
  
  ⑥voiduip_arp_out(void)。uip_arp_out()函数的作用是对要发送的IP数据包进行预先处理,根据处理的情况决定是否发送一个ARP请求包以得到IP包中对应IP地址主机的MAC地址。如果要发送的数据包的IP地址是在本地网络中(具有相同的子网掩码),则搜索arp_table寻找是否存在相应的表项,如果存在,则为要发送的IP数据包添加上以太网帧头,然后函数返回。
  
  如果ARP表项中不存在相应的表项,则生成一个ARP请求包,以得到要发送IP包目的IP地址的MAC地址。新生成的ARP包存放在uip_buf[]中代替原有的数据。IP包的重发依靠上层协议(如TCP协议)来完成。当函数返回时,全局变量uip_len的值表明网络驱动是否应该发送一个数据包。如果uip_len不为O,则值就是uipbuf口中存储的要发送的数据包的长度,数据包就存放在Iup_buf[]中。
  
  (2)IP和TCP协议简单说明及uIP中相关接口函数的实现。
  
  IP协议是TCP/IP协议族中最为核心的协议。所有的TCP、UDP数据等都是以口数据包格式传输。IP负责将数据传输到正确的目的地,同时也负责路由。lP数据的传输具有以下特点。
  
  传输数据不能保证到达目的地。数据传输的可靠性由上层协议来提供(如TCP协议)。
  
  IP协议传输的数据是无连接的。
  
  TCP协议用于在不可靠的网络上提供可靠、端到端的数据流通信协议。当传输受到干扰或网络故障等原因使传输的数据不可靠时,就需要其他协议来保证数据传输的完整性与可靠性。TCP协议正是完成这种功能。TCP采用“带重传的肯定确认”和“滑动窗口”来实现数据传输的可靠性和流量控制。具体详细过程请读者参考相关资料。
  
  TCP协议是面向连接的数据传输协议。双方通信之前,先建立连接,然后发送数据,发  送完数据之后,关闭连接。通信的双方建立连接之后,数据沿着这个连接双向传送数据,连接的双方通过序列号和确认号来对数据保持跟踪。
  
  序列号说明当前数据块在数据流中的位置。如果第一个数据块的序列号是0,并且有10字节长,那么下一个数据块的序列号应该是10。
  
  确认号表示接受数据的总数。如果初始的序列号是0,并且收到10字节需要确认,则应答中的确认号就是10。因为TCP的数据传输是双向的,每一方对它自己的传输都保留一个序列号和确认号,并且每一方都对从对方节点接收来的序列号和确认号进行跟踪TCP的操作可以使用一个具有11种状态的有限状态机来表示。
  
  各状态的描述如下表所示。

状态 描述
CLOSED 关闭状态
LISTEN 监听状态,等待连接进入
SYN RCVD 收到一个连接请求,尚未确认
SYN SENT 已经发出连接请求,等待确认
ESTABLISHED 连接已经建立,正常数据传输状态
FIN WAIT1 (主动关闭)已经发送关闭请求,等待确认
FIN WAIT2 (主动关闭)收到对方关闭确认,等待对方关闭请求
TIMED WAIT 完成双向连接,等待所有分组结束
CLOSING 双方同时尝试关闭,等待对方确认
CLOSE WAIT (被动关闭)收到对方关闭请求,已经确认
LAST ACK (被动关闭)等待最后一个关闭确认,并等待所有分组结束

  相关信号说明如下:
  
  SYN初始同步消息。    ACK确认。
  
  FIN  最后关闭消息。  RST  强迫关闭信号。
  
  下面主要对IP、TCP协议的数据包格式进行说明。IP和TCP数据帧的以太网封装格式如下图所示。

  IP和TCP数据帧头部格式如下图所示。

  uIP并没有单独将IP帧拿出来进行单独处理,定义数据结构时直接将IP和TCP部分的头部放在一个结构体uip_tcpip_HDR中:

  .Vhl:版本号和首部长度。目前的协议版本号是4,首部长度是以32为单位的IP数据帧头长度,在没有选项时,首部长度字段中的值是5。
  
  ·tos:服务类型。uIP中未使用,数值为0。
  
  ·len[2]:16位总长度。指的是整个IP数据帧的长度(包括IP帧头)。
  
  ·ipid[2]:ipid是一个无符号数,属于同一个报文的分段具有相同的标识符。IP协议每发送一个IP报文,则要把该标识符的值加1,作为下一个报文的标识符。
  
  ·Ipoffset:前3位的标志只有低两位有效。
  
  第1位:最终分段标志。若为0,表明该分段是原报文的最后一个分段。
  
  第2位:禁止分段标志。当该位为1时,该报文不能分段。假如此时IP数据包的长度大于网络的MTU值,则根据IP协议丢弃该报文。
  
  ipoffset后13位表示分段偏移,以8B为单位表示当前数据报相对于初始数据报的开头位置。
  
  ·Ttl:数据包生存时间。表明数据在进入网络后能够在网络中存留的时间,以秒为单位,最大为255。当为0时该数据包被丢弃,数据报每经过一个路由器时,该值减1。这样,当循环传送的数据包最后总是会被丢弃。
  
  ·proto:协议类型。该字段的内容指出IP数据报中数据部分属于哪一种协议(高层协议)。例如0x06为TCP协议,0x01为ICMP协议,0x17为UDP协议等。
  
  ·ipchksum:头部校验和。用于保证IP数据帧头部数据的正确性。此校验只是针对IP数据帧头。如果校验失败,数据包将被丢弃。计算方法为,把IP数据帧头作为16位二进制数(校验和本身部分字段设为0),对首部每个16位数进行二进制反码的求和,即得到校验码。接收方收到数据包后,同样对首部每个16位数进行二进制反码的求和(这次包括校验码),如果无误,结果的16位二进制数全为1,否则证明有误。
  
  ·SRCipaddr[2]:源IP地址。
  
  ·destipaddr[2]:目的IP地址。

以下为TCP数据帧头部各部分说明。
  
  ·SRCport:源端口号。
  
  ·destport:目的端口号。
  
  ·seqno[4]:顺序号,用来标识从TCP源端向TCP目的端发送的数据字节流。
  
  ·ackno[4]:确认号,确认号用来指示下一个数据块序列号。
  
  ·tcpoffset:包括4位TCP报头长度和6位保留位。4位头长度字段用来说明TCP报文段头部的长度,单位为由32位组成的字的数目。由于TCP选项字段是可选项,所以TCP报文端的头部长度可变。通常这个字段为空,缺省值为5。uIP中初始数值为5,通过与5比较大小来判断是否有选项数据。
  
  ·flags:标志。包括6位控制位标志字段。为1表明本字段有效,为0表示无效。
  
  ·URG:用来表示报文中的数据已经被发送端的高层软件标志为紧急数据。
  
  ·ACK:用来表示确认号的值有效。如果ACK为1,则表示报文段中的确认号有效;否则,报文段中的确认号无效,接收端可以忽略。
  
  ·PSH:为1表明接收方应该尽快将这个报文段交给应用层而不用等待缓冲区满。
  
  ·RST:用于复位因主机崩溃或其他原因而出现错误的连接,它还可以用于拒绝非法的报文段或拒绝连接请求。为1时表示要重新建立TCP连接。
  
  ·SYN:为1时表示连接要与序列号同步。
  
  ·FIN:用于释放连接,为1时表示发送端数据已发送完毕。
  
  ·wnd[2]:窗口大小。用于数据流量控制。字段中的值表示接收端主机可接收多少个数据块。
  
  ·tcpchksum:16位校验和。用于校验TCP报文段头部、数据和伪头部之校验和。校验和的校验方法与IP头部校验方法相同。伪头部如下图所示.

  ·urgp[2]:16位紧急指针。只有当URG标志为1时紧急指针才有效。紧急指针是一个正偏移量,和顺序号字段中的值相加表示紧急数据最后一个字节的序号。
  
  ·optdata[4]:选项数据。最常见的可选字段是最长报文大小,又称为MSS。每个连接方通常都在通信的第一个报文段(为建立连接而设置SYN标志为1的那个段)中指明这个选项,它指明本端所能接受的最大长度的报文段。
  
  uIP协议栈中使用uip.conn结构体来保存每一个TCP连接的双方IP地址、端口号、顺序号、确认号等。在uIP中定义了一个数组(见uip.c文件中的structuip_connxdatauip_conns[UIP_CONNS])。数组中的每一个元素保存了每一个连接的状态。每一个元素的类型为structuip_conn型,uip_conn结构体定义如下:

  uIP中IP、TCP协议的相关函数大部分都是在uip.c中实现。另外uip.c还包括UDP、ICMP协议的相关函数,本节不对其说明,下面主要对lP和TCP协议的相关函数进行简单分析。
  
  uip.C文件中函数并不多,对相关函数简单说明如下。
  
  ①voiduip_init(void)函数功能:uIP协议初始化。所有监听的端口置0,所有连接的状态置为CLOSED。
  
  ②structuip_conn*uip_connect(u16_t*ripaddr,u16_trport)函数功能:使用TCP协议与远程主机相连。形参分别为IP地址和端口号。
  
  函数入口:ripaddr为要连接的远程主机的IP地址首地址,rport为要连接的远程主机的TCP端口。
  
  函数返回值:如果连接成功,函数的返回值为一个指向建立连接的连接状态的指针,否则返回值为NULL。

  程序中,首先寻找本地未使用的端口,然后在uip_conns[UIP_CONNS]数组中搜索每一个元素,寻找当前未使用的连接,将当前建立的连接状态填入;若没有找到,则寻找数组中重发数据时间最多的一个连接状态元素,将将当前建立的连接状态填入,并返回新建立连接的指针。
  
  ⑧voiduip_unlisten(u16_tport)函数功能是停止监听指定的TCP连接端口。
  
  ④voiduip_listen(u16_tport)函数功能是监听指定的TCP连接端口。
  
  ⑤staticvoiduip_add_rcv_nxt(u16_tn)函数功能是将当前连接的下一个要接收的顺序号加n。只是在uip.c文件内有效。
  
  ⑥voiduip_process(u8_tflag)这个函数完成TCP处理,是uIP中最重要和复杂的函数。为了节省RAM,函数中使用了goto语句。实际上,uIP为了减少堆栈使用,设置了大量的全局变量,虽然程序可读性差,各模块之间联系复杂,但是对RAM的使用大大减少,这在8位和16位系统中是我们所期盼的。
  
  本函数只有一个形参flag,在uIP中flag有几种取值:UIP_TIMER(值为2)和UIP_DATA(值为1),uip_process函数根据flag判断进行何种操作。
  
  如果flag标志为UIP_TIMER,则对初始顺序序列号iSS(实际上用数组表示的一个32位数)加1,然后判断当前连接的TCP状态,根据不同的状态来实现不同的操作。注意:uip中的应用程序函数也是在uip_process()函数中实现的。uIP自带的一个http服务器,其文件中,如何在uip_process()函数中实现的,说明如下:

  而UIP_APPCALL()又在httpd.h定义如下:

  在httpd.c文件中应用程序函数为:

  因此程序在编译时将UIP_APPCALL替换为httpd_appcall,uip_process()函数中的UIP_APPCALL()被替换为httpd_appcall()。之所以要这么做,是因为当使用另外一个应用程序时,直接将UIP—APPCALL定义为应用程序的函数名称即可。注意,uIP的应用程序只能是一个函数,并且该函数不能有形参和返回值。

接下来函数的作用是检查IP头部的vhl字段的值,该IP包是否是分段的,IP头部的目的IP地址与本机IP地址是否相等,IP头部校验和是否正确等。然后判断上层协议字段,上层协议字段可以是TCP协议、UDP协议(需要根据需要选择了条件编译)和ICMP协议等。
  
  程序依次判断是哪一种协议,是某一种,就跳转到相应的协议处理程序段取处理。下面只对TCP处理程序部分作说明。
  
  程序跳转到TCP输入处理程序部分后,首先检查TCP校验和是否正确,然后寻找当前连接中的活动连接(连接的地址与数据包中的地址相同),若找到,则跳转到找到连接程序处理部分。若没有找到,这个数据包或者是一个重复的数据包,或者是一个SYN数据包,根据结果跳转到相应的程序处执行。
  
  found_listen程序处理部分。如果数据包符合正在监听的一个连接,则跳转到这部分。首先程序寻找uip_conn[]数组中状态为CLOSED的连接或者TIME_WAIT的连接,并用新的状态等填入这个连接状态。再接着,程序执行到tcp_send_synack部分,将uip_buf缓冲中的要发送的数据包的状态变为SYNACK,然后跳转到tcp_send程序处发送数据。
  
  found如果发现一个活动的连接,则跳转到这部分。检查TCP的数据包RST状态位,顺序号是否正确,如不正确,跳转到tcp_send_ack部分请求发送正确的序列号。然后,检查当前数据包的连接响应是否要有新的数据发送,如果有,做要发送数据的准备。
  
  程序接下来是一个switch循环判断语句,这个语句相当长,功能是判断当前连接的状态,根据各种不同的状态进行相应的处理等。
  
  uip_process()函数的最后是tcp_send_ack,tcp_send_nodata等部分。
  
  u16_thtons(u16_tval)这个函数的作用是实现不同字节格式的转换。根据定义的处理器的大小端格式实现处理器字的格式与网络数据的字格式转换。
  
  (3)其他辅助程序。
  
  uIP还包括一个uip_arch.c文件。其中的函数主要实现数据处理、校验和的计算等。
  
  ①voiduip_add32(u8_t*op32,u16_top16)函数的功能是实现32位数的进位加法。op32是定义的op32[4]数组的首指针。由于op32[4]
  
  中每个元素的类型为8位,因此uIP中用4个元素表示一个32位数。程序实现的功能是实现op32[4]数组表示的32位数与op16表示的16位数相加,结果存放在uip_aCC32[4]这个全局数组表示的32位数中。
  
  ②u16_tuip_chksum(u16_t*sdata,u16_tlen)函数的功能是实现数据的校验,被uip_ipchksum()函数调用。
  
  ③u16_tuipjpchksum(void)函数功能是实现IP头部校验和的实现。
  
  ④u16_tuip_tcpchksum(void)函数功能是计算得到TCP头部的校验和。
  
  (4)uIP应用实例:一个简单的WEBSERVER。
  
  uIP0.9版本中应用程序部分带了一个简单的WEB服务器和只读文件系统,实现了简单的CGI功能。在此对其进行了改造,使其代码更简单、便于理解。关于http协议,请读者参阅相关资料,在此不做说明。程序代码如下:

uIP各部分协议代码的分析相关推荐

  1. uIP tcp/ip协议分析及其在嵌入式系统中的应用

    网络技术的发展使越来越多的工业控制设备将网络接入功能作为其必备的特性之一.同样,嵌入式系统的发展,要求其应用能够支持网络功能,为用户提供一个简易方便的可视化图形界面.当前WEB浏览器已经成为用户的合理 ...

  2. 使用udp协议实现服务器端程序时,uIP中UDP协议实现的改进

    摘  要: uIP作为一种广泛使用的轻量级嵌入式TCP/IP协议栈,其UDP协议的实现还不够完善,目前最新的1.0版本中仅实现了UDP客户端,尚没有实现UDP服务端.为此,对其进行了以下三方面的改进: ...

  3. NET Core的代码安全分析工具 - Security Code Scan

    NET Core的代码安全分析工具 - Security Code Scan 原文:NET Core的代码安全分析工具 - Security Code Scan NET Core的代码安全分析工具 - ...

  4. Linux进程通信的四种方式——共享内存、信号量、无名管道、消息队列|实验、代码、分析、总结

    Linux进程通信的四种方式--共享内存.信号量.无名管道.消息队列|实验.代码.分析.总结 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须 ...

  5. 【u-boot】uboot代码简要分析 (u-boot 移植)

    uboot代码简要分析 (u-boot 移植) 2012-12-19 22:46:04 [转] 先来看看源码目录结构,再按照代码的执行顺序简单地分析源码 1.U-boot源码整体框架 源码解压以后,我 ...

  6. 计算机网络协议测试技术分析

    摘要:介绍了协议 测试技术以及相关标准进展情况,着重介绍了协议一致性测试和互操作性测试,分析了二者的差异,明确了它们之间的关系.便于进一步理解协议测试技术. 关键词:协议测试:互操作性测试:计算机网 ...

  7. Http实战之无状态协议、keep-alive分析

    Http1.1特性 无状态的协议 HTTP 是一种不保存状态,即无状态(stateless)协议. HTTP 协议自身不对请求和响应之间的通信状态进行保存.也就是说在 HTTP 这个级别,协议对于发送 ...

  8. 西门子S7协议底层原理分析

    -Begin- 前言 前面我们对ModbusRTU协议.ModbusTCP协议.欧姆龙FinsTCP协议.三菱SLMP协议都做了说明: 今天我们来分享一下关于西门子S7协议的通信分析. 西门子作为一个 ...

  9. 【Android车载系列】第8章 车载通信-USB协议代码实现

    1 USB协议 1.1 USB协议分层   上一篇已经简单介绍了USB协议的相关知识,其中的描述符较为重要,描述符成功返回,USB通信已经成功了一大半,具体描述符的知识点可以翻阅上一篇来了解.下面我们 ...

最新文章

  1. javascript取得鼠标的位置
  2. 区别用户使用的终端设备代码 转 https://segmentfault.com/a/1190000012957023
  3. 20181127-1附加作业 软件工程原则的应用实例
  4. 看完这一篇,再也不用担心 Git 的“黑魔法”
  5. Centos7 安装pyhton3.7.4
  6. Intel Sandy Bridge/Ivy Bridge架构/微架构/流水线 (18) - 数据预取
  7. python求高阶导数_TensorFlow:计算Hessian矩阵(和高阶导数)
  8. 微软再次“封杀” Flash Player!
  9. 无人车是怎样一步步学会开车的? | 自动驾驶科普
  10. 二叉搜索树+快速排序 查到文本中出现频率最多的100个词 【留学生作业】
  11. Oracle----MLDN
  12. 教程向 | 安装 地表最强IDE---Microsoft Visual Studio 2022
  13. Execl同时冻结行和列
  14. NLTK文本分割器是如何工作的
  15. Playwright之初体验
  16. python花猫_涨见识了,在终端执行 Python 代码的 6 种方式!
  17. Java学习记录——错题总结(十五)
  18. Win10系统pin码忘记了?
  19. Low-complexity Point Cloud Filtering for LiDAR by PCA-based Dimension Reduction
  20. CLIP Learning Transferable Visual Models From Natural Language Supervision (文本和图像的对比学习)--文献翻译和笔记

热门文章

  1. 短线股票怎么操作怎样才能炒好短线股票
  2. 阿里云主机遭受DDOS攻击IP不能使用如何更换弹性公网IP
  3. Python基础练习
  4. iOS开发网络篇 一一 SDWebImage框架的基本使用
  5. android+照片合成,图片合成修图神器手机版
  6. Dockercompose创建redis主从复制
  7. 林纳斯·托瓦兹(Linus Torvalds)为什么被称作大神?
  8. oracle 查询两张表合并,oracle的多表合并查询-工作心得
  9. Python数据挖掘—电力窃漏电用户自动识别
  10. 陶哲轩实分析-第13章 度量空间上的连续函数