本文转自music_fong博客:http://blog.csdn.net/music_fong/article/details/7191773
以前自己写了一个TCP/IP的协议栈,但是需要的48K的RAM。后来随着产品功能的增加,RAM资源就越来越不够用了。在一个偶然的机会下,发现了一个开源的协议栈--uIP。于是在网上找了一些资料。
原来现在比较流行的开源的小型协议栈有两个--lwIP和uIP。
lwIP主页:http://savannah.nongnu.org/projects/lwip/
uIP主页:http://www.sics.se/~adam/old-uip/
uIP文档:http://www.sics.se/~adam/download/?f=uip-1.0-refman.pdf
其中lwIP实现的功能比较多,所以其会较复杂;uIP主要实现了TCP和UDP以及ARP的等简单功能。并且两者都可用于裸机或有操作系统。由于我只需要简单的功能,所以选择了简单的uIP。
下面概述一下uIP及裸机的移植方法,不足之处,请大家多多指正。
uIP概述
uIP的结构不是层次式的,其整个处理过程单独为一个进程,即一个函数,下面就大概分析这个函数。
一、主循环控制
在主循环中,需要处理两个事情。
1. 检测是否有数据到来,当有数据到来时调用uip_input()函数。
2. 检测网络定时器是否超时,如果超时,则调用uip_periodic()函数。
二、体系结构相关功能定义
1. 检验计算,可以手动修改的两个函数是uip_ipchksum()和uip_tcpchksum()。
2. uip不使用32-bit的变量,所以要手动实现uip_add32()这个函数。
(这里移植的时候可以不重写这些函数,按照其默认的算法也可以)
三、内存管理
1. 整个协议栈使用一个全局的buffer(变量uip_buf)装载收到的数据包,所以有数据到来时,必须立刻处理该数据,或者将数据从buffer中考出来;只有buffer中的数据处理了,才会接受下一包数据。
2. 当程序正在处理数据包时,这时如果有数据到来,则需要网卡的驱动程序去把数据按顺序缓存
有一些网卡可以缓存好几包的数据(即网卡或驱动程序把数据包缓存起来),否则丢掉数据或
降低性能。(但这一般只会在多个TCP连接时才会发生)
3. 发送的数据的头部会使用全局buffer,同时uIP没有重传缓冲,即没有把已发送的数据排队,
所以当需要重传数据时,需要应用程序再生成重传数据。
4. 总共需要的内存可以按应用程序的发在来配置。
移植
一、下载源代码。
从http://www.sics.se/~adam/old-uip/download.html下载源代码。解压后,uip的核心代码都在uip文件夹中,所以我们只要把该目录放到自己的工程里面就行了。同时去掉
clock.h timer.c(因为我不使用这种定时方法,我只是在定时器里自加来做超时)
psock.c psock.h(因为我是裸机,所以不需用到这个功能)
uip-split.c uip-split.h(因为我的程序中不需要分包,所以也没有这个功能)
二、配置
配置文件是uipopt.h和uip-conf.h文件,其中uip-conf.h在解压后的unix文件夹下,并且include在uipopt.h文件中,具体配置参考其文档。
我修改了以下地方
//#define UIP_CONF_BYTE_ORDER UIP_LITTLE_ENDIAN // 注释这行,如果该配置,则默认配置为LITTLE_ENDIAN(小端系统)
#define UIP_CONF_BUFFER_SIZE 1500 //定义了uip_buf的大小
#include "client.h" //include了我的客户端程序的头文件
在uipopt.h中
#define UIP_APPCALL HandleClientApp //“HandleClientApp”是我的客户端处理函数。因为其收到数据或定时器到期,都会回调该函数。
三、结构和变量的定义
在我的"client.h"文件中定义了
struct client_state {
uint8 state;
};
typedef struct client_state uip_tcp_appstate_t; // 这里定义了uip_tcp_appstate_t 结构,这个结构在uip_conn结构里引用,其作用是记录应用程序的状态。
四、初始化
uip_ipaddr_t ipaddr;
struct uip_eth_addr temp_eth_addr;
uip_init();
uip_arp_init();
// 设置主机的ip地址
uip_ipaddr(ipaddr, 192, 168, 1,228);
uip_sethostaddr(ipaddr);
// 设置子网掩码
uip_ipaddr(ipaddr, 255, 255, 255,0);
uip_setnetmask(ipaddr);
// 设置网关
uip_ipaddr(ipaddr, 192, 168, 1,1);
uip_setdraddr(ipaddr);
// 设置mac
temp_eth_addr.addr[0] = SourceMAC[0];
temp_eth_addr.addr[1] = SourceMAC[1];
temp_eth_addr.addr[2] = SourceMAC[2];
temp_eth_addr.addr[3] = SourceMAC[3];
temp_eth_addr.addr[4] = SourceMAC[4];
temp_eth_addr.addr[5] = SourceMAC[5];
uip_setethaddr(temp_eth_addr);
五、与硬件相关部分(即把数据从网卡读出来或把数据发到网卡)
uint8 temp;
uint8 *inbuf;
//#define BUF ((struct uip_eth_hdr *)&uip_buf[0])
struct uip_eth_hdr *pfram;
//检测是否有数据到达
QueryRTL8019(); // 如果有数据到来,就置位EVENT_WORD
// 以太网接收数据
if(EVENT_WORD == VALID_FLAG) {
pfram = (struct uip_eth_hdr *)&uip_buf[0];
EVENT_WORD = 0;
uip_len = RTLReceivePacket(); // 这个函数的作用是吧网卡收到的数据拷贝到uip_buf中,并返回数据长度
if(uip_len > 0) {
if(pfram->type == HTONS(UIP_ETHTYPE_IP)) {
uip_arp_ipin();
uip_input();
if(uip_len > 0) {
uip_arp_out();
Eth_Send(); //这个函数发送网络数据
}
} else if(pfram->type == HTONS(UIP_ETHTYPE_ARP)) {
uip_arp_arpin();
if(uip_len > 0) {
Eth_Send();
}
}
}
}
if(ethernet_timer > 5) { //ethernet_timer在100ms定时器中自加ethernet_timer++
ethernet_timer = 0;
for(temp = 0; temp < UIP_CONNS; temp++) {
uip_periodic(temp);
//
// If the above function invocation resulted in data that
// should be sent out on the network, the global variable
// uip_len is set to a value > 0.
//
if(uip_len > 0) {
uip_arp_out();
Eth_Send();
}
}
#if UIP_UDP
for(temp = 0; temp < UIP_UDP_CONNS; temp++) {
uip_udp_periodic(temp);
//
// If the above function invocation resulted in data that
// should be sent out on the network, the global variable
// uip_len is set to a value > 0.
//
if(uip_len > 0) {
uip_arp_out();
Eth_Send();
}
}
#endif
}
//
// Process ARP Timer here.
//
if(arp_timer > 100) //arp_timer也在100ms定时器中自加
{
arp_timer = 0;
uip_arp_timer();
}
// 发送函数
void Eth_Send(void)
{
memcpy(EthernetTxData, uip_buf, UIP_LLH_LEN); // 把uip_buf中的帧的头部靠到EthernetTxData中
// 拷贝uip_buf中的剩下部分
if(uip_len <= UIP_LLH_LEN + UIP_TCPIP_HLEN) {
memcpy(&EthernetTxData[UIP_LLH_LEN], &uip_buf[UIP_LLH_LEN], uip_len - UIP_LLH_LEN);
} else {
memcpy(&EthernetTxData[UIP_LLH_LEN], &uip_buf[UIP_LLH_LEN], UIP_TCPIP_HLEN);
memcpy(&EthernetTxData[UIP_LLH_LEN+UIP_TCPIP_HLEN], uip_appdata, uip_len - UIP_TCPIP_HLEN - UIP_LLH_LEN);
}
// 把数据发到网卡
RTLSendPacket(EthernetTxData, uip_len);
uip_len = 0;
}
六、于应用程序相关部分
void HandleClientApp(void) // 配置中的函数
{
if (uip_connected()) {
//uip_send("Start\n",6);
ClientLinkStatus = CLIENT_WORK;
return;
}
if (ClientLinkStatus == CLIENT_CLOSING) {
uip_close();
ClientLinkStatus = CLIENT_UNLINK;
return;
}
if (uip_aborted() || uip_closed()) {
ClientLinkStatus = CLIENT_UNLINK;
uip_abort();
return;
}
if (uip_timedout()) {
uip_abort();
ClientLinkStatus = CLIENT_UNLINK;
return;
}
if(uip_acked()) {
ClientAcked();
}
if(uip_newdata()) {
ClientNewData(); // 这个是收到数据的处理函数
}
if(uip_rexmit() ||
uip_newdata() ||
uip_acked() ||
uip_poll()) {
ClientSendData(); // 这个函数的作用是把缓存中的数据靠到uip_buf中
}
}
//我在应用程序中会调用uint8 ClientSend(uint8 *pdata, uint16 len)函数,把要发的数据缓冲在ClientTxBuf 这个循环缓存中,然后在这里实际拷贝到uip_buf中发送
void ClientSendData(void)
{
uint16 len;
uint16 left;
uint8 *psend;
// 缓存中有数据
if (ClientTxSize>0) {
len = ClientTxSize > uip_mss() ? uip_mss():ClientTxSize;
if ((pTxBuf+len) > (ClientTxBuf+2048)) {
memcpy(ClientTxTempBuf, pTxBuf, 2048 - (pTxBuf-ClientTxBuf));
left = 2048-(pTxBuf-ClientTxBuf);
memcpy(ClientTxTempBuf+left, ClientTxBuf, len-left);
uip_send(ClientTxTempBuf, len);
} else {
uip_send(pTxBuf, len);
}
}
}
七、总结
整个移植主要处理三方面的事情,一是跟应用程序的接口,即应用程序怎么知道收到数据(在HandleClientApp里面检测uip_newdata()是否收到信数据),以及怎么发数据;二是跟硬件的接口,即怎么知道网口收到了数据和怎么发数据(我是通过循环检测处理的);三是定时问题,因为整个协议栈的定时都是通过uip_periodic(uint8 conn_id)处理的。