使用 Flutter App 运行 SocketServer 并通过 Socket 与其他手机通信

Art*_*ine 5 sockets dart flutter

我正在尝试让一组手机(可以是同一组中的Android或iOS)相互通信,并且一组可以同时达到30台设备,并且无法访问Internet或蜂窝网络。

如果您有任何建议,我想要这种通过 Wifi 或蓝牙或其他协议进行通信的电话结构。

建筑学

为此,我尝试了 Websocket,但我没有成功通过 Flutter 在手机上运行 WebSocket 服务器。

后来,我发现了SocketServer允许操作 TCP 套接字的Dart 类。在 PC 上,我能够在同一台机器上的客户端和服务器之间进行通信。

但是,一旦我尝试在电话或电话和 PC 与有效服务器之间进行通信,我就会得到一个SocketException告诉我该连接被给定端口上的主机拒绝的连接:

SocketException: OS Error : Connection refused, erno = 111, address = 172.20.10.4, port 44518
Run Code Online (Sandbox Code Playgroud)

这是我的代码:

服务器类

SocketException: OS Error : Connection refused, erno = 111, address = 172.20.10.4, port 44518
Run Code Online (Sandbox Code Playgroud)

客户端类

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);
    });
  }
}
Run Code Online (Sandbox Code Playgroud)

楷模

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;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

服务器页面

import 'dart:typed_data';

typedef DynamicCallback(dynamic data);
typedef Uint8ListCallback(Uint8List data);
Run Code Online (Sandbox Code Playgroud)

客户页面

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),
                )
              ],
            ),
          ),
        ],
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

Art*_*ine 5

感谢@MaximSagaydachny说:

当您绑定到本地主机时,没有人能够连接到服务器。尝试 '0.0.0.0' 而不是 'localhost'

我刚刚更改了下面的行,使其在使用 flutter 的两部 Android 手机之间工作,并通过此插件wifi_info_plugin获取路由器的 IP :

class Server {

  // ...

  start() async {
    runZoned(() async {
      // server = await ServerSocket.bind('localhost', 4040);
      server = await ServerSocket.bind('0.0.0.0', 4040);

      // ...
    }, onError: (e) {
      this.onError(e);
    });
  }

  // ...
}
Run Code Online (Sandbox Code Playgroud)