首发原文链接:https://mp.weixin.qq.com/s/KxgxseLEz84wxUPjzSUd3w

大家好,我是码农先森。

今天我们来分析 TCP Server 模块 的实现原理,下面这张图是来自 Swoole 的官网。

那么,我们就主要分析这段言简意赅的代码,从这段代码中可以看出设置了四个回调方法,还有构造方法 new Swoole\Server(127.0.0.1, 9503)、服务启动方法 $server->start()。别小看上面的那段代码,其中可是蕴含了巨大的能量。我们可以先通过下面这张图,来了解接下来要介绍的内容。

话不多说,开整!

Swoole\Server 类及函数的注册

我们之所以可以使用 new Swoole\Server 类,是需要先将该类注册到 PHP 中,然后才能进行调用。从下面 php_swoole_server_minit 这个方法可以看出类的注册,这个方法里面只是展示了 Swoole\Server 类的注册,其他的省略掉了,比如:Swoole\Server\Task 等,更全的代码可以翻看源文件,下面这张图介绍了类及方法的注册定义。

// swoole-src/ext-src/swoole_server.cc:454
void php_swoole_server_minit(int module_number) {
// ---------------------------------------Server-------------------------------------
// 注册一个名为 "Swoole\Server" 的 PHP 类。swoole_server 是在扩展中定义的 zend_class_entry 结构体的变量名
// "Swoole\Server" 是该类的名称,nullptr 表示基类(如果有的话),swoole_server_methods 是一个包含类方法的结构体
SW_INIT_CLASS_ENTRY(swoole_server, "Swoole\\Server", nullptr, swoole_server_methods); // Server 类不能被序列化
SW_SET_CLASS_NOT_SERIALIZABLE(swoole_server); // Server 类不能被克隆
SW_SET_CLASS_CLONEABLE(swoole_server, sw_zend_class_clone_deny); // Server 类不允许删除属性,并且禁止使用 unset() 函数删除该类的对象的属性
SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_server, sw_zend_class_unset_property_deny); // 使用自定义对象处理。其中 server_create_object 和 server_free_object 是自定义对象的创建和释放函数
// ServerObject 是自定义对象的类型,std 是一个宏定义,用于指定对象的父类
SW_SET_CLASS_CUSTOM_OBJECT(swoole_server, server_create_object, server_free_object, ServerObject, std); ...
}

在第一行代码中,有这样一个参数 swoole_server_methods,它是一个包含类方法的结构体,我们跟踪过去看看。

// swoole-src/ext-src/swoole_server.cc:373
static zend_function_entry swoole_server_methods[] = {
// 对应的代码 $server = new Swoole\Server('127.0.0.1', 9503);
PHP_ME(swoole_server, __construct, arginfo_class_Swoole_Server___construct, ZEND_ACC_PUBLIC)
// 对应的代码 $server->on('', function ($server) {})
PHP_ME(swoole_server, on, arginfo_class_Swoole_Server_on, ZEND_ACC_PUBLIC)
// 对应的代码 $server->start()
PHP_ME(swoole_server, start, arginfo_class_Swoole_Server_start, ZEND_ACC_PUBLIC) ...
}

这下可以发现 __construct()on()start() 三个方法,即在前面使用到的方法,就是在这里被定义声明的。

Server 类方法的具体实现

先看 __construct 构造方法的具体实现。首先会先判断 Server 类是否已经被实例化过了,如果是则会直接报错,即不能被重复实例化。然后,做一些参数解析、环境检测、运行模式的判断。最后,会将端口号添加是 Server 对象中,如果没有设置端口,系统则会进行自动分配。

// file: swoole-src/ext-src/swoole_server.cc:1829
static PHP_METHOD(swoole_server, __construct) {
// 判断是否已经存在一个 Server 对象的实例,如果 serv 不为空,则说明构造函数已经被调用过
ServerObject *server_object = server_fetch_object(Z_OBJ_P(ZEND_THIS));
Server *serv = server_object->serv;
if (serv) {
zend_throw_error(NULL, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS));
RETURN_FALSE;
} // 声明一些变量
zval *zserv = ZEND_THIS;
char *host;
size_t host_len = 0;
zend_long sock_type = SW_SOCK_TCP;
zend_long serv_port = 0;
zend_long serv_mode = Server::MODE_BASE; // swoole 只允许在命令行模式下运行
if (!SWOOLE_G(cli)) {
zend_throw_exception_ex(
swoole_exception_ce, -1, "%s can only be used in CLI mode", SW_Z_OBJCE_NAME_VAL_P(zserv));
RETURN_FALSE;
} // 检查 Server 是否已经在运行了
if (sw_server() != nullptr) {
zend_throw_exception_ex(
swoole_exception_ce, -3, "server is running. unable to create %s", SW_Z_OBJCE_NAME_VAL_P(zserv));
RETURN_FALSE;
} // 解析构造函数上的参数,例如:127.0.0.1、9503 等
ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 4)
Z_PARAM_STRING(host, host_len)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(serv_port)
Z_PARAM_LONG(serv_mode)
Z_PARAM_LONG(sock_type)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); // Swoole 只支持 base 和 process 两种模式
if (serv_mode != Server::MODE_BASE && serv_mode != Server::MODE_PROCESS) {
zend_throw_error(NULL, "invalid $mode parameters %d", (int) serv_mode);
RETURN_FALSE;
} ... // 在 base 模式下,只会设置一个 reactor 主线程及一个 worker 进程
if (serv_mode == Server::MODE_BASE) {
serv->reactor_num = 1;
serv->worker_num = 1;
} /* primary port */
do {
// 如果没有设置端口号,系统则会随机分配一个端口号
if (serv_port == 0 && strcasecmp(host, "SYSTEMD") == 0) {
if (serv->add_systemd_socket() <= 0) {
zend_throw_error(NULL, "failed to add systemd socket");
RETURN_FALSE;
}
} else {
// 将 host、port 参数 添加到 server 对象中
ListenPort *port = serv->add_port((enum swSocketType) sock_type, host, serv_port);
if (!port) {
zend_throw_exception_ex(swoole_exception_ce,
swoole_get_last_error(),
"failed to listen server port[%s:" ZEND_LONG_FMT "], Error: %s[%d]",
host,
serv_port,
swoole_strerror(swoole_get_last_error()),
swoole_get_last_error());
RETURN_FALSE;
}
} // 存在监听多个端口的情况
for (auto ls : serv->ports) {
php_swoole_server_add_port(server_object, ls);
} // 设置主端口
server_object->property->primary_port = (ServerPortProperty *) serv->get_primary_port()->ptr;
} while (0); ...
}

刚刚将 Server 进行了实例化,且设置了相关的参数。接下来再看 $server->on() 回调函数的设置。

// file: swoole-src/ext-src/swoole_server.cc:2343
static PHP_METHOD(swoole_server, on) {
// 检查 Server 是否已经运行
Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS);
if (serv->is_started()) {
php_swoole_fatal_error(E_WARNING, "server is running, unable to register event callback function");
RETURN_FALSE;
} // 事件名称,例如:start
zval *name;
// 回调函数 ,例如:function($server){}
zval *cb; // 参数解析
if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz", &name, &cb) == FAILURE) {
RETURN_FALSE;
} // 主要是检查回调函数是否能够被正常的调用
char *func_name = nullptr;
zend_fcall_info_cache *fci_cache = (zend_fcall_info_cache *) emalloc(sizeof(zend_fcall_info_cache));
if (!sw_zend_is_callable_ex(cb, nullptr, 0, &func_name, nullptr, fci_cache, nullptr)) {
php_swoole_fatal_error(E_ERROR, "function '%s' is not callable", func_name);
return;
}
efree(func_name); zend::String _event_name_ori(name);
zend::String _event_name_tolower(zend_string_tolower(_event_name_ori.get()), false); ServerObject *server_object = server_fetch_object(Z_OBJ_P(ZEND_THIS)); // 寻找对应的事件类型,例如: start、connect 等
auto i = server_event_map.find(_event_name_tolower.to_std_string());
// 没有找到的情况,i == server_event_map.end() 这个条件没有匹配的事件找到才会成立
if (i == server_event_map.end()) {
zval *port_object = server_object->property->ports.at(0);
zval retval;
efree(fci_cache);
sw_zend_call_method_with_2_params(port_object, swoole_server_port_ce, nullptr, "on", &retval, name, cb);
RETURN_BOOL(Z_BVAL_P(&retval));
} else {
// 找到的情况
int event_type = i->second.type;
// 拼接属性名称 例如: onstart
std::string property_name = "on" + i->second.name; // 将回调函数更新到 Server 对象中
zend_update_property(
swoole_server_ce, SW_Z8_OBJ_P(ZEND_THIS), property_name.c_str(), property_name.length(), cb); if (server_object->property->callbacks[event_type]) {
efree(server_object->property->callbacks[event_type]);
}
server_object->property->callbacks[event_type] = fci_cache; RETURN_TRUE;
}
}

设置好回调函数后,就开始调用 $server->start() 这意味这正式启动服务了。

// file: swoole-src/ext-src/swoole_server.cc:2548
static PHP_METHOD(swoole_server, start) {
zval *zserv = ZEND_THIS;
Server *serv = php_swoole_server_get_and_check_server(zserv); // 检查 Server 是否已经启动
if (serv->is_started()) {
php_swoole_fatal_error(
E_WARNING, "server is running, unable to execute %s->start()", SW_Z_OBJCE_NAME_VAL_P(zserv));
RETURN_FALSE;
} // 检查 Server 是否已经关闭
if (serv->is_shutdown()) {
php_swoole_fatal_error(
E_WARNING, "server have been shutdown, unable to execute %s->start()", SW_Z_OBJCE_NAME_VAL_P(zserv));
RETURN_FALSE;
} // 检查 事件循环 是否已经创建
if (SwooleTG.reactor) {
php_swoole_fatal_error(
E_WARNING, "eventLoop has already been created, unable to start %s", SW_Z_OBJCE_NAME_VAL_P(zserv));
RETURN_FALSE;
} ServerObject *server_object = server_fetch_object(Z_OBJ_P((zval *) serv->private_data_2));
// 注册相关的回调函数
server_object->register_callback();
// 做一些前置操作
server_object->on_before_start(); // 设置运行模式、启动守护进程、设置共享内存,最后启动底层的事件驱动循环
if (serv->start() < 0) {
php_swoole_fatal_error(E_ERROR, "failed to start server. Error: %s", sw_error);
} RETURN_TRUE;
}

回调函数的实现

这里我主要介绍上面所提到的四个回调函数的实现,start 回调函数对应的实现函数是 php_swoole_server_onStart(Server *serv)、connect 回调函数对应的实现函数是php_swoole_server_onConnect(Server *serv, DataHead *info)、receive 回调函数对应的实现函数是 php_swoole_server_onReceive(Server *serv, RecvData *req)、close 回调函数对应的实现函数是 php_swoole_server_onClose(Server *serv, DataHead *info)。这些函数的实现中都会使用到 Zend 引擎所提供的 zend::function::call() 函数,用于触发用户自定义的回调函数及参数的回传。可以先看这张图,也是简要的介绍了四个回调函数的实现逻辑,让大家先有个基本的印象。

php_swoole_server_onStart

  1. 使用 SW_SERVER_CB_onStart 常量通过在 callbacks 数组中获取到 onStart 回调函数的数据。
  2. 还会更新 Server 对象的 master_pidmanager_pid
  3. 如果启用了 library 模块,则会调用一下对应的方法,这里其实也类似于 Hook 的形式。
  4. 最后判断是否有注册 onStart 回调函数,如果有则调用该回调函数,并且将 zserv Server 对象传递给回调函数。
  5. 至此就是会执行到我们用户写的业务逻辑了。
  6. 这个 onStart 回调函数,当我们的服务一启动便会执行,可以在这里做一些初始化的操作。
// file: swoole-src/ext-src/swoole_server.cc:1371
static void php_swoole_server_onStart(Server *serv) {
serv->lock();
zval *zserv = (zval *) serv->private_data_2;
ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv));
// 从 callbacks 数组中获取 onStart 回调函数的信息
auto fci_cache = server_object->property->callbacks[SW_SERVER_CB_onStart]; // 更新服务对象的 master_pid 和 manager_pid 属性,分别表示主进程和管理进程的 PID。
zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("master_pid"), serv->gs->master_pid);
zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("manager_pid"), serv->gs->manager_pid); // 如果启用了 library 模块,则会调用对应的方法。具体的方法实现可查阅 https://github.com/swoole/library/blob/master/src/core/Server/Helper.php
if (SWOOLE_G(enable_library)) {
zend::function::call("\\Swoole\\Server\\Helper::onStart", 1, zserv);
} // 判断是否有注册 onStart 回调函数,并调用该回调函数
if (fci_cache && UNEXPECTED(!zend::function::call(fci_cache, 1, zserv, nullptr, serv->is_enable_coroutine()))) {
php_swoole_error(E_WARNING, "%s->onStart handler error", SW_Z_OBJCE_NAME_VAL_P(zserv));
}
serv->unlock();
}

php_swoole_server_onConnect

  1. 使用 SW_SERVER_CB_onConnect 常量通过 php_swoole_server_get_fci_cache 函数获取 onConnect 回调函数数据。
  2. 如果启用了事件对象 'event_object' => true,回调函数的参数,会集成到事件对象中,例如:function (Swoole\Server $serv, Swoole\Server\Event $object){}
  3. 判断是否有注册 onConnect 回调函数,并调用该回调函数。
  4. 其中的 args 是回调的参数,参数有 $server$fd$reactor_id
  5. 不过 $reactor_id 这个参数,只有在多进程模式下才会有值。
  6. 这个 onConnect 回调函数,当有用户连接之后会进行回调该函数,我们可以在这个函数里处理连接数据。
// file: swoole-src/ext-src/swoole_server.cc:1604
void php_swoole_server_onConnect(Server *serv, DataHead *info) {
// 获取 onConnect 回调函数的信息
auto fci_cache = php_swoole_server_get_fci_cache(serv, info->server_fd, SW_SERVER_CB_onConnect);
if (!fci_cache) {
return;
} zval *zserv = (zval *) serv->private_data_2;
zval args[3];
int argc;
args[0] = *zserv; // 如果启用了事件对象 'event_object' => true
// 回调函数的参数,会集成到事件对象中,例如:function (Swoole\Server $serv, Swoole\Server\Event $object){}
if (serv->event_object) {
zval *object = &args[1];
object_init_ex(object, swoole_server_event_ce);
zend_update_property_long(swoole_server_event_ce, SW_Z8_OBJ_P(object), ZEND_STRL("fd"), (zend_long) info->fd);
zend_update_property_long(
swoole_server_event_ce, SW_Z8_OBJ_P(object), ZEND_STRL("reactor_id"), (zend_long) info->reactor_id);
zend_update_property_double(
swoole_server_event_ce, SW_Z8_OBJ_P(object), ZEND_STRL("dispatch_time"), info->time);
argc = 2;
} else {
ZVAL_LONG(&args[1], info->fd);
ZVAL_LONG(&args[2], info->reactor_id);
argc = 3;
} // 判断是否有注册 onConnect 回调函数,并调用该回调函数
// 其中的 args 是回调的参数,例如:$server、$fd
if (UNEXPECTED(!zend::function::call(fci_cache, argc, args, nullptr, serv->enable_coroutine))) {
php_swoole_error(E_WARNING, "%s->onConnect handler error", SW_Z_OBJCE_NAME_VAL_P(zserv));
} if (serv->event_object) {
zval_ptr_dtor(&args[1]);
}
}

php_swoole_server_onReceive

  1. 使用 SW_SERVER_CB_onReceive 常量通过 php_swoole_server_get_fci_cache 函数获取 onReceive 回调函数数据。
  2. 如果启用了事件对象 'event_object' => true,回调函数的参数,会集成到事件对象中,例如:function (Swoole\Server $serv, Swoole\Server\Event $object){}
  3. 判断是否有注册 onReceive 回调函数,并调用该回调函数。
  4. 其中的 args 是回调的参数,参数有 $server$fd$reactor_id$data
  5. 不过 $reactor_id 这个参数,只有在多进程模式下才会有值。
  6. 相较于 onConnect 回调函数,这里多了一个 $data 参数,这个参数就是用户发送的数据。
  7. 我们接收到数据,那么就可以进行业务逻辑的处理了,处理完后,要及时响应。
  8. 通常情况下,为了提高服务的处理能力,会使用到协程来分发请求。
// file: swoole-src/ext-src/swoole_server.cc:1076
int php_swoole_server_onReceive(Server *serv, RecvData *req) {
// 获取 onReceive 回调函数的信息
auto fci_cache = php_swoole_server_get_fci_cache(serv, req->info.server_fd, SW_SERVER_CB_onReceive); if (fci_cache) {
zval *zserv = (zval *) serv->private_data_2;
zval args[4];
int argc; args[0] = *zserv; // 如果启用了事件对象 'event_object' => true
// 回调函数的参数,会集成到事件对象中,例如:function (Swoole\Server $serv, Swoole\Server\Event $object){}
if (serv->event_object) {
zval *object = &args[1];
zval data;
object_init_ex(object, swoole_server_event_ce);
zend_update_property_long(
swoole_server_event_ce, SW_Z8_OBJ_P(object), ZEND_STRL("fd"), (zend_long) req->info.fd);
zend_update_property_long(
swoole_server_event_ce, SW_Z8_OBJ_P(object), ZEND_STRL("reactor_id"), (zend_long) req->info.reactor_id);
zend_update_property_double(
swoole_server_event_ce, SW_Z8_OBJ_P(object), ZEND_STRL("dispatch_time"), req->info.time);
php_swoole_get_recv_data(serv, &data, req);
zend_update_property(swoole_server_event_ce, SW_Z8_OBJ_P(object), ZEND_STRL("data"), &data);
zval_ptr_dtor(&data);
argc = 2;
} else {
// 对参数 args 进行赋值
ZVAL_LONG(&args[1], (zend_long) req->info.fd);
ZVAL_LONG(&args[2], (zend_long) req->info.reactor_id);
php_swoole_get_recv_data(serv, &args[3], req);
argc = 4;
} // 判断是否有注册 onReceive 回调函数,并调用该回调函数
// 其中的 args 是回调的参数,例如:$server, $fd, $reactor_id, $data
if (UNEXPECTED(!zend::function::call(fci_cache, argc, args, nullptr, serv->enable_coroutine))) {
php_swoole_error(E_WARNING, "%s->onReceive handler error", SW_Z_OBJCE_NAME_VAL_P(zserv));
serv->close(req->info.fd, false);
}
if (serv->event_object) {
zval_ptr_dtor(&args[1]);
} else {
// 释放内存资源
zval_ptr_dtor(&args[3]);
}
} return SW_OK;
}

php_swoole_server_onClose

  1. 关闭服务之前,要先清除的相关协程资源,避免资源浪费。
  2. 使用 SW_SERVER_CB_onClose 常量通过 php_swoole_server_get_fci_cache 方法获取到 onClose 回调函数信息。
  3. 如果是 Webscoket 连接,则获取对应的回调函数 onDisconnect 信息。
  4. 如果启用了事件对象 'event_object' => true,回调函数的参数,会集成到事件对象中,例如:function (Swoole\Server $serv, Swoole\Server\Event $object){}
  5. 判断是否有注册 onClose 回调函数,并调用该回调函数。
  6. 其中的 args 是回调的参数,参数包括:$server, $fd, $reactor_id
  7. 回调到用户的函数中,做一些业务上的收尾处理,至此服务就结束了。
// file: swoole-src/ext-src/swoole_server.cc:1639
void php_swoole_server_onClose(Server *serv, DataHead *info) {
zval *zserv = (zval *) serv->private_data_2;
ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv));
SessionId session_id = info->fd; // 如果启用了协程,则需要清除这个连接的相关协程资源
if (serv->enable_coroutine && serv->send_yield) {
auto _i_co_list = server_object->property->send_coroutine_map.find(session_id);
if (_i_co_list != server_object->property->send_coroutine_map.end()) {
auto co_list = _i_co_list->second;
server_object->property->send_coroutine_map.erase(session_id);
while (!co_list->empty()) {
Coroutine *co = co_list->front();
co_list->pop_front();
swoole_set_last_error(ECONNRESET);
co->resume();
}
delete co_list;
}
} // 获取 onClose 回调信息
auto *fci_cache = php_swoole_server_get_fci_cache(serv, info->server_fd, SW_SERVER_CB_onClose);
Connection *conn = serv->get_connection_by_session_id(session_id);
if (!conn) {
return;
} // 如果是 Webscoket 连接,则获取对应的回调函数 onDisconnect 信息
if (conn->websocket_status != swoole::websocket::STATUS_ACTIVE) {
ListenPort *port = serv->get_port_by_server_fd(info->server_fd);
if (port && port->open_websocket_protocol &&
php_swoole_server_isset_callback(serv, port, SW_SERVER_CB_onDisconnect)) {
fci_cache = php_swoole_server_get_fci_cache(serv, info->server_fd, SW_SERVER_CB_onDisconnect);
}
}
if (fci_cache) {
zval *zserv = (zval *) serv->private_data_2;
zval args[3];
int argc;
args[0] = *zserv; // 如果启用了事件对象 'event_object' => true
// 回调函数的参数,会集成到事件对象中,例如:function (Swoole\Server $serv, Swoole\Server\Event $object){}
if (serv->event_object) {
zval *object = &args[1];
object_init_ex(object, swoole_server_event_ce);
zend_update_property_long(
swoole_server_event_ce, SW_Z8_OBJ_P(object), ZEND_STRL("fd"), (zend_long) session_id);
zend_update_property_long(
swoole_server_event_ce, SW_Z8_OBJ_P(object), ZEND_STRL("reactor_id"), (zend_long) info->reactor_id);
zend_update_property_double(
swoole_server_event_ce, SW_Z8_OBJ_P(object), ZEND_STRL("dispatch_time"), info->time);
argc = 2;
} else {
// 对参数 args 进行赋值
ZVAL_LONG(&args[1], session_id);
ZVAL_LONG(&args[2], info->reactor_id);
argc = 3;
} // 判断是否有注册 onClose 回调函数,并调用该回调函数
// 其中的 args 是回调的参数,例如:$server, $fd, $reactor_id
if (UNEXPECTED(!zend::function::call(fci_cache, argc, args, nullptr, serv->enable_coroutine))) {
php_swoole_error(E_WARNING, "%s->onClose handler error", SW_Z_OBJCE_NAME_VAL_P(zserv));
} // 释放资源
if (serv->event_object) {
zval_ptr_dtor(&args[1]);
}
} // 释放 HTTP2 流资源
if (conn->http2_stream) {
swoole_http2_server_session_free(conn);
}
}

总结

从 Swoole 官网的这段短小精悍的代码,就可以看出 Server 服务的关键要点。再通过对类、构造方法、回调函数的层层剖析,我们逐渐的了解了底层的实现原理。TCP Server 模式是基础模块,我们经常使用的 HTTP 模块就是基于此模块进行扩展的,所以我们需要有一定的了解。对我们来说掌握了原理性的内容,在回过头去看用户级的代码,往往会更轻松,所以我们需要有耐心的琢磨。

Swoole 源码分析之 TCP Server 模块的更多相关文章

  1. jQuery1.9.1源码分析--数据缓存Data模块

    jQuery1.9.1源码分析--数据缓存Data模块 阅读目录 jQuery API中Data的基本使用方法介绍 jQuery.acceptData(elem)源码分析 jQuery.data(el ...

  2. jQuery 源码分析(十) 数据缓存模块 data详解

    jQuery的数据缓存模块以一种安全的方式为DOM元素附加任意类型的数据,避免了在JavaScript对象和DOM元素之间出现循环引用,以及由此而导致的内存泄漏. 数据缓存模块为DOM元素和JavaS ...

  3. tcprstat源码分析之tcp数据包分析

    tcprstat是percona用来监测mysql响应时间的.不过对于任何运行在TCP协议上的响应时间,都可以用.本文主要做源码分析,如何使用tcprstat请大家查看博文<tcprstat分析 ...

  4. Hadoop2源码分析-HDFS核心模块分析

    1.概述 这篇博客接着<Hadoop2源码分析-RPC机制初识>来讲述,前面我们对MapReduce.序列化.RPC进行了分析和探索,对Hadoop V2的这些模块都有了大致的了解,通过对 ...

  5. 【Canal源码分析】Canal Server的启动和停止过程

    本文主要解析下canal server的启动过程,希望能有所收获. 一.序列图 1.1 启动 1.2 停止 二.源码分析 整个server启动的过程比较复杂,看图难以理解,需要辅以文字说明. 首先程序 ...

  6. Tornado源码分析 --- 静态文件处理模块

    每个web框架都会有对静态文件的处理支持,下面对于Tornado的静态文件的处理模块的源码进行分析,以加强自己对静态文件处理的理解. 先从Tornado的主要模块 web.py 入手,可以看到在App ...

  7. Python 源码分析:queue 队列模块

    起步 queue 模块提供适用于多线程编程的先进先出(FIFO)数据结构.因为它是线程安全的,所以多个线程很轻松地使用同一个实例. 源码分析 先从初始化的函数来看: 从这初始化函数能得到哪些信息呢?首 ...

  8. jQuery 源码分析(十六) 事件系统模块 底层方法 详解

    jQuery事件系统并没有将事件监听函数直接绑定到DOM元素上,而是基于数据缓存模块来管理监听函数的,事件模块代码有点多,我把它分为了三个部分:分底层方法.实例方法和便捷方法.ready事件来讲,好理 ...

  9. jQuery 源码分析(十三) 数据操作模块 DOM属性 详解

    jQuery的属性操作模块总共有4个部分,本篇说一下第2个部分:DOM属性部分,用于修改DOM元素的属性的(属性和特性是不一样的,一般将property翻译为属性,attribute翻译为特性) DO ...

  10. Eureka 源码分析之 Eureka Server

    文章首发于公众号<程序员果果> 地址 : https://mp.weixin.qq.com/s/FfJrAGQuHyVrsedtbr0Ihw 简介 上一篇文章<Eureka 源码分析 ...

随机推荐

  1. 使用 Grafana 统一监控展示-对接 Zabbix

    概述 在某些情况下,Metrics 监控的 2 大顶流: Zabbix: 用于非容器的虚拟机环境 Prometheus: 用于容器的云原生环境 是共存的.但是在这种情况下,统一监控展示就不太方便,本文 ...

  2. 及刻周边惠:拥抱HarmonyOS原子化服务

    原文链接:https://mp.weixin.qq.com/s/Y75eiRlvDLXzoZWzAiZdeg,点击链接查看更多技术内容: 开发背景 及刻周边惠是梦享网络旗下本地生活服务平台,旨在为消费 ...

  3. sql 语句系列(每个季度的开始日期和结束日期)[八百章之第二十二章]

    前言 基本上统计财务一定会用到. mysql select QUARTER(ADDDATE(y.dy,-1)) QTR, DATE_ADD(y.dy,INTERVAL -3 MONTH) Q_star ...

  4. PyTorch分分钟快速安装

    PyTorch的前身是Torch,其底层和Torch框架一样,但是使用Python重新写了很多内容,不仅更加灵活,支持动态图,而且提供了Python接口. 它是由Torch7团队开发,是一个以Pyth ...

  5. 常见的PLC通信协议及相关介绍

    2023-07-26 一.常见的PLC通信协议: Modbus:Modbus是一种串行通信协议,它支持多种物理层接口(如RS-232.RS-485等),可以实现PLC与其他设备(如HMI.变频器等)之 ...

  6. 【布局进阶】巧用 :has & drop-shadow 实现复杂布局效果

    最近,群里聊到了一个很有意思的布局效果.大致效果如下所示,希望使用 CSS 实现如下所示的布局效果: 正常而言,我们的 HTML 结构大致是如下所示: <div class="g-co ...

  7. 图片验证码识别,标签中onehot编码对应多个1怎么做?

    一张验证码图片直接做整体识别 也就是图片的最后输出节点有4*26=104个,经过一个softmax,使用交叉熵损失,与真实值4*26=104个标签做计算,然后反向传播 104个onehot编码真实值当 ...

  8. 安装以及破解Navicat

    1.下载Navicat软件安装包 链接:https://pan.baidu.com/s/1RltCPjg1mmpOjC7vxAjQ4g 提取码:v4k8 2.下载好文件打开是这样的,先运行 " ...

  9. 写给大家看的“不负责任” K8s 入门文档

    前言 2019 年下半年,我做了一次转岗,开始接触到 Kubernetes,虽然对 K8s 的认识还非常的不全面,但是非常想分享一下自己的一些收获,希望通过本文能够帮助大家对 K8s 有一个入门的了解 ...

  10. 优化搜索排序结果从而“ 提升CTR、CVR业务指标”

    简介: 搭建搜索功能不难,难的是如何提高搜索质量,帮助用户快速找到心中所想的内容或商品,那么搜索结果的相关性排序则是影响用户体验最关键的一环,本文通过阿里云开放搜索电商行业解决方案和大家聊一聊如何优化 ...