Skip to content

NETCONN

使用操作系统的IPC机制, 对网络连接进行抽象, 使用同一个接口实现UDP和TCP连接

实际是对RAW接口的封装, 或者直接调用底层的函数, 默认的时候是直接调用底层函数

数据类型

image-20240712234610546

使用这一个结构体进行管理

image-20240712234953500

使用这一个结构体进行管理数据

c
/**
 * LWIP_SO_SNDTIMEO==1: Enable send timeout for sockets/netconns and
 * SO_SNDTIMEO processing.
 */
#if !defined LWIP_SO_SNDTIMEO || defined __DOXYGEN__
#define LWIP_SO_SNDTIMEO                0
#endif

/**
 * LWIP_SO_RCVTIMEO==1: Enable receive timeout for sockets/netconns and
 * SO_RCVTIMEO processing.
 */
#if !defined LWIP_SO_RCVTIMEO || defined __DOXYGEN__
#define LWIP_SO_RCVTIMEO                1
#endif

把这一个配置为1的时候, 发送以及接收有一个超时时间, 控制块里面有一个send_timeout, recv_timeout变量, 用于设置超时时间

image-20240713103011627

API

image-20240713103353573

连接

c
#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);

建立了一个没有默认IP和状态改变回调函数的控制块

c
/** @ingroup netconn_common
 * Protocol family and type of the netconn
 */
enum netconn_type {
  NETCONN_INVALID     = 0,
  /** TCP IPv4 */
  NETCONN_TCP         = 0x10,
  /** UDP IPv4 */
  NETCONN_UDP         = 0x20,
  /** UDP IPv4 lite */
  NETCONN_UDPLITE     = 0x21,
  /** UDP IPv4 no checksum */
  NETCONN_UDPNOCHKSUM = 0x22,
  /** Raw connection IPv4 */
  NETCONN_RAW         = 0x40
};
c
/**
 * @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)

断开连接以及释放资源

c
/**
 * @ingroup netconn_common
 * Bind a netconn to a specific local IP address and port.
 * Binding one netconn twice might not always be checked correctly!
 *
 * @param conn the netconn to bind
 * @param addr the local IP address to bind the netconn to
 *             (use IP4_ADDR_ANY/IP6_ADDR_ANY to bind to all addresses)
 * @param port the local port to bind the netconn to (not used for RAW)
 * @return ERR_OK if bound, any other err_t on failure
 */
err_t
netconn_bind(struct netconn *conn, const ip_addr_t *addr, u16_t port)

绑定本地的IP地址

c
/**
 * @ingroup netconn_common
 * Connect a netconn to a specific remote IP address and port.
 *
 * @param conn the netconn to connect
 * @param addr the remote IP address to connect to
 * @param port the remote port to connect to (no used for RAW)
 * @return ERR_OK if connected, return value of tcp_/udp_/raw_connect otherwise
 */
err_t
netconn_connect(struct netconn *conn, const ip_addr_t *addr, u16_t port)

建立连接

c
/**
 * @ingroup netconn_udp
 * Disconnect a netconn from its current peer (only valid for UDP netconns).
 *
 * @param conn the netconn to disconnect
 * @return See @ref err_t
 */
err_t
netconn_disconnect(struct netconn *conn)

断开连接

c
#define	TCP_DEFAULT_LISTEN_BACKLOG			0xff
#define netconn_listen(conn) netconn_listen_with_backlog(conn, TCP_DEFAULT_LISTEN_BACKLOG)
/**
 * @ingroup netconn_tcp
 * Set a TCP netconn into listen mode
 *
 * @param conn the tcp netconn to set to listen mode
 * @param backlog the listen backlog, only used if TCP_LISTEN_BACKLOG==1
 * @return ERR_OK if the netconn was set to listen (UDP and RAW netconns
 *         don't return any error (yet?))
 */
err_t
netconn_listen_with_backlog(struct netconn *conn, u8_t backlog)
c
/**
 * @ingroup netconn_tcp
 * Accept a new connection on a TCP listening netconn.
 *
 * @param conn the TCP listen netconn
 * @param new_conn pointer where the new connection is stored
 * @return ERR_OK if a new connection has been received or an error
 *                code otherwise
 */
err_t
netconn_accept(struct netconn *conn, struct netconn **new_conn)

获取一个TCP连接

数据

c
/**
 * @ingroup netconn_common
 * Receive data (in form of a netbuf containing a packet buffer) from a netconn
 *
 * @param conn the netconn from which to receive data
 * @param new_buf pointer where a new netbuf is stored when received data
 * @return ERR_OK if data has been received, an error code otherwise (timeout,
 *                memory error or another error)
 */
err_t
netconn_recv(struct netconn *conn, struct netbuf **new_buf)

获取数据, 获取的数据是一个netbuf类型的数据

c
/**
 * @ingroup netconn_udp
 * Send data over a UDP or RAW netconn (that is already connected).
 *
 * @param conn the UDP or RAW netconn over which to send data
 * @param buf a netbuf containing the data to send
 * @return ERR_OK if data was sent, any other err_t on error
 */
err_t
netconn_send(struct netconn *conn, struct netbuf *buf)

发送UDP数据

c
#define netconn_write(conn, dataptr, size, apiflags) \
          netconn_write_partly(conn, dataptr, size, apiflags, NULL)
/**
 * @ingroup netconn_tcp
 * Send data over a TCP netconn.
 *
 * @param conn the TCP netconn over which to send data
 * @param dataptr pointer to the application buffer that contains the data to send
 * @param size size of the application data to send
 * @param apiflags combination of following flags :
 * - NETCONN_COPY: data will be copied into memory belonging to the stack
 * - NETCONN_MORE: for TCP connection, PSH flag will be set on last segment sent
 * - NETCONN_DONTBLOCK: only write the data if all data can be written at once
 * @param bytes_written pointer to a location that receives the number of written bytes
 * @return ERR_OK if data was sent, any other err_t on error
 */
err_t
netconn_write_partly(struct netconn *conn, const void *dataptr, size_t size,
                     u8_t apiflags, size_t *bytes_written)

发送TCP数据

数据块

c
/**
 * @ingroup netbuf
 * Create (allocate) and initialize a new netbuf.
 * The netbuf doesn't yet contain a packet buffer!
 *
 * @return a pointer to a new netbuf
 *         NULL on lack of memory
 */
struct
netbuf *netbuf_new(void)

获取一个数据控制块

c
/**
 * @ingroup netbuf
 * Allocate memory for a packet buffer for a given netbuf.
 *
 * @param buf the netbuf for which to allocate a packet buffer
 * @param size the size of the packet buffer to allocate
 * @return pointer to the allocated memory
 *         NULL if no memory could be allocated
 */
void *
netbuf_alloc(struct netbuf *buf, u16_t size)

申请数据区域

c
/**
 * 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)

获取本地或对方的IP地址和端口号

解析域名

c
/**
 * @ingroup netconn_common
 * Execute a DNS query, only one IP address is returned
 *
 * @param name a string representation of the DNS host name to query
 * @param addr a preallocated ip_addr_t where to store the resolved IP address
 * @param dns_addrtype IP address type (IPv4 / IPv6)
 * @return ERR_OK: resolving succeeded
 *         ERR_MEM: memory error, try again later
 *         ERR_ARG: dns client not initialized or invalid hostname
 *         ERR_VAL: dns server response was invalid
 */
err_t netconn_gethostbyname(const char *name, ip_addr_t *addr)

解析域名

UDP

  1. 使用netconn_new创建一个控制块
  2. 定义时间超时函数
  3. 使用netconn_bind绑定本地的IP和端口
  4. 使用netconn_connect建立连接
c
void lwip_demo(void)
{
    err_t err;
    static struct netconn *udpconn;
    static struct netbuf  *recvbuf;
    static struct netbuf  *sentbuf;
    ip_addr_t destipaddr;
    uint32_t data_len = 0;
    struct pbuf *q;
    BaseType_t lwip_err;
    
    /* 第一步:创建udp控制块 */
    udpconn = netconn_new(NETCONN_UDP);
    /* 定义接收超时时间 */
    udpconn->recv_timeout = 10;

    if (udpconn != NULL)                                        /* 判断创建控制块释放成功 */
    {
        /* 第二步:绑定控制块、本地IP和端口 */
        err = netconn_bind(udpconn, IP_ADDR_ANY, LWIP_DEMO_PORT);
        /*构造目的IP地址 */
        IP4_ADDR(&destipaddr, DEST_IP_ADDR0,DEST_IP_ADDR1,DEST_IP_ADDR2,DEST_IP_ADDR3);
        /* 第三步:连接或者建立对话框 */
        netconn_connect(udpconn, &destipaddr, LWIP_DEMO_PORT);  /* 连接到远端主机 */

        if (err == ERR_OK)                                      /* 绑定完成 */
        {
            while (1)
            {
                /* 第四步:如果指定的按键按下时,会发送信息 */
                if ((g_lwip_send_flag & LWIP_SEND_DATA) == LWIP_SEND_DATA)
                {
                    sentbuf = netbuf_new();
                    netbuf_alloc(sentbuf, strlen((char *)g_lwip_demo_sendbuf));
                    memcpy(sentbuf->p->payload, (void *)g_lwip_demo_sendbuf, strlen((char *)g_lwip_demo_sendbuf));
                    err = netconn_send(udpconn, sentbuf);               /* 将netbuf中的数据发送出去 */

                    if (err != ERR_OK)
                    {
                        printf("发送失败\r\n");
                        netbuf_delete(sentbuf);                         /* 删除buf */
                    }

                    g_lwip_send_flag &= ~LWIP_SEND_DATA;                /* 清除数据发送标志 */
                    netbuf_delete(sentbuf);                             /* 删除buf */
                }

                /* 第五步:接收数据 */
                netconn_recv(udpconn, &recvbuf);

                if (recvbuf != NULL)                                    /* 接收到数据 */
                {
                    memset(g_lwip_demo_recvbuf, 0, LWIP_DEMO_RX_BUFSIZE); /* 数据接收缓冲区清零 */

                    for (q = recvbuf->p; q != NULL; q = q->next)        /* 遍历完整个pbuf链表 */
                    {
                        /* 判断要拷贝到UDP_DEMO_RX_BUFSIZE中的数据是否大于UDP_DEMO_RX_BUFSIZE的剩余空间,如果大于 */
                        /* 的话就只拷贝UDP_DEMO_RX_BUFSIZE中剩余长度的数据,否则的话就拷贝所有的数据 */
                        if (q->len > (LWIP_DEMO_RX_BUFSIZE - data_len)) memcpy(g_lwip_demo_recvbuf + data_len, q->payload, (LWIP_DEMO_RX_BUFSIZE - data_len)); /* 拷贝数据 */
                        else memcpy(g_lwip_demo_recvbuf + data_len, q->payload, q->len);

                        data_len += q->len;

                        if (data_len > LWIP_DEMO_RX_BUFSIZE) break;     /* 超出TCP客户端接收数组,跳出 */
                    }

                    data_len = 0;                                       /* 复制完成后data_len要清零 */
				  //获取的数据在另一个任务里面进行处理
                    lwip_err = xQueueSend(g_display_queue,&g_lwip_demo_recvbuf,0);
                    
                    if (lwip_err == errQUEUE_FULL)
                    {
                        printf("队列Key_Queue已满,数据发送失败!\r\n");
                    }
                    
                    netbuf_delete(recvbuf);                             /* 删除buf */
                }   
                
                else vTaskDelay(5);                                     /* 延时5ms */
                
                vTaskDelay(10);
            }
        }
        else printf("UDP绑定失败\r\n");
    }
    else printf("UDP连接创建失败\r\n");
}

TCP

客户端

  1. 使用netconn_new获取一个控制块
  2. 使用函数netconn_connet连接服务器
  3. 使用netconn_getaddr获取本地的ip和端口
  4. 使用netconn_recv和netconn_write进行数据交流
c
/**
 * @brief       lwip_demo实验入口
 * @param
 * @retval
 */
void lwip_demo(void)
{
    static struct netconn *tcp_clientconn = NULL; /* TCP CLIENT网络连接结构体 */
    uint32_t data_len = 0;
    struct pbuf *q;
    err_t err,recv_err;
    ip4_addr_t server_ipaddr,loca_ipaddr;
    static uint16_t server_port,loca_port;
    BaseType_t lwip_err;
    char *tbuf;
    
    server_port = LWIP_DEMO_PORT;
    IP4_ADDR(&server_ipaddr,DEST_IP_ADDR0,DEST_IP_ADDR1,DEST_IP_ADDR2,DEST_IP_ADDR3); //构造目的IP地址
    
    tbuf = mymalloc(SRAMIN, 200); /* 申请内存 */
    sprintf((char *)tbuf, "Port:%d", LWIP_DEMO_PORT); /* 客户端端口号 */
    lcd_show_string(5, 150, 200, 16, 16, tbuf, BLUE);
    
    while (1) 
    {
        tcp_clientconn = netconn_new(NETCONN_TCP);/* 创建一个TCP链接 */
        err = netconn_connect(tcp_clientconn,&server_ipaddr,server_port);/* 连接服务器 */
      
        if(err != ERR_OK)
        {
            printf("接连失败\r\n");
            netconn_delete(tcp_clientconn);/* 返回值不等于ERR_OK,删除tcp_clientconn连接 */
        }
        else if (err == ERR_OK)/* 处理新连接的数据 */
        { 
            struct netbuf *recvbuf;
            tcp_clientconn->recv_timeout = 10;
            netconn_getaddr(tcp_clientconn,&loca_ipaddr,&loca_port,1);/* 获取本地IP主机IP地址和端口号 */
            printf("连接上服务器%d.%d.%d.%d,本机端口号为:%d\r\n",DEST_IP_ADDR0,DEST_IP_ADDR1, DEST_IP_ADDR2,DEST_IP_ADDR3,loca_port);
            lcd_show_string(5, 90, 200, 16, 16, "State:Connection Successful", BLUE);
            
            while(1)
            {
                if((g_lwip_send_flag & LWIP_SEND_DATA) == LWIP_SEND_DATA)/* 有数据要发送 */
                {
                    err = netconn_write(tcp_clientconn ,g_lwip_demo_sendbuf,strlen((char*)g_lwip_demo_sendbuf),NETCONN_COPY); /* 发送tcp_server_sentbuf中的数据 */
                  
                    if(err != ERR_OK)
                    {
                        printf("发送失败\r\n");
                    }
                    
                    g_lwip_send_flag &= ~LWIP_SEND_DATA;
                }
                  
                if((recv_err = netconn_recv(tcp_clientconn,&recvbuf)) == ERR_OK)/* 接收到数据 */
                {
                    taskENTER_CRITICAL(); /* 进入临界区 */
                    memset(g_lwip_demo_recvbuf,0,LWIP_DEMO_RX_BUFSIZE);                       /* 数据接收缓冲区清零 */

                    for(q = recvbuf->p;q != NULL;q = q->next)  /* 遍历完整个pbuf链表 */
                    {
                        /* 判断要拷贝到TCP_CLIENT_RX_BUFSIZE中的数据是否大于TCP_CLIENT_RX_BUFSIZE的剩余空间,如果大于 */
                        /* 的话就只拷贝TCP_CLIENT_RX_BUFSIZE中剩余长度的数据,否则的话就拷贝所有的数据 */
                        if(q->len > (LWIP_DEMO_RX_BUFSIZE - data_len)) 
                        {
                            memcpy(g_lwip_demo_recvbuf + data_len,q->payload,(LWIP_DEMO_RX_BUFSIZE - data_len));/* 拷贝数据 */
                        }
                        else 
                        {
                            memcpy(g_lwip_demo_recvbuf + data_len,q->payload,q->len);
                        }
                        
                        data_len += q->len;
                        
                        if(data_len > LWIP_DEMO_RX_BUFSIZE) 
                        {
                            break;                  /* 超出TCP客户端接收数组,跳出 */
                        }
                    }
                    
                    taskEXIT_CRITICAL();            /* 退出临界区 */
                    data_len = 0;                   /* 复制完成后data_len要清零 */
                    
                    lwip_err = xQueueSend(g_display_queue,&g_lwip_demo_recvbuf,0);
                    
                    if (lwip_err == errQUEUE_FULL)
                    {
                        printf("队列Key_Queue已满,数据发送失败!\r\n");
                    }
                    
                    netbuf_delete(recvbuf);
                }
                else if(recv_err == ERR_CLSD)       /* 关闭连接 */
                {
                    netconn_close(tcp_clientconn);
                    netconn_delete(tcp_clientconn);
                    printf("服务器%d.%d.%d.%d断开连接\r\n",DEST_IP_ADDR0,DEST_IP_ADDR1, DEST_IP_ADDR2,DEST_IP_ADDR3);
                    lcd_fill(5, 89, lcddev.width,110, WHITE);
                    lcd_show_string(5, 90, 200, 16, 16, "State:Disconnect", BLUE);
                    myfree(SRAMIN, tbuf);
                    break;
                }
            }
        }
    }
}

服务器

  1. 调用函数netconn_new建立一个控制块
  2. 使用netconn_bind绑定TCP控制块, 本地的IP以及端口
  3. netconn_listen进入监听模式
  4. netconn_accept接收连接请求
  5. netconn_getaddr获取远端的IP以及端口
  6. netconn_write和recv进行数据的交流
c
/**
 * @brief       lwip_demo实验入口
 * @param
 * @retval
 */
void lwip_demo(void)
{
    static struct netconn *tcp_serverconn = NULL; /* TCP SERVER网络连接结构体 */
    uint32_t  data_len = 0;
    struct    pbuf *q;
    err_t     err,recv_err;
    uint8_t   remot_addr[4];
    struct netconn *newconn;
    static    ip_addr_t ipaddr;
    static    u16_t  port;
    BaseType_t lwip_err;
    char *tbuf;
    
    tbuf = mymalloc(SRAMIN, 200); /* 申请内存 */
    sprintf((char *)tbuf, "Port:%d", LWIP_DEMO_PORT); /* 客户端端口号 */
    lcd_show_string(5, 150, 200, 16, 16, tbuf, BLUE);
    
    /* 第一步:创建一个TCP控制块 */
    tcp_serverconn = netconn_new(NETCONN_TCP);                      /* 创建一个TCP链接 */
    /* 第二步:绑定TCP控制块、本地IP地址和端口号 */
    netconn_bind(tcp_serverconn,IP_ADDR_ANY,LWIP_DEMO_PORT);        /* 绑定端口 8080号端口 */
    /* 第三步:监听 */
    netconn_listen(tcp_serverconn);                                 /* 进入监听模式 */
    tcp_serverconn->recv_timeout = 10;                              /* 禁止阻塞线程 等待10ms */
    
    while (1) 
    {
        /* 第四步:接收连接请求 */
        err = netconn_accept(tcp_serverconn,&newconn);              /* 接收连接请求 */
        if(err == ERR_OK) newconn->recv_timeout = 10;

        if (err == ERR_OK)                                          /* 处理新连接的数据 */
        { 
            struct netbuf *recvbuf;
            netconn_getaddr(newconn,&ipaddr,&port,0);               /* 获取远端IP地址和端口号 */
            
            remot_addr[3] = (uint8_t)(ipaddr.addr >> 24); 
            remot_addr[2] = (uint8_t)(ipaddr.addr>> 16);
            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);
            lcd_show_string(5, 90, 200, 16, 16, "State:Connection Successful", BLUE);
            
            while(1)
            {
                if((g_lwip_send_flag & LWIP_SEND_DATA) == LWIP_SEND_DATA) /* 有数据要发送 */
                {
                    err = netconn_write(newconn , 
                    g_lwip_demo_sendbuf,strlen((char*)g_lwip_demo_sendbuf),NETCONN_COPY); 
                    /* 发送g_lwip_demo_sendbuf中的数据 */

                    if(err != ERR_OK)
                    {
                        printf("发送失败\r\n");
                    }

                    g_lwip_send_flag &= ~LWIP_SEND_DATA;
                }
                
                if((recv_err = netconn_recv(newconn,&recvbuf)) == ERR_OK) /* 接收到数据 */
                { 
                    taskENTER_CRITICAL();/* 进入临界区 */
                    memset(g_lwip_demo_recvbuf,0,LWIP_DEMO_RX_BUFSIZE);/* 数据接收缓冲区清零 */

                    for(q = recvbuf->p;q != NULL;q = q->next) /* 遍历完整个pbuf链表 */
                    {
                    /* 判断要拷贝到LWIP_DEMO_RX_BUFSIZE中的数据是否大于LWIP_DEMO_RX_BUFSIZE
                    的剩余空间,如果大于 */
                   /* 的话就只拷贝LWIP_DEMO_RX_BUFSIZE中剩余长度的数据,否则的话就拷贝所有的数据 */
                        if(q->len > (LWIP_DEMO_RX_BUFSIZE-data_len))
                        {
                            memcpy(g_lwip_demo_recvbuf + data_len,q->payload,
                                   (LWIP_DEMO_RX_BUFSIZE - data_len));/* 拷贝数据 */
                        }
                        else
                        {
                            memcpy(g_lwip_demo_recvbuf + data_len,q->payload,q->len);
                        }
                        
                        data_len += q->len;

                        if(data_len > LWIP_DEMO_RX_BUFSIZE)
                        {
                            break;   /*超出TCP客户端接收数组,跳出*/
                        }
                    }

                    taskEXIT_CRITICAL();   /* 退出临界区 */
                    data_len=0;            /* 复制完成后data_len要清零 */
                    
                    lwip_err = xQueueSend(g_display_queue,&g_lwip_demo_recvbuf,0);
                    
                    if (lwip_err == errQUEUE_FULL)
                    {
                        printf("队列Key_Queue已满,数据发送失败!\r\n");
                    }
                    
                    netbuf_delete(recvbuf);
                }
                else if(recv_err == ERR_CLSD)                           /* 关闭连接 */
                {
                    netconn_close(newconn);
                    netconn_delete(newconn);
                    printf("主机:%d.%d.%d.%d断开与服务器的连接\r\n",remot_addr[0],
                           remot_addr[1],remot_addr[2],remot_addr[3]);
                    lcd_fill(5, 89, lcddev.width,110, WHITE);
                    lcd_show_string(5, 90, 200, 16, 16, "State:Disconnect", BLUE);
                    myfree(SRAMIN, tbuf);
                    break;
                }
            }
        }
    }
}