在学习 Flutter 过程中,一直有一个疑惑:既然 Flutter 是单线程模型,那在进行网络请求或 IO 操作时,为什么没有阻塞线程呢? 下面以网络请求为例来分析 Flutter 底层是如何处理的。
结论
这里省流先说结论。
以http
请求为例:
- 主要利用
Socket
可以设置非阻塞的性质,避免如connect
、read
和write
等耗时操作阻塞线程。另外它还使用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
内部会创建SendPort
和RawReceivePort
进行跨线程消息的发送和接收。
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);
}
创建NativeMessageHandler
和Port
,并将它们相互关联。
NativeMessageHandler
主要是对上面IOServiceCallback
的包装,并对消息管理和分发。
另外可以看到,在准备工作完成后,就调用NativeMessageHandler
的Run
方法,并传入一个线程池,这意味着任务将会在子线程中执行。
看到这儿可以知道通过_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
对应的仅有三个,像是connect
、read
和write
这些操作哪去了? 带着疑问我们下面接续分析。
这里将声明全部列出来。
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);
}
}
}
先来看在EventHandlerImplementation
的start
方法。
它创建了一个线程来运行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;
}
}