基于小凌派RK2206开发板wifi-tcp通信实验 原创
在开发过程中想要与开发板进行通信一般使用串口通信,当开发板使用串口与pc通信还需要专门的串口转换工具才行。而小凌派开发板自带wifi功能。因此与pc通信时可以使用wifi功能进行tcp通信这样就不需要专门的转换工具非常方便。
一、在使用小凌派开发板wifi进行tcp通信的步骤
1、要确定pc机所连接路由的wifi名称和密钥。通过修改代码使小凌派连接到与pc同一网络。
修改文件device/rockchip/rk2206/sdk_liteos/board/src/config_network.c 中的SSID 即wifi名称,和PASSWORD 即wifi密码。
#define SSID “凌智电子”
#define PASSWORD “********”
2、确认小凌派wifi功能是否开启
查看device/rockchip/rk2206/sdk_liteos/board/main.c 文件
是否调用ExternalTaskConfigNetwork();
3、确认小凌派开发板与开发板在同一网段。
在修改以上配置后先编译烧录程序然后查看log确认小凌派开发板获取到的ip地址。
在确认pc的ip地址,在控制台输入ipconfig
可以看到两个ip地址都是点2网段,说明已经在同一局域网。
4、 修改wifi_tcp 例程中服务地址及端口号
#define OC_SERVER_IP “192.168.2.49”
#define SERVER_PORT 6666
这个ip地址即PC的ip地址,修改后重新编译烧录程序。
5、 pc上打开两个网络调试工具,一个客户端和一个服务端,并设置ip地址和端口号
服务端 ip地址:0.0.0.0
端口号:6666
客户端ip地址:192.168.2.50 (之前查看到小凌派的ip地址)
端口号:6666
6 、查看log等待小凌派的tcp客户端和服务端任务启动
可以看到客户端连接地址192.168.2.49:6666 即pc的ip地址
服务端监听端口为6666
这表示小凌派tcp客户端和服务端任务都已经启动。
7、 在pc网络调试助手点击启动客户端和服务端
可以观察到网络调试助手服务端有设备连接成功并且接收到了调试数据。
网络调试助手的客户端也显示连接成功。
8、 使用网络调试助手发送数据
可以查看log发现小凌派开发板已经可以正常收发数据了。
这样就可以通过使用wifi与pc进行通信。
二、接下来分析一下代码的工作流程。
首先包含必要的头文件
#include "ohos_init.h"
#include "cmsis_os2.h"
#include "los_task.h"
#include "lz_hardware.h"
#include "config_network.h"
#include "lwip/tcp.h"
#include "lwip/ip_addr.h"
#include "lwip/priv/tcp_priv.h"
#include "lwip/stats.h"
#include "lwip/inet_chksum.h"
这些定义主要是 ip地址和端口号以及缓存大小
#define LOG_TAG "tcp"
#define OC_SERVER_IP "192.168.2.49"
#define SERVER_PORT 6666
#define BUFF_LEN 256
这部分是获取wifi连接信息,通过查询wifi连接信息确认wifi是否连接成功。只有wifi连接成功了才能进行tcp通信
{
int ret = -1;
int gw, netmask;
memset(info, 0, sizeof(WifiLinkedInfo));
unsigned int retry = 15;
while (retry) {
if (GetLinkedInfo(info) == WIFI_SUCCESS) {
if (info->connState == WIFI_CONNECTED) {
if (info->ipAddress != 0) {
LZ_HARDWARE_LOGD(LOG_TAG, "rknetwork IP (%s)", inet_ntoa(info->ipAddress));
if (WIFI_SUCCESS == GetLocalWifiGw(&gw)) {
LZ_HARDWARE_LOGD(LOG_TAG, "network GW (%s)", inet_ntoa(gw));
}
if (WIFI_SUCCESS == GetLocalWifiNetmask(&netmask)) {
LZ_HARDWARE_LOGD(LOG_TAG, "network NETMASK (%s)", inet_ntoa(netmask));
}
if (WIFI_SUCCESS == SetLocalWifiGw()) {
LZ_HARDWARE_LOGD(LOG_TAG, "set network GW");
}
if (WIFI_SUCCESS == GetLocalWifiGw(&gw)) {
LZ_HARDWARE_LOGD(LOG_TAG, "network GW (%s)", inet_ntoa(gw));
}
if (WIFI_SUCCESS == GetLocalWifiNetmask(&netmask)) {
LZ_HARDWARE_LOGD(LOG_TAG, "network NETMASK (%s)", inet_ntoa(netmask));
}
ret = 0;
goto connect_done;
}
}
}
LOS_Msleep(1000);
retry--;
}
connect_done:
return ret;
}
这部分是tcp服务端接收消息处理
先进入accept()会处于阻塞状态,即没有客户端连接时一直阻塞。
单客户端连接后又进入接收数据状态,此状态也是阻塞状态。
没有数据时一直阻塞,不过需要注意的是在此状态下当客户端断开连接时recv会返回-1
接收到pc客户端的消息后通过send()发响应消息给PC客户端。
void tcp_server_msg_handle(int fd)
{
char buf[BUFF_LEN]; //接收缓冲区
socklen_t client_addr_len;
int cnt = 0, count;
int client_fd;
struct sockaddr_in client_addr = {0};
printf("waitting for client connect...\n");
/* 监听socket 此处会阻塞 */
client_fd = accept(fd, (struct sockaddr*)&client_addr, &client_addr_len);
// client_fd = lwip_accept(fd, (struct sockaddr*)&client_addr, &client_addr_len);
printf("[tcp server] accept <%s:%d>\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
while (1)
{
memset(buf, 0, BUFF_LEN);
printf("-------------------------------------------------------\n");
printf("[tcp server] waitting client msg\n");
count = recv(client_fd, buf, BUFF_LEN, 0); //read是阻塞函数,没有数据就一直阻塞
// count = lwip_read(client_fd, buf, BUFF_LEN); //read是阻塞函数,没有数据就一直阻塞
if (count == -1)
{
printf("[tcp server] recieve data fail!\n");
LOS_Msleep(3000);
break;
}
printf("[tcp server] rev client msg:%s\n", buf);
memset(buf, 0, BUFF_LEN);
sprintf(buf, "I have recieved %d bytes data! recieved cnt:%d", count, ++cnt);
printf("[tcp server] send msg:%s\n", buf);
send(client_fd, buf, strlen(buf), 0); //发送信息给client
// lwip_write(client_fd, buf, strlen(buf)); //发送信息给client
}
lwip_close(client_fd);
lwip_close(fd);
}
这部分是tcp服务端任务代码
服务端处理流程
socket–>bind–>listen–>accept–>recv–>send–>lwip_close
先通过socket()接口打开一个服务端socket文件
然后设置需要绑定的服务端ip地址及端口号。
在进行监听,需要注意的是此处监听不会处于阻塞态。
int wifi_server(void* arg)
{
int server_fd, ret;
while(1)
{
server_fd = socket(AF_INET, SOCK_STREAM, 0); //AF_INET:IPV4;SOCK_STREAM:TCP
// server_fd = lwip_socket(AF_INET, SOCK_STREAM, 0); //AF_INET:IPV4;SOCK_STREAM:TCP
if (server_fd < 0)
{
printf("create socket fail!\n");
return -1;
}
/*设置调用close(socket)后,仍可继续重用该socket。调用close(socket)一般不会立即关闭socket,而经历TIME_WAIT的过程。*/
int flag = 1;
ret = setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(int));
if (ret != 0) {
printf("[CommInitTcpServer]setsockopt fail, ret[%d]!\n", ret);
}
struct sockaddr_in serv_addr = {0};
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //IP地址,需要进行网络序转换,INADDR_ANY:本地地址
// serv_addr.sin_addr.s_addr = inet_addr(OC_SERVER_IP); //IP地址,需要进行网络序转换,INADDR_ANY:本地地址
serv_addr.sin_port = htons(SERVER_PORT); //端口号,需要网络序转换
/* 绑定服务器地址结构 */
ret = bind(server_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
// ret = lwip_bind(server_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if (ret < 0)
{
printf("socket bind fail!\n");
lwip_close(server_fd);
return -1;
}
/* 监听socket 此处不阻塞 */
ret = listen(server_fd, 64);
// ret = lwip_listen(server_fd, 64);
if(ret != 0)
{
printf("socket listen fail!\n");
lwip_close(server_fd);
return -1;
}
printf("[tcp server] listen:%d<%s:%d>\n",server_fd, inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port));
tcp_server_msg_handle(server_fd); //处理接收到的数据
LOS_Msleep(1000);
}
}
这部分是tcp客户端的接收消息处理函数
先进行尝试连接pc机的服务端,如果失败则延迟5秒后重新连接直到连接成功。
连接成功后先发消息给PC的服务端,然后进入接收状态,此状态是阻塞态。
当接收到pc的消息后进入循环发送状态。
void tcp_client_msg_handle(int fd, struct sockaddr* dst)
{
socklen_t len = sizeof(*dst);
int cnt = 0, count = 0;
while (connect(fd, dst, len) < 0)
{
printf("connect server failed...%d\n", ++count);
lwip_close(fd);
LOS_Msleep(5000);
fd = socket(AF_INET, SOCK_STREAM, 0); //AF_INET:IPV4;SOCK_STREAM:TCP
}
while (1)
{
char buf[BUFF_LEN];
sprintf(buf, "TCP TEST cilent send:%d", ++cnt);
count = send(fd, buf, strlen(buf), 0); //发送数据给server
// count = lwip_write(fd, buf, strlen(buf)); //发送数据给server
printf("------------------------------------------------------------\n");
printf("[tcp client] send:%s\n", buf);
printf("[tcp client] client sendto msg to server %d,waitting server respond msg!!!\n", count);
memset(buf, 0, BUFF_LEN);
count = recv(fd, buf, BUFF_LEN, 0); //接收来自server的信息
// count = lwip_read(fd, buf, BUFF_LEN); //接收来自server的信息
if(count == -1)
{
printf("[tcp client] recieve data fail!\n");
LOS_Msleep(3000);
break;
}
printf("[tcp client] rev:%s\n", buf);
}
lwip_close(fd);
}
这部分代码是tcp客户端代码
客户端处理流程
socket–>connect–>send–>recv–>lwip_close
先通过socket()接口创建客户端的socket文件。
然后设置客户端连接PC服务端的ip地址及端口号。
在进行connect连接。
int wifi_client(void* arg)
{
int client_fd, ret;
struct sockaddr_in serv_addr;
while(1)
{
client_fd = socket(AF_INET, SOCK_STREAM, 0);//AF_INET:IPV4;SOCK_STREAM:TCP
if (client_fd < 0)
{
printf("create socket fail!\n");
return -1;
}
/*设置调用close(socket)后,仍可继续重用该socket。调用close(socket)一般不会立即关闭socket,而经历TIME_WAIT的过程。*/
int flag = 1;
ret = setsockopt(client_fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(int));
if (ret != 0) {
printf("[CommInitTcpServer]setsockopt fail, ret[%d]!\n", ret);
}
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(OC_SERVER_IP);
serv_addr.sin_port = htons(SERVER_PORT);
printf("[tcp client] connect:%d<%s:%d>\n",client_fd, inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port));
tcp_client_msg_handle(client_fd, (struct sockaddr*)&serv_addr);
LOS_Msleep(1000);
}
return 0;
}
这部分是tcp创建客户端和服务端任务
可以看到在创建客户端和服务端任务前先阻塞判断wifi的连接状态。
只有wifi连接成功后才创建客户端和服务端任务。
void wifi_process(void *args)
{
unsigned int threadID_client, threadID_server;
unsigned int ret = LOS_OK;
WifiLinkedInfo info;
while(get_wifi_info(&info) != 0) ;
CreateThread(&threadID_client, wifi_client, NULL, "client@ process");
CreateThread(&threadID_server, wifi_server, NULL, "server@ process");
}
这部分是tcp创建客户端和服务端任务
可以看到在创建客户端和服务端任务前先阻塞判断wifi的连接状态。
只有wifi连接成功后才创建客户端和服务端任务。
void wifi_process(void *args)
{
unsigned int threadID_client, threadID_server;
unsigned int ret = LOS_OK;
WifiLinkedInfo info;
while(get_wifi_info(&info) != 0) ;
CreateThread(&threadID_client, wifi_client, NULL, "client@ process");
CreateThread(&threadID_server, wifi_server, NULL, "server@ process");
}
这部分是创建wifi tcp 通信任务主要是为了使用APP_FEATURE_INIT(wifi_tcp_example);
这样当OpenHarmony初始化完成后会自动执行此任务。
void wifi_tcp_example(void)
{
unsigned int ret = LOS_OK;
unsigned int thread_id;
TSK_INIT_PARAM_S task = {0};
printf("%s start ....\n", __FUNCTION__);
task.pfnTaskEntry = (TSK_ENTRY_FUNC)wifi_process;
task.uwStackSize = 10240;
task.pcName = "wifi_process";
task.usTaskPrio = 24;
ret = LOS_TaskCreate(&thread_id, &task);
if (ret != LOS_OK)
{
printf("Falied to create wifi_process ret:0x%x\n", ret);
return;
}
}
APP_FEATURE_INIT(wifi_tcp_example);