Flutter局域网广播(UDP通信)与TCP通信
前言
现在有一个需求,手机和ESP32通过WIFI进行通信。流程如下:
- 手机创建TCP服务器
- 手机向192.168.0.255的1002端口广播自己的ip地址以及TCP服务器的端口号
- ESP32监听到1002的广播内容后,连接手机的TCP服务器。
- 最后就是ESP32硬件和TCP服务器进行数据收发
因此我们要了解Flutter如何使用UDP进行广播、如何创建TCP服务器以及通过TCP进行通信
验证工具
假如我们代码写好了,和硬件直接调试发现出现了错误,我们就无法定位是Flutter的代码问题还是硬件的问题。因此需要一个验证工具来验证是哪方面出现了问题
这里我们使用验证工具是“山外多功能调试助手”
模拟TCP服务器
- 打开山外多功能调试助手
- 点击网络调试助手,再点击TCP服务器
- 输入端口号点击监听,硬件根据ip地址和端口号进行连接,在消息区我们可以看到是否有设备连接
- ESP32能够连接这个软件说明错误问题在手机程序中
模拟TCP客户端
- 前端创建好了TCP服务端后,可以通过这个软件进行连接测试
- 打开“山外多功能调试助手”,点击网络调试助手,点击TCP客户端
- 输入服务器IP地址以及端口号,点击连接后查看消息是否连接成功
如果能够连接成功则说明我们的TCP服务器创建成功
模拟UDP广播
- 点击网络调试助手——>点击UDP,输入广播的端口号后点击连接
- 手机程序发送一个广播,在消息区可以看到广播的内容
创建TCP服务器
要在Flutter中创建TCP服务器,您可以使用dart:io库中的ServerSocket类。
下面代码使用ServerSocket.bind()
方法绑定服务器的地址和端口。然后,我们使用server.listen()
来监听来自客户端的连接。当客户端连接到服务器时,我们打印连接信息并向客户端发送欢迎消息。
接下来,我们通过socket.listen()
来监听客户端发送的数据。当接收到数据时,我们打印收到的消息,并向客户端发送回复消息。如果在接收数据时出现错误或客户端断开连接,我们关闭与客户端的连接。
startServer() async {
try {
_serverSocket?.close();
_serverSocket = await ServerSocket.bind(InternetAddress.anyIPv4, 5556);
print( '服务器已启动,地址:${_serverSocket?.address.address}:${_serverSocket?.port}');
_serverSocket?.listen(serverOnReceive);
} catch (e, stackTrace) {
print('e---------:$e');
}
}
serverOnReceive(Socket socket) {
_socket = socket;
print('客户端已连接: ${socket.remoteAddress}:${socket.remotePort}');
socket.writeln('欢迎连接到服务器'); // 向客户端发送欢迎消息
_socket?.listen(
(data) {
print('接收到客户端消息: $data');
socket.writeln('服务器收到消息: $data'); // 回复客户端收到的消息
},
onError: (error) {
print('接收数据时出现错误: $error');
socket.close(); // 关闭与客户端的连接
},
onDone: () {
print('客户端已断开连接');
socket.close(); // 关闭与客户端的连接
},
);
}
局域网广播
权限设置
android
在Flutter中发送广播消息所涉及的权限取决于您的目标平台和网络环境。以下是一些常见的权限,您可能需要在Flutter应用程序中配置以发送广播消息:
对于Android平台:
android.permission.INTERNET
:用于访问互联网。android.permission.ACCESS_NETWORK_STATE
:用于访问网络状态信息。android.permission.ACCESS_WIFI_STATE
:用于访问Wi-Fi状态信息。android.permission.CHANGE_WIFI_MULTICAST_STATE
:用于更改Wi-Fi多播状态。
对于ios平台
NSLocalNetworkUsageDescription
:描述应用程序使用本地网络的目的,添加该键值对到应用程序的Info.plist
文件中。
示例Info.plist
文件中的键值对配置部分:
<key>NSLocalNetworkUsageDescription</key>
<string>需要使用本地网络以发送广播消息。</string>
具体代码
在Flutter中,可以使用dart:io
库中的RawDatagramSocket
类来发送广播消息。以下是一个简单的示例代码,演示如何在Flutter中发送广播消息:
void sendBroadcastMessage() {
String message = 'BB192.168.0.174:5556';
print(utf8.encode(message));
RawDatagramSocket.bind(InternetAddress.anyIPv4, 0)
.then((RawDatagramSocket socket) {
rawDatagramSocket = socket;
var broadcastAddr = InternetAddress('255.255.255.255');
var port = 1002; // 广播目标端口
socket.broadcastEnabled = true;
socket.send(utf8.encode(message), broadcastAddr, port);
socket.close();
}).catchError((error) {
print('发送广播消息时出现错误: $error');
});
}
全部代码
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
ServerSocket? _serverSocket;
Socket? _socket;
RawDatagramSocket? rawDatagramSocket;
class TCPServe extends StatefulWidget {
const TCPServe({super.key});
@override
State<TCPServe> createState() => _TCPServeState();
}
class _TCPServeState extends State<TCPServe> {
startServer() async {
print("InternetAddress.anyIPv4:${InternetAddress.loopbackIPv4}");
try {
_serverSocket?.close();
// _serverSocket = await ServerSocket.bind("localhost", 1234);
_serverSocket = await ServerSocket.bind(InternetAddress.anyIPv4, 5556);
print(
'服务器已启动,地址: ${_serverSocket?.address.address}:${_serverSocket?.port}');
_serverSocket?.listen(serverOnReceive);
} catch (e, stackTrace) {
print('e---------:$e');
}
}
serverOnReceive(Socket socket) {
_socket = socket;
print('客户端已连接: ${socket.remoteAddress}:${socket.remotePort}');
socket.writeln('欢迎连接到服务器'); // 向客户端发送欢迎消息
_socket?.listen(
(data) {
print('接收到客户端消息: $data');
socket.writeln('服务器收到消息: $data'); // 回复客户端收到的消息
},
onError: (error) {
print('接收数据时出现错误: $error');
socket.close(); // 关闭与客户端的连接
},
onDone: () {
print('客户端已断开连接');
socket.close(); // 关闭与客户端的连接
},
);
}
clean() async {
await _socket?.close();
print("_socket关闭");
await _serverSocket?.close();
print("_serverSocket关闭");
}
void sendBroadcastMessage() {
String message = 'BB192.168.0.174:5556';
print(utf8.encode(message));
RawDatagramSocket.bind(InternetAddress.anyIPv4, 0)
.then((RawDatagramSocket socket) {
rawDatagramSocket = socket;
var broadcastAddr = InternetAddress('255.255.255.255');
var port = 1002; // 广播目标端口
socket.broadcastEnabled = true;
socket.send(utf8.encode(message), broadcastAddr, port);
socket.close();
}).catchError((error) {
print('发送广播消息时出现错误: $error');
});
}
cleanRawDatagramSocket() {
rawDatagramSocket?.close();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("TCP服务端测试")),
body: Center(
child: Column(
children: [
TextButton(
onPressed: startServer,
child: const Text("创建服务"),
),
TextButton(
onPressed: clean,
child: const Text("关闭服务"),
),
TextButton(
onPressed: () {
_socket?.writeln('REVERSE_LED');
},
child: const Text("发送REVERSE_LED"),
),
TextButton(
onPressed: sendBroadcastMessage,
child: const Text("开始广播"),
),
TextButton(
onPressed: cleanRawDatagramSocket,
child: const Text("关闭广播"),
),
],
),
),
);
}
}