uIP中文协议文档:Ch01

来源:互联网 发布:蜘蛛软件 爬虫软件 编辑:程序博客网 时间:2024/05/22 06:18

uIP中文协议文档:Ch01 

第一章:uIP TCP/IP协议栈

uIP TCP/IP协议栈的目标是:即便是8位微控制器也能够使用TCP/IP协议栈进行网络通信。尽管小而简单, uIP不需要与他们通信的节点配有复杂,全尺寸协议栈,只要通过运行轻量级协议栈能够通信便可。代码只有区区几k字节 ,RAM消耗最低也只有几百个字节。

1.1uIP简介

随着互联网的成功,TCP/IP 协议簇已成为全球通讯标准。TCP/IP 是底层协议用于通过进行网页传输, 电子邮件传送,文件传输以及点对点网络互联。对于嵌入式系统,能够运行本地 TCP/IP 使得系统可以直连企业内部网甚至是全球互联网。嵌入式设备有了全功能的TCP/IP 支持,将可以与网络中的其他主机进行通信。

传统的 TCP/IP 实现,其代码体积大占用资源多,对于8位或者16位的系统来说显得有点吃力。对于仅能容纳小于100k的系统,是不可能移植一个完整的TCP/IP协议栈的。

uIP设计只实现了进行网络通信所需的必要的TCP/IP组件。提供基础的UDP服务,重点是IP,ICMP(ping), 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协议以及高层的协议,我们称之为“应用”,对于底层协议(比如数据链路层协议),这些一般由硬件或者固件实现,我们称之为由网络驱动程序控制的“网络设备”。


图 1-1  TCP/IP 模型

TCP是面向连接的,有保障的通信协议。应用层生成数据向下传递到传输层,根据使用的协议(TCP or UDP)不同加上不同协议头信息,并把数据分割成互联网层所能传递的最大数据单元(MTU,一般默认1500的不是),继续向下传递到互联网层,该层很负责路由(根据IP地址找路),并加上相应的头信息(包含目的地址,源地址等等),然后向下传给网络接口层,该层一般实现为数据链路层和物理层,数据链路层加入和设备相连的链路类型(以太网,还是点对点链路?),并把它们加入头部;物理层就是把bit流转换成电压电流信号发出去;对面相连的设备进行相反地操作。

协议的相关文档以RFC形式发布,看TCP/IP协议最权威的就是看RFC文档了。可以看看RFC1122文档,他定义了端到端的通信和模型中层与层之间的通信所应遵循的。

在uIP中,影响host-to-host通信的RFC要求都实现了,只是为了减小代码体积,移除了不必要的应用程序和协议栈之间的接口,比如软件错误报告机制和动态的TCP连接相关的服务类型配置。因为很少有软件使用这方面的机制,移除了也不失一般性。

1.3 内存管理

uIP所针对的目标架构,其RAM都是稀缺的。这样TCP/IP协议栈能用到的RAM就非常有限,也许就那么几K。也就是不能像传统的TCP/IP实现那样,资源不够。

uIP并不显式的使用动态内存分配。相反,他使用一个单独的全局buffer:uip_buf来处理数据包,同时使用一个固定大小的表来保持连接状态。包缓存buffer被设计的足够大可以容纳最大的包分组。当网络设备接收到数据时,网络驱动程序会将数据放到这个全局的buffer中,然后调用TCP/IP协议栈,进行相应处理。这里有一个linux内核架构图,也许对你了解应用程序、协议栈、驱动、控制器之间的关系有所帮助。如果包中有数据(有可能只是握手协议数据,没有应用数据),应用程序需要尽快的从buffer中取走数据处理或者复制到第二缓存中去稍后处理,因为只有一个全局buffer,不然的话可能会被下一个包覆盖掉。直到数据被应用程序处理完之前,包缓存是不会被新包覆盖的(?),如果应用程序正在进行处理数据,后续的包必需进行硬件级或者驱动级的排队。大多数的以太网控制器有一个硬件缓存至少可容纳4个最大尺寸的以太网帧。如果缓存满了,后续的包就会丢弃。只有当打开多个连接时,才可能出现这种情形以致影响性能。原因在于uIP通告了一个很小的接收窗口(窗口,协议中的概念),也就意味着每个连接上只有个TCP分组。

在uIP中,用于接收包的全局包缓冲buffer同样适用于数据报头的发送。假如应用程序需要发送动态大小的数据,可能会使用不作为报头临时缓存的全局缓存的一部分。为了发送数据,应用程序会向协议栈传递指向数据的指针和数据的长度。一旦TCP/IP报头生成并复制到全局缓存后,设备驱动就会发送出报头和数据(数据不是被一层层的协议头封装的么)。如果需要重传数据只能重新生成数据了,而不会有队列等待的。

uIP的内存消耗很大程度上取决于实现其的设备上应用程序的情况。内存配置决定了系统所能处理的流量值和同时在线连接的数量。在发送一个超大邮件的同时还运行着大量动态网页请求的web服务器所消耗的内存肯定要比只运行简单Telnet服务器的大的多。仅有200字节RAM的设备上运行uIP也是可以的,只是这种配置极大的影响了网络的吞吐以及可同时在线的连接数。

1.4 应用程序接口(API)

应用程序接口 (API) 定义了应用程序如何和TCP/IP协议栈进行交互。最长使用的API是大多数unix类系统中BSD风格的套接字API,而且对Windows下的WinSock API也有着巨大的影响。因为套接字API使用stop-and-wait语义,因而需要多任务操作系统的支持。这样一来,繁重的任务管理以及上下文切换,任务栈分配对于uIP的目标群体来说是不现实的,也就是说BSD套接字对于uIP的不合适的。

相反,uIP使用事件驱动接口,有事件发生时相应的应用程序(UIP_APPCALL( ),如无标注下文中的应用程序均指UIP_APPCALL宏对应的函数)会被调用。一个被实现为C函数运行在uIP顶层的应用程序会被uIP调用来响应特定的事件。就像telnet.h文件中的:

#ifndef UIP_APPCALL

#define UIP_APPCALL     telnetd_app

#endif

当接收到数据、成功将数据发送到连接的另一端、建立了一新连接亦或是重传了数据时,uIP就会调用相应的应用程序(uip_process->UIP_APPCALL();)。应用程序同时会周期的轮询以查看是否有新数据到达。由应用程序映射不同服务到不同端口和连接,对于栈只提供一个回调函数。也就是说尽可能的减少协议栈的响应时间,既便是低端的系统也能做到快速响应到来的数据或者连接请求,处理数据就推到上层去吧

不像其他的TCP/IP协议栈,uIP重传机制要借助于应用程序。像有的TCP/IP协议栈会保留一个要发送数据的副本在内存里直到收到对方的接收成功反馈才丢到数据,即便需要重传时应用程序生成数据速度很快,因为内存多多保存会儿没事,要重传直接发送就得了。

前文说了,uIP的目标架构起RAM都是很少的,保存个副本等待反馈是不明智的。uIP的作法同样是把这事推给上层去做:应用程序负责生成数据并重传。当网络设备驱动将数据发送出去之后,uIP并不追踪包的内容,丢了还是怎么滴,他只要求上层的应用能够积极参与重传便好,各司其职嘛。当uIP决定要重传某个片段(segment)时,会设置重传标记(UIP_STAT(++uip_stat.tcp.rexmit);)然后告诉应用程序:那个谁把这个重传一下。应用程序就检查一下这个标记,然后就生成数据重传。从应用程序角度看重传无异于数据原始发送,就是说重传的代码和发送数据的代码是可以复用的。尽管重传是应用程序进行的,但何时需要重传,协议栈必需负起责任。如果协议栈什么也不做会因为应用程序参与重传而增加其复杂度。

1.4.1  应用程序事件 
当有事件发生时,uIP 就会调用有C 语言实现的应用程序(这里应用程序指的是上层处理协议栈 接收到的数据的函数,比如telnet  中的telnet_app )UIP_APPCALL() ,是以C 语言宏的形式定义 的,在具体的应用协议(telnet ,smtp )中会检查宏定义,未定义则赋值。区别不同的事件是由相 
应的响应测试函数完成的。 
1.4.2   连接指针 
一旦应用程序被uIP 调用,全局变量uip_conn 会指向表示当前连接的uip_conn 结构(uip_connect() 中会返回该结构)。uip_conn 结构中的域可以告知当前所连接的ip 地址,以及通过uip_conn->lport 来获得提供的服务。比如,如果lport 为80 则对应的是http 服务;如果是23 则是telnet 服务。
1.4.3   接收数据 
uIP 通过调用 uip_newdata() 可以知道远程主机已经发送了新的数据,其长度可以通过调用 uip_datalen()来获得。uIP 并不会缓存数据,而且一旦从应用程序返回,数据就会被覆盖,因此, 应用程序要么直接处理到来的数据,要么复制到其他什么地方去稍后处理。 
1.4.4   发送数据 
当发送数据时,uIP       会根据可利用 buffer    大小以及 TCP     窗来调节应用程序发过来的数据。通过 uip_mss() 函数来获得当前连接所能传输的最大分组大小。 应用程序通过调用uIP  中uip_send() 函数发送数据。uip_send()使用俩个参数:要发送的数据指针 
和数据的长度。如果应用程序要RAM 空间来生成实际的数据,可以使用包缓存(指向uip_appdata 指针)来实现。 在一个连接上,应用程序一次只能发送一块数据,在数据发送完成前,同时对 uip_send()进行多 次调用时不可能的。 
1.4.5   数据重传 
重传是由TCP 计数器来驱动的。每一次对周期计数器的调用,都会对每连接重传计数器进行减一 操作。当重传计数为零时,就需要进行重传了。uIP 不追踪由网卡驱动发送出去的包,这就要求 应用程序能够积极参与重传。当 uIP 决定需要重传时,应用程序通过 uip_rexmit()来检测是否有 UIP_REXMIT 标志,有则开始重传。telnet.c 文件中: 

if(uip_rexmit() || uip_newdata() || uip_acked()) { 
  senddata(s); 
 } else if(uip_poll()) { 
senddata(s); 
 } 

1.4.6   关闭连接 
应用程序通过调用uip_close()来关闭当前的连接: 
if(s->flags & FLAG_CLOSE) { 
    uip_close(); 
    return; 


  } 
也可通过调用uip_abort() 函数来终止产生了致命错误的连接: 
if(s->flags & FLAG_ABORT) { 
    uip_abort(); 
    return; 
  } 
如果连接已由远端关闭,uip_closed()返回1,应用程序可能会做必要的清理。 
1.4.7 差错报告 
uIP 通过uip_aborted()和uip_timeout()来测试是否发生了相应的错误。 
1.4.8 轮询 
当连接处于空闲状态时,uIP 会周期的轮询各个应用程序。应用程序则通过uip_poll()来检测是否 正在被uIP 轮询,轮询就采取动作。 
轮询有俩个主要作用:一、让应用程序可以可以关掉空闲太久的连接;二、让准备好数据的应用 程序可以发送数据。应用程序只有被uIP 调用的时候才能发数据,也就是说想在空闲连接上发送 数据只有等到轮询到的时候才行。 
1.4.9 监听端口 
uIP 维护着一个TCP 端口监听列表。uip_listen()用于向uip_listenports[UIP_LISTENPORTS] 数组添 加一个新的监听端口。当一个在指定端口上的连接请求到达时,uIP 会创建一个新的连接并调用 相应的应用程序。一个连接已建立时,uip_connected()会返回真。 
检查 uip_conn   结构中 lport   域可以知道哪个端口和当前连接相对应。这个过程是在 uip_connect(*ripaddr ,rport) 函数中完成对uip_conn 结构赋值,并建立一个连接:通过找一个未使 用的最小的端口号赋给lport ,就实现和rport 对应。 
1.4.10 打开连接 
uIP 可以通过uip_connect()打开一个新的连接,正如上小节说的uip_connect()会分配一个本地端口 号,初始化一些域,设置tcp 状态标志位,赋值ripaddr 等等,最后返回表示一个连接的uip_conn 
结构。当然如果最大连接数已满就返回NULL 指针。 
因为uIP 用包含俩个 16bit 元素的数组来表示一个32 位的ip 地址,使用uip_ipaddr()可以用来封 装一个ip 地址到2 个 16 位的。 
下面展示了两个例子,第一个展示了使用uip_connect() 尝试建立一个到TCP 端口号为 8080  的连 接,如果达到最大连接数,则其返回NULL ,并且使用uip_abort()来中止当前连接。 
void connect_example1_app(void) { 
if(uip_connect(uip_conn->ripaddr, HTONS(8080)) == NULL) { 
uip_abort(); 


第二个例子展示了如何打开一个到指定ip 地址的连接,该例中没有进行错误检查。 
void connect_example2(void) { 
u16_t ipaddr[2]; 
uip_ipaddr(ipaddr, 192,168,0,1); 
uip_connect(ipaddr, HTONS(8080)); 

1.5   uIP 设备驱动

 


从网络设备驱动角度看,如图1-5,uIP  由uip_input()和uip_periodic()这两个C 语言函数组成。当 接收到IP 包时设备驱动会调用uip_input()来把包放到包缓存uip_buf  中。uip_input() 负责处理包, 当他返回时往外发送的数据也许也准备好放在 uip_buf 中了。接着网络设备驱动应该吧数据发出 去。 uip_periodic()会周期的轮询各个连接,典型的是每隔 1s 进行轮询。uIP 用该函数来驱动协议计数 器和重传,一旦他返回了,需要的包数据可能已经在uip_buf  中了。 
1.6   架构相关的函数 
uIP 要求那些打算运行uIP  的目标架构需要实现架构相关的函数。uIP 代码中也提供了一些通用的 C 实现函数,在uip-arch.c 文件中实现了简单的CRC 校验。 
1.6.1 校验和计算 
TCP 和IP 协议为TCP 和IP 的数据和协议头包实现了校验和。由于该校验和是通过对发送和接收 数据一个字节一个字节的进行计算,故而效率必需高效。也就是说uIP  的目标架构对于校验和的 计算也是架构相关的。进而 uIP  没有实现架构相关的校验和计算函数,在 uip-arch.c  文件中,你 必须实现 uip_ipchksum()和 uip_tcpchksum()这俩个架构相关的函数。处于对效率的考虑,你可以 
用汇编而不是C 语言来写。 uIP 发行版中提供了一个用C 语言实现的校验和计算函数,同样是在uip-arch.c 文件中。 
1.6.2 32 位运算 
因为TCP 协议使用32bit  的序列号,所以任一个TCP 实现作为对常规协议的处理都会涉及到32 位的计算。但是uIP  的目标架构一般都是8 位或者 16 位的,因此uIP  的主代码并没有涉及到32 位的计算,而是把这块留给了架构相关的代码去负责。 
架构相关的代码中必需实现uip_add32()这个用来进行32 位加法的函数,其结果存储在uip_acc32 这个全局变量中了。 
1.7  实例
这一小节中介绍一些非常简单的uIP 应用程序,在uIP 发行包中包含了一些更复杂的应用,telnet , smtp 等等。 

1.7.1 一个非常简单的应用程序 
第一个简单的应用程序用来监听 1234  端口上的连接。一旦连接建立完成,他会用“ok ”来应答 所有发给他的数据。 
void example1_init(void) { // 初始化函数 
uip_listen(HTONS(1234)); // 在端口 1234 上监听 

// 这意味着你需要在应用程序文件中进行如下的宏定义: 
// #ifndef UIP_APPCALL 
// #define UIP_APPCALL example1_app 
// #endif 

void example1_app(void) { 
   if(uip_newdata() || uip_rexmit()) { //有新数据要发送或者需要重传了 
     uip_send("ok\n", 3);   // 发送“ok ”
    }

初始化函数调用uIP  的uip_listen() 函数来注册一个要监听的端口号。应用程序example1_app()用 uip_newdata()和uip_rexmit()来判断被调用的原因。如果是连接另一端发送了数据,就用“ok ”来 应答。如果是数据丢失需要重传也用“ok ”进行应答。例子已经展示了一个完整的应用范本。并 没有限定应用程序必须实现所有的事件类型比如uip_connected()或者uip_timedout() 。 
1.7.2 一个高级应用 
第二个例子展示了uip_conn 结构体中应用程序状态域是如何使用的。 和第一个例子相似,当监听的端口上有数据发来时就用“ok”进行应答。最大的区别在于,第二个 例子还会在连接建立完成时输出一个“Welcome !” 。 
尽管看上去没啥区别,但是这一点细微的变化却对应用程序的实现产生了不小的影响。复杂度增 加的原因在于如果网络中的数据丢失了,应用程序必须知道哪个数据需要重传。如果是“Welcome !” 消息丢了,应用程序必须重传welcome,如果是“ok”丢了,就得重新发送ok 。 
如果远程主机没有对“Welcome !”作出应答,应用程序可一断定数据丢读了。但是一旦主机作出 应答就可以确信丢掉的数据是“ok” 。这样应用程序就处在俩中状态之一:WELCOM-SENT , welcome  已经发出去,但是没有收到应答;WELCOME-ACKED ,发送成功且收到应答。 
当远端主机成功连接到应用程序,应用程序会发送“Welcome !” 并且把自身状态设置成 WELCOME-SENT 。当远端主机应答成功,状态变成WELCOME-ACKED 。如果主机发送进一步 的数据,应用程序就以发送“ok”来应答。 如果请求应用程序重传上一个消息,应用程序首先看一下他的状态,如果是 WELCOME-SENT, 他就知道 welcome      发送出去了但是没有收到应答,该重传的是“welcome !” 。如果处在 WELCOME-ACKED 状态,则需要重传的是”ok“ 。 
应用程序实现如下,一些配置信息紧随其后(一个简单的状态机): 

struct example2_state { 
    enum {WELCOME_SENT, WELCOME_ACKED} state; 
}; 

void example2_init(void) { 
   uip_listen(HTONS(2345)); 

void example2_app(void) { 
    struct example2_state *s; 
    s = (struct example2_state *)uip_conn->appstate; 
    if(uip_connected()) { 

 s->state= WELCOME_SENT;

              uip_send("Welcome!\n",9);

              return;

       }

       if(uip_acked()&& s->state == WELCOME_SENT) {

              s->state= WELCOME_ACKED;

       }

       if(uip_newdata()){

              uip_send("ok\n",3);

       }

       if(uip_rexmit()){

              switch(s->state){

              caseWELCOME_SENT:

                     uip_send("Welcome!\n",9);

                     break;

              caseWELCOME_ACKED:

                     uip_send("ok\n",3);

                     break;

              }

       }

}

配置:

#define UIP_APPCALL     example2_app

#define UIP_APPSTATE_SIZE sizeof(structexample2_state)

1.7.3   如何区分不同的应用程序

如果一个系统要运行多个应用时,区分他们做好的方法就是用TCP端口号。下面的代码显示了如何将上面俩个例子绑定到一个应用上去:

void example3_init(void) {

       example1_init();

       example2_init();

}

void example3_app(void) {

       switch(uip_conn->lport){

       caseHTONS(1234):

              example1_app();

              break;

       caseHTONS(2345):

              example2_app();

              break;

       }

}

1.7.4   使用TCP 流量控制 

下面的例子展示了向一个主机发送HTTP请求下载文件到一个慢速的存储设备上,如何使用流量控制功能:

void example4_init(void) {

       u16_tipaddr[2];

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

       uip_connect(ipaddr,HTONS(80));

}

void example4_app(void) {

       if(uip_connected()|| uip_rexmit()) {

              uip_send("GET/file HTTP/1.0\r\nServer:192.186.0.1\r\n\r\n", 48);

              return;

       }

       if(uip_newdata()){

              device_enqueue(uip_appdata,uip_datalen());

              if(device_queue_full()){

                     uip_stop();// 这里只是设置了tcpstateflag标志位而已

              }

       }

       if(uip_poll()&& uip_stopped()) {  // 判断是轮询且之前是被停止的

              if(!device_queue_full()){            // 队列未满则重启连接

                     uip_restart();

              }

       }

}

因为改应用程序只发送GET请求,所以无论是连接成功还是需要重传其代码是一样的;当从远程主机接收到数据时调用设备驱动中的device_enqueue()函数将数据入列。注意:这里假定device_enqueue()会把数据复制到他自己的buffer中。uip_appdata中的数据会被下一次到来的包覆盖掉。

如果设备的缓冲队列满了,应用程序通过调用uIPuip_stop()函数来停止继续下载文件。在调用uip_restart()调用之前,应用程序可以确保不会继续接收数据。应用程序轮询事件可以用来检查设备队列是否还是满的,未满则数据流可以通过uip_restart()重新启用。

1.7.5   简单的web 服务器 

下面的代码是一个简单的文件服务器,其监听2个端口,根据端口号来选择发送哪个文件:

struct example5_state{

       char *dataptr;

       unsigned int dataleft;

};

voidexample5_init(void) {

       uip_listen(HTONS(80));

       uip_listen(HTONS(81));

}

voidexample5_app(void) {

       struct example5_state *s;

       s = (structexample5_state)uip_conn->appstate;

       if(uip_connected()) {

              switch(uip_conn->lport) {

              case HTONS(80):

                     s->dataptr =data_port_80;

                     s->dataleft =datalen_port_80;

                     break;

              case HTONS(81):

                     s->dataptr =data_port_81;

                     s->dataleft =datalen_port_81;

                     break;

              }

              uip_send(s->dataptr,s->dataleft);

              return;

       }

       if(uip_acked()) {

              if(s->dataleft < uip_mss()){

                     uip_close();

                     return;

              }

              s->dataptr += uip_conn->len;

              s->dataleft -=uip_conn->len;

              uip_send(s->dataptr,s->dataleft);

       }

}

1.7.6  结构化应用程序设计 

下面的例子给出了一个结构化设计范本:

voidexample6_app(void) {

       if(uip_aborted()) {      // 连接中止

              aborted();

       }

       if(uip_timedout()) {     // 超时

              timedout();

       }

       if(uip_closed()) {              // 连接关闭

              closed();

       }

       if(uip_connected()) {   // 已建立连接

              connected();

       }

       if(uip_acked()) {        // 应答

              acked();

       }

       if(uip_newdata()) {     // 处理新数据,设置要发送数据指针

              newdata();

       }

       if(uip_rexmit() ||  uip_newdata() ||  //重传、有新数据、应答、已连接、轮询?发送数据

                     uip_acked()||uip_connected() || uip_poll()) {

              senddata();

       }

}

函数从检查任何的错误开始:uip_aborted()或者uip_timedout()。如果确实有错误产生,则执行相应的动作。接着调用uip_connected()检查是否已建立连接,是则调用connected()函数执行相应的动作(发送“Welcome!”等等),比如还有初始化应用状态。最后一个if语句中,建立完成后可能要传数据,则senddata()

接下来的代码,告诉我们上面用到的一些处理函数其形式大概是啥样子的:应用简单的等待连接上的数据到达,并通过发送“Hello World!”来应答。并且,为了展示如何编写状态机,消息被拆分成俩部分进行发送:“Hello”“World!”

#define STATE_WAITING 0

#define STATE_HELLO             1

#define STATE_WORLD           2

struct example6_state{ // 程序状态变量

       u8_t        state;        // 当前状态

       char        *textptr;           // 数据指针

       int          textlen;             // 数据长度

};

static void aborted(void){}

static voidtimedout(void) {}

static voidclosed(void) {}

static voidconnected(void) {

       struct example6_state *s = (structexample6_state *)uip_conn->appstate;

       s->state = STATE_WAITING;

       s->textlen = 0;

}

static voidnewdata(void) {

       struct example6_state *s = (structexample6_state *)uip_conn->appstate;

       if(s->state == STATE_WAITING) {

              s->state = STATE_HELLO;

              s->textptr = "Hello";

              s->textlen = 6;

       }

}

static voidacked(void) {

       struct example6_state *s = (structexample6_state *)uip_conn->appstate;

       s->textlen -= uip_conn->len;

       s->textptr += uip_conn->len;

       if(s->textlen == 0) {

              switch(s->state) {

              case STATE_HELLO:

                     s->state = STATE_WORLD;

                     s->textptr ="world!\n";

                     s->textlen = 7;

                     break;

              case STATE_WORLD:

                     uip_close();

                     break;

              }

       }

}

static voidsenddata(void) {

       struct example6_state *s = (structexample6_state *)uip_conn->appstate;

       if(s->textlen > 0) {

              uip_send(s->textptr,s->textlen);

       }

}

 

 

 

 

 

 

1-7-6 简单的状态机

真正进行数据发送的是senddata()函数,acked()newdata()只是标识有数据需要发送,其长度是多少。

senddata()最终回调用uip_send()来发送数据。切记senddata()函数永远不能做改变应用程序状态的事,相反这些应该在acked()newdata()中进行。

 

Chapter 1 is over…

To be continued!



原创粉丝点击