简介
pbuf
pbuf贯穿了所有层
/** Main packet buffer struct */
struct pbuf
{
/** next pbuf in singly linked pbuf chain */
struct pbuf *next; (1)
/** pointer to the actual data in the buffer */
void *payload; (2)
u16_t tot_len; (3)
/** length of this buffer */
u16_t len; (4)
u8_t type_internal; (5)
/** misc flags */
u8_t flags; (6)
LWIP_PBUF_REF_T ref; (7)
/** For incoming packets, this contains the input netif's index */
u8_t if_idx; (8)
};
- next 是一个 pbuf 类型的指针,指向下一个 pbuf,因为网络中的数据包可能很大,而 pbuf 能管理的数据包大小有限,就会采用链表的形式将所有的 pbuf 包连接起来,这样子才能完整描述一个数据包,这些连接起来的 pbuf 包会组成一个链表,我称之为 pbuf 链表。
- payload 是一个指向数据区域的指针,指向该 pbuf 管理的数据区域起始地址,这里的数据区域可以是紧跟在 pbuf 结构体地址后面的 RAM 空间,也可以是 ROM 中的某个地址上,取决于 pbuf 的类型。
- tot_len 中记录的是当前 pbuf 及其后续 pbuf 所有数据的长度,例如如果当前 pbuf是 pbuf 链表上第一个数据结构,那么 tot_len 就记录着整个 pbuf 链表中所有 pbuf 中数据的长度;如果当前 pbuf 是链表上最后一个数据结构,那就记录着当前 pbuf 的长度。
- len 表示当前 pbuf 中有效的数据长度。
- type_internal 表示 pbuf 的类型,LwIP 中有 4 种 pbuf 的类型,并且使用了一个枚举类型的数据结构定义他们。
- flags 字段在初始化的时候一般被初始化为 0,此处就不对 flags 字段进行过多讲解。
- ref 表示该 pbuf 被引用的次数,引用表示有其他指针指向当前 pbuf,这里的指针可以是 pbuf 的 next 指针,也可以是其他任意形式的指针,初始化一个 pbuf 的时候,ref 会被设置为 1,因为该 pbuf 的地址一点会被返回一个指针变量,当有其他指针指向 pbuf 的时候,就必须调用相关函数将 ref 字段加 1。
- if_idx 用于记录传入的数据包中输入 netif 的索引,也就是 netif 中 num 字段。
netbuf数据缓冲区
netbuf结构体
- netbuf发送或者接收数据用的,它是基于 pbuf 进行更高一层的封装,并且记录了主机的 IP 地址与端口号.
- 端口号对应的其实就是应用线程。在接收的时候,应用程序肯定需要知道到底是谁发数据给自己,而在发送的时候,应用程序需要将自己的端口号与 IP 地址填充到 netbuf结构体对应字段中
struct netbuf
{
struct pbuf *p, *ptr; //netbuf 的 p 字段的指针指向 pbuf 链表,这是基于 pbuf 上封装的结构体,因此,ptr 字段的指针也是指向 pbuf,
//但是它与 p 字段的指针有一点不一样,因为它可以指向任意的 pbuf,由 netbuf_next() 与 netbuf_first() 函数来控制。
ip_addr_t addr; //addr 字段记录了数据发送方的 IP 地址
u16_t port; //port 记录了数据发送方的端口号
};
netbuf API
- netbuf 是 LwIP 描述用户数据很重要的一个结构体,因为 LwIP 是不可能让我们直接操作 pbuf 的,因为分层的思想,应用数据必然是由用户操作的,因此 LwIP 会提供很多函数接口让用户对 netbuf进行操作
- 无论是 UDP 报文还是 TCP 报文段,其本质都是数据,要发送出去的数据都会封装在netbuf 中,然后通过邮箱发送给内核线程(tcpip_thread 线程),然后经过内核的一系列处理,放入发送队列中,然后调用底层网卡发送函数进行发送,反之,应用线程接收到数据,也是通过netbuf 进行管理。
具体看野火开发手册上
netbuf_delete(struct netbuf *buf)
1、简介
- 使用完一个数据缓冲区,如果想再次使用就需要给他删除掉
2、使用
struct netbuf *inbuf;
netbuf_delete(inbuf);
3、源码
/**
* @ingroup netbuf
* Deallocate a netbuf allocated by netbuf_new().
*
* @param buf pointer to a netbuf allocated by netbuf_new()
*/
void
netbuf_delete(struct netbuf *buf)
{
if (buf != NULL)
{
if (buf->p != NULL) {
pbuf_free(buf->p);
buf->p = buf->ptr = NULL;
}
memp_free(MEMP_NETBUF, buf);
}
}
netconn连接结构
netconn结构体
- 在使用 RAW 编程接口的时候,对于 UDP 和 TCP 连接使用的是两种不同的编程函数:udp_xxx 和 tcp_xxx。NETCONN 对于这两种连接提供了统一的编程接口,用于可以使用同一的连接结构和编程函数,在 api.h 中定了了 netcon 结构体
struct netconn
{
enum netconn_type type; //连接类型,TCP UDP 或者 RAW
enum netconn_state state; //当前连接状态
union {
//内核中与连接相关的控制块指针
struct ip_pcb *ip; //IP 控制块
struct tcp_pcb *tcp; //TCP 控制块
struct udp_pcb *udp; //UDP 控制块
struct raw_pcb *raw; //RAW 控制块
} pcb;
err_t last_err; //此连接上最新的错误
sys_sem_t op_completed; //用于两部分 API 同步的信号量
sys_mbox_t recvmbox; //接收数据的邮箱
#if LWIP_TCP
sys_mbox_t acceptmbox; //用于 TCP 服务器端,连接请求的缓冲队列
#endif
#if LWIP_SOCKET
int socket; //socket 描述符,用于 socket API
#endif
#if LWIP_SO_SNDTIMEO
s32_t send_timeout; //发送数据时的超时时间
#endif
#if LWIP_SO_RCVTIMEO
int recv_timeout; //接收数据时的超时时间
#endif
#if LWIP_SO_RCVBUF
int recv_bufsize; //接收消息队列长度
s16_t recv_avail; //数据邮箱 recvmbox 中已缓存的数据长度
#endif
u8_t flags; //标识符
#if LWIP_TCP
//当调用 netconn_write 发送数据但缓存不足的时候,数据会暂时存放在 current_msg 中,等待下
一次数据发送,write_offset 记录下一次发送时的索引
size_t write_offset;
struct api_msg_msg *current_msg;
#endif
netconn_callback callback; //连接相关回调函数,实现 socket API 时使用
};
在 api.h 文件中还定义了连接状态和连接类型,这两个都是枚举类型。
//枚举类型,用于描述连接类型
enum netconn_type {
NETCONN_INVALID = 0, //无效类型
NETCONN_TCP = 0x10, //TCP
NETCONN_UDP = 0x20, //UDP
NETCONN_UDPLITE = 0x21, //UDPLite
NETCONN_UDPNOCHKSUM= 0x22, //无校验 UDP
NETCONN_RAW = 0x40 //原始链接
};
//枚举类型,用于描述连接状态,主要用于 TCP 连接中
enum netconn_state
{
NETCONN_NONE, //不处于任何状态
NETCONN_WRITE, //正在发送数据
NETCONN_LISTEN, //侦听状态
NETCONN_CONNECT, //连接状态
NETCONN_CLOSE //关闭状态
};
netconn API
- api_lib.c 中有很多 netconn 的函数,其中大部分是 LWIP 内部调用的,我们最后使用到的有 9 个函数
netconn_new
1、简介
- 函数 netconn_new () 本质上是一个宏定义,它用来创建一个新的连接结构,连接结构的类型可以选择为 TCP 或 UDP 等,参数 type 描述了连接的类型,可以为 NETCONN_TCP 或 NETCONN_UDP等,在这个函数被调用时,会初始化相关的字段,而并不会创建连接
2、使用
struct netconn *tcp_clientconn; //TCP CLIENT网络连接结构体
tcp_clientconn = netconn_new(NETCONN_TCP); //创建一个TCP链接
static struct netconn *udpconn;
udpconn = netconn_new(NETCONN_UDP); //创建一个UDP链接
3、源码
#define netconn_new(t) netconn_new_with_proto_and_callback(t, 0, NULL)
/**
* Create a new netconn (of a specific type) that has a callback function.
* The corresponding pcb is also created.
*
* @param t the type of 'connection' to create (@see enum netconn_type)
* @param proto the IP protocol for RAW IP pcbs
* @param callback a function to call on status changes (RX available, TX'ed)
* @return a newly allocated struct netconn or
* NULL on memory error
*/
struct netconn *
netconn_new_with_proto_and_callback(enum netconn_type t, u8_t proto, netconn_callback callback)
{
struct netconn *conn;
API_MSG_VAR_DECLARE(msg);
API_MSG_VAR_ALLOC_RETURN_NULL(msg);
conn = netconn_alloc(t, callback);
if (conn != NULL) {
err_t err;
API_MSG_VAR_REF(msg).msg.n.proto = proto;
API_MSG_VAR_REF(msg).conn = conn;
err = netconn_apimsg(lwip_netconn_do_newconn, &API_MSG_VAR_REF(msg));
if (err != ERR_OK) {
LWIP_ASSERT("freeing conn without freeing pcb", conn->pcb.tcp == NULL);
LWIP_ASSERT("conn has no recvmbox", sys_mbox_valid(&conn->recvmbox));
#if LWIP_TCP
LWIP_ASSERT("conn->acceptmbox shouldn't exist", !sys_mbox_valid(&conn->acceptmbox));
#endif /* LWIP_TCP */
#if !LWIP_NETCONN_SEM_PER_THREAD
LWIP_ASSERT("conn has no op_completed", sys_sem_valid(&conn->op_completed));
sys_sem_free(&conn->op_completed);
#endif /* !LWIP_NETCONN_SEM_PER_THREAD */
sys_mbox_free(&conn->recvmbox);
memp_free(MEMP_NETCONN, conn);
API_MSG_VAR_FREE(msg);
return NULL;
}
}
API_MSG_VAR_FREE(msg);
return conn;
}
/** @ingroup netconn_common
* Protocol family and type of the netconn
*/
enum netconn_type {
NETCONN_INVALID = 0,
/** TCP IPv4 */
NETCONN_TCP = 0x10,
#if LWIP_IPV6
/** TCP IPv6 */
NETCONN_TCP_IPV6 = NETCONN_TCP | NETCONN_TYPE_IPV6 /* 0x18 */,
#endif /* LWIP_IPV6 */
/** UDP IPv4 */
NETCONN_UDP = 0x20,
/** UDP IPv4 lite */
NETCONN_UDPLITE = 0x21,
/** UDP IPv4 no checksum */
NETCONN_UDPNOCHKSUM = 0x22,
#if LWIP_IPV6
/** UDP IPv6 (dual-stack by default, unless you call @ref netconn_set_ipv6only) */
NETCONN_UDP_IPV6 = NETCONN_UDP | NETCONN_TYPE_IPV6 /* 0x28 */,
/** UDP IPv6 lite (dual-stack by default, unless you call @ref netconn_set_ipv6only) */
NETCONN_UDPLITE_IPV6 = NETCONN_UDPLITE | NETCONN_TYPE_IPV6 /* 0x29 */,
/** UDP IPv6 no checksum (dual-stack by default, unless you call @ref netconn_set_ipv6only) */
NETCONN_UDPNOCHKSUM_IPV6 = NETCONN_UDPNOCHKSUM | NETCONN_TYPE_IPV6 /* 0x2a */,
#endif /* LWIP_IPV6 */
/** Raw connection IPv4 */
NETCONN_RAW = 0x40
#if LWIP_IPV6
/** Raw connection IPv6 (dual-stack by default, unless you call @ref netconn_set_ipv6only) */
, NETCONN_RAW_IPV6 = NETCONN_RAW | NETCONN_TYPE_IPV6 /* 0x48 */
#endif /* LWIP_IPV6 */
};
netconn_delete(struct netconn *conn)
1、简介
- 这个函数的功能与 netconn_new() 函数刚好是相反的,它用于删除一个 netconn 连接结构
- 对于TCP 连接,如果此时是处于连接状态的,在调用该函数后,将请求内核执行终止连接操作,此时应用线程是无需理会到底是怎么运作的,因为 LwIP 内核将会完成所有的挥手过程,需要注意的是此时的 TCP 控制块还是不会立即被删除的,因为需要完成真正的断开挥手操作,这些状态可以参考 TCP 协议状态转移图。
- 对于 UDP 协议,UDP 控制块将被删除,终止通信
2、使用
else if(err_receive == ERR_CLSD) //关闭连接
{
netconn_close(tcp_clientconn);
netconn_delete(tcp_clientconn);
printf("关闭连接\r\n");
break;
}
3、源码
/**
* @ingroup netconn_common
* Close a netconn 'connection' and free its resources.
* UDP and RAW connection are completely closed, TCP pcbs might still be in a waitstate
* after this returns.
*
* @param conn the netconn to delete
* @return ERR_OK if the connection was deleted
*/
err_t
netconn_delete(struct netconn *conn)
{
err_t err;
/* No ASSERT here because possible to get a (conn == NULL) if we got an accept error */
if (conn == NULL) {
return ERR_OK;
}
#if LWIP_NETCONN_FULLDUPLEX
if (conn->flags & NETCONN_FLAG_MBOXINVALID) {
/* Already called netconn_prepare_delete() before */
err = ERR_OK;
} else
#endif /* LWIP_NETCONN_FULLDUPLEX */
{
err = netconn_prepare_delete(conn);
}
if (err == ERR_OK) {
netconn_free(conn);
}
return err;
}
netconn_getaddr(struct netconn *conn, ip_addr_t *addr, u16_t *port, u8_t local)
1、简介
- netconn_getaddr() 函数的作用,就是获取一个 netconn 连接结构的IP地址和端口号
- local 如果是 1 则表示获取本地IP 地址与端口号,如果为 0 表示远端 IP 地址与端口号。
- 同样的,该函数会调用 netconn_apimsg()函数构造一个 API 消息,并且请求内核执行 lwip_netconn_do_getaddr() 函数,然后通过 netconn 连接结构的信号量进行同步
2、使用
/* This is the aligned version of ip_addr_t,
used as local variable, on the stack, etc. */
struct ip_addr {
u32_t addr;
};
typedef struct ip_addr ip_addr_t;
netconn_getaddr(newconn,&ipaddr,&port,0); //获取远端IP地址和端口号
remot_addr[3] = (uint8_t)(ipaddr.addr >> 24); //比如IP地址是192.168.0.4 是点分十进制记法,192是第一个8bit的十进制等价数
remot_addr[2] = (uint8_t)(ipaddr.addr>> 16);//所以一共需要32位表示,那么打印的时候只需要把对应的8bit直接用十进制打印就行了
remot_addr[1] = (uint8_t)(ipaddr.addr >> 8);
remot_addr[0] = (uint8_t)(ipaddr.addr);
printf("%d.%d.%d.%d连接上服务器,主机端口号为:%d\r\n",remot_addr[0], remot_addr[1],remot_addr[2],remot_addr[3],port);
netconn_getaddr(tcp_clientconn,&loca_ipaddr,&loca_port,1); //获取本地IP主机IP地址和端口号
3、源码
/**
* Get the local or remote IP address and port of a netconn.
* For RAW netconns, this returns the protocol instead of a port!
*
* @param conn the netconn to query
* @param addr a pointer to which to save the IP address
* @param port a pointer to which to save the port (or protocol for RAW)
* @param local 1 to get the local IP address, 0 to get the remote one
* @return ERR_CONN for invalid connections
* ERR_OK if the information was retrieved
*/
err_t
netconn_getaddr(struct netconn *conn, ip_addr_t *addr, u16_t *port, u8_t local)
{
struct api_msg msg;
err_t err;
LWIP_ERROR("netconn_getaddr: invalid conn", (conn != NULL), return ERR_ARG;);
LWIP_ERROR("netconn_getaddr: invalid addr", (addr != NULL), return ERR_ARG;);
LWIP_ERROR("netconn_getaddr: invalid port", (port != NULL), return ERR_ARG;);
msg.function = do_getaddr;
msg.msg.conn = conn;
msg.msg.msg.ad.ipaddr = addr;
msg.msg.msg.ad.port = port;
msg.msg.msg.ad.local = local;
err = TCPIP_APIMSG(&msg);
NETCONN_SET_SAFE_ERR(conn, err);
return err;
}