dart-lang / io

Utilities for the Dart VM's dart:io.
https://pub.dev/packages/io
BSD 3-Clause "New" or "Revised" License
49 stars 15 forks source link

Loss of Data when sending over Socket #66

Open vilamonon opened 3 years ago

vilamonon commented 3 years ago

I want to send Data from my Dart Application on Machine A to the Dart Application on Machine B using a TCP Socket over Darts Socket Class. The Sending also works theoretically except that I loose some Packages that are sent from Machine A on Machine B...

To handle that I implemented then that Machine B sends an Acknowledge of every Object it receives and Machine A resends an Object it get's no Acknowledge of.

Since I've created now a Websocket interface on Machine A for Clients, where the sending works without some Sort of Acknowledgement Implementation. I'm wondering why the Underlying Protocols of the Sockets(TCP/IP? and Ethernet? I guess) don't take care of Packageloss in my Socket Implementation?

Even though in the Documentation of SecureSocket it states:

A high-level class for communicating securely over a TCP socket

could it be that it somehow still uses UDP from my Socket?

this is how I open up and await a single connection from B:

  void start(config) {
    try {
      var securityContext = SecurityContext()
        ..useCertificateChain(config['certchain'])
        ..usePrivateKey(config['privatekey']);

      int portSend = config['port_sender'];
      int portRecive = config['port_reciver'];

      SecureServerSocket.bind(
              InternetAddress.anyIPv4, portSend, securityContext)
          .then((secureSocket) => secureSocket.listen((socket) {
                _writeSocket = socket;
                _writeSocket.setOption(SocketOption.tcpNoDelay, true);
              }));
    } catch (e) {
      print(e);
    }
  }

then B connects to A with this Code here:

  void start(config) async {
    try {
      _config = config;
      await SecureSocket.connect(config['ipaddress'], config['port_receiver'],
          onBadCertificate: (X509Certificate c) => true).then((socket) {
        print('connected socket');
        _listenSocket = socket;
        _listenSocket.encoding = utf8;
        receive();
      });
    } catch (e) {
      print(e);
      Timer(const Duration(seconds: 15), () {
        start(_config);
      });
    }
  }

And listens on Data after Connecting:

  void receive() {
    print('listening started on ${DateTime.now()}');
    try {
      _listenSocket.listen((data) => onData(data), onDone: onDone);
    } catch (e) {
      print(e);
    }
  }

The sending happens with the following Methods; the send Method gets triggered by Events (Here I tried several different approaches which none really worked like socket.flush() no flush I also do them sequentially right now so the flush can properly be awaited.)

  void send(DataPackage event) {

  toBeSentToB.add(PriorityQueueElement(event, PriorityQueueElement.highPriority));
    _sendPackages();
  }

  void _sendPackages() async {
    if (isSending) {
      return;
    }
    isSending = true;
    while (toBeSentToBackend.isNotEmpty) {
      PriorityQueueElement sendNext = toBeSentToBackend.removeFirst();
      try {
        _writeSocket.add(Uint8List.fromList(utf8.encode(json.encode(sendNext.dataPackage.toJson()) + _separator)));
        await _writeSocket.flush();
      } catch (e) {
        print(e);
      }
    }
    isSending = false;
  }

On B the OnData Method of the Socket stream should then be triggered, that looks as follow:

  void onData(Uint8List data) async {
    try {
      String utfData = utf8.decode(data);
      if (_buffer != null) {
        utfData = _buffer + utfData;
        _buffer = null;
      }
      List<String> listData = utfData.split(_separator);
      if (!utfData.endsWith(_separator)) {
        _buffer = listData.removeLast();
      }
      for (var element in listData) {
        if (element.isNotEmpty) {
          var recieved = DataPackage.fromJson(json.decode(element));
        }
      }
    } catch (e) {
      print(e);
    }
  }

The Data Objects I send are simple looking Json Objects:

@JsonSerializable()
class DataPackage {
  String uuid;
  String test;

  DataPackage();

  factory DataPackage.fromJson(Map<String, dynamic> json) =>
      _$DataPackageFromJson(json);
  Map<String, dynamic> toJson() => _$DataPackageToJson(this);
}