聊一聊 Flutter 中网络请求为么不阻塞主线

在学习 Flutter 过程中,一直有一个疑惑:既然 Flutter 是单线程模型,那在进行网络请求或 IO 操作时,为什么没有阻塞线程呢? 下面以网络请求为例来分析 Flutter 底层是如何处理的。

结论

这里省流先说结论。

http请求为例:

  • 主要利用Socket可以设置非阻塞的性质,避免如connectreadwrite等耗时操作阻塞线程。另外它还使用epoll机制用来获取和发送数据。
  • 对于不能利用非阻塞性质的操作,如域名转 ip,会通过SendPort将操作转换到线程中执行。

源码分析

这里以建立链接开始分析。假设发送的是http请求。

lib_http\http_impl.dart

dart 复制代码
Future<_ConnectionInfo> connect(Uri uri, String uriHost, int uriPort,
    _Proxy proxy, _HttpClient client, _HttpProfileData? profileData) {
...
Future<ConnectionTask<Socket>> connectionTask;
final cf = connectionFactory;
if (cf != null) {
...
} else {
  connectionTask = (isSecure && proxy.isDirect
      ? SecureSocket.startConnect(host, port,
          context: context,
          onBadCertificate: callback,
          keyLog: client._keyLog)
      : Socket.startConnect(host, port));
}
_connecting++;
return connectionTask.then((ConnectionTask<Socket> task) {
  _socketTasks.add(task);
  Future<Socket> socketFuture = task.socket;
  final Duration? connectionTimeout = client.connectionTimeout;
...
}

被标记为外部函数。

sdk\lib\io\socket.dart

dart 复制代码
  external static Future<ConnectionTask<Socket>> _startConnect(host, int port,
      {sourceAddress, int sourcePort = 0});

具体的实现在这里 sdk\lib_internal\vm\bin\socket_patch.dart

dart 复制代码
  @patch
  static Future<ConnectionTask<Socket>> _startConnect(dynamic host, int port,
      {dynamic sourceAddress, int sourcePort = 0}) {
    return RawSocket.startConnect(host, port,
            sourceAddress: sourceAddress, sourcePort: sourcePort)
        .then((rawTask) {
      Future<Socket> socket =
          rawTask.socket.then((rawSocket) => new _Socket(rawSocket));
      return new ConnectionTask<Socket>._(socket, rawTask._onCancel);
    });
  }
}

继续调用_NativeSocket中的startConnect

dart 复制代码
  static Future<ConnectionTask<_RawSocket>> startConnect(
      dynamic host, int port, dynamic sourceAddress, int sourcePort) {
    return _NativeSocket.startConnect(host, port, sourceAddress, sourcePort)
        .then((ConnectionTask<_NativeSocket> nativeTask) {
      final Future<_RawSocket> raw =
          nativeTask.socket.then((_NativeSocket nativeSocket) {
        if (!const bool.fromEnvironment("dart.vm.product")) {
          _SocketProfile.collectNewSocket(nativeSocket.nativeGetSocketId(),
              _tcpSocket, nativeSocket.address, port);
        }
        return _RawSocket(nativeSocket);
      });
      return ConnectionTask<_RawSocket>._(raw, nativeTask._onCancel);
    });
  }

这里 host 如果是域名,则需要转换为 ip 地址。我们分析 host 是域名的情况。

dart 复制代码
  static Future<ConnectionTask<_NativeSocket>> startConnect(
      dynamic host, int port, dynamic sourceAddress, int sourcePort) {
    ...
    return new Future.value(host).then<ConnectionTask<_NativeSocket>>((host) {
      if (host is String) {
        // Attempt to interpret the host as a numeric address
        // (e.g. "127.0.0.1"). This will prevent [InternetAddress.lookup] from
        // generating an unnecessary address in a different address family e.g.
        // `InternetAddress.lookup('127.0.0.1', InternetAddressType.IPv6)`
        // may return `InternetAddress('::ffff:127.0.0.1').
        host = _InternetAddress.tryParse(host) ?? host;
      }
      if (host is _InternetAddress) {
        return tryConnectToResolvedAddresses(host, port, source, sourcePort,
            Stream.value(<_InternetAddress>[host]), stackTrace);
      }
      final hostname = host as String;
      // 将域名转换为 ip
      final Stream<List<InternetAddress>> addresses =
          staggeredLookup(hostname, source);
      return tryConnectToResolvedAddresses(
          host, port, source, sourcePort, addresses, stackTrace);
    });
  }

将域名解析为 ipv4 和 ipv6。重点是lookup函数。

dart 复制代码
  static Stream<List<InternetAddress>> staggeredLookup(
      String host, _InternetAddress? source) {
    final controller = StreamController<List<InternetAddress>>(sync: true);

    controller.onListen = () {
      // Completed when there are no further addresses, or when the returned
      // stream is canceled,
      // The latter signals that no further addresses are needed.
      // When both completers are completed, one way or another, the stream is
      // closed.
      final ipv4Completer = Completer<void>();
      final ipv6Completer = Completer<void>();
      // Only report an error if no address lookups were sucessful.
      var anySuccess = false;

      void lookupAddresses(
          InternetAddressType type, Completer<void> done) async {
        try {
          // 开始解析域名
          final addresses = await lookup(host, type: type);
          anySuccess = true;
          if (done.isCompleted) {
            // By the time lookup is done, [connectNext] might have
            // been able to connect to one of the resolved addresses.
            return;
          }
          controller.add(addresses);
          done.complete();
        } catch (e, st) {
          if (done.isCompleted) {
            // By the time lookup is done, [connectNext] might have
            // been able to connect to one of the resolved addresses.
            return;
          }
          done.completeError(e, st);
        }
      }

      const concurrentLookupDelay = Duration(milliseconds: 10);
      Timer? ipv6LookupDelay;
      // 解析 ipv4
      lookupAddresses(InternetAddressType.IPv4, ipv4Completer);
      if (source != null && source.type == InternetAddressType.IPv4) {
        // Binding to an IPv4 address and connecting to an IPv6 address will
        // never work.
        ipv6Completer.complete();
      } else { // ipv6 延迟 10 毫秒
        // Introduce a delay before IPv6 lookup in order to favor IPv4.
        ipv6LookupDelay = Timer(concurrentLookupDelay,
            () => lookupAddresses(InternetAddressType.IPv6, ipv6Completer));
      }
      // 等待全部解析完成。  
      Future.wait([ipv4Completer.future, ipv6Completer.future])
          .then((_) => controller.close(), onError: (e, st) {
        if (!anySuccess) {
          controller.addError(e, st);
        }
        controller.close();
      });
     ...
    };
    return controller.stream;
  }

这里通过_IOService调用到 c 层解析 ip,切换线程也在这个过程中。

_IOService 内部会创建SendPortRawReceivePort进行跨线程消息的发送和接收。

dart 复制代码
  static Future<List<InternetAddress>> lookup(String host,
      {InternetAddressType type = InternetAddressType.any}) {
    return _IOService._dispatch(_IOService.socketLookup, [host, type._value])
        .then((response) {
      if (isErrorResponse(response)) {
        throw createError(response, "Failed host lookup: '$host'");
      }// 将返回的 ip 地址包装为 _InternetAddress 保存到列表中
      return [
        for (List<Object?> result in (response as List).skip(1))
          _InternetAddress(
              InternetAddressType._from(result[0] as int),
              result[1] as String,
              host,
              result[2] as Uint8List,
              result[3] as int)
      ];
    });
  }

SendPort 创建

通过分析SendPort的创建过程,来看一下 c 层如何通过线程处理上层发送过来的事件。

发送事件,并设置接收回调。当前逻辑下发送的是_IOService.socketLookup事件。 sdk\lib_internal\vm\bin\io_service_patch.dart

dart 复制代码
  @patch
  static Future _dispatch(int request, List data) {
    int id;
    do {
      id = _getNextId(); // 作为 SendPort 的 id,从 0 自增
    } while (_messageMap.containsKey(id)); // 找到一个未使用的。
    // _servicePorts 管理 SendPort,复用或新建
    final SendPort servicePort = _servicePorts._getPort(id);
    _ensureInitialize(); // 初始化接收端及其回调。
    final Completer completer = new Completer();
    _messageMap[id] = completer;
    try {
      // 发送事件。
      servicePort.send(<dynamic>[id, _replyToPort, request, data]);
    } catch (error) {
      _messageMap.remove(id)!.complete(error);
      if (_messageMap.length == 0) {
        _finalize();
      }
    }
    return completer.future;
  }

这里复用不成,就会创建一个新的SendPort

dart 复制代码
  SendPort _getPort(int forRequestId) {
    assert(!_usedPorts.containsKey(forRequestId));
    // 每个 isolate 最多 32 个 port。
    if (_freePorts.isEmpty && _ports.length < maxPorts) {
    // 没有空闲的 port 创建一个新的。
      final SendPort port = _newServicePort();
      _ports.add(port); // 记录 port
      _useCounts.add(0); // 记录使用 port 的数量。
      // 追加到闲置列表
      _freePorts.add(_ports.length - 1);
    }
    // 如果有空闲的,则取出最后一个。
    // 否则根据 id 与 32 取余,找一个使用中的 port 进行共用。
    // Use a free port if one exists.
    final index = _freePorts.isNotEmpty
        ? _freePorts.removeLast()
        : forRequestId % maxPorts;
    _usedPorts[forRequestId] = index; // index 为 _ports 的索引
    _useCounts[index]++;
    return _ports[index];
  }

dart 调用本地函数

这里看如何调用到本地函数的。

注解vm:external-name 可以为一个extern类型的函数指定一个名字。 底层通过当前库注册的XXXResolver函数查找对应的本地函数实现。不同的库,会注册不同的Resolver函数。

这里指定的名字为IOService_NewServicePort

dart 复制代码
  @pragma("vm:external-name", "IOService_NewServicePort")
  external static SendPort _newServicePort();

io_natives 中利用宏声明函数。这里为文件、Socket等相关函数进行声明。

runtime\bin\io_natives.cc

c++ 复制代码
#define IO_NATIVE_LIST(V)                                                      \
...
  V(ResourceHandleImpl_toRawDatagramSocket, 1)                                 \
  V(InternetAddress_Parse, 1)                                                  \
  V(InternetAddress_ParseScopedLinkLocalAddress, 1)                            \
  V(InternetAddress_RawAddrToString, 1)                                        \
  V(IOService_NewServicePort, 0)                                               \
  V(Namespace_Create, 2)                                                       \
  V(Namespace_GetDefault, 0)                                                   \
...
IO_NATIVE_LIST(DECLARE_FUNCTION);

runtime\bin\builtin.h

c++ 复制代码
#define FUNCTION_NAME(name) Builtin_##name
#define REGISTER_FUNCTION(name, count) {"" #name, FUNCTION_NAME(name), count},
#define DECLARE_FUNCTION(name, count)                                          \
  extern void FUNCTION_NAME(name)(Dart_NativeArguments args);

宏扩展后会生成如下的函数声明

c++ 复制代码
Builtin_IOService_NewServicePort(Dart_NativeArguments args);

定义结构体,用来搜索。 通过宏,为结构体赋值。

例:

name_ 为IOService_NewServicePort。表示查找的函数名字。

function_ 为函数引用即Builtin_IOService_NewServicePort(Dart_NativeArguments args) argument_count_ 表示函数的参数。

c++ 复制代码
static const struct NativeEntries {
  const char* name_;
  Dart_NativeFunction function_;
  int argument_count_;
} IOEntries[] = {IO_NATIVE_LIST(REGISTER_FUNCTION)};

这里查找名字对应的函数实现。如果找到,返回对应的函数引用,之后就可以从 dart 调用到 c 中的函数。

c++ 复制代码
Dart_NativeFunction IONativeLookup(Dart_Handle name,
                                   int argument_count,
                                   bool* auto_setup_scope) {
  const char* function_name = nullptr;
  Dart_Handle result = Dart_StringToCString(name, &function_name);
  ASSERT(!Dart_IsError(result));
  ASSERT(function_name != nullptr);
  ASSERT(auto_setup_scope != nullptr);
  *auto_setup_scope = true;
  int num_entries = sizeof(IOEntries) / sizeof(struct NativeEntries);
  for (int i = 0; i < num_entries; i++) {
    const struct NativeEntries* entry = &(IOEntries[i]);
    if ((strcmp(function_name, entry->name_) == 0) &&
        (entry->argument_count_ == argument_count)) {
      return reinterpret_cast<Dart_NativeFunction>(entry->function_);
    }
  }
  return nullptr;
}

由上面我们知道IOService_NewServicePort对应的函数是Builtin_IOService_NewServicePort 创建 port 并返回引用到 dart 层。 runtime\bin\io_service.cc

c++ 复制代码
void FUNCTION_NAME(IOService_NewServicePort)(Dart_NativeArguments args) {
  Dart_SetReturnValue(args, Dart_Null());
  Dart_Port service_port = IOService::GetServicePort();
  if (service_port != ILLEGAL_PORT) {
    // Return a send port for the service port.
    Dart_Handle send_port = Dart_NewSendPort(service_port);
    Dart_SetReturnValue(args, send_port);
  }
}

通过SendPort发送过来的消息会触发IOServiceCallback来处理。

注:这个回调运行在子线程中。

c++ 复制代码
Dart_Port IOService::GetServicePort() {
  return Dart_NewNativePort("IOService", IOServiceCallback, true);
}

创建NativeMessageHandlerPort,并将它们相互关联。
NativeMessageHandler主要是对上面IOServiceCallback的包装,并对消息管理和分发。

另外可以看到,在准备工作完成后,就调用NativeMessageHandlerRun 方法,并传入一个线程池,这意味着任务将会在子线程中执行。

看到这儿可以知道通过_IOService._dispatch发送的消息,会都在线程中执行。

因此这里的域名转 ip 就是在子线程中执行的。

runtime\vm\native_api_impl.cc

c++ 复制代码
DART_EXPORT Dart_Port Dart_NewNativePort(const char* name,
                                         Dart_NativeMessageHandler handler,
                                         bool handle_concurrently) {
  if (name == nullptr) {
    name = "<UnnamedNativePort>";
  }
  if (handler == nullptr) {
    OS::PrintErr("%s expects argument 'handler' to be non-null.\n",
                 CURRENT_FUNC);
    return ILLEGAL_PORT;
  }
  if (!Dart::SetActiveApiCall()) {
    return ILLEGAL_PORT;
  }
  // Start the native port without a current isolate.
  IsolateLeaveScope saver(Isolate::Current());

  NativeMessageHandler* nmh = new NativeMessageHandler(name, handler);
  //创建 Dart_Port 并与 handler 相互关联。后面可以根据 port 在 PortMap 中找到对应的 handler。
  Dart_Port port_id = PortMap::CreatePort(nmh);
  if (port_id != ILLEGAL_PORT) {
    // 线程池作为参数。
    if (!nmh->Run(Dart::thread_pool(), nullptr, nullptr, 0)) {
      PortMap::ClosePort(port_id);
      nmh->RequestDeletion();
      port_id = ILLEGAL_PORT;
    }
  }
  Dart::ResetActiveApiCall();
  return port_id;
}

这里调用线程池的Run方法,并将自身包装为MessageHandlerTask放入线程池。

runtime\vm\message_handler.cc

c++ 复制代码
bool MessageHandler::Run(ThreadPool* pool,
                         StartCallback start_callback,
                         EndCallback end_callback,
                         CallbackData data) {
...             
  pool_ = pool;
  start_callback_ = start_callback;
  end_callback_ = end_callback;
  callback_data_ = data;
  task_running_ = true;
  // 以 MessageHandler 为参数隐式创建 MessageHandlerTask
  bool result = pool_->Run<MessageHandlerTask>(this);
...
}

ThreadPool中的run方法会继续调用RunImpl

复用或创建一个新的线程执行任务。

找到后会返回一个Worker,并调用它的StartThread来执行任务。

runtime\vm\thread_pool.cc

c++ 复制代码
bool ThreadPool::RunImpl(std::unique_ptr<Task> task) {
  Worker* new_worker = nullptr;
  {
    MonitorLocker ml(&pool_monitor_);
    if (shutting_down_) {
      return false;
    }
    new_worker = ScheduleTaskLocked(&ml, std::move(task));
  }
  if (new_worker != nullptr) {
    new_worker->StartThread(); // 执行任务
  }
  return true;
}

将任务添加到任务队列中。

如果有空闲线程,则唤醒该线程执行任务。

如果有设置最大线程数,当达到最大线程数时,需要等待。否则创建一个新的线程。

runtime\vm\thread_pool.cc

c++ 复制代码
ThreadPool::Worker* ThreadPool::ScheduleTaskLocked(MonitorLocker* ml,
                                                   std::unique_ptr<Task> task) {
  // Enqueue the new task.
  tasks_.Append(task.release());
  pending_tasks_++;
  ASSERT(pending_tasks_ >= 1);

  // Notify existing idle worker (if available).
  if (count_idle_ >= pending_tasks_) {
    ASSERT(!idle_workers_.IsEmpty());
    ml->Notify();
    return nullptr;
  }

  // If we have maxed out the number of threads running, we will not start a
  // new one.
  if (max_pool_size_ > 0 && (count_idle_ + count_running_) >= max_pool_size_) {
    if (!idle_workers_.IsEmpty()) {
      ml->Notify();
    }
    return nullptr;
  }

  // Otherwise start a new worker.
  auto new_worker = new Worker(this);
  idle_workers_.Append(new_worker);
  count_idle_++;
  return new_worker;
}

最终会调用到上面传入的IOServiceCallback回调函数,来处理事件。 runtime\vm\native_message_handler.cc

c++ 复制代码
MessageHandler::MessageStatus NativeMessageHandler::HandleMessage(
    std::unique_ptr<Message> message) {
  if (message->IsOOB()) {
    // We currently do not use OOB messages for native ports.
    UNREACHABLE();
  }
  // We create a native scope for handling the message.
  // All allocation of objects for decoding the message is done in the
  // zone associated with this scope.
  ApiNativeScope scope;
  Dart_CObject* object = ReadApiMessage(scope.zone(), message.get());
  (*func())(message->dest_port(), object);
  return kOK;
}

与前面函数声明类似,这里也利用宏来扩展switch的子语句。

runtime\bin\io_service.cc

c++ 复制代码
void IOServiceCallback(Dart_Port dest_port_id, Dart_CObject* message) {
  Dart_Port reply_port_id = ILLEGAL_PORT;
  CObject* response = CObject::IllegalArgumentError();
  CObjectArray request(message);
  if ((message->type == Dart_CObject_kArray) && (request.Length() == 4) &&
      request[0]->IsInt32() && request[1]->IsSendPort() &&
      request[2]->IsInt32() && request[3]->IsArray()) {
    CObjectInt32 message_id(request[0]);
    CObjectSendPort reply_port(request[1]);
    CObjectInt32 request_id(request[2]);
    CObjectArray data(request[3]);
    reply_port_id = reply_port.Value();
    switch (request_id.Value()) {
      IO_SERVICE_REQUEST_LIST(CASE_REQUEST); // 利用宏进行扩展
      default:
        UNREACHABLE();
    }
  }
  // 设置返回结果  
  CObjectArray result(CObject::NewArray(2));
  result.SetAt(0, request[0]);
  result.SetAt(1, response);
  ASSERT(reply_port_id != ILLEGAL_PORT);
  Dart_PostCObject(reply_port_id, result.AsApiCObject());
}

结合上面逻辑,这里列出的操作都是在单独线程中执行的。

可以看到主要有文件、网络和目录这三类相关操作。

但奇怪的是socket对应的仅有三个,像是connectreadwrite 这些操作哪去了? 带着疑问我们下面接续分析。

这里将声明全部列出来。

runtime\bin\io_service.h

c++ 复制代码
#define IO_SERVICE_REQUEST_LIST(V)                                             \
  V(File, Exists, 0)                                                           \
  V(File, Create, 1)                                                           \
  V(File, Delete, 2)                                                           \
  V(File, Rename, 3)                                                           \
  V(File, Copy, 4)                                                             \
  V(File, Open, 5)                                                             \
  V(File, ResolveSymbolicLinks, 6)                                             \
  V(File, Close, 7)                                                            \
  V(File, Position, 8)                                                         \
  V(File, SetPosition, 9)                                                      \
  V(File, Truncate, 10)                                                        \
  V(File, Length, 11)                                                          \
  V(File, LengthFromPath, 12)                                                  \
  V(File, LastAccessed, 13)                                                    \
  V(File, SetLastAccessed, 14)                                                 \
  V(File, LastModified, 15)                                                    \
  V(File, SetLastModified, 16)                                                 \
  V(File, Flush, 17)                                                           \
  V(File, ReadByte, 18)                                                        \
  V(File, WriteByte, 19)                                                       \
  V(File, Read, 20)                                                            \
  V(File, ReadInto, 21)                                                        \
  V(File, WriteFrom, 22)                                                       \
  V(File, CreateLink, 23)                                                      \
  V(File, DeleteLink, 24)                                                      \
  V(File, RenameLink, 25)                                                      \
  V(File, LinkTarget, 26)                                                      \
  V(File, Type, 27)                                                            \
  V(File, Identical, 28)                                                       \
  V(File, Stat, 29)                                                            \
  V(File, Lock, 30)                                                            \
  V(File, CreatePipe, 31)                                                      \
  V(Socket, Lookup, 32)                                                        \
  V(Socket, ListInterfaces, 33)                                                \
  V(Socket, ReverseLookup, 34)                                                 \
  V(Directory, Create, 35)                                                     \
  V(Directory, Delete, 36)                                                     \
  V(Directory, Exists, 37)                                                     \
  V(Directory, CreateTemp, 38)                                                 \
  V(Directory, ListStart, 39)                                                  \
  V(Directory, ListNext, 40)                                                   \
  V(Directory, ListStop, 41)                                                   \
  V(Directory, Rename, 42)                                                     \

runtime\bin\io_service.cc

c++ 复制代码
#define CASE_REQUEST(type, method, id)                                         \
  case IOService::k##type##method##Request:                                    \
    response = type::method##Request(data);                                    \
    break;

例:上面宏扩展为:

c++ 复制代码
  case IOService::kSocketLookup32:
    response = Socket::LookupRequest(data);
    break;

接上的分析,会调用LookupRequest来进行域名的解析。
SocketBase::LookupAddress会根据平台有不同的实现。

以 android 平台为例,是通过调用getaddrinfo函数将域名转换为 ip 地址。

runtime\bin\socket.cc

c++ 复制代码
CObject* Socket::LookupRequest(const CObjectArray& request) {
...
    CObject* result = nullptr;
    OSError* os_error = nullptr;
    AddressList<SocketAddress>* addresses =
        SocketBase::LookupAddress(host.CString(), type.Value(), &os_error);
    if (addresses != nullptr) {
      CObjectArray* array =
          new CObjectArray(CObject::NewArray(addresses->count() + 1));
      array->SetAt(0, new CObjectInt32(CObject::NewInt32(0)));
      for (intptr_t i = 0; i < addresses->count(); i++) {
        SocketAddress* addr = addresses->GetAt(i);
        CObjectArray* entry = new CObjectArray(CObject::NewArray(4));
       ...
      }
      result = array;
      delete addresses;
    } else {
      result = CObject::NewOSError(os_error);
      delete os_error;
    }
    return result;
  }
  return CObject::IllegalArgumentError();
}

回到 dart 层的_IOService中,看接收部分的处理。

这里仅将对应消息从_messageMap中移除。

dart 复制代码
  static void _ensureInitialize() {
    if (_receivePort == null) {
      _receivePort = new RawReceivePort(null, 'IO Service');
      _replyToPort = _receivePort!.sendPort;
      _receivePort!.handler = (List<Object?> data) {
        assert(data.length == 2);
        _messageMap.remove(data[0])!.complete(data[1]);
        _servicePorts._returnPort(data[0] as int);
        if (_messageMap.length == 0) {
          _finalize();
        }
      };
    }
  }

创建链接

上面留了一个疑问,为什么仅有三个socket操作在线程中执行,其余操作怎么处理呢?

这里接着分析链接和读写部分如何处理。

需要开启线程处理的三个操作。

c++ 复制代码
  V(Socket, Lookup, 32)                                                        \
  V(Socket, ListInterfaces, 33)                                                \
  V(Socket, ReverseLookup, 34)                                                 \

这里来接着分析域名解析完成后的流程。

这里拆成两部分,先看建立链接部分。

注:在创建链接后,由于是非阻塞的,所以设置了一个 Timer 来判断是否超时。

dart 复制代码
  static ConnectionTask<_NativeSocket> tryConnectToResolvedAddresses(
      dynamic host,
      int port,
      _InternetAddress? source,
      int sourcePort,
      Stream<List<InternetAddress>> addresses,
      StackTrace callerStackTrace) {
      
    ...
    Object? createConnection(InternetAddress address, _InternetAddress? source,
        _NativeSocket socket) {
       if (address.type == InternetAddressType.unix) {
        ...
      } else {
        final address_ = address as _InternetAddress;
        if (source == null && sourcePort == 0) {
          // 创建链接
          connectionResult = socket.nativeCreateConnect(
              address_._in_addr, port, address_._scope_id);
        } else {
        ...
        }
        assert(connectionResult == true || connectionResult is OSError);
      }
      return connectionResult;       
    }
    ...
    connectNext() {
    ...
      final address = pendingLookedUp.removeFirst();
      final socket = new _NativeSocket.normal(address);
      // Will contain values of various types representing the result
      // of trying to create a connection.
      // A value of `true` means success, everything else means failure.
      final Object? connectionResult =
          createConnection(address, source, socket); // 创建链接
      ...   
      // 设置链接超时时间,25 或 250 毫秒
      final duration =
          address.isLoopback ? _retryDurationLoopback : _retryDuration;
      timer = new Timer(duration, connectNext); // 超时调用 connectNext 重新链接。
      connecting.add(socket);  // 记录正在链接的 socket 实例。   
      // 设置 socket 发送的事件,这里注册了写和出错的事件。
      socket.setHandlers(write: () { // write 回调触发,表示 socket 以准备好发送事件
      ...
      );
      socket.setListening(read: false, write: true);
    }
    ...
    connectNext();
    return new ConnectionTask<_NativeSocket>._(result.future, onCancel);    

这里根据不同平台,接口略有不同,但大同小异。

android平台为例,创建socket时会将它设置为非阻塞,因此它并不会卡住当前线程。由于是非阻塞对于读写也是一样,后下面读为例继续分析。

sdk\lib_internal\vm\bin\socket_patch.dart

dart 复制代码
  @pragma("vm:external-name", "Socket_CreateConnect")
  external nativeCreateConnect(Uint8List addr, int port, int scope_id);

runtime\bin\socket.cc

c++ 复制代码
void FUNCTION_NAME(Socket_CreateConnect)(Dart_NativeArguments args) {
  ...
  intptr_t socket = Socket::CreateConnect(addr);
  OSError error;
  if (socket >= 0) {
    Socket::SetSocketIdNativeField(Dart_GetNativeArgument(args, 0), socket,
                                   Socket::kFinalizerNormal);
    Dart_SetReturnValue(args, Dart_True());
  } else {
    Dart_SetReturnValue(args, DartUtils::NewDartOSError(&error));
  }
}

以 android 平台实现为例。

runtime\bin\socket_android.cc

c++ 复制代码
intptr_t Socket::CreateConnect(const RawAddr& addr) {
  intptr_t fd = Create(addr);
  if (fd < 0) {
    return fd;
  }

  return Connect(fd, addr);
}

这里的重点是,它会将 socket 设置为非阻塞。

c++ 复制代码
static intptr_t Create(const RawAddr& addr) {
  intptr_t fd;
  fd = NO_RETRY_EXPECTED(socket(addr.ss.ss_family, SOCK_STREAM, 0));
  if (fd < 0) {
    return -1;
  }
  if (!FDUtils::SetCloseOnExec(fd) || !FDUtils::SetNonBlocking(fd)) {
    FDUtils::SaveErrorAndClose(fd);
    return -1;
  }
  return fd;
}

创建 socket 链接,由于是非阻塞的,所以它会立即返回结果。

c++ 复制代码
static intptr_t Connect(intptr_t fd, const RawAddr& addr) {
  intptr_t result = TEMP_FAILURE_RETRY(
      connect(fd, &addr.addr, SocketAddress::GetAddrLength(addr)));
  if ((result == 0) || (errno == EINPROGRESS)) {
    return fd;
  }
  FDUtils::SaveErrorAndClose(fd);
  return -1;
}

读写事件处理

这一部分,主要看一下对socket的读写的处理。

下面接着看链接建立后的处理过程。

调用setHandlers设置接收socket 的可写和错误事件。

触发可写事件说明socket已经和对端建立好了链接,可以传输数据了。

dart 复制代码
  static ConnectionTask<_NativeSocket> tryConnectToResolvedAddresses(
      dynamic host,
      int port,
      _InternetAddress? source,
      int sourcePort,
      Stream<List<InternetAddress>> addresses,
      StackTrace callerStackTrace) {
    ...
    connectNext() {
    ...
      // 设置 socket 发送的事件,这里注册了写和出错的事件。
      socket.setHandlers(write: () { // write 回调触发,表示 socket 以准备好发送事件
        // First remote response on connection.
        // If error, drop the socket and go to the next address.
        // If success, complete with the socket
        // and stop all other open connection attempts.
        connecting.remove(socket); // 移除,表示链接以创建完成。
        // From 'man 2 connect':
        // After select(2) indicates writability, use getsockopt(2) to read
        // the SO_ERROR option at level SOL_SOCKET to determine whether
        // connect() completed successfully (SO_ERROR is zero) or
        // unsuccessfully.
        final osError = socket.nativeGetError(); // 获取错误信息
        if (osError != null) {
          socket.close(); // 关闭链接
          error ??= osError;
          connectNext(); // 重新建立链接
          return;
        }
        // Connection success!
        // Stop all other connecting sockets and the timer.
        timer!.cancel(); // 取消链接超时
        socket.setListening(read: false, write: false);
        for (var s in connecting) { // 关闭正在进行中的链接。
          s.close();
          s.setHandlers();
          s.setListening(read: false, write: false);
        }
        connecting.clear();
        addressesSubscription.cancel();
        if (!result.isCompleted) {
          // Might be already completed via onCancel
          result.complete(socket);
        }
      }, error: (e, st) {
        connecting.remove(socket);
        socket.close();
        socket.setHandlers();
        socket.setListening(read: false, write: false);
        // Keep first error, if present.
        error ??= e;
        connectNext(); // Try again after failure to connect.
      });
      socket.setListening(read: false, write: true);
    }
    ...     
 }

设置是否接收可读或可写的事件通知。

dart 复制代码
  void setListening({bool read = true, bool write = true}) {
    sendReadEvents = read;
    sendWriteEvents = write;
    if (read) issueReadEvent();
    if (write) issueWriteEvent();
    if (!flagsSent && !isClosing) {
      flagsSent = true;
      int flags = 1 << setEventMaskCommand;
      if (!isClosedRead) flags |= 1 << readEvent;
      if (!isClosedWrite) flags |= 1 << writeEvent;
      sendToEventHandler(flags);
    }
  }

创建RawReceivePort。并将对应的sendPort传到 c 层。

dart 复制代码
  void sendToEventHandler(int data) {
    int fullData = (typeFlags & typeTypeMask) | data;
    assert(!isClosing);
    connectToEventHandler(); 
    _EventHandler._sendData(this, eventPort!.sendPort, fullData);
  }

multiplex被设置为处理消息的回调函数。

dart 复制代码
  void connectToEventHandler() {
    assert(!isClosed);
    if (eventPort == null) {
      eventPort = new RawReceivePort(multiplex, 'Socket Event Handler');
    }
  }

sdk\lib_internal\vm\bin\eventhandler_patch.dart

dart 复制代码
  @patch
  @pragma("vm:external-name", "EventHandler_SendData")
  external static void _sendData(Object? sender, SendPort sendPort, int data);

对应EventHandler_SendData的本地函数。

这里会将 dart 层中的socket实例转换为对应的 c 层实例,其实是fd

runtime\bin\eventhandler.cc

c++ 复制代码
void FUNCTION_NAME(EventHandler_SendData)(Dart_NativeArguments args) {
  // Get the id out of the send port. If the handle is not a send port
  // we will get an error and propagate that out.
  Dart_Handle handle = Dart_GetNativeArgument(args, 1);
  Dart_Port dart_port;
  handle = Dart_SendPortGetId(handle, &dart_port);
  if (Dart_IsError(handle)) {
    Dart_PropagateError(handle);
    UNREACHABLE();
  }
  // sender 对应的是 dart 中 socket 实例
  Dart_Handle sender = Dart_GetNativeArgument(args, 0);
  intptr_t id;
  if (Dart_IsNull(sender)) {
    id = kTimerId;
  } else {
    // 转换为 c 中的 socket 实例
    Socket* socket = Socket::GetSocketIdNativeField(sender);
    ASSERT(dart_port != ILLEGAL_PORT);
    socket->set_port(dart_port);
    socket->Retain();  // inc refcount before sending to the eventhandler.
    id = reinterpret_cast<intptr_t>(socket);
  }
  int64_t data = DartUtils::GetIntegerValue(Dart_GetNativeArgument(args, 2));
  event_handler->SendData(id, dart_port, data);
}

对应在 android 平台的实现。

runtime\bin\eventhandler_android.cc

c++ 复制代码
void EventHandlerImplementation::SendData(intptr_t id,
                                          Dart_Port dart_port,
                                          int64_t data) {
  WakeupHandler(id, dart_port, data);
}

在分析WakeupHandler函数前,先看一个EventHandlerImplementation的初始化。

通过这里可以看到,它创建了一个管道,并通过epoll监听管道中的事件。

runtime\bin\eventhandler_android.cc

c++ 复制代码
EventHandlerImplementation::EventHandlerImplementation()
    : socket_map_(&SimpleHashMap::SamePointerValue, 16) {
  intptr_t result;
  // 创建一个管道,将返回两个 fd。
  result = NO_RETRY_EXPECTED(pipe(interrupt_fds_));
  ...
  shutdown_ = false;
  // The initial size passed to epoll_create is ignored on newer (>= 2.6.8)
  // Linux versions
  const int kEpollInitialSize = 64;
  // 创建 epoll。
  epoll_fd_ = NO_RETRY_EXPECTED(epoll_create(kEpollInitialSize));
  if (epoll_fd_ == -1) {
    FATAL("Failed creating epoll file descriptor: %i", errno);
  }
  if (!FDUtils::SetCloseOnExec(epoll_fd_)) {
    FATAL("Failed to set epoll fd close on exec\n");
  }
  // Register the interrupt_fd with the epoll instance.
  struct epoll_event event;
  event.events = EPOLLIN;
  event.data.ptr = nullptr;
  // 监听管道读事件
  int status = NO_RETRY_EXPECTED(
      epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, interrupt_fds_[0], &event));
  if (status == -1) {
    FATAL("Failed adding interrupt fd to epoll instance");
  }
}

这里可以看到,它向管道中的发送消息消息。由于管道的另一端注册到了epoll中,所以epoll_wait 将会被唤醒。

接着就可以看调用epoll_wait的位置了。

c++ 复制代码
void EventHandlerImplementation::WakeupHandler(intptr_t id,
                                               Dart_Port dart_port,
                                               int64_t data) {
  InterruptMessage msg;
  msg.id = id;
  msg.dart_port = dart_port;
  msg.data = data;
  // WriteToBlocking will write up to 512 bytes atomically, and since our msg
  // is smaller than 512, we don't need a thread lock.
  // See: http://linux.die.net/man/7/pipe, section 'Pipe_buf'.
  ASSERT(kInterruptMessageSize < PIPE_BUF);
  intptr_t result =
      FDUtils::WriteToBlocking(interrupt_fds_[1], &msg, kInterruptMessageSize);
  if (result != kInterruptMessageSize) {
    if (result == -1) {
      FATAL("Interrupt message failure: %s", strerror(errno));
    } else {
      FATAL("Interrupt message failure: expected to write %" Pd
            " bytes, but wrote %" Pd ".",
            kInterruptMessageSize, result);
    }
  }
}

先来看在EventHandlerImplementationstart方法。

它创建了一个线程来运行Poll函数,并将外部的EventHandler作为参数传入。

由于使用线程,所以 dart 层需要用RawReceivePort接收消息。

runtime\bin\eventhandler_android.cc

c++ 复制代码
void EventHandlerImplementation::Start(EventHandler* handler) {
  int result =
      Thread::Start("dart:io EventHandler", &EventHandlerImplementation::Poll,
                    reinterpret_cast<uword>(handler));
  if (result != 0) {
    FATAL("Failed to start event handler thread %d", result);
  }
}

这个方法中可以看到它会调用epoll_wait来等待注册到它上面的fd的事件到来。

c++ 复制代码
void EventHandlerImplementation::Poll(uword args) {
  ThreadSignalBlocker signal_blocker(SIGPROF);
  const intptr_t kMaxEvents = 16;
  struct epoll_event events[kMaxEvents];
  EventHandler* handler = reinterpret_cast<EventHandler*>(args);
  EventHandlerImplementation* handler_impl = &handler->delegate_;
  ASSERT(handler_impl != nullptr);

  while (!handler_impl->shutdown_) {
    int64_t millis = handler_impl->GetTimeout(); // 超时唤醒的时间。
    ASSERT((millis == kInfinityTimeout) || (millis >= 0));
    if (millis > kMaxInt32) {
      millis = kMaxInt32;
    }
    // epoll_fd_ 构造函数中初始化。
    intptr_t result = TEMP_FAILURE_RETRY_NO_SIGNAL_BLOCKER(
        epoll_wait(handler_impl->epoll_fd_, events, kMaxEvents, millis));
    ASSERT(EAGAIN == EWOULDBLOCK);
    if (result == -1) {  // 出错
      if (errno != EWOULDBLOCK) {
        perror("Poll failed");
      }
    } else {
      handler_impl->HandleTimeout();
      handler_impl->HandleEvents(events, result); // 分发事件
    }
  }
  DEBUG_ASSERT(ReferenceCounted<Socket>::instances() == 0);
  handler->NotifyShutdownDone();
}

处理事件,并返回到 dart 层。

c++ 复制代码
void EventHandlerImplementation::HandleEvents(struct epoll_event* events,
                                              int size) {
  bool interrupt_seen = false;
  for (int i = 0; i < size; i++) {
    if (events[i].data.ptr == nullptr) {
      interrupt_seen = true;
    } else {
      DescriptorInfo* di =
          reinterpret_cast<DescriptorInfo*>(events[i].data.ptr);
      const intptr_t old_mask = di->Mask();
      const intptr_t event_mask = GetPollEvents(events[i].events, di);
      if ((event_mask & (1 << kErrorEvent)) != 0) { // 出错处理
        di->NotifyAllDartPorts(event_mask);
        UpdateEpollInstance(old_mask, di);
      } else if (event_mask != 0) {
        Dart_Port port = di->NextNotifyDartPort(event_mask);
        ASSERT(port != 0);
        UpdateEpollInstance(old_mask, di); // 更新事件
        DartUtils::PostInt32(port, event_mask); // 通过 port 通知会 dart 层。
      }
    }
  }
  if (interrupt_seen) {
    // Handle after socket events, so we avoid closing a socket before we handle
    // the current events.
    HandleInterruptFd();
  }
}

根据新旧 mask 决定增减fd。由于socket刚刚创建所以分析新增。

c++ 复制代码
void EventHandlerImplementation::UpdateEpollInstance(intptr_t old_mask,
                                                     DescriptorInfo* di) {
  intptr_t new_mask = di->Mask();
  if ((old_mask != 0) && (new_mask == 0)) {
    RemoveFromEpollInstance(epoll_fd_, di);
  } else if ((old_mask == 0) && (new_mask != 0)) {
    AddToEpollInstance(epoll_fd_, di);
  } else if ((old_mask != 0) && (new_mask != 0) && (old_mask != new_mask)) {
    ASSERT(!di->IsListeningSocket());
    RemoveFromEpollInstance(epoll_fd_, di);
    AddToEpollInstance(epoll_fd_, di);
  }
}

调用epoll_ctl新增fd和它关注的事件。

c++ 复制代码
static void AddToEpollInstance(intptr_t epoll_fd_, DescriptorInfo* di) {
  struct epoll_event event;
  event.events = EPOLLRDHUP | di->GetPollEvents();
  if (!di->IsListeningSocket()) {
    event.events |= EPOLLET;
  }
  event.data.ptr = di;
  int status =
      NO_RETRY_EXPECTED(epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, di->fd(), &event));
  if (status == -1) {
    // TODO(dart:io): Verify that the dart end is handling this correctly.

    // Epoll does not accept the file descriptor. It could be due to
    // already closed file descriptor, or unsupported devices, such
    // as /dev/null. In such case, mark the file descriptor as closed,
    // so dart will handle it accordingly.
    di->NotifyAllDartPorts(1 << kCloseEvent);
  }
}

由前面可以知道在创建RawReceivePort时传入的处理函数是multiplex

因此DartUtils::PostInt32(port, event_mask);会触发multiplex进行处理。

根据事件类型,触发相应的读写回调。

sdk\lib_internal\vm\bin\socket_patch.dart

dart 复制代码
  // Multiplexes socket events to the socket handlers.
  void multiplex(Object eventsObj) {
    // TODO(paulberry): when issue #31305 is fixed, we should be able to simply
    // declare `events` as a `covariant int` parameter.
    int events = eventsObj as int;
    for (int i = firstEvent; i <= lastEvent; i++) {
      if (((events & (1 << i)) != 0)) {
        if (isClosing && i != destroyedEvent) continue;
        switch (i) {
          case readEvent:
            if (isClosedRead) continue;
            if (isListening) { // socket 类型,通过 listen 函数创建为 true
              connections++;
              if (!isClosed) {
                // If the connection is closed right after it's accepted, there's a
                // chance the close-handler is not set.
                var handler = readEventHandler;
                if (handler != null) handler();
              }
            } else {
              if (isUdp) {
                _availableDatagram = nativeAvailableDatagram();
              } else {
                available = nativeAvailable(); // 读取有多少数据在缓冲区中。
              }
              issueReadEvent();
              continue;
            }
            break;
          case writeEvent:
            // On Windows there are two sources of write events: when pending
            // write completes and when we subscribe to write events via
            // setEventMaskCommand. Furthermore we don't always wait for a
            // write event to issue a write. This means when event triggered by
            // setEventMaskCommand arrives we might have already initiated a
            // write. This means we should check [hasPendingWrite] here to
            // be absolutely certain that the pending write operation has
            // completed.
            writeAvailable = !hasPendingWrite();
            issueWriteEvent(delayed: false);
            continue;
          case errorEvent:
          ...
        }
      }
    }
    if (!isListening) {
      tokens++;
      returnTokens(normalTokenBatchSize);
    }
  }

不断从缓冲区中读取数据,并调用设置的readEventHandler回调进行通知。

dart 复制代码
  void issueReadEvent() {
    if (closedReadEventSent) return;
    if (readEventIssued) return; // 避免 scheduleMicrotask 期间重复调用
    readEventIssued = true;
    void issue() {
      readEventIssued = false;
      if (isClosing) return;
      if (!sendReadEvents) return;
      if (stopRead()) { // 缓冲区中是否还有数据
        if (isClosedRead && !closedReadEventSent) {
          if (isClosedWrite) close();
          var handler = closedEventHandler;
          if (handler == null) return;
          closedReadEventSent = true;
          handler();
        }
        return;
      }
      var handler = readEventHandler;
      if (handler == null) return;
      readEventIssued = true;
      handler();
      scheduleMicrotask(issue);
    }

    scheduleMicrotask(issue);
  }

上面分析了注册和接收epoll事件,这里分析如何进一步处理这些事件。

在返回过程中会使用_RawSocket_NativeSocket进行包装。

dart 复制代码
  static Future<ConnectionTask<_RawSocket>> startConnect(
      dynamic host, int port, dynamic sourceAddress, int sourcePort) {
    return _NativeSocket.startConnect(host, port, sourceAddress, sourcePort)
        .then((ConnectionTask<_NativeSocket> nativeTask) {
      final Future<_RawSocket> raw =
          nativeTask.socket.then((_NativeSocket nativeSocket) {
        if (!const bool.fromEnvironment("dart.vm.product")) {
          _SocketProfile.collectNewSocket(nativeSocket.nativeGetSocketId(),
              _tcpSocket, nativeSocket.address, port);
        }
        return _RawSocket(nativeSocket);
      });
      return ConnectionTask<_RawSocket>._(raw, nativeTask._onCancel);
    });
  }

可以看到_RawSocket中对socket的事件设置了处理函数。

并通过StreamController对外分发对应的事件。

dart 复制代码
  _RawSocket(this._socket) {
    var zone = Zone.current;
    _controller
      ..onListen = _onSubscriptionStateChange
      ..onCancel = _onSubscriptionStateChange
      ..onPause = _onPauseStateChange
      ..onResume = _onPauseStateChange;
    _socket.setHandlers(
        read: () => _controller.add(RawSocketEvent.read),
        write: () {
          // The write event handler is automatically disabled by the
          // event handler when it fires.
          writeEventsEnabled = false;
          _controller.add(RawSocketEvent.write);
        },
        closed: () => _controller.add(RawSocketEvent.readClosed),
        destroyed: () {
          _controller.add(RawSocketEvent.closed);
          _controller.close();
        },
        error: zone.bindBinaryCallbackGuarded((Object e, StackTrace? st) {
          _controller.addError(e, st);
          _socket.close();
        }));
  }

在返回过程中会使用_Socket_RawSocket进行包装。

sdk\lib_internal\vm\bin\socket_patch.dart

dart 复制代码
  @patch
  static Future<ConnectionTask<Socket>> _startConnect(dynamic host, int port,
      {dynamic sourceAddress, int sourcePort = 0}) {
    return RawSocket.startConnect(host, port,
            sourceAddress: sourceAddress, sourcePort: sourcePort)
        .then((rawTask) {
      Future<Socket> socket =
          rawTask.socket.then((rawSocket) => new _Socket(rawSocket));
      return new ConnectionTask<Socket>._(socket, rawTask._onCancel);
    });
  }
}
dart 复制代码
  _Socket(RawSocket raw) : _raw = raw {
    _controller
      ..onListen = _onSubscriptionStateChange
      ..onCancel = _onSubscriptionStateChange
      ..onPause = _onPauseStateChange
      ..onResume = _onPauseStateChange;
    _consumer = new _SocketStreamConsumer(this);
    _sink = new IOSink(_consumer);

    // Disable read events until there is a subscription.
    raw.readEventsEnabled = false;

    // Disable write events until the consumer needs it for pending writes.
    raw.writeEventsEnabled = false;
  }
dart 复制代码
  void _onSubscriptionStateChange() {
    final raw = _raw;
    if (_controller.hasListener) {
      _ensureRawSocketSubscription();
      // Enable read events for providing data to subscription.
      if (raw != null) {
        raw.readEventsEnabled = true;
      }
    } else {
      _controllerClosed = true;
      if (raw != null) {
        raw.shutdown(SocketDirection.receive);
      }
    }
  }

_RawSocket注册监听函数,用来接收socket事件。

dart 复制代码
  void _ensureRawSocketSubscription() {
    final raw = _raw;
    if (_subscription == null && raw != null) {
      _subscription = raw.listen(_onData,
          onError: _onError, onDone: _onDone, cancelOnError: true);
    }
  }

到此可以看到读事件会发送给外部监听者,写事件会去缓冲区中取数据写入```socket···。

dart 复制代码
  void _onData(event) {
    switch (event) {
      case RawSocketEvent.read: // epoll 发出可读事件时,
        if (_raw == null) break;
        var buffer = _raw!.read(); // 读取数据,
        // 将数据发送给外部监听。
        if (buffer != null) _controller.add(buffer);
        break;
      case RawSocketEvent.write:// epoll 发出可写事件时,
        _consumer.write(); // 如果缓冲区中有数据则向 socket 写入数据。
        break;
      case RawSocketEvent.readClosed:
        _controllerClosed = true;
        _controller.close();
        break;
    }
  }
相关推荐
yuanlaile15 小时前
纯Dart Flutter库适配HarmonyOS
flutter·华为·harmonyos·flutter开发鸿蒙·harmonyos教程
yuanlaile15 小时前
Flutter开发HarmonyOS 鸿蒙App的好处、能力以及把Flutter项目打包成鸿蒙应用
flutter·华为·harmonyos·flutter开发鸿蒙
zacksleo17 小时前
鸿蒙原生开发手记:04-一个完整元服务案例
flutter
jcLee952 天前
Flutter/Dart:使用日志模块Logger Easier
flutter·log4j·dart·logger
tmacfrank2 天前
Flutter 异步编程简述
flutter
tmacfrank2 天前
Flutter 基础知识总结
flutter
叫我菜菜就好2 天前
【Flutter_Web】Flutter编译Web第三篇(网络请求篇):dio如何改造方法,变成web之后数据如何处理
前端·网络·flutter
AiFlutter2 天前
Flutter-底部分享弹窗(showModalBottomSheet)
java·前端·flutter
m0_748247803 天前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
迷雾漫步者3 天前
Flutter组件————PageView
flutter·跨平台·dart