
??想了解更多關(guān)于開源的內(nèi)容,請訪問:???
??51CTO 開源基礎(chǔ)軟件社區(qū)??
??https://ost.51cto.com??
一、前言
學(xué)習(xí)OpenHarmony南向設(shè)備開發(fā)中的網(wǎng)絡(luò)通信,它可以將底層開發(fā)板獲得的數(shù)據(jù)傳輸?shù)缴蠈拥姆?wù)器,服務(wù)器亦可通過網(wǎng)絡(luò)通信控制底層開發(fā)板。
二、TCP簡介
傳輸控制協(xié)議(TCP,Transmission Control Protocol)是一種面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議,由IETF的RFC 79 定義。
TCP旨在適應(yīng)支持多網(wǎng)絡(luò)應(yīng)用的分層協(xié)議層次結(jié)構(gòu)。 連接到不同但互連的計算機通信網(wǎng)絡(luò)的主計算機中的成對進程之間依靠TCP提供可靠的通信服務(wù)。TCP假設(shè)它可以從較低級別的協(xié)議獲得簡單的,可能不可靠的數(shù)據(jù)報服務(wù)。 原則上,TCP應(yīng)該能夠在從硬線連接到分組交換或電路交換網(wǎng)絡(luò)的各種通信系統(tǒng)之上操作。
網(wǎng)絡(luò)編程開發(fā)繞不開socket(套接字)的使用,socket就是整合好TCP/IP協(xié)議的一個工具。讓我們無需過度關(guān)注于底層協(xié)議的實現(xiàn),直接用封裝好的socket就行了.
TCP服務(wù)器端與TCP客戶端進行通信的流程??

三、分析代碼
本次實驗使用的是OpenHarmony1.0.0的源碼:??源碼壓縮包地址??參考HiSpark WiFi-IoT 鴻蒙套件樣例開發(fā)–網(wǎng)絡(luò)編程(tcpclient)
1.導(dǎo)入樣例
將潤和提供的21_tcpclient開發(fā)樣例文件夾復(fù)制到源碼applications/sample/wifi-iot/app路徑下:

在app路徑下的BUILD.gn添加需要編譯的靜態(tài)庫名稱:tcpclient:net_demo。
import("http://build/lite/config/component/lite_component.gni")
lite_component("app") {
features = [
"startup",
"tcpclient:net_demo",
]
}
靜態(tài)庫名稱可在21_tcpclient文件夾下的BUILD.gn里查看。

踩坑:一開始直接寫靜態(tài)庫名net_demo是會報錯的!

報錯內(nèi)容??一般都是BUILD.gn文件出現(xiàn)問題:

2、分析代碼
- demo_entry_cmsis.c : 鴻蒙liteos-m程序入口,支持Hi3861。
- demo_entry_posix.c :鴻蒙liteos-a和Unix系統(tǒng)程序入口,Hi3516、Hi3518、PC。
- net_common.h :系統(tǒng)網(wǎng)絡(luò)接口頭文件。
- net_demo.h :demo腳手架頭文件。
- net_params.h :網(wǎng)絡(luò)參數(shù),包括WiFi熱點信息,服務(wù)器IP、端口信息。
- tcp_client_test.c :TCP客戶端。
- wifi_connecter.c :鴻蒙WiFi STA模式API的封裝實現(xiàn)文件,比鴻蒙原始接口更容易使用。
- wifi_connecter.h :鴻蒙WiFi STA模式API的封裝頭文件,比鴻蒙原始接口更容易使用。
事先在net_params.h文件里修改WiFi的配置。

程序入口:demo_entry_cmsis.c文件。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "iot_gpio.h"
#include "ohos_init.h"
#include "cmsis_os2.h"
#include "net_demo.h"
#include "net_params.h"
#include "wifi_connecter.h"
#define LED_TASK_GPIO 9
static void NetDemoTask(void *arg) //一開始線程入口函數(shù)
{
(void)arg;
WifiDeviceConfig config = {0}; //表示用于連接到指定 Wi-Fi 設(shè)備的 Wi-Fi 站配置。
IoTGpioInit(LED_TASK_GPIO); //初始化IO口,為后文點燈做準備
IoTGpioSetDir(LED_TASK_GPIO, IOT_GPIO_DIR_OUT); //設(shè)置GPIO為輸出模式
// 準備AP的配置參數(shù)
strcpy(config.ssid, PARAM_HOTSPOT_SSID); //從net_params.h拷貝WiFi的參數(shù)
strcpy(config.preSharedKey, PARAM_HOTSPOT_PSK);
config.securityType = PARAM_HOTSPOT_TYPE; //配置WiFi的安全模式
osDelay(10);
int netId = ConnectToHotspot(&config); //連接熱點
int timeout = 10;
while (timeout--) //等待10秒后開始執(zhí)行NetDemoTest
{
printf("After %d seconds, I will start %s test!\r\n", timeout, GetNetDemoName());
osDelay(100);
}
while (1)
{
NetDemoTest(PARAM_SERVER_PORT, PARAM_SERVER_ADDR); //開始TCP連接,輸入端口號,ip地址
}
printf("disconnect to AP ...\r\n");
// DisconnectWithHotspot(netId);
printf("disconnect to AP done!\r\n");
}
static void NetDemoEntry(void)
{
osThreadAttr_t attr;
attr.name = "NetDemoTask";
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = 10240;
attr.priority = osPriorityNormal;
if (osThreadNew(NetDemoTask, NULL, &attr) == NULL)
{
printf("[NetDemoEntry] Falied to create NetDemoTask!\n");
}
}
SYS_RUN(NetDemoEntry);
①成功連接wifi后,接下來就是創(chuàng)建socket套接字準備進行TCP連接。
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // AF_INET:IP 協(xié)議系列。SOCK_STREAM=1:TCP協(xié)議
跳轉(zhuǎn)到socket的定義。

- domain:協(xié)議族(family),常用的協(xié)議族有 AFL INET(ipv4 )、AF INET6、AF LOCAL(或稱AF UNIX, Unix成socket) AF ROUTE 等。協(xié)議族決定了 socket 的地址類型,在通信中必須采用對應(yīng)的地址。
- type:指定 Socket 類型。

- 流式 socket (SOCK STREAM)是一種面向連接的 Socket, 針對于面向連接的 TCP 服務(wù)應(yīng)用。數(shù)據(jù)報式 socket(SOCK DGRAM) 是一種無連接的 Socket,對應(yīng)于 無連接的 UDP 服務(wù)應(yīng)用。
- protocol: 表示傳輸協(xié)議,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分別表示 TCP 傳輸協(xié)議和 UDP 傳輸協(xié)議。protocol 的值設(shè)為 0,系統(tǒng)會自動推演出應(yīng)該使用什么協(xié)議。
②配置
struct sockaddr_in serverAddr = {0}; //描述互聯(lián)網(wǎng)套接字地址的結(jié)構(gòu)體
serverAddr.sin_family = AF_INET; // AF_INET表示IPv4協(xié)議
serverAddr.sin_port = htons(port); // 端口號,從主機字節(jié)序轉(zhuǎn)為網(wǎng)絡(luò)字節(jié)序
if (inet_pton(AF_INET, host, &serverAddr.sin_addr) <= 0)
{ // 將主機IP地址從“點分十進制”字符串 轉(zhuǎn)化為 標準格式(32位整數(shù))
printf("inet_pton failed!\r\n");
goto do_cleanup;
}
③與主機連接。
// 嘗試和目標主機建立連接,連接成功會返回0 ,失敗返回 -1
if (connect(sockfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0)
{
printf("connect failed!\r\n");
goto do_cleanup;
}
printf("connect to server %s success!\r\n", host);
④連接成功后,發(fā)送數(shù)據(jù)給目標主機測試是否發(fā)送成功。
// 建立連接成功之后,這個TCP socket描述符 —— sockfd 就具有了 “連接狀態(tài)”,發(fā)送、接收 對端都是 connect 參數(shù)指定的目標主機和端口
retval = send(sockfd, request, sizeof(request), 0); //發(fā)送request給目標主機,成功會返回字符串長度 ,失敗返回 -1
if (retval < 0)
{
printf("send request failed!\r\n");
goto do_cleanup;
}
printf("send request{%s} %ld to server done!\r\n", request, retval);
⑤接收服務(wù)器發(fā)送過來的數(shù)據(jù)。
retval = recv(sockfd, &response, sizeof(response), 0);//接收目標主機的消息存入response,成功會返回字符串長度 ,失敗返回 -1
if (retval <= 0) {
printf("send response from server failed or done, %ld!\r\n", retval);
goto do_cleanup;
}
response[retval] = '\0';
printf("recv response{%s} %ld from server done!\r\n", response, retval);
3、修改代碼,實現(xiàn)開關(guān)燈操作
①在入口demo_entry_cmsis.c 文件中初始化LED燈的io口。
代碼在上文已貼出
②tcp_client_test.c文件。
由上文分析原始的代碼可知:開發(fā)板(客戶端)與主機(服務(wù)器)完成一次消息交互后就會關(guān)閉socket套接字,再關(guān)閉WiFi。
所以可以把關(guān)閉套接字的函數(shù)(close(sockfd))注釋掉,再加個while死循環(huán)即可。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "net_demo.h"
#include "net_common.h"
#define LED_TASK_GPIO 9
static char request[] = "Hello";
static char response[128] = "";
void TcpClientTest(const char *host, unsigned short port)
{
ssize_t retval = 0;
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // AF_INET:IP 協(xié)議系列。SOCK_STREAM=1:TCP協(xié)議
struct sockaddr_in serverAddr = {0}; //描述互聯(lián)網(wǎng)套接字地址的結(jié)構(gòu)體
serverAddr.sin_family = AF_INET; // AF_INET表示IPv4協(xié)議
serverAddr.sin_port = htons(port); // 端口號,從主機字節(jié)序轉(zhuǎn)為網(wǎng)絡(luò)字節(jié)序
if (inet_pton(AF_INET, host, &serverAddr.sin_addr) <= 0)
{ // 將主機IP地址從“點分十進制”字符串 轉(zhuǎn)化為 標準格式(32位整數(shù))
printf("inet_pton failed!\r\n");
goto do_cleanup;
}
// 嘗試和目標主機建立連接,連接成功會返回0 ,失敗返回 -1
if (connect(sockfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0)
{
printf("connect failed!\r\n");
goto do_cleanup;
}
printf("connect to server %s success!\r\n", host);
// 建立連接成功之后,這個TCP socket描述符 —— sockfd 就具有了 “連接狀態(tài)”,發(fā)送、接收 對端都是 connect 參數(shù)指定的目標主機和端口
retval = send(sockfd, request, sizeof(request), 0); //發(fā)送request給目標主機,成功會返回字符串長度 ,失敗返回 -1
if (retval < 0)
{
printf("send request failed!\r\n");
goto do_cleanup;
}
printf("send request{%s} %ld to server done!\r\n", request, retval);
while (1)
{
retval = recv(sockfd, &response, sizeof(response), 0); //接收目標主機的消息存入response,成功會返回字符串長度 ,失敗返回 -1
if (retval <= 0)
{
printf("send response from server failed or done, %ld!\r\n", retval);
goto do_cleanup;
}
response[retval] = '\0';
printf("recv response{%s} %ld from server done!\r\n", response, retval);
if (response[0] == 'o' && response[1] == 'n')
{
IoTGpioSetOutputVal(LED_TASK_GPIO, 0); //開燈
printf("The led is on\n");
}
if (response[0] == 'o' && response[1] == 'f' && response[2] == 'f')
{
IoTGpioSetOutputVal(LED_TASK_GPIO, 1); //關(guān)燈
printf("The led is off\n");
}
}
do_cleanup:
printf("do_cleanup...\r\n");
// close(sockfd);//關(guān)閉套接字
}
CLIENT_TEST_DEMO(TcpClientTest);
四、測試
1.安裝netcat(一個非常強大的網(wǎng)絡(luò)實用工具,可以用它來調(diào)試TCP/UDP應(yīng)用程序)
二選一:
- Linux上:sudo apt-get install netcat。
- Windows上:Windows版netcat。

將解壓出來的文件全部復(fù)制到C:\Windows\System32的文件夾下。

Windows+R cmd 打開命令行。輸入nc 命令即可。

2.開始測試
先是PC機開啟TCP服務(wù)端監(jiān)聽(我選擇的是Windows啟動netcat)。

-l: 開始監(jiān)聽。
-p:指定端口 (端口號必須保持一致,可在net_params.h文件配置)。

開發(fā)板燒錄新的固件后rest啟動后可觀察到服務(wù)端接收到了客戶端傳輸過來的數(shù)據(jù)"hello"。
開發(fā)板??一開始燈是亮的狀態(tài)。


PC服務(wù)端。

服務(wù)端輸入"off",可讓開發(fā)板關(guān)燈,完成交互。


繼續(xù)開燈。

五、總結(jié)
這次實踐中還有一些地方不能完全理解,在net_demo.h文件中。

為什么有這么多斜杠?
testFun是什么?它又是怎樣跳轉(zhuǎn)到tcp_client_test.c文件執(zhí)行TcpClientTest()函數(shù)的呢?
??想了解更多關(guān)于開源的內(nèi)容,請訪問:???
??51CTO 開源基礎(chǔ)軟件社區(qū)??
??https://ost.51cto.com??。