TCP/IP

15

一、TCP/IP实现基本原理
 1、TCP/IP的实现方式:
  TSR常驻内存程序是一种安装在Windows之前在DOS上运行的程序。缺点,不能动态分配内存,TSR需要动态链接库DLL帮助,才能让Windows程序访问网络。目前只有在DOS环境下才使用TSR方式
  DLL动态链接库是一个16位的Windows程序函数库,只有当用到其中的过程时才会被调用。缺点,它们不能直接与网卡通信,它们依赖于Windows的调度程序。
  VxD虚拟设备是在Windows 32位保护方式下实现的,用于实现一些关键的部分,如视频、鼠标及通信端口驱动程序。它是通过硬件中断方式响应网络中的通信,可以彻底地访问Windwos和DOS程序。
 2、网络配置基本参数:PC中网络适配卡基本参数,I/O端口地址、内存地址及中断号IRQ。与Microsoft相关的网络信息,主机标识、工作组名、WINS服务器地址、DHCP服务器地址;与TCP/IP网络信息有关,IP地址、子网掩码、主机名、域名、域名服务器、默认网关IP地址。
二、Windows NT平台的TCP/IP联网
三、UNIX平台的TCP/IP联网
 1、建立UNIX联网的几个步骤:设计物理和逻辑的网络结构;分配IP地址;安装网络硬件;为每个主机配置启动时候的网络接口;设立服务程序或者静态路由。
 2、IP地址的获取和分配:可能通过/etc/hosts文件、DNS或者其他域名系统来实现。
 3、网卡的配置:ifconfig命令可以设置网卡IP地址、子网掩码、广播地址、网卡的使能状态及其他选项参数。Ifconfig interface [family] address up option ,其中interface是指定的网卡名,可以用netstat-i来检查当前系统网卡的芯片类型。Loopback网卡通常叫lo0它是一个假想的硬件,用来作本机内部网络包的路由,
 4、路由配置:route配置静态路由,route [-f] op [type] destination gateway hop-count ,op参数如果是add就是增加一个路由表项,如果delete就是删除一个路由表项。
 5、routed标准路由daemon,只支持RIP,它使用hop作为距离计数单位。Routed有两种运行方式:服务器模式和安静模式。两种模式都要监听广播包,但只有服务器模式才能发布自己的路由信息,通常只有多网卡的机器才设置成服务器模式,如果未说明就是安静模式。
 6、gated一个更好的路由daemon,gated配置文件在/etc/gated.conf的语法中加入BGP后有了很大改动,gated能细粒度地控制广播路由、广播地址、信任策略、距离向量等。
四、Linux网络的安装与配置
 1、手工进行网络硬件配置:
  系统启动时会自动检测网卡,有两个缺点:一个是不通正确的检查所有的网卡,特别是一些比较廉价的网卡,二是核心程序不会自动检测一个以上的网卡,这点是为了使用户可以控制将山上设置到指定的端口上。如果使用两个以上的网卡,自动检测网卡就会失败。
手动进行配置,一种方法是在核心程序的源代码的/drivers/net/space.c文件中修改或添加信息,然后重新编译内核。另一种方法在系统启动过程中将这些信息提供给内核程序。在LILO系统时可以通过lilo.conf文件中的append参数来传递给内核。
 2、手工TCP/IP网络配置
  设置主机名:hostname name,为接口进行IP配置:ifconfig interface ip-address
  route add -net 202.112.58.0 -net的含义,因为route既可以处理到网络的路由,又可以处理到单个主机的路由。通过net来告诉它此地址是代表的一个网络,用host来告诉它此地址是代表一个主机。如果为了方便,还可以在/etc/networks中定义网络名字,route后面直接使用网络名字就可以了。
  route add default gw 2-2.112.58.254 网络名字default是0.0.0.0的简写,指示默认的路径,并不需要将这个名字加入到/etc/networks文件。
 3、编辑hosts与networks文件
  如果不打算使用DNS或者NIS进行地址解析时,就必须将所有的主机名字都放入hosts文件中。伴随hosts文件的还有一个/etc/networks文件,它在网络的名字和网络号之间建立映射。
 4、编译内核
  命令如下:cd/usr/src/linux make config
  新的Linux核心版本中,对核心的配置除了上述make config命令外,还增加了字符状态下以菜单形式对核心进行配置的命令make colormenu以及在X窗口系统中运行的图形配置界面命令make xconfig
五、高级TCP/IP应用配置
 1、网络配置文件:在Linux中是通过/etc/rc.d/rc.inet1和/etc/rc.d/rc.inet2两个文件实现的,/etc/rc.d/rc.inet1主要是通过ifconfig和route命令进行基本的TCP/IP接口配置,主要由两部分组成,第一部分是对回送接口的配置,第二部分是对以太网接口的配置。/etc/rc.d/rc.inet2主要是用来启动一些网络监控的进程,inetd portmapper 等。
 2、名字服务和解析器配置
  运行named:大多数UNIX机器上提供域名服务的程序叫named它是一个服务器程序,用来向客户或其他名字服务器提供域名服务。它从配置文件/etc/named.boot中获取信息,以及各种包含域名到地址映射的数据文件,后者称为”区文件”zone file。Named包含的主文named.hosts。

Category : TCP/IP | 生活随记 | Blog
17
十二

ICMP协议
它是TCP/IP协议集中的一个子协议,属于网络层协议,主要用于在主机与路由器之间传递控制信息,包括报告错误、交换受限控制和状态信息等。当遇到IP数据无法访问目标、IP路由器无法按当前的传输速率转发数据包等情况时,会自动发送ICMP消息。我们可以通过Ping命令发送ICMP回应请求消息并记录收到ICMP回应回复消息。通过这些消息来对网络或主机的故障提供参考依据
ICMP是“Internet Control Message Protocol”(Internet控制消息协议)的缩写。它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。
我们在网络中经常会使用到ICMP协议,只不过我们觉察不到而已。比如我们经常使用的用于检查网络通不通的Ping命令,这个“Ping”的过程实际上就是ICMP协议工作的过程。还有其他的网络命令如跟踪路由的Tracert命令也是基于ICMP协议的。

ICMP的重要性
ICMP协议对于网络安全具有极其重要的意义。ICMP协议本身的特点决定了它非常容易被用于攻击网络上的路由器和主机。例如,在1999年8月海信集团“悬赏”50万元人民币测试防火墙的过程中,其防火墙遭受到的ICMP攻击达334050次之多,占整个攻击总数的90%以上!可见,ICMP的重要性绝不可以忽视!
比如,可以利用操作系统规定的ICMP数据包最大尺寸不超过64KB这一规定,向主机发起“Ping of Death”(死亡之Ping)攻击。“Ping of Death” 攻击的原理是:如果ICMP数据包的尺寸超过64KB上限时,主机就会出现内存分配错误,导致TCP/IP堆栈崩溃,致使主机死机。
此外,向目标主机长时间、连续、大量地发送ICMP数据包,也会最终使系统瘫痪。大量的ICMP数据包会形成“ICMP风暴”,使得目标主机耗费大量的CPU资源处理,疲于奔命。

ICMP的主要功能

从技术角度来说,ICMP就是一个“错误侦测与回报机制”,其目的就是让我们能够检测网路的连线状况﹐也能确保连线的准确性﹐其功能主要有:

· 侦测远端主机是否存在。
· 建立及维护路由资料。
· 重导资料传送路径。
· 资料流量控制。

ICMP的消息类型

我们需要认识到,ICMP协议在某些情况下不会发送错误信息。ICMP不会对ICMP信息做出响应。如果ICMP回应其它ICMP消息,这些消息的数量会爆炸性增长而演变为一场ICMP消息风暴。为了防止出现广播风暴,ICMP消息也不会回应一个广播或者多播地址。
最有用的ICMP数据包类型“目标不可达”(类型三)的消息。错误消息一般由路由器生成,并且发送给数据包的来源。大多数错误信息还将发送给与发送的数据包有关的应用程序。在这种情况下,TCP协议将广泛使用ICMP协议。

在IPv4协议中最常用的ICMP消息类型有以下几种:
•回显应答(类型0)和回显请求(类型8):这是Ping程序发送的信息。
•目标不可达(类型3)
•源抑制(类型4):这是一种用于通知发送者路由器或者主机出现阻塞现象的ICMP消息,发送者需要降低发送速度。
•重定向(类型5):这个消息用来向可以访问两台路由器的主机说“请使用另一台路由器”。我们在此系列讲座中未来的路由问题中再详细讨论这个问题。
•路由器信息应答(类型9)和路由器信息请求(类型10)
•超时(类型11):这个消息有两种用途。第一,当超过IP生存期时向发送系统发出错误信息。第二,如果分段的IP数据报没有在某种时限内重新组合,这个消息将通知发送系统。

当然,上述各种类型的消息中都包含子类型代码。类型三消息“目标不可达”本身有15个子类型代码。我们就不提供每一项的细节了。但是,ICMP协议中有一项非常重要的应用要依靠类型三的消息。

Category : TCP/IP | Blog
8
十二

什么是ARP RARP

ARP,全称Address Resolution Protocol,中文名为地址解析协议,它工作在数据链路层,在本层和硬件接口联系,同时对上层提供服务。
IP数据包常通过以太网发送,以太网设备并不识别32位IP地址,它们是以48位以太网地址传输以太网数据包。因此,必须把IP目的地址转换成以太网目的地址。在以太网中,一个主机要和另一个主机进行直接通信,必须要知道目标主机的MAC地址。但这个目标MAC地址是如何获得的呢?它就是通过地址解析协议获得的。ARP协议用于将网络中的IP地址解析为的硬件地址(MAC地址),以保证通信的顺利进行。

RARP(Reverse Address Resolution Protocol):反向地址转换协议
反向地址转换协议(RARP)允许局域网的物理机器从网关服务器的 ARP 表或者缓存上请求其 IP 地址。网络管理员在局域网网关路由器里创建一个表以映射物理地址(MAC)和与其对应的 IP 地址。当设置一台新的机器时,其 RARP 客户机程序需要向路由器上的 RARP 服务器请求相应的 IP 地址。假设在路由表中已经设置了一个记录,RARP 服务器将会返回 IP 地址给机器,此机器就会存储起来以便日后使用。

ARP是在仅知道主机的IP地址时确定其物理地址的一种协议。因IPv4和以太网的广泛应用,其主要用作将IP地址翻译为以太网的MAC地址,但其也能在ATM和FDDIIP网络中使用。从IP地址到物理地址的映射有两种方式:表格方式和非表格方式。
ARP具体说来就是将网络层(IP层,也就是相当于OSI的第三层)地址解析为数据连接层(MAC层,也就是相当于OSI的第二层)的MAC地址。
假设:
计算机A的IP为192.168.1.1,MAC地址为00-11-22-33-44-01;
计算机B的IP为192.168.1.2,MAC地址为00-11-22-33-44-02;

ARP工作原理
在TCP/IP协议中,A给B发送IP包,在包头中需要填写B的IP为目标地址,但这个IP包在以太网上传输的时候,还需要进行一次以太包的封装,在这个以太包中,目标地址就是B的MAC地址。
计算机A是如何得知B的MAC地址的呢?解决问题的关键就在于ARP协议。
在A不知道B的MAC地址的情况下,A就广播一个ARP请求包,请求包中填有B的IP(192.168.1.2),以太网中的所有计算机都会接收这个请求,而正常的情况下只有B会给出ARP应答包,包中就填充上了B的MAC地址,并回复给A。
A得到ARP应答后,将B的MAC地址放入本机缓存,便于下次使用。
本机MAC缓存是有生存期的,生存期结束后,将再次重复上面的过程。
ARP协议并不只在发送了ARP请求才接收ARP应答。当计算机接收到ARP应答数据包的时候,就会对本地的ARP缓存进行更新,将应答中的IP和MAC地址存储在ARP缓存中。因此,当局域网中的某台机器B向A发送一个自己伪造的ARP应答,而如果这个应答是B冒充C伪造来的,即IP地址为C的IP,而MAC地址是伪造的,则当A接收到B伪造的ARP应答后,就会更新本地的ARP缓存,这样在A看来C的IP地址没有变,而它的MAC地址已经不是原来那个了。由于局域网的网络流通不是根据IP地址进行,而是按照MAC地址进行传输。所以,那个伪造出来的MAC地址在A上被改变成一个不存在的MAC地址,这样就会造成网络不通,导致A不能Ping通C!这就是一个简单的ARP欺骗。
概括的说:
1. 首先,每台主机都会在自己的ARP缓冲区 (ARP Cache)中建立一个 ARP列表,以表示IP地址和MAC地址的对应关系。
2. 当源主机需要将一个数据包要发送到目的主机时,会首先检查自己 ARP列表中是否存在该 IP地址对应的MAC地址,如果有﹐就直接将数据包发送到这个MAC地址;如果没有,就向本地网段发起一个ARP请求的广播包,查询此目的主机对应的MAC地址。此ARP请求数据包里包括源主机的IP地址、硬件地址、以及目的主机的IP地址。
3. 网络中所有的主机收到这个ARP请求后,会检查数据包中的目的IP是否和自己的IP地址一致。如果不相同就忽略此数据包;如果相同,该主机首先将发送端的MAC地址和IP地址添加到自己的ARP列表中,如果ARP表中已经存在该IP的信息,则将其覆盖,然后给源主机发送一个 ARP响应数据包,告诉对方自己是它需要查找的MAC地址;
4. 源主机收到这个ARP响应数据包后,将得到的目的主机的IP地址和MAC地址添加到自己的ARP列表中,并利用此信息开始数据的传输。如果源主机一直没有收到ARP响应数据包,表示ARP查询失败。
RARP的工作原理
1. 发送主机发送一个本地的RARP广播,在此广播包中,声明自己的MAC地址并且请求任何收到此请求的RARP服务器分配一个IP地址;
2. 本地网段上的RARP服务器收到此请求后,检查其RARP列表,查找该MAC地址对应的IP地址;
3. 如果存在,RARP服务器就给源主机发送一个响应数据包并将此IP地址提供给对方主机使用;
4. 如果不存在,RARP服务器对此不做任何的响应;
5. 源主机收到从RARP服务器的响应信息,就利用得到的IP地址进行通讯;如果一直没有收到RARP服务器的响应信息,表示初始化失败。

ARP和RARP报头结构

ARP和RARP使用相同的报头结构,如图1所示。

硬件类型

协议类型

硬件地址长度

协议长度

操作类型

发送方的硬件地址(0-3字节)

源物理地址(4-5字节)

源IP地址(0-1字节)

源IP地址(2-3字节)

目标硬件地址(0-1字节)

目标硬件地址(2-5字节)

目标IP地址(0-3字节)

(图1 ARP/RARP报头结构)

  • 硬件类型字段指明了发送方想知道的硬件接口类型,以太网的值为1;
  • 协议类型字段指明了发送方提供的高层协议类型,IP为0800(16进制);
  • 硬件地址长度和协议长度指明了硬件地址和高层协议地址的长度,这样ARP报文就可以在任意硬件和任意协议的网络中使用;
  • 操作字段用来表示这个报文的类型,ARP请求为1,ARP响应为2,RARP请求为3,RARP响应为4;
  • 发送方的硬件地址(0-3字节):源主机硬件地址的前3个字节;
  • 发送方的硬件地址(4-5字节):源主机硬件地址的后3个字节;
  • 发送方IP(0-1字节):源主机硬件地址的前2个字节;
  • 发送方IP(2-3字节):源主机硬件地址的后2个字节;
  • 目的硬件地址(0-1字节):目的主机硬件地址的前2个字节;
  • 目的硬件地址(2-5字节):目的主机硬件地址的后4个字节;
  • 目的IP(0-3字节):目的主机的IP地址。
Category : TCP/IP | Blog
6
十二

使用每个项目一个的配置文件uipopt.h来配置uIP。这个文件包含了uIP的所有的编译时选项,并且应该与特定的项目匹配。
uIP类型定义
typedef unsigned char u8_t;
typedef unsigned short u16_t;
typedef unsigned short uip_stats_t;
uIP静态配置选项
只有在UIP_FIXEDADDR设置为1时,这些配置选项才可以用来静态的设置IP地址。一个特定节点的配置选项包括IP地址,子网掩码,默认网关(router),以及以太网地址。子网掩码,默认网关和以太网地址仅仅当uIP运行在以太网上才适用。
所有这些应该被修改以适应你的项目。
#define UIP_FIXEDADDR 1
决定uIP是否使用一个固定的IP地址。
如果uIP使用一个固定的IP地址,应该置位(set)这些uipopt.h中的选项。如果不的话,则应该使用宏uip_sethostaddr(),uip_setdraddr() 和 uip_setnetmask()。
#define UIP_PINGADDRCONF 0
Ping IP地址赋值。
如果置位这个选项,那么uIP使用一个”ping” packets来设置它自己的IP地址。如果是这样的话,uIP启动时的IP地址是空的,并且第一个到来的”ping”(ICMP echo)packet的目的IP地址将被用于设置主机的IP地址。??
注意这只有在UIP_FIXEDADDR为0的时候才起作用。
#define UIP_FIXEDETHADDR 0
指明uIP ARP模块是否在编译时使用一个固定的以太网MAC地址。
如果这个选项为0,那么宏uip_setethaddr()可以用来在运行时指定以太网MAC地址。
#define UIP_TTL 255
uIP发送的IP packets的IP TTL (time to live)。
#define UIP_REASSEMBLY 0
uIP支持IP packets的分片和重组。这个特征需要另外的RAM用作重组缓冲区,并且重组代码的大小大约是700字节。重组缓冲区的大小与uip_buf一样(由UIP_BUFSIZE配置)。
注意IP packet重组没有经过严格的测试。
#define UIP_REASS_MAXAGE 40
一个IP fragment在被丢弃之前可以在重组缓冲区中存在的最大时间。
#define UIP_UDP 0
是否编译UDP的开关。
#define UIP_ACTIVE_OPEN 1
决定是否支持uIP打开一个连接。
如果运行在uIP上层的应用程序不必打开一个outgoing TCP连接,那么可以关闭这个选项来减小uIP的代码大小。
#define UIP_CONNS 10
同时可以打开的TCP连接的最大数目。
由于TCP连接是静态分配的,减小这个数目将占用更少的RAM。每一个TCP连接需要大约30字节的内存。
#define UIP_LISTENPORTS 10
同时监听的TCP端口的最大数目。
每一个TCP监听端口需要2个字节的内存。
#define UIP_RECEIVE_WINDOW 32768
建议的接收窗口的大小。
如果应用程序处理到来的数据比较慢,那么应该设置的小一点(即,相对与uip_buf缓冲区的大小来说),相反如果应用程序处理数据很快,可以设置的大一点(32768字节)。
#define UIP_URGDATA 1
决定是否支持TCP urgent data notification。
Urgent data (out-of-band data)是一个用的比较少TCP特征,它很少需要。
#define UIP_RTO 3
The initial retransmission timeout counted in timer pulses.
不要改变它。
#define UIP_MAXRTX 8
在中止连接之前,应该重发一个段的最大次数。
不要改变它。
#define UIP_TCP_MSS (UIP_BUFSIZE – UIP_LLH_LEN – 40)
TCP段的最大长度。
它不能大于UIP_BUFSIZE – UIP_LLH_LEN – 40.
#define UIP_TIME_WAIT_TIMEOUT 120
一个连接应该在TIME_WAIT状态等待多长。
不要改变它。
#define UIP_ARPTAB_SIZE 8
ARP表的大小。
如果本地网络中有许多到这个uIP节点的连接,那么这个选项应该设置为一个比较大的值。
#define UIP_ARP_MAXAGE 120

#define UIP_BUFSIZE 1500
uIP packet缓冲区不能小于60字节,但也不必大于1500字节。
#define UIP_STATISTICS 1
决定是否支持统计数字。
统计数字对调试很有帮助,并展示给用户。
#define UIP_LOGGING 0
Determines if logging of certain events should be compiled in.
它对调试非常有用。如果打开这个选项,必须根据特定的体系结构来实现函数uip_log()。
void uip_log(char *msg);
输出应该uIP登陆信息。
必须在使用uIP的模块中实现这个函数,并且在生成一个登陆信息时调用这个函数。
#define UIP_LLH_LEN 14
链接层头部长度。
对于SLIP,应该设置成0。
#ifndef BYTE_ORDER
#define BYTE_ORDER LITTLE_ENDIAN
#endif /* BYTE_ORDER */

使用一个应用程序函数来实现一个uIP应用,每当发生了一个TCP/IP事件时uIP就调用这个函数。这个函数的名字必须在编译时使用宏UIP_APPCALL注册到uIP。
通过使用宏UIP_APPSTATE_SIZE指出结构uip_conn的大小,uIP应用可以在这个结构中保存它的状态。必须在uipopt.h中包含定义这些变量的文件(在此例中为httpd.h)。
下面的例子展示它看起来的样子。
void httpd_appcall(void);
#define UIP_APPCALL httpd_appcall
struct httpd_state {
u8_t state;
u16_t count;
char *dataptr;
char *script;
};
#define UIP_APPSTATE_SIZE (sizeof(struct httpd_state))
/* Include the header file for the application program that should be
used. If you don’t use the example web server, you should change
this. */
#include “httpd.h”
#endif /* __UIPOPT_H__ */

from: jupiter

http://blog.china-pub.com/more.asp?name=xijun168&id=25095

Category : TCP/IP | Blog
21
十一

平台移植:

uIP的设备驱动程序接口

uIP内核中有两个函数直接需要底层设备驱动程序的支持。

一是uip_input()。当设备驱动程序从网络层收到一个数据包时要调用这个函数,设备驱动程序必须事先将数据包存放到uip_buf[]中,包长放到uip_len,然后交由uip_input()处理。当函数返回时,如果uip_len不为0,则表明有带外数据(如SYN,ACK等)要发送。当需要ARP支持时,还需要考虑更新ARP表或发出ARP请求和回应。

另一个需要驱动程序支持的函数是uip_periodic(conn)。这个函数用于uIP内核对各连接的定时轮循,因此需要一个硬件支持的定时程序周期性地用它轮循各连接,一般用于检查主机是否有数据要发送,如有,则构造IP包。

uIP应用程序接口

为了将用户的应用程序挂接到uIP中,必须将宏UIP_APPCALL()定义成实际的应用程序函数名, 这样每当某个uIP事件发生时,内核就会调用该应用程序进行处理。如果要加入应用程序状态的话,必须将宏UIP_APPSTATE_SIZE定义成应用程序状态结构体的长度。在应用程序函数中,依靠uIP事件检测函数来决定处理的方法,另外可以通过判断当前连接的端口号来区分处理不同的连接。

详细请看源码公开的TCP/IP协议栈uIP的应用

快速向导: 移植uIP到其它的平台

实际的TCP/IP代码无需任何修改, 但是目标网络设备的驱动程序(以太网控制器/串
口/其它)和实际的系统集成部分(比如, 主控制循环, 当数据到达或定期时钟超时需调用uI
P函数)需要重写.

移植步骤:

1. 阅读提供的文档.

2. 在uip-1.0/目录下为你的端口创建新的目录(选择一个简短的CPU架构名称, 比如
i386,用于C编译器).

3. 从unix/子目录拷贝uip_arch.c文件到新创建的目录. ls包含了一个普通的C校验
算法实现, 是一个32位的函数).

4. 从unix/子目录拷贝uipopt.h文件.

5. 根据你的项目编辑uipopt.h文件(文件是自描述型的).

6. 为你的硬件写设备驱动.(这大概是最难的部分.) 查阅unix/tapdev.c和uip/sli
pdev.c实例了解设备驱动如何实现.

7. 写好主控制循环, 然后调用uIP函数. 查阅unix/main.c实例了解主控制循环如何
实现.通过unix/main.c主循环实例了解如何使用ARP协议.

8. 写makefile文件, 然后编译代码. 确认你的项目(在你子目录中的.c文件)包含了../uip/uip.c文件. 如果你正使用web服务器程序, 请记住包含文件../apps/httpd/httpd.c ../apps/httpd/cgi.c ../apps/httpd/fs.c. 如果需支持
ARP, 请包含文件 ../uip/uip_arp.c.

9. 发现和纠正所有的程序错误. (这是平台移植中最需技巧的部分:)

Uip 源代码介绍

1.文件关系

弄清楚源代码之间的联系和各个主要函数的功能,移植就不困难了。

Apps  à  这是作者写的一些应用代码。包括Http、Smtp、Telnet和Webclient等等。

Uip   à  这是uip的核心代码。

Unix  à  这是作者在unix系统下的一个工程。

Doc   à  这是说明文档。

重点说说Uip和Unix这两个文件夹中比较重要的函数。

Uip:

Uip.c :

uip_process( )  完成对ip,udp,tcp的数据的解析。

uip_udp_new() 建立一个udp。

uip_listen()    监听一个端口。

uip_connect()  准备建立一个tcp连接,发送同步信号。

Uip_arp.c

uip_arp_update()更新arp列表。

uip_arp_init()   初始化arp缓冲块

uip_arp_out()   发送一个arp请求

uip_arp_arpin()  处理收到一个arp包

uip_arp_ipin()   处理收到是arp包的ip包。

Slipdev.c        是关于SLIP 协议的文件

Unix:

Main.c              uip服务的主循环。

Tapdev.c 和 tapdev.h  是低层数据的接收与发送。

Uip_arch.c                   一些计算校验和的函数。

Uipopt.h                这是一个uip的配置文件。你可以修改IP,NETMASK,GATEWAY,MAC等等以便符合你的网络。

2,移植细节

在你的工程中,你需要包含如下文件:

Uip.c

Uip_arch.c

Uip_arp.c

Tapdev.c

Main.c

A, 网卡驱动移植

Tapdev.c 包含3个函数:

a>  tapdev_init(void)                   网卡初始化.

b>  unsigned int  tapdev_read(void)  接收网卡数据

c> void tapdev_send(void)           发送网卡数据

这3个函数与低层硬件有关.大家只需按照上述函数的接口,完成低层数据的收发便可.

B, UDP的移植

a>    uipopt.h 中修改

#define UIP_UDP                       1

#define UIP_UDP_CONNS    10(同时可建立udp的连接数)

#define UIP_UDP_APPCALL  udp_appcall (UDP的用户接口函数)

添加 #include

b>    新建udp.c 和udp.h 两个文件

udp.c中定义两个函数

udpinit(void)

{

uip_ipaddr(ipaddr, RIPADDR0, RIPADDR1, RIPADDR2, RIPADDR3);

uip_udp_new(ipaddr, UDP_LPORT, UDP_RPORT);                                                           //建立一个udp,指定udp端口。

//注意:我对uip_udp_new()函数做了修改,原来本地端口是随机的,我改为了可以配置的。

}

//当UDP数据被tapdev_read ()收到,uip_process()函数对UDP进行解析,如果正确,则调用udp_appcall()来交给用户处理。此时,用户可以用uip_newdata()检测是否有新数据,新数据放在uip_appdata指针中,长度为uip_len。

//当定时轮询到达,后也会调用udp_appcall(),可以用uip_poll()检查。此时,用户可以主动发送数据。只须将数据放入指针uip_appdata后的空间中,并将数据长度传给uip_slen便可。

下面给出了函数的模型(仅供参考):

void udp_appcall(void)

{

if(uip_udp_conn->rport == HTONS(UDP_RPORT))

{

if(uip_poll())

{

/*

for(u16_t i=0; i<1000; i++)

{

*(uip_appdata++) = (u8_t)i;

}

*uip_appdata = ‘\0′;

uip_slen = 1000;      //strlen((char*)uip_appdata);*/

}

if(uip_newdata())

{

uip_appdata[uip_len]=’\0′;

uip_slen = uip_len;

}

}

}

uIP0.9 keil移植要点

1. 打开Keil新建项目uIP0.9.uv2, 设置项目属性.
memory_model 设置为large 模式,这样默认的存储方式是xdata
因为uIP0.9编译后占用20K rom, 所以必须选一个32K(>20K)的rom的单片机,
比如Device可设置为SST89×58或者SST89×516xx,
解压缩官方下载的uIP0.9压缩包,添加文件至项目,
需添加至项目的文件有:uip\uip.c, uip.c\uip.h, uip.c\uip_arch.h,
uip.c\uip_arp.c,uip.c\uip_arp.h
unix\main.c, unix\uip_arch.c, unix\uipopt.h,
apps\httpd\所有文件

2. 因为data是系统关键字, 所以标识符data => dat
以下文件需要改动:   fs.h, fsdata.h,httpd.c

3. 为RTL8019AS 编写驱动程序(具体如何操作寄存器老古的网站有详细的教程),
内容在压缩包中的RTL8019AS.c, RTL8019AS.h
需要更改main.h中的如下地方:
include “tapdev.h” => #include “rtl8019as.h”
tapdev_init() -> rtl8019as_init()
tapdev_send() -> rtl8019as_send()
tapdev_read() -> rtl8019as_read()

4. fsdata.c 首行添加 #include “fsdata.h”
关键字替换:
static const char -> const char code
const struct fsdata_file -> const struct fsdata_file code

fsdata.h 文件末尾添加:
#define FS_ROOT file_tcp_header_html
#define FS_NUMFILES 14

const char code data_cgi_files[];
const char code data_cgi_stats[];
const char code data_cgi_tcp[];
const char code data_img_bg_png[];
const char code data_about_html[];
const char code data_control_html[];
const char code data_404_html[];
const char code data_files_footer_plain[];
const char code data_files_header_html[];
const char code data_index_html[];
const char code data_stats_footer_plain[];
const char code data_stats_header_html[];
const char code data_tcp_footer_plain[];
const char code data_tcp_header_html[];
const struct fsdata_file code file_cgi_files[];
const struct fsdata_file code file_cgi_stats[];
const struct fsdata_file code file_cgi_tcp[];
const struct fsdata_file code file_img_bg_png[];
const struct fsdata_file code file_about_html[];
const struct fsdata_file code file_control_html[];
const struct fsdata_file code file_404_html[];
const struct fsdata_file code file_files_footer_plain[];
const struct fsdata_file code file_files_header_html[];
const struct fsdata_file code file_index_html[];
const struct fsdata_file code file_stats_footer_plain[];
const struct fsdata_file code file_stats_header_html[];
const struct fsdata_file code file_tcp_footer_plain[];
const struct fsdata_file code file_tcp_header_html[];

5. fs.c 第55行删除: #include “fsdata.c”

6. uipopt.h
181行: #define UIP_FIXEDETHADDR 0 -> 1
299行: #define UIP_ACTIVE_OPEN 1 ->0
497行: #define BYTE_ORDER     LITTLE_ENDIAN -> BIG_ENDIAN
280行: #define UIP_UDP_APPCALL  udp_appcall -> httpd_appcall

文件末尾添加:
#ifndef NULL
#define NULL (void *)0
#endif /* NULL */

7. httpd.c
删除以下内容:
extern const struct fsdata_file file_index_html;
extern const struct fsdata_file file_404_html;
220行更改:
fs_open(file_index_html.name, &fsfile);    =>   file_index_html->name
224行同上更改

Category : TCP/IP | Blog
19
十一

源码公开的TCP/IP协议栈在远程监测中的应用

摘 要:介绍一个适用于8/16位单片机的嵌入式TCP/IP协议栈(uIP)在发电机远程监测系统中的应用。重点阐述uIP的功能特性、体系结构和相关接口,并详细介绍如何在该协议栈上实现一个嵌入式Web服务器。目前uIP已成功地移植到51单片机上。

关键词:TCP/IP协议栈 uIP 嵌入式Web服务器 远程监测

言:

目前,随着互联网的发展,越来越多的工业测控设备已经将网络接入功能作为其默认配置,以实现设备的远程监控和信息分布式处理。笔者曾参与某发电机射频监测仪的开发,该设备主要用于诊断和预警发电机早期故障,并通过RS232接口定时输出电平和状态数据,现场专门设一台PC作接收、显示及存储。每年都要有专家到各发电厂对以往数据作检查和诊断,不胜其烦。因此有必要设计一个RS232到Internet的数据传输模块,以便对发电机的运行状况作远程监测。设计该模块的关键在于如何实现一个嵌入式TCP/IP协议栈,根据以往的经验,自己设计一个协议栈的难度很可能超过应用本身控制工程网版权所有,而采用商业的协议栈似乎又无必要(功能过于复杂),最后笔者选用一种功能简易的免费TCP/IP协议栈uIP 0. 9作为设计核心。

1 嵌入式TCP/IP协议栈

目前,市面上几乎所有的嵌入式TCP/IP协议栈都是根据BSD版的TCP/IP协议栈改写的。在商业嵌入式TCP/IP协议栈大都相当昂贵的情况下,很多人转而使用一些源代码公开的免费协议栈,并加以改造应用。目前较为著名的免费协议栈有:

lwIP(Light weight TCP/IP Stack)——支持的协议比较完整,一般需要多任务环境支持,代码占用ROM>40KB,不适合8位机系统,没有完整的应用文档;

uC/IP (TCP/IP stack for uC/OS)——基于uC/OS的任务管理,接口较复杂,没有说明文档。

笔者采用的协议栈系瑞典计算机科学研究所Adam Dunkels开发的uIP0.9 。其功能特性总结如下:
◇完整的说明文档和公开的源代码(全部用C语言编写,并附有详细注释);
◇极少的代码占用量和RAM资源要求,尤其适用于8/16位单片机(见表1);
◇高度可配置性,以适应不同资源条件和应用场合;
◇支持ARP、IP、ICMP、TCP、UDP(可选)等必要的功能特性;
◇支持多个主动连接和被动连接并发,支持连接的动态分配和释放;
◇简易的应用层接口和设备驱动层接口;
◇完善的示例程序和应用协议实现范例。

t001

正是由于uIP所具有的显著特点,自从0.6版本以来就被移植到多种处理器上,包括MSP430、AVR和Z80等。笔者使用的uIP0.9是2003年11月发布的版本。目前,笔者已将它成功移植到MCS-51 上了。

2 uIP0.9的体系结构

uIP0.9是一个适用于8/16位机上的小型嵌入式TCP/IP协议栈,简单易用,资源占用少是它的设计特点。它去掉了许多全功能协议栈中不常用的功能,而保留网络通信所必要的协议机制。 其设计重点放在IP、ICMP和TCP协议的实现上,将这三个模块合为一个有机的整体,而将UDP和ARP协议实现作为可选模块。uIP0.9的体系结构如图1所示。

image001

uIP0.9处于网络通信的中间层,其上层协议在这里被称之为应用程序,而下层硬件或固件被称之为网络设备驱动。显然,uIP0.9并不是仅仅针对以太网设计的,它具有媒体无关性。

为了节省资源占用, 简化应用接口, uIP0.9在内部实现上作了特殊的处理。
① 注意各模块的融合,减少处理函数的个数和调用次数,提高代码复用率,以减少ROM占用。
② 基于单一全局数组的收发数据缓冲区,不支持内存动态分配, 由应用负责处理收发的数据。
③ 基于事件驱动的应用程序接口,各并发连接采用轮循处理,仅当网络事件发生时,由uIP内核唤起应用程序处理。这样,uIP用户只须关注特定应用就可以了。传统的TCP/IP实现一般要基于多任务处理环境,而大多数8位机系统不具备这个条件。
④ 应用程序主动参与部分协议栈功能的实现(如TCP的重发机制,数据包分段和流量控制),由uIP内核设置重发事件,应用程序重新生成数据提交发送,免去了大量内部缓存的占用。基于事件驱动的应用接口使得这些实现较为简单。

3 uIP的设备驱动程序接口

uIP内核中有两个函数直接需要底层设备驱动程序的支持。

一是uip_input()。当设备驱动程序从网络层收到一个数据包时要调用这个函数,设备驱动程序必须事先将数据包存放到uip_buf[]中,包长放到uip_len,然后交由uip_input()处理。当函数返回时,如果uip_len不为0,则表明有带外数据(如SYN,ACK等)要发送。当需要ARP支持时,还需要考虑更新ARP表或发出ARP请求和回应,示例如下。

#define BUF ((struct uip_eth_hdr *)&uip_buf[0])
uip_len = ethernet_devicedriver_poll(); //接收以太网数据包
//(设备驱动程序)
if(uip_len>0){ //收到数据
if(BUF->type = = HTONS(UIP_ETHTYPE_IP)) { //是IP包吗?
uip_arp_ipin(); //去除以太网头结
//构,更新ARP表
uip_input(); //IP包处理
if(uip_len>0){ //有带外回应数据
uip_arp_out(); //加以太网头结构,在主动连接时可能要
//构造ARP请求
ethernet_devicedriver_send(); //发送数据到以太网
//(设备驱动程序)
}
}else if (BUF->type = = HTONS(UIP_ETHTYPE_ARP)) {
//是ARP请求包
uip_arp_arpin(); //如是是ARP回应,更新ARP表;如果是
//请求,构造回应数据包
if(uip_len>0) { //是ARP请求,要发送回应
ethernet_devicedriver_send(); //发ARP回应到以太网上
}
}
另一个需要驱动程序支持的函数是uip_periodic(conn)。这个函数用于uIP内核对各连接的定时轮循,因此需要一个硬件支持的定时程序周期性地用它轮循各连接,一般用于检查主机是否有数据要发送,如有,则构造IP包。使用示例如下。
for(i=0 ; i
uip_periodic(i);
if(uip_len > 0){
uip_arp_out();
ethernet_devicedriver_send();
}
}

从本质上来说, uip_input()和uip_periodic()在内部是一个函数,即uip_process (u8t flag), UIP的设计者将uip_process(UIP_DATA)定义成uip_input(),而将uip_process(UIP_TIMER)定义成uip_periodic(),因此从代码实现上来说是完全复用的。

4 uIP应用程序接口

为了将用户的应用程序挂接到uIP中,必须将宏UIP_APPCALL()定义成实际的应用程序函数名, 这样每当某个uIP事件发生时,内核就会调用该应用程序进行处理。如果要加入应用程序状态的话,必须将宏UIP_APPSTATE_SIZE定义成应用程序状态结构体的长度。在应用程序函数中,依靠uIP事件检测函数来决定处理的方法,另外可以通过判断当前连接的端口号来区分处理不同的连接。下面的示例程序是笔者实现的一个Web服务器应用的框架。

#define UIP_APPCALL uip51_appcall
#define UIP_APPSTATE_SIZE sizeof(struct uip51app_state)
struct uip51app_state{
unsigned char *dataptr;
unsigned int dataleft;};

void uip51_initapp{ //设置主机地址
u16_t ipaddr[2];
uip_ipaddr(ipaddr, 202 ,120,127,192 );
uip_sethostaddr(ipaddr);
uip_listen(HTTP_PORT); //HTTP WEB PORT(80);
}

void uip51_appcall(void){
struct uip51app_state *s;
s = (struct uip51app_state *)uip_conn->appstate;
//获取当前连接状态指针
if(uip_connected()) {
… //有一个客户机连上}
if(uip_newdata()||uip_rexmit()) { //收到新数据或需要重发
if(uip_datalen()>0){
if(uip_conn->lport = = 80) { //收到GET HTTP请求
update_table_data(); //根据电平状态数据表动态
//生成网页
s->dataptr=newpage;
s->dataleft=2653;
uip_send(s->dataptr,s->dataleft);
//发送长度为2653 B的网页
}
}
}
if(uip_acked()) {

//收到客户机的ACK
if(s->dataleft>uip_mss()&&uip_conn->lport = = 80){
//发送长度>最大段长时
s->dataptr+=uip_conn->len; //继续发送剩下的数据
s->dataleft-=uip_conn->len;
uip_send(s->dataptr,s->dataleft);
}
return;
}
if(uip_poll())
{ … //将串口缓存的数据复制到
//电平状态数据表
return;
}
if(uip_timedout()|| //重发确认超时
uip_closed()|| //客户机关闭了连接
uip_aborted()){ //客户机中断连接
return; }
}

5 uIP0.9在发电机远程监测系统中的应用

笔者设计了一个嵌入式Web模块UIPWEB51,用于将发电机射频监测仪串口输出的数据上网,以实现对发电机工作状态的远程监测,目前已取得初步成功。该模块的硬件框图如图2所示。

image002

单片机采用的是Atmel的AT89C55WD,它内置20KB 程序Flash,512字节RAM,3个定时器/计数器,工作在22.1184MHz时具有约2MIPS的处理速度。 网卡芯片同样采用的是低成本的RTL8019AS, 是一款NE2000兼容的网卡芯片。系统外扩了32KB的SRAM,用于串口数据和网络数据的缓冲,另外还存放了uIP的许多全局变量。

UIPWEB51的主程序采用中断加轮循的方式,用中断触发的方式接收发电机射频监测仪发出的数据,并设置了一个接收队列暂存这些数据。在程序中轮循有无网络数据包输入,如有则调用uIP的相关处理函数(如上uip_input()使用示例);如无则检测定时轮循中断是否发生。这里将T2设为uIP的定时轮循计数器, 在T2中断中设置轮循标志,一旦主程序检测到这一标志就调用uip_periodic()轮循各连接(如上uip_periodic()使用示例)。

UIPWeb51的应用程序(如uIP的应用程序接口示例),这个Web服务器首先打开80端口的监听,一旦有客户机要求连上,uIP内部会给它分配一个连接项, 接着等收到客户机IE浏览器发出的“GET HTTP…”请求后, 将发电机电平与状态数据队列中的数据填入网页模板,生成一幅新的网页发给客户机。因为这幅网页的大小已经超过uIP的最大段长(MSS), 因此在uIP内核第一次实际只发出了MSS个字节, 在等到下一次轮循到该连接并且收到上次数据包的ACK时,发送剩下的网页数据。在连接处于空闲的时候(uip_poll()),应用程序可以从串口队列中读出原始数据,经格式处理后再存到发电机电平与状态数据队列中,而在这个队列中保存着当前1min的设备工作数据,以便下次更新网页时使用。在网页中添加了更新按钮控制工程网版权所有,一旦浏览器用户点击了按钮, 浏览器会自动发出CGI请求, UIPWEB51收到后,立即发送包含最新数据的网页。如果uIP接收ACK超时,它会自动设置重发标志,应用程序中可以用uip_rexmit()来检测这个标志,重新生成网页并发送。一旦用户关闭了浏览器,uIP也会自动检测到这一事件(应用程序中可以用uip_closed()来检测),并且释放掉这个连接项。
图3是UIPWEB51的总体程序结构图。

image003

6 测试结果

将uIP0.9配置成允许4个并发连接,1个监听端口, 10个ARP表项,去掉UDP支持,UIP_BUFSIZE=1500和其它优化选项。用KEIL C编译,整个uIP0.9内核模块代码量小于8KB(含Web应用程序),内核对RAM的占用小于2KB(不含网页)。整个系统程序的代码量小于12KB,占用的RAM小于10KB。另外控制工程网版权所有,在公网上测试了该模块的传输速度,大于20Kbps,对于此项应用已达到要求。目前,该模块正准备应用于新一代的发电机射频监测系统中。

参考文献
1 RTL8019AS Realtek Full-Duplex Ethernet Controller with Plug and Play Function Specification, 2002
2 ATMEL AT89C55WD datasheet, 2001
3 Adam Dunkels . uIP 0.9 reference manual, 2003
4 Adam Dunkels. uIP – A free Small TCP/IP Stack, 2002
5 Douglas E.Comer. 用TCP/IP进行网际互联(卷1). 林瑶等译. 北京:电子工业出版社, 2001
6 王罡,林立志编著. 基于Windows的TCP/IP编程. 北京: 清华大学出版社, 2000

Category : TCP/IP | Blog
13
十一

uIP 协议栈是一种免费的可实现的极小的TCP/IP协议栈,可以使用于由8位或16位微处理器构建的嵌入式系统。本文分析了uIP协议栈的结构和应用接口,并讨论了如何将其应用到51系列单片机上。
关键字:TCP/IP 单片机   嵌入式系统

一 引言

近几年来,人们对连接各种装置到一个现有的IP网络例如因特网上产生了浓厚的兴趣。为了可以通过因特网通讯,一个可实现的TCP/IP协议栈是必须的。对于由32位嵌入式处理器构建的中、高端网络接入嵌入式系统中,通常会运行一个集成有TCP/IP协议栈的操作系统。但是对于由8位和16位低端处理器构建的系统,由于其所具有的处理能力和资源十分有限,通常不运行操作系统,这就要求系统开发者根据应用的要求以及所选用的处理器的实际情况构建自己的TCP/IP协议栈。而TCP/IP协议的透明性掩盖了其实现的复杂性,从无到有构建一个协议栈是一件艰巨的任务,并且缺少有效的调试工具。uIPTCP/IP协议栈是使用于低端8位或16位微处理器构建的嵌入式系统的一个可实现的极小的TCP/IP协议栈。它可以自由分发和使用于商业和非商业目的。uIP使用C语言编写,使其方便于移植。并且uIP协议栈的代码大小和RAM的需求比其它一般的TCP/IP栈要小,这就使得它可以方便的应用到各种低端系统上。本文将简要描述uIP的实现方法,分析uIP协议栈的应用接口,并讨论如何将其应用到51系列单片机上。

二 uIP协议栈的实现方法简述

uIP实现了TCP/IP协议集的四个基本协议:ARP地址解析协议,IP网际互联协议,ICMP网络控制报文协议和TCP传输控制协议。为了在8位16位处理器上应用,uIP协议栈在各层协议实现时采用有针对性的方法,保持代码大小和存储器使用量最小。

1 实现ARP地址解析协议时为了节省存储器,ARP应答包直接覆盖ARP请求包。

2 实现IP网络协议时对原协议进行了极大的简化,它没有实现分片和重组。

3 实现ICMP网络控制报文协议时,只实现echo(回响)服务。uIP在生成回响报文时并不重新分配存储器空间,而是直接修改echo请求报文来生成回响报文。将ICMP类型字段从“echo”类型改变成“echoreply”类型,重新计算校验和修改校验和字段。

4 uIP里的TCP没有实现发送和接收数据的滑动窗口。每个TCP连接的状态由uip_conn结构保存,uip_conn结构包括当地和远端的TCP端口编号,远程主机的IP地址,重发时间值,上一段重发的编号,和连接的段的最大尺寸等信息。一个uip_conn结构数组用于保存所有的连接,数组的大小为支持的同时连接的最大数量。为了减少储存器的使用量,在处理重发时uIP并不缓存发送的数据包,而是由应用程序在需要重发时重新生成发送的数据。

三 uIP协议栈的接口

uIP协议栈为了具有最大的通用性,在实现时将底层硬件驱动和顶层应用层之外的所有协议集“打包“在一个“库“里。协议栈通过接口与底层硬件和顶层应用“通信“。通过这种方式,uIP具有极高的通用性和独立性,移植到不同系统和实现不同的应用都很方便,很好的体现了TCP/IP协议平台无关性的特点。uIP协议栈与系统底层和应用程序之间的接口关系如图(一)所示:

1

1uIP协议栈与系统底层的接口

uIP与系统底层的接口包括与设备驱动的接口和与系统定时器的接口两类。

1.1 uIP与设备驱动接口

uIP通过函数uip_input()和全局变量uip_buf、uip_len来实现与设备驱动的接口。uip_buf用于存放接收到的和要发送的数据包,为了减少存储器的使用,接收数据包和发送数据包使用相同的缓冲区。uip_len表明接收发送缓冲区里的数据长度,通过判断uip_len的值是否为0来判断是否接收到新的数据,是否有数据要发送。当设备驱动接收到一个IP包并放到输入包缓存里(uip_buf)后,应该调用uip_input()函数。uip_input()函数是uIP协议栈的底层入口,由它处理收到的IP包。当uip_input()返回,若有数据要发送,则发送数据包放在包缓冲区里。包的大小由全局变量uip_len指明。如果uip_len是0,没有包要发送;如果uip_len大于0则调用网络设备驱动发送数据包。

1.2 uIP与系统计时接口

TCP/IP协议要处理许多定时事件,例如包重发、ARP表项更新。系统计时用于为所有uIP内部时钟事件计时。当周期计时激发,每一个TCP连接应该调用uIP函数uip_periodic()。TCP连接编号作为参数传递给uip_periodic()函数。uip_periodic()函数检查参数指定的连接的状态,如果需要重发则将重发数据放到包缓冲区(uip_buf)中并修改uip_len的值。当uip_periodic()函数返回后,应该检查uip_len的值,若不为0则将uip_buf缓冲区中的数据包发送到到网络上。

ARP协议对于构建在以太网上的TCP/IP协议是必须的,但对于构建与其他网络接口(例如:串行链路)上的TCP/IP则不是必需的。为了结构化的目的,uIP将ARP协议作为一个可添加的模块单独实现。因此,ARP表项的定时更新要单独处理。系统定时器对ARP表的更新进行定时,定时时间到则调用uip_arp_timer()函数对过期表项进行清除。

2 uIP协议栈与应用程序的接口

应用程序作为单独的模块由用户实现,uIP协议栈提供一系列接口函数供用户程序调用。用户需将应用层入口程序作为接口提供给uIP协议栈,定义为宏UIP_APPCALL()。uIP在接收到底层传来的数据包后,若需要送上层应用程序处理,它就调用UIP_APPCALL()。uIP提供给应用程序的接口函数按功能描述如下:

2.1 接收数据接口:应用程序利用uip_newdata()函数检测是否有新数据到达。全局变量uip_appdata指针指向实际数据。数据的大小通过uip_datalen()函数获得。

2.2 发送数据接口:应用程序通过使用uIP函数uip_send()发送数据。uip_send()函数采用两个参数;一个指针指向发送数据起始地址,另一个指明数据的长度。

2.3 重发数据接口:应用程序通过测试函数uip_rexmit()来判断是否需要重发数据,如果需要重发则调用uip_send()函数重发数据包。

2.4 关闭连接接口:应用程序通过调用uip_close()函数关闭当前连接。

2.5 报告错误接口:uIP提供错误报告函数检测连接中出现的错误。应用程序可以使用两个测试函数uip_aborted()和uip_timedout()去测试那些错误情况。

2.6 轮询接口:当连接空闲时,uIP会周期性地轮询应用程序,判断是否有数据要发送。应用程序使用测试函数uip_poll()去检查它是否被轮询过。

2.7 监听端口接口:uIP维持一个监听知名TCP端口的列表。通过uip_listen()函数,一个新的监听端口打开并添加到监听列表中。当在一个监听端口上接收到一个新的连接请求时,uIP产生一个新的连接和调用该端口对应的应用程序。

2.8 打开连接接口:在uIP里面通过使用uip_connect()函数打开一个新连接。这个函数打开一个新连接到指定的IP地址和端口,返回一个新连接的指针到uip_conn结构。如果没有空余的连接槽,函数返回空值。

2.9 数据流控制接口:uIP提供函数uip_stop()和uip_restart()用于TCP连接的数据流控制。应用程序可以通过函数uip_stop()停止远程主机发送数据。当应用程序准备好接收更多数据,调用函数uip_restart()通知远程终端再次发送数据。函数uip_stopped()可以用于检查当前连接是否停止。

四 uIP在51系列单片机上的应用

51系列单片机具有悠久的历史和广泛的应用,许多公司推出了具有更高的处理速度的51内核的8位单片机,被应用在各个领域内。因此使用uIP这种免费的TCP/IP协议栈解决由51内核的单片机构建的低端嵌入式设备的网络接入问题具有一定的代表性。下面将讨论利用uIP协议栈在51单片机上实现简单的WEBSERVER,远端用户可以通过浏览器访问存储在单片机系统上的WEB页面。

2

硬件平台结构如图(二)所示:其中单片机选用PHILIPS公司的P89C51RD2,64K字节的串行EEPROM可以用于存储WEB页面。采用ISA接口的以太网接口芯片RTL8019AS连接到以太网上。通过MAX232实现与PC机的串行连接,可以显示调试信息。

uIP协议栈是以函数库的形式提供的,本身不提供底层网络驱动和上层应用程序。因此为了完成指定的功能,开发者必须添加以下几个模块:底层RTL8019AS网卡芯片的驱动、应用层基于HTTP协议的WEBSERVER的实现、系统定时器。

RTL8019AS的驱动主要包括三部分:init_8019as()函数完成网卡芯片的上电初始化,包括设定网卡物理地址,设定收发缓冲区位置和大小等;eth_send()函数完成数据的发送;eth_rcve()函数完成以太网数据的接收。底层网络设备驱动程序与uIP协议栈通过两个全局变量进行接口:变量uip_buf为收发缓冲区的首地址;uip_len为收发的数据长度。eth_send()函数将uip_buf里的uip_len长度的数据发送到以太网上。eth_rcve()函数将接收到的数据存储到uip_buf指定的缓冲区中,同时修改uip_len的值。

uIP提供的源代码中包括一个基于HTTP协议的WEBSERVER示例,该WEBSERVER通过简单的文件系统在数据存储器中存储静态页面,同时具有CGI功能。用户可以参照该示例以及uIP提供给应用程序的接口函数说明实现自己的应用层功能。用户的应用程序中必须将UIP_APPCALL宏定义为该层的服务程序。例如:在示例程序中WEBSERVER的处理程序为httpd()函数,则要进行如下的宏定义#defineUIP_APPCALLhttpd。

51系列单片机上都有2到3个定时计数器,可以选择其中的一个来为TCP/IP协议中与时间有关的事件定时。需要由用户处理的定时事件包括:为uip_periodic()函数的执行提供基准,还要为ARP表项的更新定时。uip_periodic()函数每0.5秒执行一次,ARP表项每10秒更新一次。

uIP的设置单独包含在一个叫uipopt.h的头文件里,都是以宏的形式定义方便于修改。用户应根据自己的应用在uipopt.h文件里设置本地的物理地址、IP地址、网关地址、收发缓冲区的大小、支持的最大连接数、ARP表大小等等选项。

添加了必须的模块,对uIP进行了正确地配置后,需要编写主程序函数。针对基于以太网的WEBSERVER应用,主程序在完成初始化后将不停的进行查询,如果有新数据包到达则送uip_input()函数处理;如果没有新数据包到达则处理定时事件。框架代码如下所示:

void main(void) //主程序开始

{ …… //省略部分代码

timer0_init(); //定时器初始化函数由开发者完成

serial_init(); //串口初始化函数由开发者完成

init_8019(); //网卡芯片初始化函数由开发者完成

uip_init(); //uIP协议栈初始化函数由uIP协议栈提供

httpd_init(); //HTTP应用程序初始化函数由WEB SERVER示例程序提供

uip_arp_init();//ARP协议初始化函数由ARP模块提供

while(1)

{ uip_len = eth_rcve(); //查询网卡是否有数据到来

if(uip_len == 0) //如果没有数据到来则处理定时事件

{ if(0.5秒定时时间到)

{ for(i = 0; i < UIP_CONNS; i++) // UIP_CONNS为TCP连接数

{ uip_periodic(i); //处理每一个TCP连接

if(uip_len > 0) //说明本连接有数据要发送或重发

{ uip_arp_out(); //由ARP处理部分添加以太网帧头

eth_send(); //由网卡驱动程序发送

}//对应于:for()

}// 对应于:if(0.5秒定时时间到)

if(ARP表项更新时间到)

uip_arp_timer(); //进行ARP表项更新

}else if(uip_len > 0) //说明接收到新的数据包

{ if(BUF->type == htons(UIP_ETHTYPE_IP))//如果收到IP数据包

{ uip_arp_ipin(); //送ARP模块进行表项更新

uip_len -= sizeof(struct uip_eth_hdr); //去除以太网帧头

uip_input(); //送uip_input()进行处理

if(uip_len > 0) //若uip_input()返回后uip_len不为零说明有数据要回送

{ uip_arp_out(); //由ARP部分添加以太网帧头

eth_send(); //送交网卡驱动发送

}//对应于:if()收到IP数据包

}else if(BUF->type == htons(UIP_ETHTYPE_ARP))//如果收到ARP包

{ uip_arp_arpin(); //由uip_arp_arpin()处理,如果为应答包则进行表项

//更新

//如果为请求包,则构造应答数据包

if(uip_len > 0) //说明收到的是ARP请求包,需要回送ARP应答包

eth_send(); //送网卡驱动发送

}//对应于:else if()收到ARP数据包

}//对应于:else if() 说明接收到新的数据包

}}

以上实例在keilC51编译器下设置大模式,优化等级6(速度优先)进行编译,对uIP代码部分可以不做任何修改,对HTTP示例代码仅需针对类型表达进行极少量的修改即可编译通过。在硬件平台上运行良好。

五 总结

uIP协议栈采用有效的方法和结构化的代码,使其存储器占用量很小并且可以很方便的应用到不同的工程项目中。同时它又是免费的可以自由使用于商业和非商业目的。uIP为低端嵌入式设备的网络接入提供了很好的解决方案,具有很高的应用价值。

参考文献

[1]DOUGLASE.COMER著,用TCP/IP进行网际互连(卷一、卷二)电子工业出版社,2000

[2]JEREMYBENTHAM著,嵌入式系统Web服务器——TCP/IPLean机械工业出版社,2003

[3]uIP协议栈网络站点http://dunkels.com/adam/uip/

[4]REALTEK公司.RTL8019ASDatasheet,2000

邓治国 张维新 供稿 来源:网络

Category : TCP/IP | Blog