ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

ART-PI之UDP非阻塞客户端

2022-02-23 23:33:53  阅读:217  来源: 互联网

标签:文件 UDP ART addr tv sock 描述符 PI data


服务端控制客户端的小电机(PWM 方式)

客户端: ART-PI,向服务端发送天气信息和客户端状态,消息格式s:%d;v:%d;n:%d;l:%s

服务端:自制Python服务端,端口绑定8887,发送电机控制命令  60/61/62/63/64  (hex 0x36 0x30...)

遇到的问题:虽然使用的是UDP 连接, 默认状态下recvfrom是阻塞的, 如果服务端没有发送指令,socket并没有收到数据,客户端任务阻塞等待在这个接收函数。

如果这个任务还有其他任务要求,比如发送状态,那就成了问题。那么,是否有一种方法为UDP的RECV设置超时。

方法一:设置socket超时时间, timeout后, 读取recvfrom的返回值,判断状态并继续loop

 struct timeval tv_out;
 tv_out.tv_sec = 3;
 tv_out.tv_usec  = 0;
 setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv_out,izeof(tv_out));

方法二:使用select实现非阻塞通讯

/*
 int select(int maxfdp1, fd_set* readset, fd_set* writeset, fd_set* exceptset, const struct timeval* timeout)

 返回值:就绪描述符的数目,超时返回0,出错返回 - 1
*/

/*
 参数列表:
 int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!
 fd_set* readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。
 d_set* writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。
 fd_set* errorfds同上面两个参数的意图,用来监视文件错误异常。
 struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态:
 第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
 第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;
 第三,timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
 * /

/*
      说明两个结构体:

      第一,struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作。比如清空集合 FD_ZERO(fd_set*),将一个给定的文件描述符加入集合之中FD_SET(int, fd_set*),将一个给定的文件描述符从集合中删除FD_CLR(int, fd_set*),检查集合中指定的文件描述符是否可以读写FD_ISSET(int, fd_set*)。
      第二,struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。
 */

 

void udp_demo(void)
{
     int ret;
    int sock = -1;
    struct hostent *host;
    struct sockaddr_in server_addr, local_addr;
    int bytes_received;
    char *recv_data;
    fd_set readset;
    int maxfdp1,i;

    int state = 0;
    int sn = 0;
    char sl[1024] = {0};
    int vol = 0;
    char buff[128] = {0};
    char ip_addr_buf[64];
    recv_data = rt_calloc(1, TEST_BUFSZ);
    if (recv_data == RT_NULL)
    {
        rt_kprintf("calloc failed. No memory!");
        return;
    }
   // rt_thread_mdelay(5000);
   //udp 收发的一些心得
   //https://www.cnblogs.com/wf-skylark/p/9008138.html
    /* 创建一个socket,类型是SOCK_DGRAM,UDP类型 */
    if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        rt_kprintf("Socket error\n");
        return;
    }
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(udp_remote_port);
    server_addr.sin_addr.s_addr = inet_addr(udp_remote_ip);
    rt_kprintf("ip: %s, port: %d \n",udp_remote_ip, udp_remote_port);
    if ((ret = connect(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr))) < 0)
    {
        LOG_E("Connect <%d> fail! ret:%d", sock, ret);
        goto __exit;
    }

    LOG_I("connect  success\n");
    //https://blog.csdn.net/songjun007/article/details/119987087
    //udp receive timeout
   struct timeval tv_out;
   tv_out.tv_sec = 3;
   tv_out.tv_usec  = 0;
   // setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv_out, sizeof(tv_out));
   struct timeval tv;                    //设置select 超时时间
   tv.tv_sec = 3;
   tv.tv_usec =0;

   int fromlen =sizeof(struct sockaddr_in);
   while(1)
   {
       //rt_thread_mdelay(20);
       wifi_audio_infor(&state, &sn, &vol,sl);
       rt_sprintf(req_data, "s:%d;v:%d;n:%d;l:%s", state, vol, sn,sl);
       // ret = send(sock, req_data, strlen(req_data), 0);
       LOG_I("send: %s \n",req_data );
       sendto(sock, req_data, strlen(req_data), 0, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in));
       rt_thread_mdelay(2);
       LOG_I("select");
       FD_ZERO(&readset);                                //清除这个集合
       FD_SET(sock,&readset);                            //增加网络描述符
       i = select(sock+1, &readset,0,0,&tv);
       if(i < 0)
       {
          LOG_I("select error \n ");
       }
       else if( i ==0 )
       {
           LOG_I("time out \n ");      //等于零代表超时
       }
        else if (i > 0)
       {
         //bytes_received = recv(sock, recv_data, TEST_BUFSZ - 1, 0); //等待收取数据
         //检测套接字的文件描述符是否在该集合中可读写
         if(FD_ISSET(sock,&readset))
         {
            bytes_received = recvfrom(sock, recv_data, TEST_BUFSZ - 1, 0, (struct sockaddr *)&server_addr,&fromlen);
            if (bytes_received < 0)
           {
             LOG_I("no receive, socket <%d>.", sock);
            // goto __exit;
            }
         else {

            recv_data[bytes_received] = '\0' ;
            LOG_I("\n(%s,%d) say:", inet_ntoa(server_addr.sin_addr),ntohs(server_addr.sin_port));
            LOG_I("recv data: %s \n", recv_data);
            process_server_data(recv_data);
            }
        }
       }


   }
__exit:
   LOG_I(" @@ exit \r\n");
    if (recv_data)
        rt_free(recv_data);

    if (sock >= 0)
    {
        closesocket(sock);
        sock = -1;
    }
}

 

发现ulog用起来蛮舒服的,几个MSH常用命令来设置日志级别、标签、输出关键字、过滤器。相信以后调试一定还要用。.

ulog_lvl 0 

ulog_lvl 7
ulog_lvl 4
ulog_tag udp
ulog_kw wifi
ulog_filter 
ulog_flush

总结:

 对于UDP而言,服务器也不知道客户端的状态(面向非连接,没有连接这个东西,因此只剩下判断那个客户端句柄需要收数据)。

“大多数情况下,TCP服务器是并发的,UDP的服务器是迭代的“。人说,UDP没有必要使用多路复用。

这个例子也只是起到了超时控制的作用,就当做以后TCP服务端编程做个练习实验吧。

 

标签:文件,UDP,ART,addr,tv,sock,描述符,PI,data
来源: https://www.cnblogs.com/7star/p/15929737.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有