【问题标题】:Implement protocol - How to talk with server?实现协议 - 如何与服务器通信?
【发布时间】:2019-12-19 21:05:51
【问题描述】:

我正在尝试在 Dart 中实现一个协议。

基本上,我需要创建一些类似于ask_for_help的方法(见下文)

这是我目前的代码:

const int DEFAULT_TIMEOUT = 3;

class MySocket {
  /// The host server
  String _host;

  /// The TCP port number
  int _port;

  /// The status of the socket (connected or not)
  bool _isConnected = false;

  /// The connection timeout
  Duration _timeout;

  /// The connected socket (if any, see [_isConnected])
  Socket _socket;

  /// Establish a connection with the [host] on given [port]
  ///
  /// Throws a [SocketException] if connection cannot be established.
  Future<void> connect(String host, int port,
      {int timeout = DEFAULT_TIMEOUT}) async {
    _host = host;
    _port = port;

    _timeout = Duration(seconds: timeout);

    _socket = await Socket.connect(host, port, timeout: _timeout);
    _isConnected = true;

    _socket.listen(default_handler);
  }

  /// Send [cmd] command to the connected server
  void send_request(String cmd) {
    _socket.writeln(cmd);
  }

  /// Close the connection with the server
  void close() {
    _isConnected = false;
    _socket.close();
  }

  void ask_for_help() {
    var cmd = 'HELP';
    send_request(cmd);
    // Start listening to the socket
    // Wait for a response
    // Stop listening to the socket
    // Consume response from the Stream
    // Do something with that response (e.g print it on stdout)
  }
}

void default_handler(Uint8List message) {
  print('----- RESPONSE STARTS HERE -----');
  print(String.fromCharCodes(message).trim());
  print('----- RESPONSE ENDS HERE -----');
}

然后我有几个问题:

  1. 如何让ask_for_help 监听套接字直到收到响应?
  2. 如何确保每个方法都能读取正确的响应而不是另一个? (如果在服务器应答之前发送了多个请求)

关于第二个问题的备注:使用上面的代码,当我发送多个请求时,它们之间没有延迟,所有响应都聚集在一起。这意味着我不知道应该用什么函数来处理它。

欢迎提出任何建议。

【问题讨论】:

    标签: dart


    【解决方案1】:

    拥有一项进行实际通信的服务(即SocketService)。当某物想通过套接字发送消息时,它可以调用消息。然后公开一个其他人可以订阅的流对象以侦听返回的消息。

    class SocketService {
      SocketService _instance;
      SocketService._();
      factory SocketService() {
        if (_instance == null) _instance = SocketService._();
        return _instance;
      }
    
      final _streamController = StreamController.broadcast();
      Stream get stream => _streamController.stream;
    
      Future init() async {
        // TODO: Initialize the socket connection
        // TODO: Setup incoming messages to get routed to _onMessage
      }
    
      void _onMessage(dynamic message) {
        _streamController.add(message);
      }
    
      StreamSubscription listen(void Function(dynamic) onMessage) {
        return stream.listen(onMessage);
      }
    
      void sendMessage(dynamic message) {
        // TODO: Send the message across the socket connection
      }
    
      void close() {
        // TODO: Close the socket connection
        _streamController.close();
      }
    }
    

    其他地方:

    // Listen for incoming messages
    var subscription = SocketService().subscribe((msg) {
      print(msg);
    });
    
    // Send a message
    SocketService().sendMessage('This is a message');
    

    特定于套接字的内容会根据您用于处理连接本身的包而有所不同。这个服务类在上面被实现为一个过于简单的单例来理解这一点,但你可以用任何你想要的模式自己实现这个概念。

    在订阅者端,您还可以利用流转换过滤掉除您真正感兴趣的消息之外的任何消息:

    // Filter stream messages so only messages 
    // that are string objects get printed
    var subscription = SocketService().stream.where((msg) => msg is String).listen(msg) {
      print(msg);
    });
    

    传递给where方法的过滤谓词可以实现你想要的任何条件。

    仅供参考:如果您要在流和流操作方面做很多事情,我强烈推荐 rxdart 包。它使处理流比处理原始流更容易、更直观。

    【讨论】:

    • 首先,感谢您的回答。也许我没有正确理解它,但我看不出它如何解决我的问题。为了澄清这一点,我更新了我的原始帖子并包含了我编写的代码,以便您更好地理解我想要做什么。
    • @Nhqml 套接字连接本质上具有个性化的消息,因此仅凭连接无法判断哪个响应属于哪个消息。您将不得不在消息本身中包含一些元数据,服务器会在其响应中保留这些元数据,以便在您的应用收到消息后知道将消息路由到哪里。也许您可以将消息包装在“信封”中并生成与消息关联的 UUID,然后在响应中检查 UUID,如果匹配,则将其路由到需要去的地方。
    最近更新 更多