本篇使用的是lwip编写tcp服务端。需要提前准备好一个PARAM_HOTSPOT_SSID宏定义的热点,并且密码为PARAM_HOTSPOT_PSK
LwIP简介
LwIP是什么?
A Lightweight TCP/IP stack 一个轻量级的TCP/IP协议栈
详细介绍请参考LwIP项目官网:lwIP - A Lightweight TCP/IP stack - Summary [Savannah]
LwIP在openharmony上的应用情况
目前,openharmony源码树有两份LwIP:
- third_party/lwip
-
- 源码形式编译
- 供liteos-a内核使用
- 还有一部分代码在kernel/liteos_a中,一起编译
- vendor/hisi/hi3861/hi3861/third_party/lwip_sack
-
- hi3861-sdk的一部分
- 静态库形式编译
- 不可修改配置
- 可以查看当前配置(vend
LwIP Socket API编程主要是6个步骤:
创建Tcp Server Socket:socket
绑定指定的IP和Port:bind
设置socket为监听状态:listen
阻塞方式等待client连接:accept
阻塞方式receive client的消息:recv
调用send发送消息给TCP Client
修改网络参数
在Hi3861开发板上运行上述四个测试程序之前,需要根据你的无线路由、Linux系统IP修改 net_params.h文件的相关代码:
- PARAM_HOTSPOT_SSID 修改为你的热点名称
- PARAM_HOTSPOT_PSK 修改为你的热点密码;
代码编写
修改D:\DevEcoProjects\test\src\vendor\rtplay\rt_hi3861\demo\BUILD.gn文件
# Copyright (c) 2023 Beijing HuaQing YuanJian Education Technology Co., Ltd
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import("//build/lite/config/component/lite_component.gni")
lite_component("demo") {
features = [
#"base_00_helloworld:base_helloworld_example",
#"base_01_led:base_led_example",
#"base_02_loopkey:base_loopkey_example",
#"base_03_irqkey:base_irqkey_example",
#"base_04_adc:base_adc_example",
#"base_05_pwm:base_pwm_example",
#"base_06_ssd1306:base_ssd1306_example",
#"kernel_01_task:kernel_task_example",
#"kernel_02_timer:kernel_timer_example",
#"kernel_03_event:kernel_event_example",
#"kernel_04_mutex:kernel_mutex_example",
#"kernel_05_semaphore_as_mutex:kernel_semaphore_as_mutex_example",
#"kernel_06_semaphore_for_sync:kernel_semaphore_for_sync_example",
#"kernel_07_semaphore_for_count:kernel_semaphore_for_count_example",
#"kernel_08_message_queue:kernel_message_queue_example",
#"wifi_09_hotspot:wifi_hotspot_example",
#"wifi_10_sta:wifi_sta_example",
"tcp_11_server:tcp_server_example",
]
}
创建D:\DevEcoProjects\test\src\vendor\rtplay\rt_hi3861\demo\tcp_11_server文件夹
文件夹中创建D:\DevEcoProjects\test\src\vendor\rtplay\rt_hi3861\demo\tcp_11_server\BUILD.gn文件
#Copyright (C) 2021 HiHope Open Source Organization .
#Licensed under the Apache License, Version 2.0 (the "License");
#you may not use this file except in compliance with the License.
#You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#Unless required by applicable law or agreed to in writing, software
#distributed under the License is distributed on an "AS IS" BASIS,
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#See the License for the specific language governing permissions and
#
#limitations under the License.
static_library("tcp_server_example") {
# uncomment one of following line, to enable one test:
sources = ["tcp_server_example.c"]
sources += ["wifi_connecter.c"]
include_dirs = [
"//utils/native/lite/include",
"//kernel/liteos_m/kal",
"//foundation/communication/wifi_lite/interfaces/wifiservice",
]
}
添加了wifi_connecter.c文件的编译,这个文件中有链接wifi的函数。
文件夹中创建D:\DevEcoProjects\test\src\vendor\rtplay\rt_hi3861\demo\tcp_11_server\net_common.h文件,文件主要引入一些头文件。
/*
* Copyright (C) 2021 HiHope Open Source Organization .
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
*
* limitations under the License.
*/
#ifndef NET_COMMON_H
#define NET_COMMON_H
// __arm__ and __aarch64__ for HarmonyOS with liteos-a kernel
// __i386__ and __x86_64__ for Unix like OS
#if defined(__arm__) || defined(__aarch64__) || defined(__i386__) || defined(__x86_64__)
#define HAVE_BSD_SOCKET 1
#else
#define HAVE_BSD_SOCKET 0
#endif
#if defined(__riscv) // for wifiiot(HarmonyOS on Hi3861 with liteos-m kernel)
#define HAVE_LWIP_SOCKET 1
#else
#define HAVE_LWIP_SOCKET 0
#endif
#if HAVE_BSD_SOCKET
#include <sys/types.h> // for AF_INET SOCK_STREAM
#include <sys/socket.h> // for socket
#include <netinet/in.h> // for sockaddr_in
#include <arpa/inet.h> // for inet_pton
#elif HAVE_LWIP_SOCKET
#include "lwip/sockets.h"
#ifndef close
#define close(fd) lwip_close(fd)
#endif
#else
#error "Unknow platform!"
#endif
#endif // NET_COMMON_H
文件夹中创建D:\DevEcoProjects\test\src\vendor\rtplay\rt_hi3861\demo\tcp_11_server\wifi_connecter.h文件,该头文件包含wifi连接的宏。
/*
* Copyright (C) 2021 HiHope Open Source Organization .
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
*
* limitations under the License.
*/
#ifndef WIFI_CONNECTER_H
#define WIFI_CONNECTER_H
#include "wifi_device.h"
#ifndef PARAM_HOTSPOT_SSID
#define PARAM_HOTSPOT_SSID "HarmonyOS" // your AP SSID
#endif
#ifndef PARAM_HOTSPOT_PSK
#define PARAM_HOTSPOT_PSK "1234567890" // your AP PSK
#endif
#ifndef PARAM_HOTSPOT_TYPE
#define PARAM_HOTSPOT_TYPE WIFI_SEC_TYPE_PSK // defined in wifi_device_config.h
#endif
#ifndef PARAM_SERVER_ADDR
#define PARAM_SERVER_ADDR "192.168.1.100" // your PC IP address
#endif
#ifndef PARAM_SERVER_PORT
#define PARAM_SERVER_PORT 5678
#endif
int ConnectToHotspot(WifiDeviceConfig* apConfig);
void DisconnectWithHotspot(int netId);
#endif // WIFI_CONNECTER_H
文件夹中创建D:\DevEcoProjects\test\src\vendor\rtplay\rt_hi3861\demo\tcp_11_server\wifi_connecter.c文件,wifi连接的实现。
/*
* Copyright (C) 2021 HiHope Open Source Organization .
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
*
* limitations under the License.
*/
#include "wifi_device.h"
#include "cmsis_os2.h"
#include "lwip/netifapi.h"
#include "lwip/api_shell.h"
#define IDX_0 0
#define IDX_1 1
#define IDX_2 2
#define IDX_3 3
#define IDX_4 4
#define IDX_5 5
#define DELAY_TICKS_10 (10)
#define DELAY_TICKS_100 (100)
static 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[IDX_0], mac[IDX_1], mac[IDX_2], mac[IDX_3], mac[IDX_4], mac[IDX_5]);
printf("bssid: %s, rssi: %d, connState: %d, reason: %d, ssid: %s\r\n",
macAddress, info->rssi, info->connState, info->disconnectedReason, info->ssid);
}
static volatile int g_connected = 0;
static void OnWifiConnectionChanged(int state, WifiLinkedInfo* info)
{
if (!info) return;
printf("%s %d, state = %d, info = \r\n", __FUNCTION__, __LINE__, state);
PrintLinkedInfo(info);
if (state == WIFI_STATE_AVALIABLE) {
g_connected = 1;
} else {
g_connected = 0;
}
}
static void OnWifiScanStateChanged(int state, int size)
{
printf("%s %d, state = %X, size = %d\r\n", __FUNCTION__, __LINE__, state, size);
}
static WifiEvent g_defaultWifiEventListener = {
.OnWifiConnectionChanged = OnWifiConnectionChanged,
.OnWifiScanStateChanged = OnWifiScanStateChanged
};
static struct netif* g_iface = NULL;
err_t netifapi_set_hostname(struct netif *netif, char *hostname, u8_t namelen);
int ConnectToHotspot(WifiDeviceConfig* apConfig)
{
WifiErrorCode errCode;
int netId = -1;
errCode = RegisterWifiEvent(&g_defaultWifiEventListener);
printf("RegisterWifiEvent: %d\r\n", errCode);
errCode = EnableWifi();
printf("EnableWifi: %d\r\n", errCode);
errCode = AddDeviceConfig(apConfig, &netId);
printf("AddDeviceConfig: %d\r\n", errCode);
g_connected = 0;
errCode = ConnectTo(netId);
printf("ConnectTo(%d): %d\r\n", netId, errCode);
while (!g_connected) { // wait until connect to AP
osDelay(DELAY_TICKS_10);
}
printf("g_connected: %d\r\n", g_connected);
g_iface = netifapi_netif_find("wlan0");
if (g_iface) {
err_t ret = 0;
char* hostname = "rtplay";
ret = netifapi_set_hostname(g_iface, hostname, strlen(hostname));
printf("netifapi_set_hostname: %d\r\n", ret);
ret = netifapi_dhcp_start(g_iface);
printf("netifapi_dhcp_start: %d\r\n", ret);
osDelay(DELAY_TICKS_100); // wait DHCP server give me IP
#if 1
ret = netifapi_netif_common(g_iface, dhcp_clients_info_show, NULL);
printf("netifapi_netif_common: %d\r\n", ret);
#else
// 下面这种方式也可以打印 IP、网关、子网掩码信息
ip4_addr_t ip = {0};
ip4_addr_t netmask = {0};
ip4_addr_t gw = {0};
ret = netifapi_netif_get_addr(g_iface, &ip, &netmask, &gw);
if (ret == ERR_OK) {
printf("ip = %s\r\n", ip4addr_ntoa(&ip));
printf("netmask = %s\r\n", ip4addr_ntoa(&netmask));
printf("gw = %s\r\n", ip4addr_ntoa(&gw));
}
printf("netifapi_netif_get_addr: %d\r\n", ret);
#endif
}
return netId;
}
void DisconnectWithHotspot(int netId)
{
if (g_iface) {
err_t ret = netifapi_dhcp_stop(g_iface);
printf("netifapi_dhcp_stop: %d\r\n", ret);
}
WifiErrorCode errCode = Disconnect(); // disconnect with your AP
printf("Disconnect: %d\r\n", errCode);
errCode = UnRegisterWifiEvent(&g_defaultWifiEventListener);
printf("UnRegisterWifiEvent: %d\r\n", errCode);
RemoveDevice(netId); // remove AP config
printf("RemoveDevice: %d\r\n", errCode);
errCode = DisableWifi();
printf("DisableWifi: %d\r\n", errCode);
}
文件夹中创建D:\DevEcoProjects\test\src\vendor\rtplay\rt_hi3861\demo\tcp_11_server\tcp_server_example.c文件
/*
* Copyright (C) 2021 HiHope Open Source Organization .
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
*
* limitations under the License.
*/
#include <errno.h>
#include <stdio.h>
#include <string.h>
// #include <stddef.h>
#include <unistd.h>
#include "ohos_init.h"
#include "cmsis_os2.h"
#include "net_common.h"
#include "wifi_connecter.h"
#define DELAY_1S (1)
#define STACK_SIZE (10240)
#define DELAY_TICKS_10 (10)
#define DELAY_TICKS_100 (100)
static char request[128] = "";
void TcpServerTest(void)
{
WifiDeviceConfig config = {0};
// 准备AP的配置参数
// strcpy(config.ssid, PARAM_HOTSPOT_SSID);
// strcpy(config.preSharedKey, PARAM_HOTSPOT_PSK);
strcpy_s(config.ssid, WIFI_MAX_SSID_LEN, PARAM_HOTSPOT_SSID);
strcpy_s(config.preSharedKey, WIFI_MAX_KEY_LEN, PARAM_HOTSPOT_PSK);
config.securityType = PARAM_HOTSPOT_TYPE;
osDelay(DELAY_TICKS_10);
int netId = ConnectToHotspot(&config);
ssize_t retval = 0;
int backlog = 1;
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // TCP socket
int connfd = -1;
struct sockaddr_in clientAddr = {0};
socklen_t clientAddrLen = sizeof(clientAddr);
struct sockaddr_in serverAddr = {0};
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PARAM_SERVER_PORT); // 端口号,从主机字节序转为网络字节序
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 允许任意主机接入, 0.0.0.0
retval = bind(sockfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)); // 绑定端口
if (retval < 0) {
printf("bind failed, %ld!\r\n", retval);
goto do_cleanup;
}
printf("bind to port %hu success!\r\n", PARAM_SERVER_PORT);
retval = listen(sockfd, backlog); // 开始监听
if (retval < 0) {
printf("listen failed!\r\n");
goto do_cleanup;
}
printf("listen with %d backlog success!\r\n", backlog);
// 接受客户端连接,成功会返回一个表示连接的 socket , clientAddr 参数将会携带客户端主机和端口信息 ;失败返回 -1
// 此后的 收、发 都在 表示连接的 socket 上进行;之后 sockfd 依然可以继续接受其他客户端的连接,
// UNIX系统上经典的并发模型是“每个连接一个进程”——创建子进程处理连接,父进程继续接受其他客户端的连接
// 鸿蒙liteos-a内核之上,可以使用UNIX的“每个连接一个进程”的并发模型
// liteos-m内核之上,可以使用“每个连接一个线程”的并发模型
connfd = accept(sockfd, (struct sockaddr *)&clientAddr, &clientAddrLen);
if (connfd < 0) {
printf("accept failed, %d, %d\r\n", connfd, errno);
goto do_cleanup;
}
printf("accept success, connfd = %d!\r\n", connfd);
printf("client addr info: host = %s, port = %hu\r\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
// 后续 收、发 都在 表示连接的 socket 上进行;
retval = recv(connfd, request, sizeof(request), 0);
if (retval < 0) {
printf("recv request failed, %ld!\r\n", retval);
goto do_disconnect;
}
printf("recv request{%s} from client done!\r\n", request);
retval = send(connfd, request, strlen(request), 0);
if (retval <= 0) {
printf("send response failed, %ld!\r\n", retval);
goto do_disconnect;
}
printf("send response{%s} to client done!\r\n", request);
do_disconnect:
sleep(DELAY_1S);
close(connfd);
sleep(DELAY_1S); // for debug
do_cleanup:
printf("do_cleanup...\r\n");
close(sockfd);
DisconnectWithHotspot(netId);
}
SYS_RUN(TcpServerTest);
使用build,编译成功后,使用upload进行烧录。