UDP通讯 ======= 作者:陈之敏 时间:2020年08月15日 关键字:csdk、RDA8910、二次开发、UDP ## 目录 `点击这里查看所有博文 `__   最近听说有的小伙伴看了我的教程后,有一些问题都跑到官方的gitee上面去问去了。导致官方的人没搞懂问的是啥,小伙伴们也没能知道自己想要的答案。给大家造成了困扰,这里我说声抱歉。   既然出现了这个问题,我这里就声明一下,本系列教程所涉及的内容(demo)不是官方的作品。我个人觉得官方的demo内容太多太全,往往都是把一个模块内所有的东西全部放在一起。这样的话对新手不是很友好,阅读起来也比较费劲。我就把官方的部分demo进行相关的简化,并推出教程这样的话可能会对新手朋友们有一定的帮助。   有时候周末闲暇时间我也会加上一些我觉得好玩的模块在里面,这些可能在官方的demo都没有,比如cJSON、PAHO-MQTT、http-client。   这就是官方的代码仓库。 .. code:: c git clone --recursive https://gitee.com/openLuat/Luat_CSDK_Air724U.git   当然各位小伙伴在看本教程时,我建议还是使用我下面提供的仓库比较好,看完之后在迁移到官方的仓库⇧。 .. code:: c git clone --recursive https://gitee.com/chenxiahuaxu/RDA8910_CSDK.git   再看本教程过程中如果遇到了问题,可以在本人的代码仓库下面评论。也可以在本人的\ `博客 `__\ 下面评论。我要是看到的话,并且这个问题在我的能力范围的话我会尽力解答的(非官方,不要对我要求太多哦,要求太多我可能就不管啦)。 一、前言 --------   上篇博客我们一起了解过tcp通讯之后,再来学习udp会轻松得多。程序基本是一样的,就改了几个传入的参数,上篇博客对应的实验要是做出来了,这篇udp的博客对于各位来讲想必不是什么问题。 |在这里插入图片描述|   udp和tcp之间最明显的区别就是udp是\ ``无连接``\ 的通讯,UDP是\ ``点对点``\ 的通讯,UDP通讯模式只要指定IP和端口就可以的,UDP服务\ ``没有客户端和服务器的概念``\ ,UDP是对等网络。tcp服务才有服务器和客户端的概念。UDP服务既可以主动给任何地址发消息,也可以被动接收任何地址传入进来的消息。(\ ``注意:这里的服务端是指软件的状态,并不是指远程的服务器``\ )。 |image1|   UDP服务没有客户端和服务器的概念并不是说没有UDP服务器(硬件)。将一个UDP的服务,运行在一台远端服务器上,那就是一个提供UDP服务的服务器。本系列教程的第7篇博客—RDA8910CSDK二次开发:\ `通过DNS解析迈向互联网的第一步 `__\ 就是一个\ ``UDP服务``\ ,通过向远端的UDP服务器请求解析数据获得网址对应的的ip。 二、编写测试程序 ---------------- 2.1、了解本例程所用到的函数 ---------------------------   使用udp服务需要包含\ ``#include "iot_socket.h""``\ 头文件,我们这里只用到了6个函数,分别是: >/**设置网络状态回调函数 *@param indCb: 回调函数*\ @return TRUE: 成功 \*    FALSE: 失败 \**/ - BOOL ``iot_network_set_cb`` (F_OPENAT_NETWORK_IND_CB indCb ) .. /**创建socket @param domain: 仅支持AF_INET (IPV4 网络协议) @param type: 支持SOCK_STREAM/SOCK_DGRAM,分别表示TCP、UDP连接 @param protocol: 仅支持0\ @return >=0: socket描述符,用于后续操作     <0: 创建socket失败\ @note 创建的socket不用后需要用close将其关闭**/ - int ``socket``\ (int domain, int type, int protocol) .. /**本地字节顺序转化为网络字节顺序(16bits) @param n: 本地字节书序数据\ @return 网络字节顺序数据**/ ``htons``\ (n) ((n & 0xff) << 8) \| ((n & 0xff00) >> 8) /**将ip地址字符串转为数值,转化后的数值为网络字节顺序 @param cp: ip地址字符串,例如“192.168.1.1”\ @param addr: struct in_addr 返回的ip地址数值 @return 1: 成功    0: 失败**/ ``inet_aton``\ (cp, addr) ipaddr_aton(cp, (openat_ip_addr_t*)addr) /**发送数据到指定ip地址,一般用于udp发送数据 *@param socketfd: 调用socket接口返回的socket描述符 @param buf: 数据内容 @param len: 数据长度 @param flags: 仅支持0 @param to_p: 指定ip地址和端口号 @param tolen: sizeof(struct openat_sockaddr)*\ @return >=0: 实际发送的长度 \*    <0 发送错误 \**/ - int ``sendto``\ (int socketfd, const void *buf,size_t len,int flags, const struct openat_sockaddr*\ to_p, openat_socklen_t tolen) .. /**接收指定ip地址发送来的数据,一般用于UDP收取数据 *@param socketfd: 调用socket接口返回的socket描述符 @param buf: 用于存放数据的缓存 @param len: buf的长度 @param flags: 仅支持0 @param addr: 支持ip地址和端口 @param addrlen: sizeof(struct openat_sockaddr)*\ @return >0: 接收到的数据长度 *    =0: 对方已经断开连接*    <0: 读取错误 \*@note 当flags没有设置MSG_DONTWAIT,该函数会阻塞,直到有数据或者读取超时 \**/ - int ``recvfrom``\ (int sockfd, void *buf, size_t len, int flags,struct openat_sockaddr*\ src_addr,openat_socklen_t \*addrlen) 2.2、编写主程序 ---------------   主程序负责注册网络回调函数,以及创建一个消息处理函数。 .. code:: c //系统休眠 iot_os_sleep(10000); //注册网络状态回调函数 iot_network_set_cb(NetWorkCb); //创建一个任务 //TestTask_HANDLE = TestTask_HANDLE = iot_os_create_task(TestTask, NULL, 2048, 10, OPENAT_OS_CREATE_DEFAULT, "TestTask"); return 0; 2.3、编写网络回调函数 ---------------------   消息回调函数负责通知系统消息,最好不要在其中处理复杂的动作。我这里将系统消息转存到全局变量中,然后再任务中定时查询并处理其他的逻辑。 .. code:: c static void NetWorkCb(E_OPENAT_NETWORK_STATE state) { NetWorkCbMessage = state; } 2.4、编写消息处理任务 ---------------------   在消息处理函数中,定时查询全局变量转存的网络状态,进行相应的处理。网络正常后调用\ ``UdpInit``\ 建立套接字。执行完成后进行任务自毁。 .. code:: c bool NetLink = FALSE; while (NetLink == FALSE) { T_OPENAT_NETWORK_CONNECT networkparam = {0}; switch (NetWorkCbMessage) { case OPENAT_NETWORK_DISCONNECT: //网络断开 表示GPRS网络不可用澹,无法进行数据连接,有可能可以打电话 iot_debug_print("[socket] OPENAT_NETWORK_DISCONNECT"); iot_os_sleep(10000); break; case OPENAT_NETWORK_READY: //网络已连接 表示GPRS网络可用,可以进行链路激活 iot_debug_print("[socket] OPENAT_NETWORK_READY"); memcpy(networkparam.apn, "CMNET", strlen("CMNET")); //建立网络连接,实际为pdp激活流程 iot_network_connect(&networkparam); iot_os_sleep(500); break; case OPENAT_NETWORK_LINKED: //链路已经激活 PDP已经激活,可以通过socket接口建立数据连接 iot_debug_print("[socket] OPENAT_NETWORK_LINKED"); NetLink = TRUE; break; } } if (NetLink == TRUE) { UdpInit(); } iot_os_delete_task(TestTask_HANDLE); 2.5、编写UdpInit ----------------   这是一个子程序,网络连接正常后在消息处理函数中被调用,只会执行一次。首先创建一个套接字,判断创建是否正常。若创建套接字正常,继续创建两个任务,一个负责接收数据,一个负责发送数据。 .. code:: c static void UdpInit() { //创建套接字,Udp连接 socketfd = socket(OPENAT_AF_INET, OPENAT_SOCK_DGRAM, 0); while (socketfd < 0) { iot_debug_print("[socket] create udp socket error"); iot_os_sleep(3000); } iot_debug_print("[socket] udp connect success"); iot_os_create_task(SentTask, NULL, 2048, 10, OPENAT_OS_CREATE_DEFAULT, "SentTask"); iot_os_create_task(RecvTask, NULL, 2048, 10, OPENAT_OS_CREATE_DEFAULT, "RecvTask"); } 2.6、编写发送任务 -----------------   在发送任务中,定时对建立的套接字做循环发送字符串的动作,并进行相应的次数标记。要注意的是,发送的时候需要带上远端设备网络连接结构体。 .. code:: c struct openat_sockaddr_in udp_remote_addr = {0}; udp_remote_addr.sin_family = OPENAT_AF_INET; udp_remote_addr.sin_port = htons((unsigned short)UDP_REMOTE_PORT); inet_aton(UDP_REMOTE_IP, &udp_remote_addr.sin_addr); uint8 num = 0; int len = 0; char data[512] = {0}; while (1) { if (socketfd >= 0) { len = sprintf(data, "RDA8910 Sent:%d", num); data[len] = '\0'; iot_debug_print(data); if (len > 0) { // UDP 发送数据 len = sendto(socketfd, data, len + 1, 0, (const struct openat_sockaddr *)&udp_remote_addr, sizeof(struct openat_sockaddr)); if (len < 0) { iot_debug_print("[socket] udp send data False"); } else { iot_debug_print("[socket] udp send data Len = %d", len); num += 1; } } } iot_os_sleep(3000); } 2.7、编写接收任务 -----------------   在接收任务中,将接收的数据打印在日志中显示,recvfrom函数会陷入阻塞状态,直到接收到数据。程序中提供的测试服务端自带回环功能,会将接收的的数据原封不动返回。所以我们接收到的数据就是自己发送的数据。 .. code:: c struct openat_sockaddr_in udp_remote_addr = {0}; openat_socklen_t udp_remote_len = 0; int len = 0; unsigned char data[512] = {0}; while (1) { if (socketfd >= 0) { // UDP 接受数据 len = recvfrom(socketfd, data, sizeof(data), 0, (struct openat_sockaddr *)&udp_remote_addr, &udp_remote_len); if (len < 0) { iot_debug_print("[socket] udp Recv data False"); } else { //发现获取不到远端ip和端口 iot_debug_print("[socket] udp Recv from ip:%s,port:%d", inet_ntoa(udp_remote_addr.sin_addr),ntohs(udp_remote_addr.sin_port)); iot_debug_print("[socket] udp Recv data result = %s", data); } } }   recvfrom函数接收到数据的时候会将远端连接结构体数据,写在传入的空结构体内,但是我们这个好像没什么用,不知道是不是我用错了,打印出来的远端ip的端口都是0。我看这函数的说明用的应该是没错的。 |image2| ## 三、编译并下载程序   完整代码在这,自取。 .. code:: c /* * @Author: your name * @Date: 2020-05-19 14:05:32 * @LastEditTime: 2020-05-26 21:50:08 * @LastEditors: Please set LastEditors * @Description: In User Settings Edit * @FilePath: \RDA8910_CSDK\USER\user_main.c */ #include "string.h" #include "cs_types.h" #include "osi_log.h" #include "osi_api.h" #include "am_openat.h" #include "am_openat_vat.h" #include "am_openat_common.h" #include "iot_debug.h" #include "iot_uart.h" #include "iot_os.h" #include "iot_gpio.h" #include "iot_pmd.h" #include "iot_adc.h" #include "iot_vat.h" #include "iot_network.h" #include "iot_socket.h" //Udp Demo //UDP模式指定IP和端口就可以的 //UDP服务没有客户端和服务器的概念,UDP是对等网络。tcp服务才有服务器和客户端的概念。 //错的人多了就变成对的了 //远端ip和port #define UDP_REMOTE_IP "121.40.170.41" #define UDP_REMOTE_PORT 12414 HANDLE TestTask_HANDLE = NULL; uint8 NetWorkCbMessage = 0; int socketfd = -1; static void SentTask(void *param) { struct openat_sockaddr_in udp_remote_addr = {0}; udp_remote_addr.sin_family = OPENAT_AF_INET; udp_remote_addr.sin_port = htons((unsigned short)UDP_REMOTE_PORT); inet_aton(UDP_REMOTE_IP, &udp_remote_addr.sin_addr); uint8 num = 0; int len = 0; char data[512] = {0}; while (1) { if (socketfd >= 0) { len = sprintf(data, "RDA8910 Sent:%d", num); data[len] = '\0'; iot_debug_print(data); if (len > 0) { // UDP 发送数据 len = sendto(socketfd, data, len + 1, 0, (const struct openat_sockaddr *)&udp_remote_addr, sizeof(struct openat_sockaddr)); if (len < 0) { iot_debug_print("[socket] udp send data False"); } else { iot_debug_print("[socket] udp send data Len = %d", len); num += 1; } } } iot_os_sleep(3000); } } static void RecvTask(void *param) { struct openat_sockaddr_in udp_remote_addr = {0}; openat_socklen_t udp_remote_len = 0; int len = 0; unsigned char data[512] = {0}; while (1) { if (socketfd >= 0) { // UDP 接受数据 len = recvfrom(socketfd, data, sizeof(data), 0, (struct openat_sockaddr *)&udp_remote_addr, &udp_remote_len); if (len < 0) { iot_debug_print("[socket] udp Recv data False"); } else { //发现获取不到远端ip和端口 iot_debug_print("[socket] udp Recv from ip:%s,port:%d", inet_ntoa(udp_remote_addr.sin_addr),ntohs(udp_remote_addr.sin_port)); iot_debug_print("[socket] udp Recv data result = %s", data); } } } } static void UdpInit() { //创建套接字,Udp连接 socketfd = socket(OPENAT_AF_INET, OPENAT_SOCK_DGRAM, 0); while (socketfd < 0) { iot_debug_print("[socket] create udp socket error"); iot_os_sleep(3000); } iot_debug_print("[socket] udp connect success"); iot_os_create_task(SentTask, NULL, 2048, 10, OPENAT_OS_CREATE_DEFAULT, "SentTask"); iot_os_create_task(RecvTask, NULL, 2048, 10, OPENAT_OS_CREATE_DEFAULT, "RecvTask"); } static void TestTask(void *param) { bool NetLink = FALSE; while (NetLink == FALSE) { T_OPENAT_NETWORK_CONNECT networkparam = {0}; switch (NetWorkCbMessage) { case OPENAT_NETWORK_DISCONNECT: //网络断开 表示GPRS网络不可用澹,无法进行数据连接,有可能可以打电话 iot_debug_print("[socket] OPENAT_NETWORK_DISCONNECT"); iot_os_sleep(10000); break; case OPENAT_NETWORK_READY: //网络已连接 表示GPRS网络可用,可以进行链路激活 iot_debug_print("[socket] OPENAT_NETWORK_READY"); memcpy(networkparam.apn, "CMNET", strlen("CMNET")); //建立网络连接,实际为pdp激活流程 iot_network_connect(&networkparam); iot_os_sleep(500); break; case OPENAT_NETWORK_LINKED: //链路已经激活 PDP已经激活,可以通过socket接口建立数据连接 iot_debug_print("[socket] OPENAT_NETWORK_LINKED"); NetLink = TRUE; break; } } if (NetLink == TRUE) { UdpInit(); } iot_os_delete_task(TestTask_HANDLE); } static void NetWorkCb(E_OPENAT_NETWORK_STATE state) { NetWorkCbMessage = state; } //main函数 int appimg_enter(void *param) { //系统休眠 iot_os_sleep(10000); //注册网络状态回调函数 iot_network_set_cb(NetWorkCb); //创建一个任务 //TestTask_HANDLE = TestTask_HANDLE = iot_os_create_task(TestTask, NULL, 2048, 10, OPENAT_OS_CREATE_DEFAULT, "TestTask"); return 0; } //退出提示 void appimg_exit(void) { OSI_LOGI(0, "application image exit"); }   查看输出,发现接收到的数据和发送的数据一致,唯一的问题就是recvfrom获取不到远端的地址。 .. figure:: https://img-blog.csdnimg.cn/20200526215343184.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDU3MDA4Mw==,size_16,color_FFFFFF,t_70 :alt: 在这里插入图片描述 在这里插入图片描述 .. 不会下载的\ `点击这里 `__\ ,进去查看我的\ ``RDA8910 CSDK二次开发入门教程``\ 专题第一篇博文\ ``1、RDA8910CSDK二次开发:环境搭建``\ 里面讲了怎么下载 这里只是我的学习笔记,拿出来给大家分享,欢迎大家批评指正,本篇教程到此结束 .. |在这里插入图片描述| image:: https://img-blog.csdnimg.cn/20200526213636553.png .. |image1| image:: https://img-blog.csdnimg.cn/2020052621371165.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDU3MDA4Mw==,size_16,color_FFFFFF,t_70 .. |image2| image:: https://img-blog.csdnimg.cn/20200526215610519.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDU3MDA4Mw==,size_16,color_FFFFFF,t_70