您的位置:首页 > 产品设计 > UI/UE

uIP中国的协议文件:Ch01

2015-06-13 15:05 501 查看
uIP0.9 参考

by Doxygen 1.3.3

Tue Oct 7 15:51:00 2003

译: fishOnFly(鱼在飞)

uIP-refman中文pdf下载

第1章 uIP TCP/IP协议栈

uIP TCP/IP协议栈的目标是:即便是8位微控制器也可以使用TCP/IP协议栈进行网络通信。虽然小而简单, uIP不须要与他们通信的节点配有复杂,全尺寸协议栈,仅仅要通过执行轻量级协议栈可以通信便可。

代码仅仅有区区几k字节 。RAM消耗最低也仅仅有几百个字节。

1.1 uIP 介绍

随着互联网的成功,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的内存消耗非常大程度上取决于实现其的设备上应用程序的情况。内存配置决定了系统所能处理的流量值和同一时候在线连接的数量。在发送一个超大邮件的同一时候还执行着大量动态网页请求的webserver所消耗的内存肯定要比仅仅执行简单Telnetserver的大的多。

仅有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();)。应用程序同一时候会周期的轮询以查看是否有新数据到达。由应用程序映射不同服务到不同port和连接。对于栈仅仅提供一个回调函数。也就是说尽可能的降低协议栈的响应时间,既便是低端的系统也能做到高速响应到来的数据或者连接请求,处理数据就推到上层去吧

不像其它的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()使用俩个參数:要发送的数据指针和数据的长度。和通常的TCP/IP协议栈一样,栈和驱动层使用一个缓存:sk_buff;uip_send仅仅是将应用程序的数据指针拷贝到全局的uip_sappdata中,其会赋给uip_appdata。终于会赋给uip_buf。假设应用程序要RAM空间来生成实际的数据。能够使用包缓存(指向uip_appdata指针)来实现。

在uip协议栈中。是怎样传递给驱动层的呢?由于uip_buff是全局的,这样驱动直接就调用dev_send_data(uip_buff,len);把数据发送出去了。

在一个连接上,应用程序一次仅仅能发送一块数据,在数据发送完毕前,同一时候对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 监听port

uIP维护着一个TCPport监听列表。

uip_listen()用于向uip_listenports[UIP_LISTENPORTS]数组加入一个新的监听port。当一个在指定port上的连接请求到达时。uIP会创建一个新的连接并调用对应的应用程序。

一个连接已建立时,uip_connected()会返回真。

检查uip_conn结构中lport域能够知道哪个端口和当前连接相相应。

这个过程是在uip_connect(*ripaddr,rport)函数中完毕对uip_conn结构赋值。并建立一个连接:通过找一个未使用的最小的端口号赋给lport,就实现和rport相应。

1.4.10 打开连接

uIP能够通过uip_connect()打开一个新的连接,正如上小节说的uip_connect()会分配一个本地port号,初始化一些域,设置tcp状态标志位,赋值ripaddr等等。最后返回表示一个连接的uip_conn结构。当然假设最大连接数已满就返回NULL指针。

由于uIP用包括俩个16bit元素的数组来表示一个32位的ip地址,使用uip_ipaddr()能够用来封装一个ip地址到2个16位的。

以下展示了两个样例,第一个展示了使用uip_connect()尝试建立一个到TCPport号为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

从网络设备驱动角度看。如图1-5,uIP由uip_input()和uip_periodic()这两个C语言函数组成。

当接收到IP包时设备驱动会调用uip_input()来把包放到包缓存uip_buf中。uip_input()负责处理包。当他返回时往外发送的数据或许应经准备好并放在uip_buf中,接着调用网络设备驱动的dev_send()把数据发出去。

uip_periodic()会周期的轮询各个连接。典型的是每隔1s进行轮询。uIP用该函数来驱动协议计数器和重传。一旦他返回了,须要的包数据可能已经在uip_buf中了。接着就须要掉用驱动程序的dev_send()函数发出数据;

uip_input()和uip_periodic()都是对uip_process(arg)的调用;

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 实例

这一小节中介绍一些很easy的uIP应用程序,在uIP发行包中包括了一些更复杂的应用。telnet,smtp等等。

1.7.1 一个很easy的应用程序

第一个简单的应用程序用来监听1234port上的连接。一旦连接建立完毕,他会用“ok”来应答全部发给他的数据。

void example1_init(void) { // 初始化函数

uip_listen(HTONS(1234)); // 在port1234上监听

}

// 这意味着你须要在应用程序文件里进行例如以下的宏定义:

// #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()函数来注冊一个要监听的port号。应用程序example1_app()用uip_newdata()和uip_rexmit()来推断被调用的原因。

假设是连接还有一端发送了数据,就用“ok”来应答。假设是数据丢失须要重传也用“ok”进行应答。

样例已经展示了一个完整的应用范本。

并没有限定应用程序必须实现全部的事件类型比方uip_connected()或者uip_timedout()。

1.7.2 一个高级应用

第二个样例展示了uip_conn结构体中应用程序状态域是怎样使用的。

和第一个样例相似,当监听的port上有数据发来时就用“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) {

structexample2_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 怎样区分不同的应用程序

假设一个系统要执行多个应用时,区分他们做好的方法就是用TCPport号。

以下的代码显示了怎样将上面俩个样例绑定到一个应用上去:

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中的数据会被下一次到来的包覆盖掉。

假设设备的缓冲队列满了,应用程序通过调用uIP的uip_stop()函数来停止继续下载文件。在调用uip_restart()调用之前。应用程序能够确保不会继续接收数据。应用程序轮询事件能够用来检查设备队列是否还是满的,未满则数据流能够通过uip_restart()又一次启用。

1.7.5 简单的webserver

以下的代码是一个简单的文件server,其监听2个port,依据port号来选择发送哪个文件:

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);

}

}

程序状态由数据指针和要发送数据大小两部分组成。这里的appstate[UIP_APPSTATE_SIZE]非常像驱动中的*private_data结构用于存储设备相关的结构体。

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!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: