如何使用鴻蒙Hi3861來完成Wifi熱點的連接,並啓動TCP SocketServer?

這次使用Hi3861來完成Wifi熱點的連接,並啓動TCP SocketServer,接收消息並將消息反饋TcpCLient。
一、連接Wifi熱點

主要做法是啓動開發板Wifi,然後設置熱點和密碼等配置信息,再連接熱點。

1、先定義兩個Wifi監聽器,一個連接改變、一個狀態改變,並註冊監聽器。

其中重要的是OnWifiConnectionChanged連接狀態事件處理函數。該函數會在連接成功後設置全局變量g_connected=1,代表已經連接成功。

WifiEvent eventListener = {
  .OnWifiConnectionChanged = OnWifiConnectionChanged,
  .OnWifiScanStateChanged = OnWifiScanStateChanged
};
WifiErrorCode errCode = RegisterWifiEvent(&eventListener);
 
void OnWifiConnectionChanged(int state, WifiLinkedInfo* info) {
  if (!info) return;
 
  if (state == WIFI_STATE_AVALIABLE) {
    g_connected = 1;
  } else {
    g_connected = 0;
  }
}
2、啓動Wifi
EnableWifi();
3、設置Wifi熱點信息,並返回NetworkId
WifiDeviceConfig apConfig = {};
strcpy(apConfig.ssid, "MyWifi");
strcpy(apConfig.preSharedKey, "12345678");
apConfig.securityType = WIFI_SEC_TYPE_PSK;
 
int netId = -1;
AddDeviceConfig(&apConfig, &netId);
4、連接熱點,注意此時的g_connected變量,true代表已連接,false代表未連接。

這個狀態在事件處理函數中設置。未連接成功時,系統會循環等待,知道事件設置該值。

ConnectTo(netId);
while (!g_connected) {
  osDelay(10);
}
二、進行聯網,找到wlan0的network interface,然後啓動DHCP客戶端,獲取IP地址。
struct netif* iface = netifapi_netif_find("wlan0");
if (iface) {
  err_t ret = netifapi_dhcp_start(iface);
  osDelay(300);
}
三、啓動TcpSocketServer,並收發消息
1、創建SocketServer,設置服務端口,並啓動監聽
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
 
struct sockaddr_in serverAddr = {0};
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(port);
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
 
int backlog = 1;
listen(sockfd, backlog)
2、客戶端連接。接收客戶端消息併發送回去。注意連接後,會創建一個新的Socket File Description。
int connfd = -1;
connfd = accept(sockfd, (struct sockaddr *)&clientAddr, &clientAddrLen);
 
recv(connfd, request, sizeof(request), 0);
send(connfd, request, strlen(request), 0);
3、關閉TcpSocketServer
lwip_close(connfd);
lwip_close(socketfd);
四、聯網結束,關閉DHCP客戶端,斷開Wifi,移除熱點的配置信息,禁用Wifi。
err_t ret = netifapi_dhcp_stop(iface);
Disconnect();
RemoveDevice(netId);
DisableWifi();
五、測試情況如下:
1、啓動開發板,連接Wifi熱點,啓動TcpServer

如何使用鴻蒙Hi3861來完成Wifi熱點的連接,並啓動TCP SocketServer?如何使用鴻蒙Hi3861來完成Wifi熱點的連接,並啓動TCP SocketServer?

2、TcpClient(網絡調試助手)連接開發板的TcpServer(HiBurn)。

如何使用鴻蒙Hi3861來完成Wifi熱點的連接,並啓動TCP SocketServer?如何使用鴻蒙Hi3861來完成Wifi熱點的連接,並啓動TCP SocketServer?如何使用鴻蒙Hi3861來完成Wifi熱點的連接,並啓動TCP SocketServer?如何使用鴻蒙Hi3861來完成Wifi熱點的連接,並啓動TCP SocketServer?

3、TcpClient輸入數據併發送,TcpServer接收後再發送回TcpClient。

如何使用鴻蒙Hi3861來完成Wifi熱點的連接,並啓動TCP SocketServer?如何使用鴻蒙Hi3861來完成Wifi熱點的連接,並啓動TCP SocketServer?如何使用鴻蒙Hi3861來完成Wifi熱點的連接,並啓動TCP SocketServer?如何使用鴻蒙Hi3861來完成Wifi熱點的連接,並啓動TCP SocketServer?

六、全部源代碼,我都註釋了,希望大家能夠有所參考。
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <unistd.h>
 
#include "ohos_init.h"
#include "cmsis_os2.h"
#include "wifi_device.h"
 
#include "lwip/netifapi.h"
#include "lwip/api_shell.h"
#include "lwip/sockets.h"
 
// 接收、發送的數據
static char request[128] = "";
// 未連接熱點=0,已連接熱點=1
static int g_connected = 0;
 
// 輸出連接信息字符串
// 打印內容樣例--> bssid: 38:47:BC:49:01:FA, rssi: 0, connState: 0, reason: 0, ssid: MyMobile
void PrintLinkedInfo(WifiLinkedInfo* info) {
  if (!info) return;
 
  static char macAddress[32] = {0};
  unsigned char* mac = info->bssid;
  snprintf(macAddress, sizeof(macAddress), "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  printf("bssid: %s, rssi: %d, connState: %d, reason: %d, ssid: %srn", macAddress, info->rssi, info->connState, info->disconnectedReason, info->ssid);
}
 
// 連接狀態改變事件處理
void OnWifiConnectionChanged(int state, WifiLinkedInfo* info) {
  if (!info) return;
 
  // 輸出類似內容:OnWifiConnectionChanged 31, state = 1, info =
  printf("%s %d, state = %d, info = rn", __FUNCTION__, __LINE__, state);
  PrintLinkedInfo(info);
 
  // 根據連接狀態設置g_connected
  if (state == WIFI_STATE_AVALIABLE) {
    g_connected = 1;
  } else {
    g_connected = 0;
  }
}
 
// 掃描狀態改變事件處理
void OnWifiScanStateChanged(int state, int size) {
  printf("%s %d, state = %X, size = %drn", __FUNCTION__, __LINE__, state, size);
}
 
void DisconnectTcpSocket(int connfd) {
  sleep(1);
  printf("do_disconnect...rn");
  lwip_close(connfd);
  sleep(1); // for debug
}
 
void CloseTcpSocket(int socketfd) {
  printf("do_cleanup...rn");
 
  lwip_close(socketfd);
}
 
static void TcpServerHandler(void) {
  ssize_t retval = 0;
  unsigned short port = 9118;
 
  // 創建一個通信的Socket,並返回一個Socket文件描述符。第一個參數IpV4,第二個參數SOCK_STREAM類型,第三個指用到的協議
  int sockfd = socket(AF_INET, SOCK_STREAM, 0);
 
  // 客戶端地址和地址長度
  struct sockaddr_in clientAddr = {0};
  socklen_t clientAddrLen = sizeof(clientAddr);
 
  // 服務端地址
  struct sockaddr_in serverAddr = {0};
  serverAddr.sin_family = AF_INET;
  // htons是將整型變量從主機字節順序轉變成網絡字節順序,就是整數在地址空間存儲方式變爲高位字節存放在內存的低地址處
  serverAddr.sin_port = htons(port);
  // 監聽本機的所有IP地址,INADDR_ANY=0x0
  // 將主機數轉換成無符號長整型的網絡字節順序
  serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
 
  // 服務端綁定端口
  retval = bind(sockfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
  if (retval < 0) {
    printf("bind failed, %ld!rn", retval);
    CloseTcpSocket(sockfd);
    return;
  }
  printf("bind to port %d success!rn", port);
 
  // 開始監聽,backlog指Pending連接隊列增長到的最大長度。隊列滿了,再有新連接請求到達,則客戶端ECONNREFUSED錯誤。如果支持重傳,則請求忽略。
  int backlog = 1;
  retval = listen(sockfd, backlog);
  if (retval < 0) {
    printf("listen failed!rn");
    CloseTcpSocket(sockfd);
    return;
  }
  printf("listen with %d backlog success!rn", backlog);
 
  int outerFlag = 1;
  while (outerFlag) {
    // 接受客戶端連接,成功會返回一個表示連接的 socket。clientAddr參數將會攜帶客戶端主機和端口信息;失敗返回 -1
    // 從Pending連接隊列中獲取第一個連接,根據sockfd的socket協議、地址族等內容創建一個新的socket文件描述,並返回。
    // 此後的 收、發 都在 表示連接的 socket 上進行;之後 sockfd 依然可以繼續接受其他客戶端的連接,
    // UNIX系統上經典的併發模型是“每個連接一個進程”——創建子進程處理連接,父進程繼續接受其他客戶端的連接
    // 鴻蒙liteos-a內核之上,可以使用UNIX的“每個連接一個進程”的併發模型liteos-m內核之上,可以使用“每個連接一個線程”的併發模型
    int connfd = -1;
    connfd = accept(sockfd, (struct sockaddr *)&clientAddr, &clientAddrLen);
    if (connfd < 0) {
      printf("accept failed, %d, %drn", connfd, errno);
      CloseTcpSocket(sockfd);
      outerFlag = 0;
    }
    printf("accept success, connfd = %d !rn", connfd);
    // inet_ntoa:網絡地址轉換成“.”點隔的字符串格式。ntohs:16位數由網絡字節順序轉換爲主機字節順序。
    printf("client addr info: host = %s, port = %drn", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
 
    int innerFlag = 1;
    // 接收消息,然後發送回去
    while (innerFlag) {
      // 後續 收、發 都在 表示連接的 socket 上進行;
      // 在新的Socket文件描述上接收信息.
      retval = recv(connfd, request, sizeof(request), 0);
      if (retval < 0) {
        printf("recv request failed, %ld!rn", retval);
        innerFlag = 0;
      } else if (retval == 0) {
        // 對方主動斷開連接
        printf("client disconnected!rn");
        innerFlag = 0;
      } else {
        printf("recv request{%s} from client done!rn", request);
 
        // 發送數據
        retval = send(connfd, request, strlen(request), 0);
        if (retval <= 0) {
          printf("send response failed, %ld!rn", retval);
          innerFlag = 0;
        }
        printf("send response{%s} to client done!rn", request);
 
        // 清空緩衝區
        memset(&request, 0, sizeof(request));
    }
 
    DisconnectTcpSocket(connfd);
 
    outerFlag = 0;
  }
 
  CloseTcpSocket(sockfd);
}
 
static void TcpServerTask(void *arg) {
  (void)arg;
 
  // 先定義兩個Wifi監聽器,一個連接改變、一個狀態改變
  WifiEvent eventListener = {
    .OnWifiConnectionChanged = OnWifiConnectionChanged,
    .OnWifiScanStateChanged = OnWifiScanStateChanged
  };
 
  // 等待10個系統Ticks。每個ticks多少個us,計算方式= 1000 * 1000 / osKernelGetTickFreq()
  osDelay(10);
 
  // 註冊監聽器
  WifiErrorCode errCode = RegisterWifiEvent(&eventListener);
  printf("RegisterWifiEvent: %drn", errCode);
 
  // 設置Wifi熱點信息
  WifiDeviceConfig apConfig = {};
  strcpy(apConfig.ssid, "MyMobile");
  strcpy(apConfig.preSharedKey, "12345678");
  apConfig.securityType = WIFI_SEC_TYPE_PSK;
 
  int netId = -1;
 
  // 啓用Wifi
  errCode = EnableWifi();
  printf("EnableWifi: %drn", errCode);
  osDelay(10);
 
  // 設置Wifi熱點配置信息,返回生成的網絡Id-netId。
  errCode = AddDeviceConfig(&apConfig, &netId);
  printf("AddDeviceConfig: %drn", errCode);
 
  // 根據網絡Id連接到Wifi熱點
  g_connected = 0;
  errCode = ConnectTo(netId);
  printf("ConnectTo(%d): %drn", netId, errCode);
  // 未連接完成,則一直等待。g_connected狀態會在事件中設置。
  while (!g_connected) {
    osDelay(10);
  }
  printf("g_connected: %drn", g_connected);
  osDelay(50);
 
  // 聯網業務開始,找到netifname=wlan0的netif。
  struct netif* iface = netifapi_netif_find("wlan0");
  if (iface) {
    // 啓動DHCP客戶端,獲取IP地址
    err_t ret = netifapi_dhcp_start(iface);
    printf("netifapi_dhcp_start: %drn", ret);
    // 等待DHCP服務器反饋給予地址
    osDelay(300);
    // 執行線程安全的網絡方法,第二個參數是voidFunc,第三個參數是errFunc。如果沒有errFunc,那麼就執行voidFunc。
    // netifapi_dhcp_start/netifapi_dhcp_stop等都是調用的netifapi_netif_common方法。
    // dhcp_clients_info_show顯示信息
    /*
    server :
      server_id : 192.168.43.1
      mask : 255.255.255.0, 1
      gw : 192.168.43.1
      T0 : 3600
      T1 : 1800
      T2 : 3150
    clients <1> :
      mac_idx mac             addr            state   lease   tries   rto
      0       b4c9b9af69f8    192.168.43.56   10      0       1       2
    */
    ret = netifapi_netif_common(iface, dhcp_clients_info_show, NULL);
    printf("netifapi_netif_common: %drn", ret);
  }
 
  TcpServerHandler();
 
  // 聯網業務結束,斷開DHCP
  err_t ret = netifapi_dhcp_stop(iface);
  printf("netifapi_dhcp_stop: %drn", ret);
 
  // 斷開Wifi熱點連接
  Disconnect();
 
  // 移除Wifi熱點的配置
  RemoveDevice(netId);
 
  // 關閉Wifi
  errCode = DisableWifi();
  printf("DisableWifi: %drn", errCode);
  osDelay(200);
}
 
static void TcpServerEntry(void) {
  osThreadAttr_t attr;
 
  attr.name = "TcpServerTask";
  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((osThreadFunc_t)TcpServerTask, NULL, &attr) == NULL) {
    printf("SunLaoTest-Fail Create");
  }
}
 
APP_FEATURE_INIT(TcpServerEntry);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章