8、RDA8910CSDK二次开发:以tcp客户端的身份去勾搭服务端

目录

点击这里查看所有博文

  本系列博客所述资料均来自合宙官方,并不是本人原创(只有博客是自己写的),csdk只是得到了口头的允许公开授权。出于热心,本人将自己的所学笔记整理并推出相对应的使用教程,方面其他人学习。为国内的物联网事业发展尽自己的一份绵薄之力,没有为自己谋取私利的想法。若出现侵权现象,请告知本人,本人会立即停止更新,并删除相应的文章和代码。

  本系列博客基于紫光展锐的RDA8910 LTE Cat 1 bis芯片平台开发。理论上适用于合宙的Air720U、Air724U、广和通L610以及安信可的cat-01模块。

  各个厂家的部分配置文件可能不一样,也许会导致设备出现奇怪的问题,其他的模块我也不确定能不能用,自行测试。但是有一点编译下载和监视流程基本一样,可供参考。

  先不管支不支持,如果你用的模块是是紫光展锐的RDA8910,那都不妨一试,也许会有意外收获(也有可能变砖,慎重!!!)。

  我使用的是Air724UG开发板,如果在其他模块上不能用,那也不要强怼,也许是开发包不兼容吧。这里的代码是没有问题的。例程仅供参考!

一、前言

  上篇博客我们简单的使用了下互联网的功能,主要是为了带大家熟悉一下联网流程,必须要根据设备反馈的信息进行相应的处理,才能接入互联网。本篇博客我们一起来试一试怎么使用tcp连接与服务器交换信息。

  关于tcp通讯的详解点击这里,这位大神总结的很好,有兴趣的可以去看看底层原理。原理很复杂我这里也没什么好讲的,我只负责教会大家怎么用。

在这里插入图片描述

  要记住一点就是tcp通讯是需要建立长连接的,有服务端和客户端的区别。服务端不会主动发起连接请求,只有客户端才会主动向服务器发起连接请求,建立连接后双方均可以主动发送数据。(注意:这里的服务端是指软件的状态,并不是指远程的服务器)。

  由于我们使用的是4g模块,联网时基站给我们分配的ip不是公网ip。我又没有能力实现在模块上跑一套内网穿透的程序,所以就没有办法测试将模块作为tcp服务端使用的程序,关于tcp的例程只有这一个TcpClient,这是一个很大的遗憾。如果用的是wifi模块那就可以在局域网内测试TcpServer。
在这里插入图片描述

  lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。现在嵌入式设备联网所使用的socket库一般都是lwip库,所以不用担心在我这里学会了socket怎么用,去其他地方就不会用了,放心大胆的学,在哪都是一样的。
在这里插入图片描述

二、编写测试程序

2.1、了解本例程所用到的函数

  使用tcp服务需要包含#include "iot_socket.h""头文件,我们这里只用到了8个函数,分别是:

/*设置网络状态回调函数
*@param indCb: 回调函数
*@return TRUE: 成功
*    FALSE: 失败
*
/

/**本地字节顺序转化为网络字节顺序(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 表示有错误
*
/

/*关闭socket
*@param fd: 调用socket接口返回的socket描述符
*@return 0: 表示成功
*    <0 表示有错误
*
/

/*发送数据
*@param socketfd: 调用socket接口返回的socket描述符
@param msg: 数据内容
@param len: 数据长度
@param flags: 仅支持MSG_DONTWAIT/MSG_OOB,可以通过或来指定多个标志,一般为0
*@return >=0: 实际发送的长度
*    <0 发送错误
*
/

2.2、编写主程序

  主程序负责注册网络回调函数,以及创建一个消息处理函数。

1
2
3
4
5
6
7
8
//系统休眠
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、编写网络回调函数

  消息回调函数负责通知系统消息,最好不要在其中处理复杂的动作。我这里将系统消息转存到全局变量中,然后再任务中定时查询并处理其他的逻辑。

1
2
3
4
static void NetWorkCb(E_OPENAT_NETWORK_STATE state)
{
NetWorkCbMessage = state;
}

2.4、编写消息处理任务

  在消息处理函数中,定时查询全局变量转存的网络状态,进行相应的处理。网络正常后调用TcpConnect建立套接字,同时连接远程服务端。执行完成后进行任务自毁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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语句也进入了对应的分支。我就搞不懂为什么进行链路激活会出错。最后实在找不到原因,用全局变量转存,主动查询状态,避开了这个问题。
在这里插入图片描述

2.5、编写TcpConnect

  这是一个子程序,网络连接正常后在消息处理函数中被调用,只会执行一次。首先创建一个套接字,判断创建是否正常。然后初始化网络连接结构体,要注意的是需要将字符串格式的ip转化为网络序列的ip和主机字节port转化为网络字节顺序的端口,主要是因为大小端存储模式的字节顺序不一样。

  大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。

  小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//创建套接字
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、编写发送任务

  在发送任务中,定时对建立的套接字做循环发送字符串的动作,并进行相应的次数标记。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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函数会陷入阻塞状态,直到接收到数据。程序中提供的测试服务端自带回环功能,会将接收的的数据原封不动返回。所以我们接收到的数据就是自己发送的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
}
}
}

三、编译并下载程序

  完整代码在这,自取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/*
* @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");
}



  查看输出,发现接收到的数据和发送的数据一致,没有任何问题。
在这里插入图片描述

不会下载的点击这里,进去查看我的RDA8910 CSDK二次开发入门教程专题第一篇博文1、RDA8910CSDK二次开发:环境搭建里面讲了怎么下载
这里只是我的学习笔记,拿出来给大家分享,欢迎大家批评指正,本篇教程到此结束

上次更新 2021-01-28