共计 9149 个字符,预计需要花费 23 分钟才能阅读完成。
导读 | 这次使用 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
2、TcpClient(网络调试助手) 连接开发板的 TcpServer(HiBurn)。
3、TcpClient 输入数据并发送,TcpServer 接收后再发送回 TcpClient。
六、全部源代码,我都注释了,希望大家能够有所参考。
// 接收、发送的数据 | |
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); |
正文完
星哥玩云-微信公众号
