开发板与pc通信有很多形式,之前已经介绍过通过tcp通信,不过有些场合使用udp会更合适,因为udp没有了tcp的握手与连接步骤,传输效率会高的多。例如通过wifi传输开发板采集到传感器数据在PC端显示,这些消息是不断被覆盖的,使用udp就高效的多。
一、在使用小凌派开发板wifi进行udp通信的步骤
前面的步骤基本与之前发的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_udp 例程中服务地址及端口号。
#define OC_SERVER_IP “192.168.2.49” //需要连接服务端的ip地址。
#define SERVER_PORT 6666。
这个ip地址即PC的ip地址,修改后重新编译烧录程序。
5、pc上打开两个网络调试工具,一个用于连接小凌派udp客户端,一个用于连接小凌派udp服务端,并设置ip地址和端口号。
ip地址都填本机ip地址,即前一步查询到的IP地址如上图所示。区别在于端口号,用于连接小凌派udp客户端的端口号需要与前一步配置的(SERVER_PORT 6666)一致。
用于连接小凌派udp服务端的端口可以随意填写,不过要注意不要与常见的端口号冲突,如果有冲突就改成其他的。
- ip地址:192.168.2.49
- 用于连接小凌派udp客户端的端口号:6666
- 用于连接小凌派udp服务端的端口号:8888
6、在pc网络调试助手点击启动。
7、查看log等待小凌派的udp客户端和服务端任务启动。
可以看到小凌派udp客户端的ip地址192.168.2.48和端口号65460,因为本次实验客户端没有指定本地端口号这个端口号是自动生成的每次可能都不一样。还有一个远端端口号6666,这个远端端口号就是我们网络调试助手已配置的端口号。这时pc想与小凌派udp客户端通信的关键三个信息都确定了。
小凌派udp服务端的ip地址192.168.2.48和端口号6666,这个类似tcp的服务端,监听6666端口的数据。
8、这时用于连接小凌派udp客户端网络调试工具就已经收到开发板发送的数据如下图。
9、用网络调试工具往小凌派udp客户端发消息如下图,可以看到开发板已经收到数据。
需要注意的是网络调试工具发送消息的远程主机需与开发板一致,本地主机端口号与开发板的远端端口一致,否则开发板无法收到消息。如下图
10、往小凌派的udp服务端发送消息先填写小凌派开发板的ip与端口号如下图。
再点发送消息如下图:
从上图也可以看出小凌派udp 服务端接收到了网络调试工具的消息并且打印了消息来源的ip地址和端口号,可以看出与我们网络调试工具设置一致。
11、小凌派udp服务端监听的端口号是固定的,远程端口号并没限制,通过修改网络调试工具的端口号再与小凌派udp服务端通信。如下图把端口号改成9999再发送消息可以看出小凌派udp服务端接收打印的端口也随之改变。
12、发送字符集修改,细心的小伙伴应该早就发现小凌派开发板数据接收显示有些异常,主要原因是发送的字符集没有改成utf-8造成的。在发送窗口右击,字符集编码选择utf-8编码。然后再发送数据。
二、在使用小凌派开发板wifi-udp与虚拟机APP通信的步骤
这部分修改都是虚拟机app部分代码没特别说明以下修改都指修改虚拟机里的app文件。
这部分具体代码添加在后面。本人这里使用的虚拟机为deepin社区版20.5,gcc版本为8.3.0。
1、查看虚拟机ip是否与小凌派在同一网段,如下图ip为192.168.2.156 与小凌派在同一网段。
2、修改 udp_cilent.c中的ip与端口号。
#define SERVER_IP “192.168.2.48” //小凌派开发板的ip。
#define SERVER_PORT 6666 //小凌派开发板udp服务端绑定的本地端口号。
3、打开终端后进入 udp_cilent.c文件夹如下图 我源文件放在主目录下的work文件内。并输入gcc进行编译。
4、查看编译文件ls -l udp_cilent*。
可以看到虚拟机里已生成了udp客户端app了。
5、因为前面在测试与网络调试助手通信的时候小凌派开发板udp服务端已启动了,所以这里直接在虚拟机终端里运行udp客户端app。
如下图,左边为虚拟机udp客户端log,右边为小凌派log,可以看出虚拟机里的app 已经与小凌派正常通信了。
从上图可以看出小凌派udp服务端接收到的消息ip与虚拟机的ip一致。
虚拟机udp服务端app与客户端类似,这里就不详细说明,就强调一下不同的地方。
udp客户端的端口号是连接时产生的所以需要客户端先往服务端发送消息后,服务端解析出客户端的端口号后才能与之通信。
小凌派udp客户端发送消息通过send()函数需要先设置远端ip和端口号。虚拟机udp服务端想与小凌派开发板udp客户端通信需要先修改小凌派里服务ip和端口,修改后重新编译烧录。如果想改成根据接收到不同ip的服务端消息,发送对应的响应消息。就需要把小凌派udp客户端远程ip改成htonl(INADDR_ANY),消息处理流程是先调用recvfrom()再调用sendto()。而虚拟机的服务端在bind()之后需要调用connect()设置目标ip地址和端口号。再向目标发送消息。
#define OC_SERVER_IP “192.168.2.156” //服务ip地址这里需要填虚拟机的ip
#define SERVER_PORT 6666
虚拟机udp服务端先启动,再复位开发板。等待通信log 如下图:
三、接下来分析一下代码的工作流程。
1、小凌派udp部分代码。
首先包含必要的头文件。
#include "ohos_init.h"
#include "cmsis_os2.h"
#include "los_task.h"
#include "lz_hardware.h"
#include "config_network.h"
#include "lwip/udp.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 "udp"
#define OC_SERVER_IP "192.168.2.156"
#define SERVER_PORT 6666
#define BUFF_LEN 256
WifiLinkedInfo wifiinfo; //用于保存开发板本地ip
这部分是获取wifi连接信息,通过查询wifi连接信息确认wifi是否连接成功。只有wifi连接成功了才能进行udp通信
int udp_get_wifi_info(WifiLinkedInfo *info)
{
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;
}
这部分是udp服务端接收消息处理。
先进入recvfrom()会处于阻塞状态没有数据时一直阻塞。
接收到pc客户端的消息后通过sendto()发响应消息给PC客户端。
这里需要注意的是sendto()里的客户端ip和端口信息来自于recvfrom()。
void udp_server_msg_handle(int fd)
{
char buf[BUFF_LEN]; //接收缓冲区
socklen_t len;
int cnt = 0, count;
struct sockaddr_in client_addr = {0};
while (1)
{
memset(buf, 0, BUFF_LEN);
len = sizeof(client_addr);
printf("-------------------------------------------------------\n");
printf("[udp server] waitting client msg\n");
count = recvfrom(fd, buf, BUFF_LEN, 0, (struct sockaddr*)&client_addr, &len); //recvfrom是阻塞函数,没有数据就一直阻塞
if (count == -1)
{
printf("[udp server] recieve data fail!\n");
LOS_Msleep(3000);
break;
}
printf("[udp server] remote addr:%s port:%u\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
printf("[udp 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("[udp server] send msg:%s\n", buf);
sendto(fd, buf, strlen(buf), 0, (struct sockaddr*)&client_addr, len); //发送信息给client
}
lwip_close(fd);
}
这部分是udp服务端任务代码。
服务端处理流程。
socket–>bind—>recvfrom–>sendto–>lwip_close。
先通过socket()接口打开一个服务端socket文件。
然后设置需要绑定的服务端ip地址及端口号。
最后等待接收消息数据并发送响应消息。
int wifi_udp_server(void* arg)
{
int server_fd, ret;
while(1)
{
server_fd = socket(AF_INET, SOCK_DGRAM, 0); //AF_INET:IPV4;SOCK_DGRAM:UDP
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("[CommInitUdpServer]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 = wifiinfo.ipAddress;
serv_addr.sin_port = htons(SERVER_PORT); //端口号,需要网络序转换
/* 绑定服务器地址结构 */
ret = bind(server_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if (ret < 0)
{
printf("socket bind fail!\n");
lwip_close(server_fd);
return -1;
}
printf("[udp server] local addr:%s,port:%u\n", inet_ntoa(wifiinfo.ipAddress), ntohs(serv_addr.sin_port));
udp_server_msg_handle(server_fd); //处理接收到的数据
LOS_Msleep(1000);
}
}
这部分是udp客户端的接收消息处理函数。
先连接pc机的服务端,这里连接只是获取socket信息,然后解析出本地端口号。
接着发消息给服务端,这里不管连接与否。
发完消息进入阻塞接收消息。
当接收到pc的消息后进入循环发送状态。
void udp_client_msg_handle(int fd, struct sockaddr* dst)
{
socklen_t len = sizeof(*dst);
struct sockaddr_in client_addr;
int cnt = 0,count = 0;
connect(fd, dst, len);
getsockname(fd, (struct sockaddr*)&client_addr,&len);
printf("[udp client] local addr:%s port:%u,remote addr:%s remote port:%u\n", inet_ntoa(wifiinfo.ipAddress), ntohs(client_addr.sin_port), OC_SERVER_IP, SERVER_PORT);
while (1)
{
char buf[BUFF_LEN];
sprintf(buf, "UDP TEST cilent send:%d", ++cnt);
count = send(fd, buf, strlen(buf), 0); //发送数据给server
printf("------------------------------------------------------------\n");
printf("[udp client] send:%s\n", buf);
printf("[udp client] client sendto msg to server %dbyte,waitting server respond msg!!!\n", count);
memset(buf, 0, BUFF_LEN);
// count = recv(fd, buf, BUFF_LEN, 0); //接收来自server的信息
count = recvfrom(fd, buf, BUFF_LEN, 0, (struct sockaddr*)&client_addr, &len); //recvfrom是阻塞函数,没有数据就一直阻塞
if(count == -1)
{
printf("[udp client]No server message!!!\n");
}
else
{
printf("[udp client] remote addr:%s remote port:%u\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
printf("[udp client] rev:%s\n", buf);
}
LOS_Msleep(100);
}
lwip_close(fd);
这部分代码是udp客户端代码。
客户端处理流程。
socket–>connect–>send–>recvfrom–>lwip_close。
先通过socket()接口创建客户端的socket文件。
然后设置客户端连接PC服务端的ip地址及端口号。
再进行connect连接。
int wifi_udp_client(void* arg)
{
int client_fd, ret;
struct sockaddr_in serv_addr;
while(1)
{
client_fd = socket(AF_INET, SOCK_DGRAM, 0);//AF_INET:IPV4;SOCK_DGRAM:UDP
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("[CommInitUdpServer]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 = htonl(INADDR_ANY); //IP地址,需要进行网络序转换,INADDR_ANY:本地地址
serv_addr.sin_addr.s_addr = inet_addr(OC_SERVER_IP);
serv_addr.sin_port = htons(SERVER_PORT);
udp_client_msg_handle(client_fd, (struct sockaddr*)&serv_addr);
LOS_Msleep(1000);
}
return 0;
}
这部分是udp创建客户端和服务端任务。
可以看到在创建客户端和服务端任务前先阻塞判断wifi的连接状态。
只有wifi连接成功后才创建客户端和服务端任务。
void wifi_udp_process(void *args)
{
unsigned int threadID_client, threadID_server;
unsigned int ret = LOS_OK;
WifiLinkedInfo info;
while(udp_get_wifi_info(&info) != 0) ;
wifiinfo = info;
LOS_Msleep(1000);
CreateThread(&threadID_client, wifi_udp_client, NULL, "udp client@ process");
CreateThread(&threadID_server, wifi_udp_server, NULL, "udp server@ process");
}
这部分是创建wifi udp 通信任务主要是为了使用APP_FEATURE_INIT(wifi_udp_example)。
这样当OpenHarmony初始化完成后会自动执行此任务。
void wifi_udp_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_udp_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_udp_example);
2、虚拟机udp app代码。
这部分代码与开发板的类似就不详细说明了。
(1)udp_client.c。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#define SERVER_IP "192.168.2.48"
#define SERVER_PORT 6666
#define BUFF_LEN 256
void udp_msg_sender(int fd, struct sockaddr* dst)
{
socklen_t len = sizeof(*dst);
struct sockaddr_in src;
int cnt = 0;
connect(fd, dst, len);
while (1)
{
char buf[BUFF_LEN];
sprintf(buf, "UDP 测试 cilent send:%d", ++cnt);
write(fd, buf, strlen(buf));
printf("------------------------------------------------------------\n");
printf("client:%s\n", buf);
printf("client sendto msg to server ,waitting server respond msg!!!\n");
memset(buf, 0, BUFF_LEN);
if(read(fd, buf, BUFF_LEN) < 0)
{
printf("No server message!!!\n");
}
else
{
printf("server:%s\n", buf);
}
sleep(1);
}
}
/***************************************************************
* 功能: UDP client
* 说 明: socket-->write-->read-->close
***************************************************************/
int main(int argc, char* argv[])
{
int client_fd;
struct sockaddr_in ser_addr;
client_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (client_fd < 0)
{
printf("create socket fail!\n");
return -1;
}
memset(&ser_addr, 0, sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
ser_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
//ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
ser_addr.sin_port = htons(SERVER_PORT);
udp_msg_sender(client_fd, (struct sockaddr*)&ser_addr);
close(client_fd);
return 0;
}
(2) udp_server.c。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#define SERVER_PORT 6666
#define BUFF_LEN 512
void handle_udp_msg(int fd)
{
char buf[BUFF_LEN]; //接收缓冲区
socklen_t len;
int cnt = 0, count;
struct sockaddr_in clent_addr;
while (1)
{
memset(buf, 0, BUFF_LEN);
len = sizeof(clent_addr);
printf("-------------------------------------------------------\n");
printf("waitting client msg\n");
count = recvfrom(fd, buf, BUFF_LEN, 0, (struct sockaddr*)&clent_addr, &len); //recvfrom是阻塞函数,没有数据就一直阻塞
if (count == -1)
{
printf("recieve data fail!\n");
return;
}
sleep(2);
printf("revmsg:%s\n", buf);
memset(buf, 0, BUFF_LEN);
sprintf(buf, "I have recieved %d bytes data! recieved cnt:%d", count, ++cnt);
printf("sendmsg:%s\n", buf);
sendto(fd, buf, BUFF_LEN, 0, (struct sockaddr*)&clent_addr, len); //发送信息给client
}
}
/***************************************************************
* 功能: UDP server
* 说 明: socket-->bind-->recvfrom-->sendto-->close
***************************************************************/
int main(int argc, char* argv[])
{
int server_fd, ret;
struct sockaddr_in ser_addr;
server_fd = socket(AF_INET, SOCK_DGRAM, 0); //AF_INET:IPV4;SOCK_DGRAM:UDP
if (server_fd < 0)
{
printf("create socket fail!\n");
return -1;
}
memset(&ser_addr, 0, sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
ser_addr.sin_addr.s_addr = htonl(INADDR_ANY); //IP地址,需要进行网络序转换,INADDR_ANY:本地地址
ser_addr.sin_port = htons(SERVER_PORT); //端口号,需要网络序转换
ret = bind(server_fd, (struct sockaddr*)&ser_addr, sizeof(ser_addr));
if (ret < 0)
{
printf("socket bind fail!\n");
return -1;
}
handle_udp_msg(server_fd); //处理接收到的数据
close(server_fd);
return 0;
}