嵌入式技术论坛
直播中

风来吴山

9年用户 1537经验值
擅长:电源/新能源
私信 关注
[问答]

ESP8266 socket通信结果串口溢出如何解决呢

1、硬件平台:原子精英板 stm32f103zet6+esp8266

开发板作为client,网络调试助手模拟server

学习研究AT SOCKET有一段时间了,之前逛论坛提问解决了一些问题。遇到的问题如下:

1、at socket关闭不彻底,导致重连server时,socket连接号一直递增。(已解决)

2、socket连接断开,报错 esp0 device socket(0) wait connect result timeout.(已解决)

3、AT Client接收数据失败,串口获取数据提示超时,该问题会在程序运行一段时间后出现,还未找到原因?

[E/at_client] AT Client receive failed, uart device get data error(-2)
[E/at.skt.esp] esp0 device receive size(24) data failed.

4、程序运行一段时间后,

提示

[W/UART] Warning: There is no enough buffer for saving data, please increase the RT_SERIAL_RB_BUFSZ option.

论坛里关于这个问题的回答是:增大缓存、及时处理串口数据、降低串口波特率(目前AT串口波特率115200)

结合调试记录,当出现这个提示后,串口会一直卡在串口中断里,如下图:

针对这个问题,网络调试助手端每隔2秒会下发指令,长度24.目前RT_SERIAL_RB_BUFSZ设置为1024

我觉得增大缓存和降低波特率并不能真正彻底解决问题。还是串口数据处理不及时造成,但我没理解这句话(或者说怎样才算是及时把数据取走了),

client端使用的select方式接收数据,按照个人理解,recv接收数据,不就是把数据取走了吗?至于把数据拿去做什么应该和接收没什么关系了。请问这里该如何理解呢?

这个问题的出现最终会导致client再也连不上server。

我的client开了三个线程,

A:socket连接状态判断线程

B:接收线程

C:发送线程(发送线程里面模拟了心跳)

线程间通信用的event.

代码如下,可否帮忙看下问题出在哪里

static rt_thread_t tid25 = RT_NULL;

static rt_thread_t rx_tid25 = RT_NULL;

static rt_thread_t tx_tid25 = RT_NULL;

static rt_event_t socket_event = RT_NULL;

//socket接收线程

#define CLIENT_THREAD_PRIORITY 25

#define CLIENT_THREAD_STACK_SIZE 1024

#define CLIENT_THREAD_TIMESLICE 10

#define TCPCLIENT_CLOSE (1 << 0) //连接关闭

#define TCPCLIENT_TX (1 << 1) //发送

#define TCPCLIENT_PARSE (1 << 2) //解析接收数据

#define TCPCLIENT_RX_EXIT (1 << 3) //退出接收线程

#define TCPCLIENT_TX_EXIT (1 << 4) //退出发送线程

//连接服务器

static int tcp_connect1(void)

{

struct sockaddr_in server_addr;

struct netdev *netdev = RT_NULL;

int sock = -1;

char svrbuf[16] = "192.168.1.111";

//rt_err_t ret;

// 通过名称获取 netdev 网卡对象 */

netdev = netdev_get_by_name("esp0");

if (netdev == RT_NULL)

{

    LOG_E("get network interface device(%s) failed.", "esp0");

}

// 设置默认网卡对象 */

netdev_set_default(netdev);

// 初始化预连接的服务端地址 */

server_addr.sin_family = AF_AT;

server_addr.sin_port = htons(MySysSet.svrPort); //8887 

sprintf(svrbuf,"%d.%d.%d.%d",MySysSet.svrIp[0],MySysSet.svrIp[1],MySysSet.svrIp[2],MySysSet.svrIp[3]);//192.168.1.104

server_addr.sin_addr.s_addr = inet_addr(svrbuf);

rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));

RECONNETC:

rt_thread_mdelay(500);     

// 创建一个socket,类型是SOCKET_STREAM,TCP类型 */

if ((sock = socket(AF_AT, SOCK_STREAM, 0)) == -1)

{

    /* 创建socket失败 */

    LOG_E("Socket create error!");

    goto RECONNETC;

}

LOG_I("Socket (%d) create success!", sock);

// 连接到服务端 */

if (connect(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)

{

    // 连接失败 */

    LOG_E("Connect server fail!");

    closesocket(sock);

    goto RECONNETC;

}    

LOG_I("Client connect Server success.");

return sock;

}

// 接收线程入口函数 */

static void tcp_rx_thread(void *parameter)

{

uint16_t calcCRC,recvCRC;//接收的校验码

int maxfdp1;

int rc = 0;

int ret;

fd_set readset;

// 获取需要监听的描述符号最大值 */

maxfdp1 = socketfd + 1;

while(1)

{

    // 清空可读事件描述符列表 */

    FD_ZERO(&readset);

    // 将需要监听可读事件的描述符加入列表 */

    FD_SET(socketfd,&readset);

    // 获取需要监听的描述符号最大值 */

    //maxfdp1 = socketfd+1;    

    // 等待设定的网络描述符有事件发生 */

    rc = select(maxfdp1,&readset,0,0,0);

    // 至少有一个文件描述符有指定事件发生再向后运行 */

    if(rc == 0)        continue;

    // 查看 sock 描述符上有没有发生可读事件 */

    if (FD_ISSET(socketfd, &readset))

    {

        // 从sock连接中接收最大BUFSZ - 1字节数据 */

        rx_len = recv(socketfd, recv_data, BUFSZ - 1, 0);

        if (rx_len <= 0)    

        {

            if(!(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN))

            {

                LOG_E("recv error!");                    

                //关闭socket

                ret = closesocket(socketfd);

                if (ret == 0)

                {

                    LOG_W("rx closed socketfd (%d) success",socketfd);

                    socketfd = -1;                

                }                    

                rt_event_send(socket_event, TCPCLIENT_CLOSE);                            

                break;

            }

        }

        else

        {

            // 有接收到数据,把末端清零 */

            recv_data[rx_len] = '\0';

            //解析数据,成功后通知发送

            rt_event_send(socket_event, TCPCLIENT_TX);

        }

    }    

    rt_thread_mdelay(50); 

}

}

// 发送线程入口函数 */

static void tcp_tx_thread(void *parameter)

{

int ret;

const char *str = "alive test";

while(1)

{

    if (rt_event_recv(socket_event, TCPCLIENT_TX, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, 30000, RT_NULL) == RT_EOK)

    {

        ret = send(socketfd, send_data, tx_len, 0);            

        if (ret <= 0)    //发送出错

        {

            //发送失败

            LOG_E("data send error.");

            //关闭socket

            ret = closesocket(socketfd);

            if (ret == 0)

            {

                LOG_W("tx1 closed socketfd (%d) success",socketfd);

                socketfd = -1;                

            }

            rt_event_send(socket_event, TCPCLIENT_CLOSE);            

            break;

        }

    }

    else    //心跳包

    {

        ret = send(socketfd, str, strlen(str), 0);            

        if (ret <= 0)    //发送出错

        {

            //发送失败

            LOG_E("keepalive send error.");

            ret = closesocket(socketfd);

            if (ret == 0)

            {

                LOG_W("tx2 closed socketfd (%d) success",socketfd);

                socketfd = -1;                

            }

            rt_event_send(socket_event, TCPCLIENT_CLOSE);        

            break;

        }

    }    

    rt_thread_mdelay(40); 

}

}

//判断连接状态线程

static void client_connect_state(void *parameter)

{

//rt_err_t  result;

RECONNECT:

socketfd = tcp_connect1();

if(socketfd != -1 )

{

    //创建数据发送线程,优先级25

    tx_tid25 = rt_thread_create("client_tx", tcp_tx_thread, RT_NULL,

                                    CLIENT_THREAD_STACK_SIZE,

                                    CLIENT_THREAD_PRIORITY,

                                    CLIENT_THREAD_TIMESLICE);

    // 如果获得线程控制块,启动这个线程 */

    if (tx_tid25 != RT_NULL)    

    {

        LOG_I("create Client_tx_thread success");

        rt_thread_startup(tx_tid25);    

    }

    //创建接收线程,优先级24

    rx_tid25 = rt_thread_create("client_rx", tcp_rx_thread, RT_NULL,

                                    CLIENT_THREAD_STACK_SIZE,

                                    CLIENT_THREAD_PRIORITY-1,

                                    CLIENT_THREAD_TIMESLICE);

    // 如果获得线程控制块,启动这个线程 */

    if (rx_tid25 != RT_NULL)    

    {

        LOG_I("create Client_rx_thread success");

        rt_thread_startup(rx_tid25);    

    }

}

while(1)

{            

    if (rt_event_recv(socket_event, TCPCLIENT_CLOSE, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, 10000, RT_NULL) == RT_EOK)

    {

        //删除线程

        if( rt_thread_delete(rx_tid25) == RT_EOK )

            LOG_D("client_rx_thread Delete OK");

        rt_thread_mdelay(250);

        goto RECONNECT;

    }

    rt_thread_mdelay(50);        

}

}

//创建线程

int fnUserClient(void)

{

//

socket_event = rt_event_create("sock_event", RT_IPC_FLAG_FIFO);

if (socket_event == RT_NULL)

{

    LOG_E("tcpclient socket event create failed");

}

LOG_D("tcpclient socket event create ok");

//创建线程,优先级23

tid25 = rt_thread_create("cli2svr",

                            client_connect_state,

                            RT_NULL,

                            CLIENT_THREAD_STACK_SIZE,

                            CLIENT_THREAD_PRIORITY-2,

                            CLIENT_THREAD_TIMESLICE);

// 如果获得线程控制块,启动这个线程 */

if (tid25 != RT_NULL)    

{

    rt_thread_startup(tid25);

}    

else

{

    LOG_E("create Client_thread failed");

    return -1;

}

return 0;

}

回帖(10)

刘敏

2022-8-11 10:06:57
1.recv 接口是把数据取走,取走后释放了存储接收到的数据的空间。函数执行如下,接收的 URC 函数将数据放到 recvpkt_list 中,然后 recv() 函数从 recvpkt_list 中取出数据。

#define recv(s, mem, len, flags)                    sal_recvfrom(s, mem, len, flags, NULL, NULL) // sal_socket/include/socket/sys_socket/sys/socket.h
#define recvfrom(s, mem, len, flags, from, fromlen) sal_recvfrom(s, mem, len, flags, from, fromlen) // sal_socket/include/socket/sys_socket/sys/socket.h
  |-> sal_recvfrom // sal_socket.c
    |-> SAL_SOCKET_OBJ_GET // sal_socket.c
    |-> SAL_NETDEV_IS_UP // sal_socket.c
    |-> SAL_NETDEV_SOCKETOPS_VALID // sal_socket.c
    |-> pf->skt_ops->recvfrom // sal_socket.c
      |-> at_recvfrom // at/at_socket/at_socket.c
        |-> at_get_socket // at/at_socket/at_socket.c
        |-> at_recvpkt_get(&(sock->recvpkt_list)...) // 从链表中获取数据 at/at_socket/at_socket.c
          |-> at_recvpkt_node_delete  // 从链表中删除数据释放空间 at/at_socket/at_socket.c
2.获取网络的连接状态可以通过读取网络设备的标志位来获取,如下所示。在网络连接时会与 "link.rt-thread.org" 的 8101 端口进行数据收发测试,进而修改网络设备的连接标志,可以参考文章 AT组件源码解析

/**
* @Brief  获取esp8266的链接状态
* @Return
* @return 成功返回0,失败返回-1
*/
int get_esp8266_link_status(void)
{
    /* "esp8266" 名字来源于设备注册时使用的名字,在文件 packages/at_device-latest/samples/at_sample_esp8266.c 中定义 */
    struct at_device * esp8266_dev = at_device_get_by_name(AT_DEVICE_NAMETYPE_NETDEV, "esp0");
    if (esp8266_dev == RT_NULL)
    {
        LOG_E("func: %s. get esp8266 at device failed", __FUNCTION__);
        return -1;
    }
    if (esp8266_dev->is_init == RT_FALSE)   // 是否初始化
    {
        //LOG_E("%s is not init", "esp0");
        return -1;
    }
    if (netdev_is_up(esp8266_dev->netdev) == 0) // 网络设备不存在
    {
        return -1;
    }
    if (netdev_is_internet_up(esp8266_dev->netdev)) // 判断是否联网
    {
        return 0;
    }
    else
    {
        return -1;
    }
}
举报

风来吴山

2022-8-11 10:07:14
对于回复的第1点,将接收线程优先级设置最高,及时用recv取走数据,是不应该出现串口溢出的
举报

刘敏

2022-8-11 10:07:27
以EC200(AT设备)为例,接收到的数据会调用回调函数 urc_recv_func,在这个函数里面把收到的数据存储到了 recvpkt_list 链表中(动态开辟空间,挂在链表上),读取不读取不会影响存储,回调函数的执行逻辑如下。

urc_recv_func /* at_socket_ec200x.c */
  |-> recv_buf = (char *) rt_calloc(1, bfsz); /* 申请空间,供 at_recv_pkt 使用,挂载在 rlist 列表中 */
  |-> at_client_obj_recv  /* at_client.c */
    |-> at_client_getchar /* at_client.c */
  |-> at_evt_cb_set[AT_SOCKET_EVT_RECV](socket, AT_SOCKET_EVT_RECV, recv_buf, bfsz);    /* at_socket_ec200x.c */
    |-> at_recv_notice_cb /* at_socket.c */
      |-> at_recvpkt_put(&(sock->recvpkt_list), buff, bfsz); /* at_socket.c */
      |-> pkt->buff = (char *) ptr; /* 将buff指向之前申请到的空间at_socket.c */
      |-> rt_slist_append /* 挂载到链表中 */
读取数据就是把链表中的数据取出来,不读取链表中的内容就会越来越多,也就是说收到数据后就把收到的内容开辟出了一块空间存储起来了。
举报

刘敏

2022-8-11 10:07:39
另外我觉得你应该提高一下 AT 解析线程的优先级,这个线程里面执行了了 URC 回调函数,而你读取的优先级不用很高
client->parser = rt_thread_create(name, (void (*)(void *parameter))client_parser, client, 1024 + 512, RT_THREAD_PRIORITY_MAX / 3 - 1, 5);
举报

风来吴山

2022-8-11 10:07:51
根据你的答复,继续优化了业务逻辑,就目前的测试效果看没再出现串口溢出提示
[W/UART] Warning: There is no enough buffer for saving data, please increase the RT_SERIAL_RB_BUFSZ option.

但是还是会出现接收数据超时错误
[E/at_client] AT Client receive failed, uart device get data error(-2).
[E/at.skt.esp] esp0 device receive size(24) data failed.
举报

刘敏

2022-8-11 10:08:00
esp8266 和 服务器的通讯过程是怎样的?通讯频率和数据包的大小呢?
举报

风来吴山

2022-8-11 10:08:09
1、通信链路:开发板客户端<——>ESP8266<——>SERVER
2、数据交互流程
server这边每隔2秒轮询一次开发板数据,指令间发送间隔设的250ms.规定下发指令长度24字节,反馈数据长度42字节。根据server端log,发现读取1号数据时,第一次反馈的数据会丢失几个字节。
1.jpg
举报

刘敏

2022-8-11 10:08:21
要是增大轮询周期呢?
举报

风来吴山

2022-8-11 10:08:33
1、轮询周期增大至5秒,指令间隔为250ms,还是会报接收超时错误
2、轮询周期增大至5秒,指令间隔为500ms,还是会报接收超时错误
举报

刘敏

2022-8-11 10:08:51
跟一下源码调试一下试试,看看什么问题
举报

更多回帖

发帖
×
20
完善资料,
赚取积分