開發闆與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代碼
這部分代碼與開發闆的類似就不詳細說明了。
2.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.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;
}