Swoole 源码分析之 TCP Server 模块

首发原文链接: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 等,更全的代码可以翻看源文件,下面这张图介绍了类及方法的注册定义。

Copy
// 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,它是一个包含类方法的结构体,我们跟踪过去看看。

Copy
// 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 对象中,如果没有设置端口,系统则会进行自动分配。

Copy
// 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() 回调函数的设置。

Copy
// 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() 这意味这正式启动服务了。

Copy
// 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 回调函数,当我们的服务一启动便会执行,可以在这里做一些初始化的操作。
Copy
// 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 回调函数,当有用户连接之后会进行回调该函数,我们可以在这个函数里处理连接数据。
Copy
// 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. 通常情况下,为了提高服务的处理能力,会使用到协程来分发请求。
Copy
// 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. 回调到用户的函数中,做一些业务上的收尾处理,至此服务就结束了。
Copy
// 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 模块就是基于此模块进行扩展的,所以我们需要有一定的了解。对我们来说掌握了原理性的内容,在回过头去看用户级的代码,往往会更轻松,所以我们需要有耐心的琢磨。

posted @   Yxh_blogs  阅读(69)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示
CONTENTS