请选择 进入手机版 | 继续访问电脑版
论坛
登录 | 立即注册 设为首页收藏本站 切换到宽版
查看: 1040|回复: 4

【ESP8266】【RTOS3.0】【IDF】【IWIP】【netconn】关于UDP发送错误

[复制链接]

1

主题

29

帖子

178

积分

注册会员

Rank: 2

积分
178
发表于 2019-1-10 19:11:02 | 显示全部楼层 |阅读模式
本帖最后由 abcrazy 于 2019-1-16 14:50 编辑

学习IWIP后,百度上说,netconn 函数比用 socket 函数更省内存,netconn可以不用拷贝数据,socket发送和接收都需要拷贝数据。所以,尝试了使用 netconn 。可是,发现接收没问题,发送却会引起重启,错误提示如下:
udp_sync: UDP sync register error, socket is -1

百度寻找没有结果,只有自己解决。


出错文件:
C:\ESP8266_RTOS_SDK-3.0\components\lwip\port\esp8266\freertos\udp_sync.c
第75行,所在函数:

/*
* @brief register a UDP API message(struct api_msg) to module
*/
void udp_sync_regitser(void *in_msg)
{
    s_cur_msg = in_msg;

    struct api_msg *msg = (struct api_msg *)in_msg;
    int s = msg->conn->socket;

    if (s < 0 || s >= UDP_SYNC_MAX) {
        ESP_LOGE(TAG, "UDP sync register error, socket is %d", s);
    } else if (s_udp_syncs.msg) {
        ESP_LOGE(TAG, "UDP sync register error, msg is %p", s_udp_syncs.msg);
    }
    s_udp_sync_num++;
    s_udp_sync
s.ret = ERR_OK;
    s_udp_sync
s.retry = 0;
    s_udp_sync
s.msg = msg;
}



于是在控制台“make menuconfig” 设置:
Component config  --->  LWIP  --->  SOCKET  --->  [ *] LWIP socket UDP sync send
改为:
Component config  --->  LWIP  --->  SOCKET  --->  [ ] LWIP socket UDP sync send

重新编译,出现新的错误:
文件
C:\ESP8266_RTOS_SDK-3.0\components\lwip\port\esp8266\netif\ethernetif.c
第211行,链接不到函数:udp_sync_trigger();

所在函数:
/*
* @brief LWIP low-level AI/O sending callback function, it is to free pbuf
*
* @param aio AI/O control block pointer
*
* @return 0 meaning successs
*/
static int low_level_send_cb(esp_aio_t *aio)
{
    struct pbuf *pbuf = aio->arg;

    pbuf_free(pbuf);
    udp_sync_trigger();
    return 0;
}



此函数调用的是:
/*
* @brief trigger a UDP sync process
*/
void udp_sync_trigger(void)
{
    if (!s_udp_sync_num)
        return ;


    tcpip_callback_with_block((tcpip_callback_fn)udp_sync_trigger_null, NULL, 0);
}


看代码意思是设置回调函数的样子,深究这个回调函数,如下:
/*
* @brief NULL function and just as sync message
*/
static void udp_sync_trigger_null(void *p)
{


}

一片空白,啥都没有,看来乐鑫官方有头没尾,既然没内容,又把函数先设置了。
直接把
udp_sync_trigger();
函数注释掉,重新编译,没有出错,UDP发送正常。




分析错误原因:
原来,乐鑫默认开启UDP同步文件 udp_sync.c ,正常调用 sockets.c 文件里面的函数时,没有错误。因为就在调用 int lwip_socket(int domain, int type, int protocol); 函数时,内部自动创建 sockets 结构体列表 和 新建 netconn 结构体,返回的是 sockets 结构体列表的索引号,而UDP同步正需要这个索引号才能正常工作。我们在外部的函数,即使得到了索引号,也无法获取获得索引号对应的 netconn 结构体指针,这个指针是发送数据所必需的,所以只能把UDP同步关掉了。


回复

使用道具 举报

1

主题

29

帖子

178

积分

注册会员

Rank: 2

积分
178
 楼主| 发表于 2019-1-10 19:29:03 | 显示全部楼层
本帖最后由 abcrazy 于 2019-1-14 22:36 编辑

netconn  发送UDP调用函数流程:格式:
源文件  所在函数   调用下一个函数
下一个函数源文件  所在函数  调用下一个函数
...
以此类推


api_lib.c                err_t netconn_sendto(struct netconn *conn, struct netbuf *buf, const ip_addr_t *addr, u16_t port)
return netconn_send(conn, buf);


api_lib.c                err_t netconn_send(struct netconn *conn, struct netbuf *buf)
err = netconn_apimsg(lwip_netconn_do_send, &API_MSG_VAR_REF(msg));


api_msg.c                void lwip_netconn_do_send(void *m)
msg->err = udp_send(msg->conn->pcb.udp, msg->msg.b->p);


udp.c        err_t udp_send(struct udp_pcb *pcb, struct pbuf *p)
return udp_sendto(pcb, p, &pcb->remote_ip, pcb->remote_port);


udp.c        err_t udp_sendto(struct udp_pcb *pcb, struct pbuf *p,  const ip_addr_t *dst_ip, u16_t dst_port)
return udp_sendto_if(pcb, p, dst_ip, dst_port, netif);


udp.c        err_t udp_sendto_if(struct udp_pcb *pcb, struct pbuf *p,  const ip_addr_t *dst_ip, u16_t dst_port, struct netif *netif)
return udp_sendto_if_src(pcb, p, dst_ip, dst_port, netif, src_ip);


udp.c        udp_sendto_if_src(struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *dst_ip, u16_t dst_port, struct netif *netif, const ip_addr_t *src_ip)
err = ip_output_if_src(q, src_ip, dst_ip, ttl, pcb->tos, ip_proto, netif);


ip4.c        ip4_output_if_src(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest, u8_t ttl, u8_t tos, u8_t proto, struct netif *netif)
return netif->output(netif, p, dest);


ethernetif.c        int8_t ethernetif_init(struct netif* netif)
netif->output = etharp_output;


eharp.c                err_t etharp_output(struct netif *netif, struct pbuf *q, const ip4_addr_t *ipaddr)
return ethernet_output(netif, q, (struct eth_addr*)(netif->hwaddr), dest, ETHTYPE_IP);


ethernet.c                err_t ethernet_output(struct netif* netif, struct pbuf* p, const struct eth_addr* src, const struct eth_addr* dst, u16_t eth_type)
return netif->linkoutput(netif, p);


ethernetif.c        int8_t ethernetif_init(struct netif* netif)
netif->linkoutput = low_level_output;


ethernetif.c        int8_t low_level_output(struct netif* netif, struct pbuf* p)
err = esp_aio_sendto(&aio, NULL, 0);


esp_socket.c        int esp_aio_sendto(esp_aio_t *aio, const struct sockaddr_ll *to, socklen_t len)


回复

使用道具 举报

1

主题

29

帖子

178

积分

注册会员

Rank: 2

积分
178
 楼主| 发表于 2019-1-10 19:35:55 | 显示全部楼层
本帖最后由 abcrazy 于 2019-1-17 21:33 编辑

UDP接收时调用的函数:
格式:文件名        函数名        调用语句
esp_wifi.c                        esp_err_t esp_wifi_init(const wifi_init_config_t *config)
esp_event_set_default_wifi_handlers();

event_default_handlers.c        void esp_event_set_default_wifi_handlers()
default_event_handlers[SYSTEM_EVENT_STA_START]        = system_event_sta_start_handle_default;

event_default_handlers.c        esp_err_t system_event_sta_start_handle_default(system_event_t *event)
tcpip_adapter_start(TCPIP_ADAPTER_IF_STA, sta_mac, &sta_ip);

tcpip_adapter_lwip.c                esp_err_t tcpip_adapter_start(tcpip_adapter_if_t tcpip_if, uint8_t *mac, tcpip_adapter_ip_info_t *ip_info)
netif_add(esp_netif[tcpip_if], &ip_info->ip, &ip_info->netmask, &ip_info->gw, (void *)s, ethernetif_init, tcpip_input);

netif.c                        struct netif *netif_add(struct netif *netif,const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw,void *state, netif_init_fn init, netif_input_fn input)
netif->input = input;

-----------以上是初始化时设置 netif->input(p, netif) 调用函数,必须找出来,因为有好几个相同功能的函数,以便知道 ethernetif.c 文件中调用哪个回调函数-----------
以下是 ESP8266 内部接收到数据后,与 LWIP 的接口调用流程:

event_loop.c                esp_err_t esp_event_loop_init(system_event_cb_t cb, void *ctx)
if(wifi_task_create(esp_event_loop_task, "esp_event_loop_task", EVENT_LOOP_STACKSIZE, NULL, wifi_task_get_max_priority() - 5) == NULL) {

event_loop.c                static void esp_event_loop_task(void *pvParameters)
esp_err_t ret = esp_event_process_default(&evt);

event_default_handlers.c        esp_err_t esp_event_process_default(system_event_t *event)
if (default_event_handlers[event->event_id] != NULL) {

event_default_handlers.c        esp_err_t system_event_sta_start_handle_default(system_event_t *event)
tcpip_adapter_start(TCPIP_ADAPTER_IF_STA, sta_mac, &sta_ip);

tcpip_adapter_lwip.c                esp_err_t tcpip_adapter_start(tcpip_adapter_if_t tcpip_if, uint8_t *mac, tcpip_adapter_ip_info_t *ip_info)
s = tcpip_adapter_bind_netcard(netcard_name, esp_netif[tcpip_if]);

tcpip_adapter_lwip.c                static int tcpip_adapter_bind_netcard(const char *name, struct netif *netif)
ret = esp_aio_event(s, ESP_SOCKET_RECV_EVENT, tcpip_adapter_recv_cb, netif);

tcpip_adapter_lwip.c                static int tcpip_adapter_recv_cb(struct esp_aio *aio)
ethernetif_input(netif, pbuf);

ethernetif.c                void ethernetif_input(struct netif* netif, struct pbuf* p)
if (netif->input(p, netif) != ERR_OK) {
上面已经找出回调函数其实是调用  err_t tcpip_input(struct pbuf *p, struct netif *inp) 函数

tcpip.c                        err_t tcpip_input(struct pbuf *p, struct netif *inp)
return tcpip_inpkt(p, inp, ethernet_input);

tcpip.c                        err_t tcpip_inpkt(struct pbuf *p, struct netif *inp, netif_input_fn input_fn)
ret = input_fn(p, inp);

ethernet.c                        err_t ethernet_input(struct pbuf *p, struct netif *netif)
ip4_input(p, netif);

ip4.c                        err_t ip4_input(struct pbuf *p, struct netif *inp)
udp_input(p, inp);

udp.c                        void udp_input(struct pbuf *p, struct netif *inp)
pcb->recv(pcb->recv_arg, pcb, p, ip_current_src_addr(), src);

--------------这里好像到底部了,看看上层应用谁设置了 pcb->recv 回调函数----------------
api.h
#define netconn_new(t)                  netconn_new_with_proto_and_callback(t, 0, NULL)
#define netconn_new_with_callback(t, c) netconn_new_with_proto_and_callback(t, 0, c)

api_lib.c                        struct netconn* netconn_new_with_proto_and_callback(enum netconn_type t, u8_t proto, netconn_callback callback)
err = netconn_apimsg(lwip_netconn_do_newconn, &API_MSG_VAR_REF(msg));

api_msg.c                        void lwip_netconn_do_newconn(void *m)
  if (msg->conn->pcb.tcp == NULL) {
    pcb_new(msg);
  }
注意:这里的意思是 TCP 回调函数不是在这条线上设置的

api_msg.c                        static void pcb_new(struct api_msg *msg)
udp_recv(msg->conn->pcb.udp, recv_udp, msg->conn);
注意:这里默认将 recv_udp 这个函数,设为回调函数。我们无权设置回调函数。

udp.c                        void udp_recv(struct udp_pcb *pcb, udp_recv_fn recv, void *recv_arg)
pcb->recv = recv;

--------------知道回调函数后,看看这个回调函数如何返回 pbuf 给我们的----------------
api_msg.c                        static void recv_udp(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
buf = (struct netbuf *)memp_malloc(MEMP_NETBUF);
buf->p = p;
if (sys_mbox_trypost(&conn->recvmbox, buf) != ERR_OK) {
    netbuf_delete(buf);
这里得知从内存池申请一个 pbuf 然后丢给了一个函数处理

sys_arch.c                        err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg)
if (xQueueSend(*mbox, &msg, (portTickType)0) == pdPASS) {
这里是 freeRTOS 的消息队列,第一个参数是队列句柄,第二个是内容,第三个是等待时间0立即返回

--------------再看 netconn 接收函数是否一样有一个 freeRTOS 消息队列等待任务--------------
api_lib.c                        err_t netconn_recv(struct netconn *conn, struct netbuf **new_buf)
err = netconn_recv_data(conn, (void **)&p);

api_lib.c                        static err_t netconn_recv_data(struct netconn *conn, void **new_buf)
if (sys_arch_mbox_fetch(&conn->recvmbox, &buf, conn->recv_timeout) == SYS_ARCH_TIMEOUT) {

sys_arch.c                u32_t sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout)        
if (pdTRUE == xQueueReceive(*mbox, &(*msg), timeout / portTICK_RATE_MS)) {
这里是 freeRTOS 的消息队列,第一个参数是队列句柄,第二个是内容,第三个是等待时间,如何 timeout=0 ,等待时间会取最大值

回复

使用道具 举报

1

主题

29

帖子

178

积分

注册会员

Rank: 2

积分
178
 楼主| 发表于 2019-3-9 13:11:44 | 显示全部楼层
停止对 netbuf 的研究了,原因是以1ms发送数据,大概4kb左右开始满内存,查看pbuf申请和释放的Log都成对没有问题,就是不知道哪里没释放。
回复

使用道具 举报

1

主题

29

帖子

178

积分

注册会员

Rank: 2

积分
178
 楼主| 发表于 2019-3-9 20:17:46 | 显示全部楼层
static void udp_server_recv_task(void *pvParameters)
{
    char rx_buffer[128];
    char addr_str[128];

    for(;;){
        struct sockaddr_in destAddr;
        destAddr.sin_addr.s_addr = htonl(INADDR_ANY);//本地IP地址
        destAddr.sin_family = AF_INET;//协议族。一般用  AF_INET 就好
        destAddr.sin_port = htons(UDP_PORT_LOCAL);//本地端口号

        /*socket函数参数:
         * domain        :PF_INET是protocol family,AF_INET是address family。使用上没啥区别,作者想多了,一般用  AF_INET 就好。
         * type                :SOCK_RAW, SOCK_DGRAM, SOCK_STREAM。分别对应:IP,UDP,TCP。网络传输有MAC地址层14字节,IP层20字节,传输层(UDP8字节)。
         * protocol        :IPPROTO_RAW, IPPROTO_IP, IPPROTO_UDP, IPPROTO_UDPLITE, IPPROTO_TCP, IPPROTO_ICMP。想要接收的协议包类型。
         * */
        int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
        if (sock < 0) {
            ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
            break;
        }
        ESP_LOGI(TAG, "Socket created");

        /* 绑定端口号 */
        int err = bind(sock, (struct sockaddr *)&destAddr, sizeof(destAddr));
        if (err < 0) {
            ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
        }
        ESP_LOGI(TAG, "Socket binded");

        while (1) {

            ESP_LOGI(TAG, "Waiting for data");
            struct sockaddr_in sourceAddr;
            socklen_t socklen = sizeof(sourceAddr);

            /**recvfrom函数参数:
             * s                :sock索引号
             * mem                :接收数据的缓冲区
             * len                :接收数据的缓冲区大小
             * flags        :MSG_PEEK, MSG_WAITALL, MSG_OOB, MSG_DONTWAIT, MSG_MORE
             *                                 默认:0 是阻塞
             *                                 MSG_OOB : 仅仅是通过tcp头的urgent 模式传送的,且只会读取一个字节作为oob数据
             *                                 MSG_PEEK  :  偷窥一下缓冲区中有没有数据,并不会从缓冲区移除数据.
             *                                 MSG_DONTWAIT : 不阻塞
             * from                :struct sockaddr类型,从哪个IP地址发来数据
             * fromlen        :socklen_t类型,上一个参数的字节数。
             * */
            int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&sourceAddr, &socklen);

            // Error occured during receiving
            if (len < 0) {
                ESP_LOGE(TAG, "recvfrom failed: errno %d", errno);
                break;
            }
            // Data received
            else {
                // Get the sender's ip address as string

                inet_ntoa_r(((struct sockaddr_in *)&sourceAddr)->sin_addr.s_addr, addr_str, sizeof(addr_str) - 1);

                rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string...
                ESP_LOGI(TAG, "Received %d bytes from %s:", len, addr_str);
                ESP_LOGI(TAG, "%s", rx_buffer);

                /**sendto函数参数:
                 * s                :sock索引号
                 * data                :发送数据的缓冲区
                 * size                :发送数据的缓冲区大小
                                 * flags        :MSG_PEEK, MSG_WAITALL, MSG_OOB, MSG_DONTWAIT, MSG_MORE
                                 *                                 默认:0 是阻塞
                                 *                                 MSG_OOB : 仅仅是通过tcp头的urgent 模式传送的,且只会读取一个字节作为oob数据
                                 *                                 MSG_PEEK  :  偷窥一下缓冲区中有没有数据,并不会从缓冲区移除数据.
                                 *                                 MSG_DONTWAIT : 不阻塞
                 * to                :struct sockaddr类型,发到哪个IP地址
                 * tolen        :socklen_t类型,上一个参数的字节数。
                 * */
                int err = sendto(sock, rx_buffer, len, 0, (struct sockaddr *)&sourceAddr, sizeof(sourceAddr));
                if (err < 0) {
                    ESP_LOGE(TAG, "Error occured during sending: errno %d", errno);
                    break;
                }
            }
        }

        if (sock != -1) {
            ESP_LOGE(TAG, "Shutting down socket and restarting...");
            shutdown(sock, 0);
            close(sock);
        }
    }
    vTaskDelete(NULL);
}
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|安信可论坛    

GMT+8, 2019-8-21 02:20 , Processed in 0.025051 second(s), 16 queries , Redis On.

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表