netconn编程

本文介绍了LwIP中的网络连接结构netconn和数据缓冲区pbuf,包括它们的结构体、API及使用场景。通过netconn_api,如netconn_new、netconn_delete等,实现TCP/UDP的连接管理和数据收发。同时,讨论了pbuf在数据包管理中的作用,如何通过pbuf链表处理大尺寸的数据包。此外,还涵盖了在cubemx中的配置步骤,如PHY设置、FreeRTOS配置等,以帮助开发者实现完整的网络通信实验。
摘要由CSDN通过智能技术生成

简介

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

成草

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值