Я пытаюсь заставить группу телефонов (может быть Android или iOS в одной группе) связываться друг с другом, и группа может одновременно подключаться к 30 устройствам, и у меня нет доступа к внешним сетям, таким как Inte rnet или сотовой сети.
Я хотел бы иметь такую структуру телефонов, обменивающихся данными по Wi-Fi, Bluetooth или другим протоколам, если у вас есть какие-либо предложения.
Для этого я попробовал Websocket, но мне не удалось запустить сервер WebSocket через Flutter на телефоне.
Позже я обнаружили класс SocketServer
Dart, который позволяет манипулировать сокетами TCP. На P C мне удалось установить связь между клиентом и сервером на одном компьютере.
Но как только я попытаюсь установить связь между телефоном или телефоном и P C с действующим сервер, у меня есть SocketException
, который говорит мне, что соединение, как было отклонено хостом на данном порту:
SocketException: OS Error : Connection refused, erno = 111, address = 172.20.10.4, port 44518
Вот мой код:
Класс сервера
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:socket_lab/class/models.dart';
class Server {
Server({this.onError, this.onData});
Uint8ListCallback onData;
DynamicCallback onError;
ServerSocket server;
bool running = false;
List<Socket> sockets = [];
start() async {
runZoned(() async {
server = await ServerSocket.bind('localhost', 4040);
this.running = true;
server.listen(onRequest);
this.onData(Uint8List.fromList('Server listening on port 4040'.codeUnits));
}, onError: (e) {
this.onError(e);
});
}
stop() async {
await this.server.close();
this.server = null;
this.running = false;
}
broadCast(String message) {
this.onData(Uint8List.fromList('Broadcasting : $message'.codeUnits));
for (Socket socket in sockets) {
socket.write( message + '\n' );
}
}
onRequest(Socket socket) {
if (!sockets.contains(socket)) {
sockets.add(socket);
}
socket.listen((Uint8List data) {
this.onData(data);
});
}
}
Клиентский класс
import 'dart:io';
import 'dart:typed_data';
import 'models.dart';
class Client {
Client({
this.onError,
this.onData,
this.hostname,
this.port,
});
String hostname;
int port;
Uint8ListCallback onData;
DynamicCallback onError;
bool connected = false;
Socket socket;
connect() async {
try {
socket = await Socket.connect(hostname, 4040);
socket.listen(
onData,
onError: onError,
onDone: disconnect,
cancelOnError: false,
);
connected = true;
} on Exception catch (exception) {
onData(Uint8List.fromList("Error : $exception".codeUnits));
}
}
write(String message) {
//Connect standard in to the socket
socket.write(message + '\n');
}
disconnect() {
if (socket != null) {
socket.destroy();
connected = false;
}
}
}
Модели
import 'dart:typed_data';
typedef DynamicCallback(dynamic data);
typedef Uint8ListCallback(Uint8List data);
Страница сервера
import 'dart:typed_data';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'class/server.dart';
class ServerPage extends StatefulWidget {
@override
_ServerPageState createState() => _ServerPageState();
}
class _ServerPageState extends State<ServerPage> {
Server server;
List<String> serverLogs = [];
TextEditingController controller = TextEditingController();
initState() {
super.initState();
server = Server(
onData: this.onData,
onError: this.onError,
);
}
onData(Uint8List data) {
DateTime time = DateTime.now();
serverLogs.add(time.hour.toString() + "h" + time.minute.toString() + " : " + String.fromCharCodes(data));
setState(() {});
}
onError(dynamic error) {
print(error);
}
dispose() {
controller.dispose();
server.stop();
super.dispose();
}
confirmReturn() {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("ATTENTION"),
content: Text("Quitter cette page éteindra le serveur de socket"),
actions: <Widget>[
FlatButton(
child: Text("Quitter", style: TextStyle(color: Colors.red)),
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
},
),FlatButton(
child: Text("Annuler", style: TextStyle(color: Colors.grey)),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Server'),
centerTitle: true,
automaticallyImplyLeading: false,
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: confirmReturn,
),
),
body: Column(
children: <Widget>[
Expanded(
flex: 1,
child: Padding(
padding: const EdgeInsets.only(left: 15, right: 15, top: 15),
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
"Server",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
Container(
decoration: BoxDecoration(
color: server.running ? Colors.green : Colors.red,
borderRadius: BorderRadius.all(Radius.circular(3)),
),
padding: EdgeInsets.all(5),
child: Text(
server.running ? 'ON' : 'OFF',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
],
),
SizedBox(
height: 15,
),
RaisedButton(
child: Text(server.running ? 'Arrêter le serveur' : 'Lancer le serveur'),
onPressed: () async {
if (server.running) {
await server.stop();
this.serverLogs.clear();
} else {
await server.start();
}
setState(() {});
},
),
Divider(
height: 30,
thickness: 1,
color: Colors.black12,
),
Expanded(
flex: 1,
child: ListView(
children: serverLogs.map((String log) {
return Padding(
padding: EdgeInsets.only(top: 15),
child: Text(log),
);
}).toList(),
),
),
],
),
),
),
Container(
color: Colors.grey,
height: 80,
padding: EdgeInsets.all(10),
child: Row(
children: <Widget>[
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'Message à broadcaster :',
style: TextStyle(
fontSize: 8,
),
),
Expanded(
flex: 1,
child: TextFormField(
controller: controller,
),
),
],
),
),
SizedBox(
width: 15,
),
MaterialButton(
onPressed: () {
controller.text = "";
},
minWidth: 30,
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 15),
child: Icon(Icons.clear),
),
SizedBox(width: 15,),
MaterialButton(
onPressed: () {
server.broadCast(controller.text);
controller.text = "";
},
minWidth: 30,
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 15),
child: Icon(Icons.send),
)
],
),
),
],
),
);
}
}
Страница клиента
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:socket_lab/class/client.dart';
class ClientPage extends StatefulWidget {
@override
_ClientPageState createState() => _ClientPageState();
}
class _ClientPageState extends State<ClientPage> {
Client client;
List<String> serverLogs = [];
TextEditingController controller = TextEditingController();
initState() {
super.initState();
client = Client(
hostname: "172.20.10.3",
port: 4040,
onData: this.onData,
onError: this.onError,
);
}
onData(Uint8List data) {
DateTime time = DateTime.now();
serverLogs.add(time.hour.toString() + "h" + time.minute.toString() + " : " + String.fromCharCodes(data));
setState(() {});
}
onError(dynamic error) {
print(error);
}
dispose() {
controller.dispose();
client.disconnect();
super.dispose();
}
confirmReturn() {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("ATTENTION"),
content: Text("Quitter cette page déconnectera le client du serveur de socket"),
actions: <Widget>[
FlatButton(
child: Text("Quitter", style: TextStyle(color: Colors.red)),
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
},
),FlatButton(
child: Text("Annuler", style: TextStyle(color: Colors.grey)),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Server'),
centerTitle: true,
automaticallyImplyLeading: false,
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: confirmReturn,
),
),
body: Column(
children: <Widget>[
Expanded(
flex: 1,
child: Padding(
padding: const EdgeInsets.only(left: 15, right: 15, top: 15),
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
"Client",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
Container(
decoration: BoxDecoration(
color: client.connected ? Colors.green : Colors.red,
borderRadius: BorderRadius.all(Radius.circular(3)),
),
padding: EdgeInsets.all(5),
child: Text(
client.connected ? 'CONNECTÉ' : 'DÉCONNECTÉ',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
],
),
SizedBox(
height: 15,
),
RaisedButton(
child: Text(!client.connected ? 'Connecter le client' : 'Déconnecter le client'),
onPressed: () async {
if (client.connected ) {
await client.disconnect();
this.serverLogs.clear();
} else {
await client.connect();
}
setState(() {});
},
),
Divider(
height: 30,
thickness: 1,
color: Colors.black12,
),
Expanded(
flex: 1,
child: ListView(
children: serverLogs.map((String log) {
return Padding(
padding: EdgeInsets.only(top: 15),
child: Text(log),
);
}).toList(),
),
),
],
),
),
),
Container(
color: Colors.grey,
height: 80,
padding: EdgeInsets.all(10),
child: Row(
children: <Widget>[
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'Message à envoyer :',
style: TextStyle(
fontSize: 8,
),
),
Expanded(
flex: 1,
child: TextFormField(
controller: controller,
),
),
],
),
),
SizedBox(
width: 15,
),
MaterialButton(
onPressed: () {
controller.text = "";
},
minWidth: 30,
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 15),
child: Icon(Icons.clear),
),
SizedBox(width: 15,),
MaterialButton(
onPressed: () {
client.write(controller.text);
controller.text = "";
},
minWidth: 30,
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 15),
child: Icon(Icons.send),
)
],
),
),
],
),
);
}
}