Toggle navigation
首页
(current)
问答
文章
话题
商城
登录
注册
8、RDA8910CSDK二次开发:以tcp客户端的身份去勾搭服务端
RDA8910
socket
TCP
csdk
# 目录 [点击这里查看所有博文](https://blog.csdn.net/weixin_44570083/article/details/104285283) 本系列博客所述资料`均来自合宙官方`,并不是本人原创(只有博客是自...
# 目录 [点击这里查看所有博文](https://blog.csdn.net/weixin_44570083/article/details/104285283) 本系列博客所述资料`均来自合宙官方`,并不是本人原创(只有博客是自己写的),csdk只是得到了`口头的允许公开授权`。出于热心,本人将自己的所学笔记整理并推出相对应的使用教程,方面其他人学习。为国内的物联网事业发展尽自己的一份绵薄之力,`没有为自己谋取私利的想法`。若出现侵权现象,请告知本人,本人会立即停止更新,并删除相应的文章和代码。 本系列博客基于紫光展锐的`RDA8910 LTE Cat 1` bis芯片平台开发。理论上适用于合宙的Air720U、Air724U、广和通L610以及安信可的cat-01模块。 各个厂家的部分配置文件可能不一样,也许会导致设备出现奇怪的问题,其他的模块我也不确定能不能用,自行测试。但是有一点`编译下载和监视流程基本一样`,可供参考。 先不管支不支持,如果你用的模块是是紫光展锐的RDA8910,那都不妨一试,也许会有意外收获(`也有可能变砖,慎重!!!`)。 我使用的是`Air724UG`开发板,如果在其他模块上不能用,那也不要强怼,也许是开发包不兼容吧。这里的代码是没有问题的。例程仅供参考! # 一、前言 上篇博客我们简单的使用了下互联网的功能,主要是为了带大家熟悉一下联网流程,必须要根据设备反馈的信息进行相应的处理,才能接入互联网。本篇博客我们一起来试一试怎么使用tcp连接与服务器交换信息。 关于tcp通讯的详解[点击这里](https://blog.csdn.net/sinat_36629696/article/details/80740678),这位大神总结的很好,有兴趣的可以去看看底层原理。原理很复杂我这里也没什么好讲的,我只负责教会大家怎么用。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200526200411431.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDU3MDA4Mw==,size_16,color_FFFFFF,t_70) 要记住一点就是tcp通讯是需要建立长连接的,有服务端和客户端的区别。服务端不会主动发起连接请求,只有客户端才会主动向服务器发起连接请求,建立连接后双方均可以主动发送数据。(`注意:这里的服务端是指软件的状态,并不是指远程的服务器`)。 由于我们使用的是4g模块,联网时基站给我们分配的ip不是公网ip。我又没有能力实现在模块上跑一套内网穿透的程序,所以就没有办法测试将模块作为tcp服务端使用的程序,关于tcp的例程只有这一个`TcpClient`,这是一个很大的遗憾。如果用的是wifi模块那就可以在局域网内测试TcpServer。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200526200256514.png) lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。现在嵌入式设备联网所使用的socket库一般都是lwip库,所以不用担心在我这里学会了socket怎么用,去其他地方就不会用了,放心大胆的学,在哪都是一样的。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200526200214752.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDU3MDA4Mw==,size_16,color_FFFFFF,t_70) # 二、编写测试程序 ## 2.1、了解本例程所用到的函数 使用tcp服务需要包含`#include "iot_socket.h""`头文件,我们这里只用到了8个函数,分别是: >/**设置网络状态回调函数 *@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) > /**建立和服务器端的连接 *@param socketfd: 调用socket接口返回的socket描述符 @param addr: 指定服务器地址和端口 @param addrlen: sizeof(struct openat_sockaddr) *@return 0: 表示成功 * <0 表示有错误 **/ * int `connect`(int socketfd, const struct openat_sockaddr *addr, openat_socklen_t addrlen) > /**关闭socket *@param fd: 调用socket接口返回的socket描述符 *@return 0: 表示成功 * <0 表示有错误 **/ * int `close` (int fd) > /**发送数据 *@param socketfd: 调用socket接口返回的socket描述符 @param msg: 数据内容 @param len: 数据长度 @param flags: 仅支持MSG_DONTWAIT/MSG_OOB,可以通过或来指定多个标志,一般为0 *@return >=0: 实际发送的长度 * <0 发送错误 **/ * int `send`(int socketfd,const void *msg,size_t len,int flags) > /**接收数据 *@param socketfd: 调用socket接口返回的socket描述符 @param buf: 用于存放数据的缓存 @param len: buf的长度 @param flags: 仅支持MSG_DONTWAIT/MSG_PEEK/MSG_OOB,可以通过或来指定多个标志,一般为0 *@return >0: 接收到的数据长度 * =0: 对方已经断开连接 * <0: 读取错误 *@note 当flags没有设置MSG_DONTWAIT,该函数会阻塞,直到有数据或者读取超时 **/ * int `recv`(int socketfd,void *buf,size_t len,int flags) * ## 2.2、编写主程序 主程序负责注册网络回调函数,以及创建一个消息处理函数。 ```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、编写网络回调函数 消息回调函数负责通知系统消息,最好不要在其中处理复杂的动作。我这里将系统消息转存到全局变量中,然后再任务中定时查询并处理其他的逻辑。 ```c static void NetWorkCb(E_OPENAT_NETWORK_STATE state) { NetWorkCbMessage = state; } ``` ## 2.4、编写消息处理任务 在消息处理函数中,定时查询全局变量转存的网络状态,进行相应的处理。网络正常后调用`TcpConnect`建立套接字,同时连接远程服务端。执行完成后进行任务自毁。 ```c 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) { TcpConnect(); } iot_os_delete_task(TestTask_HANDLE); } ``` 最开始我是用任务通知做的,这样消息的实时性更高,不需要自己主动去查询消息。我也不知道为什么,如果用消息通知的话,加上这一行`iot_network_connect(&networkparam);`设备就重启。我以人格担保任务接收到的消息指针是对的,我还打印出来看过了,switch语句也进入了对应的分支。我就搞不懂为什么进行链路激活会出错。最后实在找不到原因,用全局变量转存,主动查询状态,避开了这个问题。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200526202250497.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDU3MDA4Mw==,size_16,color_FFFFFF,t_70) ## 2.5、编写TcpConnect 这是一个子程序,网络连接正常后在消息处理函数中被调用,只会执行一次。首先创建一个套接字,判断创建是否正常。然后初始化网络连接结构体,要注意的是需要将字符串格式的ip转化为网络序列的ip和主机字节port转化为网络字节顺序的端口,主要是因为`大小端存储模式`的字节顺序不一样。 大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。 小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。 ```c //创建套接字 socketfd = socket(OPENAT_AF_INET, OPENAT_SOCK_STREAM, 0); while (socketfd < 0) { iot_debug_print("[socket] create tcp socket error"); iot_os_sleep(3000); } // 建立TCP链接 struct openat_sockaddr_in tcp_server_addr = {0}; //AF_INET 的目的就是使用 IPv4 进行通信 tcp_server_addr.sin_family = OPENAT_AF_INET; //远端端口,主机字节顺序转变成网络字节顺序 tcp_server_addr.sin_port = htons((unsigned short)TCP_SERVER_PORT); //字符串远端ip转化为网络序列ip inet_aton(TCP_SERVER_IP, &tcp_server_addr.sin_addr); iot_debug_print("[socket] tcp connect to addr %s", TCP_SERVER_IP); int connErr = connect(socketfd, (const struct openat_sockaddr *)&tcp_server_addr, sizeof(struct openat_sockaddr)); if (connErr < 0) { iot_debug_print("[socket] tcp connect error %d", socket_errno(socketfd)); close(socketfd); } iot_debug_print("[socket] tcp 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、编写发送任务 在发送任务中,定时对建立的套接字做循环发送字符串的动作,并进行相应的次数标记。 ```c 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) { // TCP 发送数据 len = send(socketfd, data, len + 1, 0); if (len < 0) { iot_debug_print("[socket] tcp send data False"); } else { iot_debug_print("[socket] tcp send data Len = %d", len); num += 1; } } } iot_os_sleep(3000); } ``` ## 2.7、编写接收任务 在接收任务中,将接收的数据打印在日志中显示,recv函数会陷入阻塞状态,直到接收到数据。程序中提供的测试服务端自带回环功能,会将接收的的数据原封不动返回。所以我们接收到的数据就是自己发送的数据。 ```c int len = 0; unsigned char data[512] = {0}; while (1) { if (socketfd >= 0) { // TCP 接受数据 len = recv(socketfd, data, sizeof(data), 0); if (len < 0) { iot_debug_print("[socket] tcp send data False"); } else { iot_debug_print("[socket] tcp Recv data result = %s", data); } } } ``` # 三、编译并下载程序 完整代码在这,自取。 ```c /* * @Author: your name * @Date: 2020-05-19 14:05:32 * @LastEditTime: 2020-05-26 19:30:56 * @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" //Tcp Client Demo #define TCP_SERVER_IP "121.40.198.143" #define TCP_SERVER_PORT 12415 HANDLE TestTask_HANDLE = NULL; uint8 NetWorkCbMessage = 0; int socketfd = -1; static void SentTask(void *param) { 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) { // TCP 发送数据 len = send(socketfd, data, len + 1, 0); if (len < 0) { iot_debug_print("[socket] tcp send data False"); } else { iot_debug_print("[socket] tcp send data Len = %d", len); num += 1; } } } iot_os_sleep(3000); } } static void RecvTask(void *param) { int len = 0; unsigned char data[512] = {0}; while (1) { if (socketfd >= 0) { // TCP 接受数据 len = recv(socketfd, data, sizeof(data), 0); if (len < 0) { iot_debug_print("[socket] tcp send data False"); } else { iot_debug_print("[socket] tcp Recv data result = %s", data); } } } } static void TcpConnect() { //创建套接字 socketfd = socket(OPENAT_AF_INET, OPENAT_SOCK_STREAM, 0); while (socketfd < 0) { iot_debug_print("[socket] create tcp socket error"); iot_os_sleep(3000); } // 建立TCP链接 struct openat_sockaddr_in tcp_server_addr = {0}; //AF_INET 的目的就是使用 IPv4 进行通信 tcp_server_addr.sin_family = OPENAT_AF_INET; //远端端口,主机字节顺序转变成网络字节顺序 tcp_server_addr.sin_port = htons((unsigned short)TCP_SERVER_PORT); //字符串远端ip转化为网络序列ip inet_aton(TCP_SERVER_IP, &tcp_server_addr.sin_addr); iot_debug_print("[socket] tcp connect to addr %s", TCP_SERVER_IP); int connErr = connect(socketfd, (const struct openat_sockaddr *)&tcp_server_addr, sizeof(struct openat_sockaddr)); if (connErr < 0) { iot_debug_print("[socket] tcp connect error %d", socket_errno(socketfd)); close(socketfd); } iot_debug_print("[socket] tcp 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) { TcpConnect(); } 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"); } ``` 查看输出,发现接收到的数据和发送的数据一致,没有任何问题。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200526204603206.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDU3MDA4Mw==,size_16,color_FFFFFF,t_70) > 不会下载的[点击这里](https://blog.csdn.net/weixin_44570083/article/details/104285283),进去查看我的`RDA8910 CSDK二次开发入门教程`专题第一篇博文`1、RDA8910CSDK二次开发:环境搭建`里面讲了怎么下载 > 这里只是我的学习笔记,拿出来给大家分享,欢迎大家批评指正,本篇教程到此结束
发表于 2020-05-26 20:56
阅读 ( 1585 )
0 推荐
收藏
你可能感兴趣的文章
13、RDA8910CSDK二次开发:将你的代码藏起来----编译静态库
5394 浏览
1.1、RDA8910CSDK二次开发:CSDK极致开发体验
7721 浏览
12、RDA8910CSDK二次开发:c语言中最好用的JSON解析库---cJSON
6023 浏览
11、RDA8910CSDK二次开发:新鲜出炉的MQTT库
7858 浏览
10、RDA8910CSDK二次开发:简易的http通讯库
1808 浏览
9、RDA8910CSDK二次开发:趁热打铁干脆顺带把UDP通讯也给撸了吧
1848 浏览
相关问题
2G模块连接阿里云每隔3s上发一次数据,但是会报错出现连接重置,日志打出来是ssl连接被关闭,请问这是信号差的原因还是什么其他的原因?
0 回答
http通信有一定几率上传失败
0 回答
air720 建立TCP连接的一半步骤 AT指令模式
1 回答
0 条评论
请先
登录
后评论
陈夏
26 篇文章
作家榜
»
技术销售Delectate
43 文章
陈夏
26 文章
国梁
24 文章
miuser
21 文章
晨旭
20 文章
朱天华
19 文章
金艺
19 文章
杨奉武
18 文章
×
发送私信
发给:
内容:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!