LwIP Application Developers Manual6---Application API layers
1.前言
lwIP提供3种应用编程接口来跟TCP/IP内核通信,如下所示:
- 低水平的内核/回调或raw API
- 2个高水平序列API:
1) netconn API
2) socket API(为了兼容POSIX/BSD-socket)
2.选择合适的应用编程接口
- 序列API
序列API提供了一种普通、有序的程序来使用lwIP内核。它与BSD的socket API非常相似。该序列API的模型是建立在阻塞的打开-读-写-关闭(open-read-write-close)模式上。
由于TCP/IP栈本质上是基于事件的,TCP/IP的代码和应用程序必须适应在不同的可执行的上下文(线程)内。
- RAW API
当原始(raw)和序列API混合使用时,你要很小心了:rawI函数只能被主tcpip_thread调用。注册回调函数(或初始化一个pcb)也一定要在那个上下文中完成(比如,在启动时调用tcpip_init_callback时或运行时调用tcpip_callback)。
- 选择合适的API的几点建议
1) netconn-和raw-API只属于lwIP本身,用这些API写的代码并不能移植并重新使用在其它协议栈上。
2) socket API与1)相反,用该API写的代码可以付出较小的工作量就可以移植到posix协议栈上。
3) socket-和netconn-API是序列API,该API需要多线程间的支持(一个线程用于使用API,一个线程用来为协议栈处理定时器、接收到的数据包等)
4) RAW API使用回调函数的机制(比如,当新的数据到达时,应用程序的回调函数被调用)。如果你习惯用序列API编程,那么你使用原始API编程会变得比较困难。
5) RAW API由于它不需要线程间的切换可以得到最好的性能。
6) raw-和netconn-API对于发送和接收支持零复制(尽管DMA使能,MACs可以防止对接收的数据进行复制)
3.原始(raw/native)API
3.1 原始API介绍
原始API是一个基于事件驱动的,该API也可以不需要操作系统的支持而且不需要重复复制接收和发送的数据。
内核协议栈经常使用原始API来进行各种协议间的交互。当lwIP没有在操作系统上运行时,只能使用原始API来编程。
应用程序是靠回调函数实现的,当与应用程序相关活动发生时该回调函数被lwIP内核调用。
一个特定的应用可以通过注册基于事件的回调函数被通知,比如刚接收到数据,发送数据,错误通知,查询定时器是否超时,连接关闭,等等。
一个应用可以提供一个回调函数来处理所有的事件。
不要将lwIP的原始API与raw ethernet或IP sockets相混淆。
原始API是lwIP网络协议栈(包括tcp和udp)的一组接口,而原始以太网或IP sockets 则关注处理raw ethernet或IP 数据而不是tcp的连接或udp的数据包。
3.2 与多线程相关的问题
当在一个多线程环境中运行时,由于原始API并没有提供并行访问保护(比如pbuf-和内存管理函数),原始API函数只能被内核线程(tcpip-thread)调用。
由于所有的数据包(输入和输出)和定时器的处理都在同一个可执行上下文中,原始API从不会被阻塞。
3.3 模块
原始API可以访问如下协议:
- 网络层协议(IPv4/IPv6)
- 传输层协议:UDP、TCP、RAW IP、ICMP、IGMP
- 应用层协议:DHCP、AUTOIP、SNMP、DNS(自1.3.0起)
- 网络接口管理函数
- 内存和数据包缓存管理函数(相关的内存池和堆栈)
- 操作系统抽象层
3.4 函数
相关函数的正式描述可以在doc/rawapi.txt找到。如下是一些附加备注:
- void tcp_accept(struct tcp_pcb *pcb, err_t (* accept)(void *arg, struct tcp_pcb *newpcb, err_t err));
说明:tcp_accept()必须在tcp_listen()后调用,由于:
(1)如果先调用tcp_accept()很可能导致丢失当调用tcp_listen(),原因是tcp_listen()分配一个新的pcb但实际上并没有从老的pcb复制相关的内容(pcb-accept)(请查阅tcp_listen_with_backlog()在lwip/src/core/tcp.c)
(2)所有lwIP原始API都在一个线程中运行,接收到的数据包并不会在tcp_listen()和tcp_accept()之间马上被处理。在pcb被tcp_accept()正确配置前,调用tcp_listen()会导致接受连接而不产生危险。
- void tcp_setprio(struct tcp_pcb *pcb, u8_t prio);
这个函数并没有被doc/rawapi.txt记录,但是它经常被当例子来使用而没有合适的说明。当内存不够时lwIP将关掉低优先级的tcp连接。这个函数设置了连接的优先级。
- err_t (* recv)(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);
这是一个接收回调函数。该函数可以返回一个错误值。当该函数的返回值与ERR_OK不相同时,数据将被lwIP所缓存。
在下一次调用fasttmr()时,这个数据还会再次发送给应用程序,这样应用程序可以取走该数据或再次拒绝它。
如果有数据等待处理,而又有新的数据包到达,那么该连接将会继续保存应用程序拒绝的数据,该回调函数会被再次调用,并试图发送挂起的数据。
如果应用程序再次拒绝刚接收到的数据那么该数据就会被丢弃。(相关的源文件:tcp.c和tcp_in.c,搜索TCP_EVENT_RECV和refused_data)。
4. Netconn API
4.1 简介
netconn API是一个序列API,该API被设计来更容易使用lwIP协议栈(与基于事件驱动的原始API相比),而且保留零复制的功能。
4.2 在多线程下的执行
使用netconn API,由于该API要求使用线程,必须需要操作系统的支持。所有的数据包处理(输入和输出)都在一个相关的线程内完成(比如,tcpip-thread)。
使用netconn API的应用线程来通过消息邮箱和信号量和内核线程进行通信
4.3 从应用的角度看netconn API
使用netconn API的应用程序可以使用api.h (netconn_*() functions) and netbuf.h(netbuf_*() functions)中定义的所有函数
netconn API支持的传输层协议包括UDP,TCP和RAW IP
(1)应用可以使用如下函数来跟netconn一起工作
- netconn_new() – create a new connection
- netconn_new_with_callback() – create new connection
- netconn_new_with_proto_and_callback() - create a new connection
- netconn_delete() - delete an existing connection
- netconn_bind() - bind a connection to a local port/ip
- netconn_connect() - connect a connection to a remote port/ip
- netconn_disconnect() - disconnect a connection from a remote port/ip
- netconn_send() - send data to the currently connected remote port/ip (not applicable for TCP)
- netconn_sendto() - send data to a specified remote port/ip (not applicable for TCP)
- netconn_recv() - receive data from a netconn
- netconn_set_recvtimeout() – set a receive timeout value for a netconn structure
- netconn_get_recvtimeout() – get the receive timeout value for a netconn structure
(2)对TCP连接,支持一些额外的功能
- netconn_listen() - set a TCP connection into listen mode
- netconn_accept() - accept an incoming connection on a listening TCP connection
- netconn_write() - send data on a connected TCP netconn
- netconn_close() - close a TCP netconn without deleting it
(3)为应用程序提供的更高层协议支持
- netconn_gethostbyname() - Does a name lookup (queries dns server if req'd) to resolve a host name to an IP address
- netconn_join_leave_group() - basic IGMP multicast support
(4)发送和接收采用netbufs
netbufs具有零复制接收和发送数据的功能。如下函数可以与netbufs一起工作
- netbuf_new()
- netbuf_delete()
- netbuf_alloc()
- netbuf_free()
- netbuf_ref()
- netbuf_chain()
- netbuf_len()
- netbuf_data()
- netbuf_copy()
- netbuf_next()
- netbuf_first()
4.4 Nonblocking IO
从2010年2月的CVS版本,netconn API支持非阻塞的连接
你可以创建自己的回调函数处理netconn_evt事件,如下所示:
- conn = netconn_new_with_callback(NETCONN_TCP, your_callback_function)
- netconn_set_nonblocking(conn, 1)
- netconn_connect(...) -> does not block (as above)
-通过NETCONN_EVT_SENDPLUS (连接) or NETCONN_EVT_ERROR(连接失败)事件等待your_callback_function()函数被调用。
留意线程间的切换:回调函数被tcpip-thread线程调用,并不是在你的应用线程
5. Socket API
5.1 函数
新加入shutdown()(1.3.0)函数并不适合执行。
5.2 外部参考连接