uIP1.0用户手册中文版第一章

来源:互联网 发布:小区网络监控方案 编辑:程序博客网 时间:2024/06/05 02:32

uIP嵌入式TCP/IP协议栈

uIP1.0用户手册

 

翻译:Leo

翻译时间:2016-8-1

说明:只翻译了其中的一部分,不是完整翻译

 

Adam Dunkels
adam@sics.se
Swedish Institute of Computer Science

 


 

第一章     uIP TCP/IP协议栈

1.1     简介

传统的TCP/IP实现要求太多的资源,无论是在代码大小还是内存使用情况在8位或16位的系统中。几十k的内存大小和不到100k的代码空间使几百k的代码大小和几百k内存空间要求的TCP/IP全栈在系统上得以实现。

 

uIP的实现被设计成仅具有绝对最小集合的所需的全部功能YCP/IP协议栈。它只能处理一个网络接口,包含IP,ICMP,UDP和TCP协议。uIP是使用C编程语言编写的。

 

许多其他的在小系统上实现的TCP/IP协议假设嵌入式设备总是和实现全栈的TCP/IP设备进行通信。在此假设下,有可能消除了在这种情况下很少使用一定的TCP / IP的机制。许多这些机制是必要的,但是,如果嵌入式设备是与另一个同样有限的设备,例如,运行的分布式对等网络服务和协议时的通信。uIP的设计要符合RFC为了让嵌入式设备作为一流的网络公民。未对任何特定的应用程序量身定制的uIP的TCP / IP实现。

 

1.2     TCP/IP通信

完整的TCP/ IP协议套件由许多协议,从低级别的协议比如ARP其IP地址转换为MAC地址,到应用程序级协议如SMTP,用于传送电子邮件。uIP大多关注的是TCP/IP协议和将被提及的叫做“应用”的上层协议。低层协议经常在硬件或固件中实现,并会由该网络设备的驱动控制的“网络设备”来表示。

 

TCP提供了可靠的字节流至上层协议。它打破字节流入适当大小的段,每段在其自己的IP包发送。IP分组的网络由网络设备驱动器上发送出去。如果该目的地不是物理连接的网络上,IP包是由位于两个网络之间的路由器转发到另一网络。如果其他网络的最大数据包大小是比IP分组的大小较小,则分组被分成由路由器较小的数据包。如果可能的话,被选择的TCP段的尺寸,使得碎片被最小化。该数据包的最终接收方将有所有零碎的IP数据包才可以被传递到更高的层次。

 

TCP/ IP协议栈的协议的正式要求是在一些由互联网工程任务组,IETF发布的RFC文档的规定。

 

RFC 1122的要求,可分为两大类;那些处理主机到主机的通信和那些处理应用程序和网络堆栈之间的通信处理。第一类的一个例子是“一个TCP必须能够接收任何段TCP选项”和第二类的例子是“必须有报告软TCP错误情况的应用程序的机制。”违反第一种要求的TCP / IP实现可以不能够与其他的TCP/ IP实现通信,并甚至可能导致网络故障。第二类的要求的违反将只影响系统内的通信,并不会影响主机到主机的通信。

 

在uIP里面,影响主机到主机的通信的所有RFC的要求落到实处。然而,为了减少码量,我们已删除的应用程序和栈之间的界面的某些机制,如软错误报告机制和类型的服务动态地配置比特TCP连接。由于只有该利用这些功能很少应用中,它们可以在不丧失一般性的去除。

 

1.3     主控制循环

uIP堆栈可以作为在多任务系统中的任务,或在一个单任务系统主程序。在这两种情况下,主控制循环做两件事情反复:

 

检查是否有一个数据包从网络到达。

 

检查是否发生了周期性暂停。

 

如果一个包到达了,输入处理函数,uip_input(),将被主控制循环调用。输入处理函数不会阻塞,但会立即返回。当它返回,传入数据包的目的是在堆栈或应用程序可能会产生那些应该被发送出去的一个或多个应答报文。如果是这样,该网络设备驱动器应该调用发出这些数据包。

 

周期超时依赖于定时器驱动TCP机制,如延迟确认,重传和往返时间的估计。当主控制循环推断周期定时器应该触发(fire),它需要调用时间处理函数uip_periodic().因为TCP/IP栈处理一个定时器事件时可能执行重传,网络设备驱动器应该被调用去发送已经生成的包。

 

1.4结构具体方法

uIP需要运行起来的时候需要很少的函数去使整个结构实现。这些功能应该是为特定的体系结构手动调整,但是通用的C实现的是作为uIP发行版本的一部分。

 

1.4.1 校验和计算

TCP和IP协议实现覆盖TCP和IP数据包的数据和报头部分的校验和。大多数情况下,这意味着校验和计算必须进行微调,在其上uIP堆栈运行特定的体系结构。

 

虽然uIP包括一个通用的校验功能,它也留下它开放的架构具体实施两个功能uip_ipchksum()和uip_tcpchksum()的。些功能的校验和计算可以写成高度优化汇编程序,而不是一般的C代码。

 

1.4.2 32位运算

TCP协议采用32位序列号,以及TCP实现将不得不做一些32位增补正常的协议处理的一部分。

 

1.5内存管理

在uIP所针对的结构中,RAM是最稀缺的资源。只有很少的RAM可以被TCP/IP协议栈使用,传统的TCP/IP协议不能直接被使用。

 

uIP不使用显示的动态内存分配。相反,它使用一个全局缓存来保存包,和一个固定的表格来保存链接状态。这个全局的包缓存区足够大去储存一个最大尺寸的包。当接收到一个从网络上来的包时,设备驱动程序将其放置在全局缓冲区并调用TCP / IP协议栈。当这个包里面有数据时,TCP/IP协议栈会通知相关的应用程序。因为缓存区里面的数据会被下一个到达的数据覆盖,所以应用程序必须立即处理这些数据或者把数据复制到其他的缓存区等待后面的处理。应用程序处理程序包必须排队,由网络设备或者由设备驱动器。大多数单芯片以太网控制器有大到足以容纳至少4最大尺寸的以太网帧的片上缓存。处理器处理设备,例如RS-232端口,可以在应用程序处理过程中把进来字节拷贝到一个单独的缓冲器。如果缓存区已满,输入数据包将被丢弃。这会导致性能下降,但只有当多个连接并行运行。这是因为uIP只有一个非常小的接收窗口,意味着每个连接中只有一个TCP字段。

 

在uIP中,同一个用来接收包的全局缓存区同样用来储存发送出去的TCP/IP的头部的数据。发送数据时,应用程序把一个指针指向数据以及要发送的长度传递到协议栈。TCP / IP报头被写入到全局缓冲器,一旦头已经产生,设备驱动器发送报头和所述应用程序数据在网络上。数据重发的时候没有排队。

 

内存配置决定两个系统应该能够处理的业务量和并发连接的最大数量。

 

1.6 应用程序接口(API)

uIP提供了两种API:protosockets,一个类似于BSD 的没有多线程的开销;一个“raw”基于事件的API比protosockets低级但是使用更少的内存。

 

16.1 uIP的raw API

“raw”的uIP API使用一种事件驱动接口,应用程序被调用去响应一些事件。uIP调用应用程序的情况有:数据被接收,数据已经成功地被传递到连接的另一端,建立了一个新的连接,数据需要被重传。应用程序也周期性的为了新的数据轮询。该应用程序只提供一个回调函数;它是由应用程序来处理映射不同的网络服务到不同的端口和连接。

 

1.6.1.1 应用程序事件

应用程序必须用C函数来实现,UIP_APPCALL(),uIP在事件发生的时候调用。每个事件有一个相关的测试函数用来区分不同的事件。该函数将以C宏定义的形式来实现,以0或者非0的形式来评估。注意某些事件可以相互结合发生。(新数据到达的同一时间可以有数据被确认)。

 

1.6.1.2 连接指针

当应用程序被uIP调用的时候,全局变量uip_conn被指向uip_conn结构体表示目前正在处理的连接,被称为当前连接。一个典型的用途是检查uip_conn->lport(本地TCP端口)来决定哪个服务当前的连接需要提供。例如:应用程序可能决定提供HTTP服务如果uip_conn->lport的值为80或者TELNET服务如果这个值是23.

 

1.6.1.3 接收数据

如果uIP测试函数uip_newdata()不是0,则远程连接的主机已经发送了新数据。Uip_appdata指针指向了实际的数据。数据的大小由uIP函数uip_datalen()来获得。该数据不被uIP缓存,应用程序返回时将被覆盖,应用程序需要立即处理接收的数据,或者自己把它复制到另外的缓存区等待以后的处理。

 

1.6.1.4 发送数据

缓存区的空间大小是由存储器的配置所决定的。因此,应用程序发送的所有数据没有到达接收机,应用程序可能使用uip_mss()函数来看多少数据将实际地由堆栈发送出去。

 

应用程序发送数据使用uIP函数uip_send().uip_send()函数需要两个参数,一个指针指向要发送的数据,数据的长度。如果应用程序需要内存空间来处理实际的要被发送的数据,包的缓存区(uip_appdata指针指向的地方)可以被用于这个目的。

 

应用程序在同一时间内只能发送一个数据块在一个连接上,而且也不能多次调用uip_send()在每次应用程序调用时,数据只有在下次调用时发送。

 

1.6.1.5 重传数据

重传由周期性TCP定时器驱动。每次周期性定时器被调用时,每次连接的重传定时器递减。如果定时器到达0,将要重传。当uIP决定某个数据段需要重传时,应用程序被调用的uip_rexmit()标志位设置,表明重传是必需的。

 

应用程序必需检查uip_rexmit()的标志位,产生之前发送的相同的数据。从应用程序的观点来看,执行重传和原始数据的发送是不一样的。因此,应用程序可以写成发送数据和重传数据用相同的代码。

 

1.6.1.6 关闭连接

应用程序关闭当前的连接通过在应用程序调用时调用uip_close()函数。这将导致连接完全关闭。为了表明一个致命的错误,应用程序可能想断开连接通过调用uip_abort()函数。

 

如果连接已被远端封闭,测试函数uip_closed()返回值为true。然后,应用程序可以做任何必要的清理。

 

1.6.1.7 报告错误

在连接的时候有两个致命的错误,连接被远程终端关闭或者连接重传上次的数据太多次将被中断。uIP调用应用程序来报告这个错误。应用程序可以使用两个测试函数uip_aborted()和uip_timedout()来测试这些错误情况。

 

1.6.1.8 轮询

当连接空闲时,每次定时器触发时,uIP轮询应用程序。应用程序调用函数uip_poll()去检查是否被uIP轮询。

 

轮询事件有两个目的。第一个是让应用程序定期知道连接是空闲的,允许应用程序关闭已闲置太久的连接。另一个目的是让应用程序发送已经产生的数据。应用程序只能通过被uIP调用来发送数据,轮询事件是闲置的连接发送数据的唯一的方法。

 

1.6.1.9 监听端口

调用uip_listen()函数来打开一个新的端口来监听。当一个连接请求到达监听端口时,uIP创建一个新的连接,调用应用程序。如果应用因为新的连接被创建而调用时测试函数uip_connected()的返回值为true。

 

应用程序可以检查uip_conn结构体的lport字段来判断哪个端口有新的连接建立。

 

1.6.1.10 打开连接

新的连接可以从uIP内部打开,通过调用函数uip_connect().这个函数分配一个新的连接,并设置一个标志,这将打开一个TCP连接到指定的IP地址和端口。这个函数返回一个指针到uip_conn结构体,如果没有空闲的连接槽,函数将返回NULL。

 

函数uip_ipaddr()可被用于打包一个IP地址到两个16位的元素中。下面给出两个打开连接的例子:

 

void connect_example1_app(void)

{

if(uip_connect(uip_conn->ripaddr, HTONS(8080))== NULL)

{

uip_abort();

}

}

 

void connect_example2(void)

{

u16_t ipaddr[2];

uip_ipaddr(ipaddr, 192,168,0,1);

uip_connect(ipaddr, HTONS(8080));

}

 

 

1 0
原创粉丝点击