lwIP相关TCP/IP应用函数

lwIP为使用TCP/IP协议通信的应用程序编程提供了两种接口接口(APIs):

* 低层次的称之为"core" / "callback" 或者 "raw" API

* 高层次的称之为"sequential" API

lwIP "sequential" API为使用TCP/IP协议栈编程提供符合常规的、通用的途径,它与BSD socket API非常相似。程序的执行过程同样是基于"open-read-write-close"模型的。从本质上讲,TCP/IP协议栈的通信过程是事件驱动的,因此,TCP/IP的代码和用户应用程序的代码必须在不同的线程里面。

**以下内容讨论"RAW" API**

RAW IP允许应用程序和TCP/IP代码紧密结合,程序的执行是基于在TCP/IP内核中被调用的回调函数事件驱动的。TCP/IP内核和应用程序可以运行在同一线程。lwIP "sequential" API接口会消耗大量的CPU资源,它并不适用于小型嵌入式系统,因为它必须运行在多线程环境中。

RAW API不仅执行速度快,而且消耗的内存资源更少。它的缺点是应用程序编写相对较难并不易理解。尽管如此,这种方式仍是资源较少的嵌入式系统的首选方法。

不同的应用程序中我们可以同时使用这两种APIs,实际上"sequential" API就是由RAW API封装后得到的。

什么是回调函数?

RAW API是基于回调函数所驱动的。每一个回调函数实际上只是一个普通的C函数,这个函数在TCP/IP内核中被调用。每一个回调函数都作为一个参数传递给当前TCP或UDP连接。而且,为了能够保存程序的特定状态,可以向回调函数传递一个指定的状态,并且这个指定的状态是独立于TCP/IP协议栈的。

--应用程序设置状态函数

- void tcp_arg(struct tcp_pcb *pcb, void *arg)

指定传给所有回调函数的特定状态参数。参数"pcb"指当前TCP连接控制块,"arg"指传递给回调函数的参数。

--TCP连接函数

这些函数用于建立连接,它们与"sequential" API以及BSD socket API非常相似。使用tcp_new()函数建立一个新的TCP标识符(也就是协议控制块-PCB)。这个PCB可以用来监听一个外来的连接(译注:作为服务器)也可以连接到另一个主机(译注:作为客户端)。

- struct tcp_pcb *tcp_new(void)

创建一个新的连接标识符(PCB)。如果没有有效的存储空间创建这个新的pcb,返回NULL。

译注:这个函数创建一个TCP协议控制块,但并不把它放到任何TCP PCB列表,直到使用tcp_bind()函数绑定。Tcp_new()函数会调用tcp_alloc函数来动态申请一块内存并初始化它,之后将这块内存的首地址返回给tcp_new()函数,如果动态内存不成功的话返回NULL。

- err_t tcp_bind(struct tcp_pcb *pcb, struct ip_addr *ipaddr,

u16_t port)

给pcb绑定一个本地IP地址和端口号。如果参数"ipaddr"为IP_ADDR_ANY,则为这个pcb绑定任意本地IP地址。

译注:这个函数的大部分代码用于检验给出的IP地址和端口号是否合适,如果合适则将给出的IP地址和端口号赋给当前PCB,更新已绑定tcp_pcb列表并返回ERR_OK.如果给出的参数不合适,则返回ERR_USE(表示端口已被使用)。

参数ipaddr如果为IP_ADDR_ANY,表示绑定到任意本地地址,那么IP_ADDR_ANY是什么呢?在lwip-1.3.0\src\include\ipv4\lwip\ip_addr.h中定义了:

#define IP_ADDR_ANY         ((struct ip_addr *)&ip_addr_any)

ip_addr_any是一个ip_addr型变量,在lwip-1.3.0\src\core\ipv4\ip_addr.c中有如下声明:

#define IP_ADDR_ANY_VALUE 0x00000000UL

const struct ip_addr ip_addr_any = { IP_ADDR_ANY_VALUE };

所以, IP_ADDR_ANY是等于0x00000000UL的. 在IP地址上规定  0.0.0.0为广播地址,也就是任意地址的意思。

- struct tcp_pcb *tcp_listen(struct tcp_pcb *pcb)

指定一个PCB进入监听状态。当一个远端连接访问时,函数 tcp_accept()指定的回调函数将被调用。在调用这个函数之前一定要使用tcp_bind()函数绑定一个本地IP和端口号。

tcp_listen() 函数返回一个新的连接标识符,原始的pcb会被释放,这是为了节省内存,使之更适合小内存系统。

如果监听连接的内存无效,tcp_listen()函数返回NULL,如果这样的话,传入的PCB参数将不会被释放。

这个函数从原理上看也比较简单,首先是做一些必要的检查,判断原始pcb是否已经处于连接状态,如果没有则申请一块tcp_pcb类型的内存,将原始的必要的pcb内容复制到新的pcb中,设置新的pcb状态为LISTEN,释放原始的pcb,并将新pcb连接放入已监听队列。

- struct tcp_pcb *tcp_listen_with_backlog(struct tcp_pcb *pcb, u8_t backlog)

这个函数和tcp_listen()函数相同,只是限制了TCP监听队列连接个数,这个个数由backlog参数指定。为了使用它,你必须在你的lwipopt.h中设置TCP_LISTEN_BACKLOG=1。

- void tcp_accepted(struct tcp_pcb *pcb)

通知lwIP一个传入的连接已经被接受。通常这个函数在“accept()”函数的回调函数中被调用。这允许lwIP处理自身内部的任务。比如,允许更多传入的连接进入监听队列。

- void tcp_accept(struct tcp_pcb *pcb,

err_t (* accept)(void *arg, struct tcp_pcb *newpcb,

err_t err))

指定应在侦听连接上的一个新的连接到达时调用的回调函数。

- err_t tcp_connect(struct tcp_pcb *pcb, struct ip_addr *ipaddr,

u16_t port, err_t (* connected)(void *arg,

struct tcp_pcb *tpcb,

err_t err));

设置打开连接的pcb连接到远程主机并发送初始的SYN段。

函数tcp_connect() 会立即返回;它并不等待这个连接是否被正确设置。相反的,当连接正确建立后它将调用第四个参数("connected"参数)指定的函数。如果这个连接不能正确的建立,可能是主机拒绝这个连接或者主机没有响应,"connected"函数将被调用并设置一个相应的参数。

当入队的SYN段内存不可用时,tcp_connect()函数能返回ERR_MEM,表示连接没有正确建立。如果SYN成功入队,tcp_connect()函数返回ERR_OK。

---TCP数据发送函数

lwIP会调用tcp_write()函数来发送队列中的数据。当数据成功的发送到远程主机,会调用一个指定的回调函数来通知应用程序。

- err_t tcp_write(struct tcp_pcb *pcb, void *dataptr, u16_t len,

u8_t copy)

参数"dataptr"指向数据队列;参数"len"传递数据的长度;参数"copy"的值为0或者1,表明是否需要申请新的内存用于数据的拷贝。如果这个参数为0,则不需要申请新的内存,此时数据只能使用指针来引用。

如果数据长度超过当前发送缓存字节数或者要发送的段队列长度超过lwipopts.h中定义的上限值,tcp_write()函数执行失败并返回ERR_MEN。可以使用tcp_sndbuf()函数来返回输出队列有效的字节数。

使用这个函数的正确方法是根据tcp_sndbuf() 函数返回的字节数来发送数据。如果函数返回ERR_MEM,应用程序应该等待直到当前队列数据成功的被远程主机收到然后尝试重新发送一次。

- void tcp_sent(struct tcp_pcb *pcb,

err_t (* sent)(void *arg, struct tcp_pcb *tpcb,

u16_t len))

当远程主机成功接收(也就是应答信号)到数据时,该函数指定的回调函数被调用。传送给回调函数的"len"参数给出了上一次已经被确认的发送的最大字节数。

--TCP数据接收函数

TCP数据接收是基于回调函数的---当一个新的数据接收到时,应用程序指定的回调函数被调用。当应用程序接收到数据后,它必须调用tcp_recved()函数来指示接收数据的大小。

- void tcp_recv(struct tcp_pcb *pcb,

err_t (* recv)(void *arg, struct tcp_pcb *tpcb,

struct pbuf *p, err_t err))

当接收到数据时,本函数设置的回调函数将被调用。如果传递给回调函数一个NULL pbuf则说明远程主机关闭了这个连接。如果函数正常运行并且回调函数返回ERR_OK,则必须释放这个pbuf,如果其它情况,必须保存这个pbuf,这样才能让lwIP内核保存它以供应用程序检查并恢复错误。

- void tcp_recved(struct tcp_pcb *pcb, u16_t len)

当应用程序接收到数据后必须调用这个函数。参数"len"表明接收到的数据的长度。

--- 应用程序轮询函数

当一个连接空的时候(也就是说,既没有数据接收也没有数据发送),lwIP会通过调用一个指定的回调函数来重复轮询应用程序。这可以用作一个看门狗定时器,用来终止空闲时间太长的连接;或者用作等待内存有效的一种方法。举例来说,如果调用tcp_write()函数时因为内存无效而失败,应用程序可以使用轮询功能在连接空闲的时候再次调用tcp_write()。

- void tcp_poll(struct tcp_pcb *pcb, u8_t interval,

err_t (* poll)(void *arg, struct tcp_pcb *tpcb))

指定轮询间隔和应用程序轮询时调用的回调函数。这个间隔是以TCP粗粒度定时器为单位的,即500毫秒一次。如果参数"interval"的值为10,则意味着每5秒轮询一次应用程序。

---关闭和终止连接函数

- err_t tcp_close(struct tcp_pcb *pcb)

关闭连接。如果关闭的连接内存无效,函数返回ERR_MEM,如果是这样的话,应用程序应该等待并通过使用acknowledgment回调函数或者轮询功能重新关闭连接。如果连接关闭成功,函数返回WRR_OK。

TCP内核调用tcp_close()后,参数"pcb"指定的连接被解除。

- void tcp_abort(struct tcp_pcb *pcb)

通过向远程主机发送一个RST(复位)段来终止连接。这个函数从不会失败。

如果这个连接因为一个错误而被终止,则应用程序可以通过err回调函数灵活的处理这个事件。通常一个连接因错误而终止的原因是内存不足。这时使用tcp_err()函数设置的回调函数被调用。

- void tcp_err(struct tcp_pcb *pcb, void (* err)(void *arg,

err_t err))

指定一个处理错误的回调函数,该回调函数不能得到本函数的"pcb"作为它的参数,因为这个pcb可能已经被解除。

--- 低层次TCP接口

在系统的较低层,TCP提供一个简单的接口。在系统初始化的时候,任何其他TCP函数被调用之前必须先调用tcp_init()函数。当系统已经运行,两个定时器函数tcp_fasttmr() 和tcp_slowtmr()必须定期被调用。tcp_fasttmr()函数必须每隔TCP_FAST_INTERVAL(定义在tcp.h中)个毫秒被调用一次,tcp_slowtmr() 函数必须每隔TCP_SLOW_INTERVAL个毫秒被调用一次。

--- UDP 接口

相比之下,UDP接口要比TCP接口类似,但UDP在低层次的复杂程度上明显比TCP简单。

- struct udp_pcb *udp_new(void)ige

创建一个用于UDP通讯的UDP pcb。这个pcb直到绑定本地地址或者连接到远程地址后才被激活。

- void udp_remove(struct udp_pcb *pcb)

删除一个指定的连接。

-err_t udp_bind(struct udp_pcb *pcb, struct ip_addr *ipaddr, u16_t port)

为pcb绑定一个本地地址。参数"ipaddr"为IP_ADDR_ANY时,指定可以监听任何本地IP地址。这个函数一般都会返回ERR_OK。

- err_t udp_connect(struct udp_pcb *pcb, struct ip_addr *ipaddr, u16_t port)

设置pcb连接到远程主机。这个函数不产生任何流量,仅设置pcb的远程地址。

- err_t udp_disconnect(struct udp_pcb *pcb)

删除远程端的pcb。这个函数不产生任何流量,近视删除pcb的远程地址。

- err_t udp_send(struct udp_pcb *pcb, struct pbuf *p)

发送pbuf结构指针p指向的数据。这个pbuf不会被释放。

- void udp_recv(struct udp_pcb *pcb,

void (* recv)(void *arg, struct udp_pcb *upcb,

struct pbuf *p, struct

ip_addr *addr,

u16_t port),

void *recv_arg)

当接收到一个数据包后,该函数指定的回调函数将被调用。

---系统初始化

一个完整通用的lwIP初始化步骤是不可能实现的,因为它还取决于配置文件(lwipopts.h)的编写以及初始化额外运行时的环境(例如硬件定时器)。

当你使用RAW API时,我们可以给你一些建议。

我们假设你使用一个单一的以太网netif和UDP、TCP传输层、IPv4和DHCP客户端。

安以下顺序调用这些函数:

- stats_init()

清楚运行时被收集的统计结构。

- sys_init()

没有多大用处,因为我们在lwipopts.h中设置NO_SYS 1

Not of much use since we set the NO_SYS 1 option in lwipopts.h, to be called for easy

configuration changes.

- mem_init()

通过定义MEM_SIZE初始化动态存储堆

- memp_init()

通过定义MEMP_NUM_x初始化内存池。

- pbuf_init()

通过定义PBUF_POOL_SIZE初始化pbuf内存池。

- etharp_init()

初始化ARP表和队列。

注:在这个初始化之后你必须每隔 ARP_TMR_INTERVAL(5秒)个周期间隔调用etharp_tmr 函数。

- ip_init()

不常用,处理将要放生的改变时被调用。

- udp_init()

清除UDP PCB列表。

- tcp_init()

清除TCP PCB列表并清除一些内部定时器。

注:在这个初始化函数之后,你必须按预先确定的每个周期内调用tcp_fasttmr() 和 tcp_slowtmr()函数。

- netif_add(struct netif *netif, struct ip_addr *ipaddr,

struct ip_addr *netmask, struct ip_addr *gw,

void *state, err_t (* init)(struct netif *netif),

err_t (* input)(struct pbuf *p, struct netif *netif))

向netif_list列表中增加你的网络接口。分配一个netif结构体并传递一个指向这个结构体的指针作为第一个参数。当使用DHCP时给定的ip_addr结构体会被清除,或者用其它数据填充它们。"state"指针可能为NULL。

函数指针"init"必须指向你的以太网netif接口初始化函数,下面举例说用该函数的应用。

err_t netif_if_init(struct netif *netif)

{

u8_t i;

for(i = 0; i < ETHARP_HWADDR_LEN; i++) netif->hwaddr[i] = some_eth_addr[i];

init_my_eth_device();

return ERR_OK;

}

为使用以太网驱动器(For ethernet drivers),函数指针"input"必须指向"netif/etharp.h"中声明的ethernet_input() 函数。其它驱动器(Other drivers)必须使用"lwip/ip.h"中声明的ip_input()函数。

- netif_set_default(struct netif *netif)

注册默认网络接口

- netif_set_up(struct netif *netif)

当netif完全配置后,这个函数必须被调用。

- dhcp_start(struct netif *netif)

在第一次调用时为这个接口创建一个新的DHCP客户端。

注:启动这个客户端后你必须按照预先设定的间隔周期性的调用dhcp_fine_tmr() 和dhcp_coarse_tmr()函数。

你可以通过结构体netif->dhcp查看真实的DHCP状态。

--- 优化提示

首先要做的是优化src/core/inet.c中的lwip_standard_checksum()程序。你可以使用

#define LWIP_CHKSUM  <your_checksum_routine>

来重写这个标准函数。

inet.c中使用C语言编写的例子,你也可以使用汇编语言编写。

RFC1071是这个主题的很好的介绍。

如果你使用小端处理器,另一个有效的改善是用汇编语言或者内联函数代替htons() 和 htonl()函数。

#define LWIP_PLATFORM_BYTESWAP 1

#define LWIP_PLATFORM_HTONS(x)  <your_htons>

#define LWIP_PLATFORM_HTONL(x)  <your_htonl>

如果你的网络读到的速度比最大线速还要大,检查你的网络接口。如果硬件不能提供良好的服务,会经常快速的发生缓冲区溢出现象。举例来说,当使用cs8900处理器时,调用cs8900if_service(ethif)函数可能很频繁出现上述现象。当使用的RTOS允许cs8900使用中断唤醒一个服务于一个你的使用一个二进制信号量或事件标志的驱动程序的高优先级任务。

当产品发布时,建议设置LWIP_STATS为0。

注意速度性能的提高和多方面有关,并不是简单的提高内存的容量。

时间: 2024-11-05 10:36:40

lwIP相关TCP/IP应用函数的相关文章

TCP/IP 相关总结

1.   三次握手协议 在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接. 第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认: 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态: 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端

TCP/IP具体解释--TCP/UDP优化设置总结&amp;amp; MTU的相关介绍

首先要看TCP/IP协议,涉及到四层:链路层,网络层.传输层,应用层. 当中以太网(Ethernet)的数据帧在链路层 IP包在网络层 TCP或UDP包在传输层 TCP或UDP中的数据(Data)在应用层 它们的关系是 数据帧{IP包{TCP或UDP包{Data}}} --------------------------------------------------------------------------------- 在应用程序中我们用到的Data的长度最大是多少,直接取决于底层的限

tcp/ip协议listen函数中backlog参数的含义

listen函数的定义如下所示: #include <sys/socket.h> int accept(int sockfd, struct sockaddr * restrict addr, socklen_t *restrict len); 返回值:若成功则返回文件(套接字)描述符,若出错则返回-1 int listen(int sockfd, int backlog);返回值:若成功则返回0:若出错则返回-1 之前看书的时候对listen函数的参数backlog不是很理解,今天看到一篇很

&lt;再看TCP/IP第一卷&gt;关于链路层的知识细节及相关协议

在TCP/IP协议族中,链路层的主要有三个目的: (1)为IP模块发送和接受数据报 (2)为ARP模块发送ARP请求和接受ARP应答 (3)为RARP发送RARP请求和接受RARP应答 TCP/IP支持多种不同的链路层协议,这取决于网络所使用的硬件,如以太网,令牌环网,FDDI,及RS-232串行线等. 两个串行接口链路层协议: SLIP:(Serial Line IP)它是一种在串行线路上对IP数据报进行封装的简单形式,SLIP适合用于家庭中每台计算机及都有的RS-232串行端口和告诉调制解调

TCP/IP基础知识

最近工作中需要自己在板卡植入TCP/IP协议栈,因为毕竟单片机性能有限,完整的TCP/IP协议栈很庞大,所以只能移植经过简化的,本来已成功将LWIP协议栈移植到项目板卡中,但老大说这个协议栈写得过于繁琐,特别是在内存管理这块,不容易理解,而且我们板卡对数据的准确性要求没那么高,重点要保证能通就行,偶尔丢失一两个包都没关系,所以按此需求自写协议栈,实现UDP以及Telnet通信,目前UDP已经实现,其余功能还在慢慢增加中,通过这段时间的研究发现实现UDP并不难,最主要是要把网络通信的一些基本概念知

TCP/IP详解学习笔记(一) 概述

生活中有舒适区,借口成为懒惰的护身符,学习也有舒适区,逃避便是阻止进步的最大障碍. 经过半年多嵌入式方面的工作和学习,我提高了很多,但同时我也对自己所面临的问题逐渐清晰: 1. 偏于实践,理论基础不牢固 2. 对算法,数据结构认知太浅 3. 对界面程序以及GUI相关学习比较排斥 我也一直主观上逃避对这些知识的学习,这也是为什么我写的内容都是以实践为主了.从本系列开始,我尝试离开大脑的舒适区,巩固自己理论相关的知识. TCP/IP作为整个现代互联网社会的基础,有着至关重要的作用,因此学习掌握协议栈

TCP/IP、Http、Socket的区别

网络由下往上分为 物理层.数据链路层.网络层.传输层.会话层.表示层和应用层. 通过初步的了解,我知道IP协议对应于网络层,TCP协议对应于传输层,而HTTP协议对应于应用层, 三者从本质上来说没有可比性, socket则是对TCP/IP协议的封装和应用(程序员层面上). 也可以说,TPC/IP协议是传输层协议,主要解决数据如何在网络中传输, 而HTTP是应用层协议,主要解决如何包装数据. 关于TCP/IP和HTTP协议的关系,网络有一段比较容易理解的介绍: “我们在传输数据时,可以只使用(传输

Linux下TCP/IP协议栈的简单脉络分析

最近在写网络编程方面的一些东西,然后遇到了关于传输上的小问题.由于之前有简单的看过一些TCP/IP详解的一些东西,所以索性就找了本<追踪LinuxTCP/IP代码运行>的书看了一上午,结果发现初次接触这些内核方面的东西,收获甚微.于是又在网上找相关类的大神博客,拿来拜读,虽然依然看的不是太明白,吸收的也不够好,但是我想以博客的形式把它记录下来,也希望能为我以后学这些东西开个好头吧 1.linux的网络协议栈的主要结构 (1)socket层 这层主要处理socket相关的东西,例如其各种结构的初

《TCP/IP详解卷2:实现》笔记--TCP:传输控制协议

传输控制协议,即TCP,是一种面向连接的传输协议,为两端的应用程序提供可靠的端到端数据流传输服务,它完全不同于 无连接的.提供不可靠数据传输服务的UDP协议. 下图描述了各TCP函数与其他内核函数之间的关系,带阴影的椭圆分别表示我们将要讨论的9个主要的TCP函数. 1.TCP的protosw结构 下图列出了TCPprotosw结构的成员变量,它定义了TCP协议与系统内其他协议之间的交互接口. 2.TCP的首部 tcphdr结构定义了tcp首部.下图给出了tcphdr结构的定义和TCP首部. 大多