Using Flutter App to run SocketServer and communicate with other phone via Socket

5,911

Thanks to @MaximSagaydachny who said :

no-one will be able to connect to the server when you bind to localhost. try '0.0.0.0' instead of 'localhost'

I've just changed the line below to make it work between two Android Phones using flutter and getting the IP of the Router through this plugin wifi_info_plugin :

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);
    });
  }

  // ...
}
Share:
5,911
Admin
Author by

Admin

Updated on December 18, 2022

Comments

  • Admin
    Admin over 1 year

    I'm trying to make a group of phones (can be Android or iOS in the same group) communicate with each other, and a group can reach 30 devices at the same time, and I have no access to external networks such as Internet or cellular network.

    I would like to have this kind of structure of phones communicating by Wifi or Bluetooth or other protocol if you have any suggestions.

    Architecture

    For this, I've tried Websocket, but I didn't succeed to run a WebSocket server through Flutter on a Phone.

    Later, I've found the SocketServer Dart Class which allow to manipulate TCP Sockets. On a PC I was able to communicate between a client and a server on the same machine.

    But as soon as I try to communicate between phones or a phone and the PC with the valid server, I have a SocketException which tells me that the connection as been refused by the host on the given port :

    SocketException: OS Error : Connection refused, erno = 111, address = 172.20.10.4, port 44518
    

    Here is my code :

    Server Class

    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);
        });
      }
    }
    

    Client Class

    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;
        }
      }
    }
    

    Models

    import 'dart:typed_data';
    
    typedef DynamicCallback(dynamic data);
    typedef Uint8ListCallback(Uint8List data);
    

    Server Page

    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),
                    )
                  ],
                ),
              ),
            ],
          ),
        );
      }
    }
    

    Client Page

    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),
                    )
                  ],
                ),
              ),
            ],
          ),
        );
      }
    }
    
    • Maxim Sagaydachny
      Maxim Sagaydachny about 4 years
      no-one will be able to connect to the server when you bind to localhost. try '0.0.0.0' instead of 'localhost'
    • abyesilyurt
      abyesilyurt about 2 years
      How do you find the IP of the master phone (wifi hotspot)?
    • Arthur Eudeline
      Arthur Eudeline about 2 years
      You can use a package like pub.dev/packages/ping_discover_network to scan the local network and find IP that has a specific port open. This package may not be the more recent option to do this, it's been a long time since I didn't try this code!
  • Siddharth Agrawal
    Siddharth Agrawal over 2 years
    I am still getting the same error, could you please tell if you did anything else differently? I am using your code only
  • Arthur Eudeline
    Arthur Eudeline over 2 years
    @SiddharthAgrawal From last I remember no, it was the only thing I’ve done to make it work but it was just a very light proof of concept I don’t even think I have the code anymore sorry… maybe something changed because 90% of the code was coming from Dart and Flutter core
  • Siddharth Agrawal
    Siddharth Agrawal over 2 years
    Yeah I actually got it to work. I see you are connected to the same network while in my case, one of the phone's was offering hotspot. But I was able to sort it out. Thank you so much for your help
  • Arthur Eudeline
    Arthur Eudeline over 2 years
    @SiddharthAgrawal I think I was able to communicate between an Android and an iPhone devices and the Android was the wifi hotspot and the iPhone was directly connected to it so I was sending messages directly to the gateway IP address, if you did something special to make it work please add it below for futures people I’ll upvote it! Great that it worked for you :)
  • Siddharth Agrawal
    Siddharth Agrawal over 2 years
    Im using the network info plus, which for some reason returns null on trying to get gateway ip. But luckily for my case, I will have a fixed IP on 1 phone. Also, I would like to mention, the default IP for android hotspot is 192.168.43.1, unless the device creator has changed it.
  • Siddharth Agrawal
    Siddharth Agrawal over 2 years
    Was 0.0.0.0 working for you? It didnt work for me. I tried setting 0.0.0.0 on both client and server side, but it gave the socket exception mentioned in the question
  • Arthur Eudeline
    Arthur Eudeline over 2 years
    Yeah it worked for me I was using localhost as a first try, weird for the null gateway maybe it is plateform specific 🤔
  • Arthur Eudeline
    Arthur Eudeline over 2 years
    I think there is something like on iOS you can’t get the MAC address of a wifi spot but on Android you can
  • Siddharth Agrawal
    Siddharth Agrawal over 2 years
    Yeah iOS doesnt offer the hotspot gateway, its very reserved in what it offers while android offers everything. But after SDK 25, it removed a lot of control from hotspot
  • Arthur Eudeline
    Arthur Eudeline over 2 years
    @SiddharthAgrawal i think you still can get the gateway IP dynamically by running a local network scan on a specific port with something like that pub.dev/packages/ping_discover_network_forked sorry I don’t remember the package I was using for doing this, don’t know if you have tried that