欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

LWIP学习笔记6——使用 NETCONN 接口编程

程序员文章站 2024-03-20 15:16:52
...

使用 NETCONN 接口编程

NETCONN API 使用了操作系统的 IPC 机制, 对网络连接进行了抽象,用户可以像操作文件一样操作网络连接(打开/关闭、读/写数据)。 但是 NETCONN API 并不如操作文件的 API 那样简单易用。举个例子,调用 f_read 函数读文件时,读到的数据会被放在一个用户指定的数组中,用户操作起来很方便,而 NETCONN API 的读数据 API,就没有那么人性化了。 用户获得的不是一个数组,而是一个特殊的数据结构 netbuf,用户如果想使用好它,就需要对内核的 pbuf 和 netbuf 结构体有所了解。
netbuf 结构体:
LwIP 为了更好描述应用线程发送与接收的数据,并且为了更好管理这些数据的缓冲区,LwIP 定义了一个 netbuf 结构体,它是基于 pbuf 上更高一层的封装,记录了主机的 IP 地址与端口号。

struct netbuf
 {
	 struct pbuf *p, *ptr; (1)
	 ip_addr_t addr; (2)
	 u16_t port; (3)
 };

LWIP学习笔记6——使用 NETCONN 接口编程
netbuf 相关函数说明:
netbuf 是 LwIP 描述用户数据很重要的一个结构体,因为 LwIP 是不可能让我们直接操作 pbuf 的,因为分层的思想,应用数据必然是由用户操作的, 因此 LwIP 会提供很多函数接口让用户对 netbuf 进行操作,无论是 UDP 报文还是 TCP 报文段,其本质都是数据,要发送出去的数据都会封装在 netbuf 中,然后通过邮箱发送给内核线程(tcpip_thread 线程),然后经过内核的一系列处理,放入发送队列中,然后调用底层网卡发送函数进行发送,反之,应用线程接收到数据,也是通过 netbuf 进行管理。

netconn 结构体:
在 LwIP 中,如 TCP 连接, UDP 通信,都是需要提供一个编程接口给用户使用的,那么为了描述这样子的一个接口, LwIP 抽象出来一个 nettonn 结构体,它能描述一个连接,供应用程序使用,同时内核的 NETCONN API 接口也对各种连接操作函数进行了统一的封装,这样子,用户程序可以很方便使 netconn 和编程函数,我们暂且将 netconn 称之为连接结体。一个连接结构体中包含的成员变量很多,如描述连接的类型,连接的状态(主要是在TCP 连接中使用),对应的控制块(如 UDP 控制块、 TCP 控制块等等),还有对应线程的消息邮箱以及一些记录的信息。

struct netconn
 {
 /** netconn 类型 */
 enum netconn_type type;
 /** 当前 netconn 状态 */
 enum netconn_state state;
 /** LwIP 的控制块指针,如 TCP 控制块、 UDP 控制块 */
 union
 {
	struct ip_pcb *ip;
 	struct tcp_pcb *tcp;
 	struct udp_pcb *udp;
	 struct raw_pcb *raw;
 } pcb;
 err_t pending_err;/** 这个 netconn 最后一个异步未报告的错误 */
 sys_sem_t op_completed; //信号量
 /** 消息邮箱,存储接收的数据,直到它们被提取 */
 sys_mbox_t recvmbox;
 /** 用于 TCP 服务器上的请求连接缓冲区 */
 sys_mbox_t acceptmbox;

 /** socket 描述符,用于 Socket API */
 #if LWIP_SOCKET
 int socket;
 #endif /* LWIP_SOCKET */


 /** 标志 */
 u8_t flags;
 #if LWIP_TCP
 /** 当调用 netconn_write()函数发送的数据不适合发送缓冲区时,
 数据会暂时存储在 current_msg 中,等待数据合适的时候进行发送 */
 struct api_msg *current_msg;
 #endif /* LWIP_TCP */
 /** 连接相关的回调函数 */
 netconn_callback callback;
 };

netconn 函数接口说明:

netconn_new()

函数 netconn_new ()本质上是一个宏定义,它用来创建一个新的连接结构, 连接结构的类型可以选择为 TCP 或 UDP 等,参数 type 描述了连接的类型,可以为 NETCONN_TCP或 NETCONN_UDP 等, 在这个函数被调用时,会初始化相关的字段,而并不会创建连接。

netconn_bind()

netconn_bind()函数用于将一个 IP 地址及端口号与 netconn 连接结构进行绑定,如果作为服务器端,这一步操作是必然需要的,作为客户端,不需要这一步,系统会自动分配端口号,使用默认网卡发送数据。同样的, 该函数会调用 netconn_apimsg()函数构造一个 API 消息,并且请求内核执行 lwip_netconn_do_bind()函数, 然后通过 netconn 连接结构的信号量进行同步,事实上内核线程的处理也是通过函数调用 xxx_bind(xxx_bing 可以是 udp_bing、 tcp_bing、 raw_bing,具体是哪个函数内核是根据 netconn 的类型决定的) 完成相应控制块的绑定工作。

netconn_connect()

netconn_connect()函数是用于连接服务器的函数,它一般在客户端中调用,将服务器端的 IP 地址和端口号与本地的 netconn 连接结构绑定,当 TCP 协议使用该函数的时候就是进行握手的过程,调用的应用线程将阻塞至握手完成;而对于 UDP 协议来说,调用该函数只是设置 UDP 控制块的目标 IP 地址与目标端口号,其实这个函数也是通过调用netconn_apimsg()函数构造一个 API 消息,并且请求内核执行 lwip_netconn_do_connect()函数, 然后通过 netconn 连接结构的信号量进行同步,在 lwip_netconn_do_connect()函数中,根据 netconn 的类型不同, 调用对应的 xxx_connect()函数进行对应的处理,如果是 TCP 连接,将调用 tcp_connect();如果是 UDP 协议,将调用 udp_connect();如果是RAW,将调用 raw_connect()函数处理。

netconn_recv()

它可以接收一个 UDP 或者 TCP的数据包,从 recvmbox 邮箱中获取pbuf数据包,如果该邮箱中没有数据包,那么线程调用这个函数将会进入阻塞状态以等待消息的到来, 如果在等待 TCP 连接上的数据时,远端主机终止连接,将返回一个终止连接的错误代码(ERR_CLSD),应用程序可以根据错误的类型进行不一样的处理。对应 TCP 连接, netconn_recv()函数将调用 netconn_recv_data_tcp()函数去获取 TCP 连接上的数据,在获取数据的过程中,调用 netconn_recv_data()函数从 recvmbox 邮箱获取pbuf, 然后通过 netconn_tcp_recvd_msg()->netconn_apimsg()函数构造一个 API 消息投递给系统邮箱, 请求内核执行 lwip_netconn_do_recv()函数, 该函数将调用 tcp_recved()函数去更新 TCP 接收窗口,同时netconn_recv()函数将完成 pbuf 数据包封装在 netbuf 中,返回个应用程序; 而对于 UDP 协议、 RAW 连接,将简单多了,将直接调用netconn_recv_data()函数获取数据,完成 pbuf 封装在 netbuf 中,返回给应用程序。
LWIP学习笔记6——使用 NETCONN 接口编程

netconn_send()

该函数会调用 netconn_apimsg()函数构造一个 API 消息,并且请求内核执行 lwip_netconn_do_send()函数, 这个函数会通过消息得到目标 IP 地址与端口号以及 pbuf 数据报等信息, 然后调用 raw_send()/udp_send()等函数发送数据,最后通过 netconn 连接结构的信号量进行同步。

netconn_write()

用于处于稳定连接状态的 TCP 协议发送数据,这个函数的功能是把 dataptr 指针指向的数据放在属于 conn 连接的 TCP 连接的发送队列中, size 参数指定了数据的长度, apiflags 参数有以下几种:

/* 没有标志位(默认标志位) */
 #define NETCONN_NOFLAG 0x00

 /* 不拷贝数据到内核线程 */
 #define NETCONN_NOCOPY 0x00
 /* 拷贝数据到内核线程 */
 #define NETCONN_COPY 0x01

 /* 尽快递交给上层应用 */
 #define NETCONN_MORE 0x02

 /* 当内核缓冲区满时,不会被阻塞,而是直接返回 */
 #define NETCONN_DONTBLOCK 0x04

 /* 不自动更新接收窗口,需要调用 netconn_tcp_recvd()函数完成 */
 #define NETCONN_NOAUTORCVD 0x08

 /* 上层已经收到数据,将 FIN 保留在队列中直到再次调用 */
相关标签: TCP/IP协议