【Lv:4】
========= 前言 =========
时隔以及,我再次回来了。这次我在论坛里看见了关于 PM 为何没落的消息,看了之后的确感觉到唏嘘不已。无奈之前就已经有了解析内核的想法,所以此时此刻我只能重操旧业,用我那不渊博的知识来解析解析 PM 背后的运行逻辑。大家可以一边看一边学。以下便是大家如果要学习,所要准备的东西。
========= 所需工具 =========
一、PocketMine 源码(获取地址,点击前往)
二、称心如意的编辑器(我使用的是 PhpStorm )
三、一台好电脑
四、PHP 基础
========= 目录 =========
第一章 开启服务器的瞬间
|
——第一节 关键的 server 方法
——第二节 关键的 server.lock
========= 第一章 开启服务器的瞬间 =========
========= 第一节 关键的 server 方法 =========
我们现在就来探究一下,当我们每次按下 Start Server(开服)按钮的时候,PM 内核脑海中想的第一件事情是什么呢?现在我们就进入到 PocketMine.php 文件里的内容,看看它是怎么运行的。不过我在主文件里看见了非常有意思的一段话,让大家看看。========= 第一节 关键的 server 方法 =========
PHP:
/*
* Startup code. Do not look at it, it may harm you.
* This is the only non-class based file on this project.
* Enjoy it as much as I did writing it. I don't want to do it again.
*/
写这个文件的程序员指出了自己写的这段代码是不符合规范的,让大家不要模仿,让我在阅读源码的时候变得开心起来。废话少说,既然作者都说了这个不是类,那么在文件里必然有一个总启动的办法,在我仔细寻找的过程中,终于发现了这个决定性的代码,也就是这一句代码让我们的服务器开始运行起来。
PHP:
\pocketmine\server();
既然找到了是 server() 这个方法是主入口,那么我们就前往这个方法代码块一看究竟。
PHP:
function server(){
if(count($messages = check_platform_dependencies()) > 0){
echo PHP_EOL;
$binary = version_compare(PHP_VERSION, "5.4") >= 0 ? PHP_BINARY : "unknown";
critical_error("Selected PHP binary does not satisfy some requirements.");
foreach($messages as $m){
echo " - $m" . PHP_EOL;
}
critical_error("PHP binary used: " . $binary);
critical_error("Loaded php.ini: " . (($file = php_ini_loaded_file()) !== false ? $file : "none"));
critical_error("Please recompile PHP with the needed configuration, or refer to the installation instructions at http://pmmp.rtfd.io/en/rtfd/installation.html.");
echo PHP_EOL;
exit(1);
}
unset($messages);
error_reporting(-1);
set_ini_entries();
$opts = getopt("", ["bootstrap:"]);
if(isset($opts["bootstrap"])){
$bootstrap = ($real = realpath($opts["bootstrap"])) !== false ? $real : $opts["bootstrap"];
}else{
$bootstrap = dirname(__FILE__, 3) . '/vendor/autoload.php';
}
if($bootstrap === false or !is_file($bootstrap)){
critical_error("Composer autoloader not found at " . $bootstrap);
critical_error("Please install/update Composer dependencies or use provided builds.");
exit(1);
}
define('pocketmine\COMPOSER_AUTOLOADER_PATH', $bootstrap);
require_once(\pocketmine\COMPOSER_AUTOLOADER_PATH);
set_error_handler([Utils::class, 'errorExceptionHandler']);
$version = new VersionString(\pocketmine\BASE_VERSION, \pocketmine\IS_DEVELOPMENT_BUILD, \pocketmine\BUILD_NUMBER);
define('pocketmine\VERSION', $version->getFullVersion(true));
$gitHash = str_repeat("00", 20);
if(\Phar::running(true) === ""){
$gitHash = Git::getRepositoryStatePretty(\pocketmine\PATH);
}else{
$phar = new \Phar(\Phar::running(false));
$meta = $phar->getMetadata();
if(isset($meta["git"])){
$gitHash = $meta["git"];
}
}
define('pocketmine\GIT_COMMIT', $gitHash);
$composerGitHash = InstalledVersions::getReference('pocketmine/pocketmine-mp');
if($composerGitHash !== null){
$currentGitHash = explode("-", \pocketmine\GIT_COMMIT)[0];
if($currentGitHash !== $composerGitHash){
critical_error("Composer dependencies and/or autoloader are out of sync.");
critical_error("- Current revision is $currentGitHash");
critical_error("- Composer dependencies were last synchronized for revision $composerGitHash");
critical_error("Out-of-sync Composer dependencies may result in crashes and classes not being found.");
critical_error("Please synchronize Composer dependencies before running the server.");
exit(1);
}
}
$opts = getopt("", ["data:", "plugins:", "no-wizard", "enable-ansi", "disable-ansi"]);
define('pocketmine\DATA', isset($opts["data"]) ? $opts["data"] . DIRECTORY_SEPARATOR : realpath(getcwd()) . DIRECTORY_SEPARATOR);
define('pocketmine\PLUGIN_PATH', isset($opts["plugins"]) ? $opts["plugins"] . DIRECTORY_SEPARATOR : realpath(getcwd()) . DIRECTORY_SEPARATOR . "plugins" . DIRECTORY_SEPARATOR);
if(!file_exists(\pocketmine\DATA)){
mkdir(\pocketmine\DATA, 0777, true);
}
$lockFile = fopen(\pocketmine\DATA . 'server.lock', "a+b");
if($lockFile === false){
critical_error("Unable to open server.lock file. Please check that the current user has read/write permissions to it.");
exit(1);
}
define('pocketmine\LOCK_FILE', $lockFile);
if(!flock(\pocketmine\LOCK_FILE, LOCK_EX | LOCK_NB)){
//wait for a shared lock to avoid race conditions if two servers started at the same time - this makes sure the
//other server wrote its PID and released exclusive lock before we get our lock
flock(\pocketmine\LOCK_FILE, LOCK_SH);
$pid = stream_get_contents(\pocketmine\LOCK_FILE);
critical_error("Another " . \pocketmine\NAME . " instance (PID $pid) is already using this folder (" . realpath(\pocketmine\DATA) . ").");
critical_error("Please stop the other server first before running a new one.");
exit(1);
}
ftruncate(\pocketmine\LOCK_FILE, 0);
fwrite(\pocketmine\LOCK_FILE, (string) getmypid());
fflush(\pocketmine\LOCK_FILE);
flock(\pocketmine\LOCK_FILE, LOCK_SH); //prevent acquiring an exclusive lock from another process, but allow reading
//Logger has a dependency on timezone
$tzError = Timezone::init();
if(isset($opts["enable-ansi"])){
Terminal::init(true);
}elseif(isset($opts["disable-ansi"])){
Terminal::init(false);
}else{
Terminal::init();
}
$logger = new MainLogger(\pocketmine\DATA . "server.log");
$logger->registerStatic();
foreach($tzError as $e){
$logger->warning($e);
}
unset($tzError);
emit_performance_warnings($logger);
$exitCode = 0;
do{
if(!file_exists(\pocketmine\DATA . "server.properties") and !isset($opts["no-wizard"])){
$installer = new SetupWizard();
if(!$installer->run()){
$exitCode = -1;
break;
}
}
//TODO: move this to a Server field
define('pocketmine\START_TIME', microtime(true));
/*
* We now use the Composer autoloader, but this autoloader is still for loading plugins.
*/
$autoloader = new \BaseClassLoader();
$autoloader->register(false);
new Server($autoloader, $logger, \pocketmine\DATA, \pocketmine\PLUGIN_PATH);
$logger->info("Stopping other threads");
$killer = new ServerKiller(8);
$killer->start(PTHREADS_INHERIT_NONE);
usleep(10000); //Fixes ServerKiller not being able to start on single-core machines
if(ThreadManager::getInstance()->stopAll() > 0){
$logger->debug("Some threads could not be stopped, performing a force-kill");
Process::kill(Process::pid());
}
}while(false);
$logger->shutdown();
$logger->join();
echo Terminal::$FORMAT_RESET . PHP_EOL;
if(!flock(\pocketmine\LOCK_FILE, LOCK_UN)){
critical_error("Failed to release the server.lock file.");
}
if(!fclose(\pocketmine\LOCK_FILE)){
critical_error("Could not close server.lock resource.");
}
exit($exitCode);
}
这个代码块非常冗长,我们从现在开始慢慢的一步步解析下去。我在解析过程中贴的代码比较多,大家可以慢慢去消化。那么我们继续从这个方法的第一个代码区块开始研究。
PHP:
if(count($messages = check_platform_dependencies()) > 0){
echo PHP_EOL;
$binary = version_compare(PHP_VERSION, "5.4") >= 0 ? PHP_BINARY : "unknown";
critical_error("Selected PHP binary does not satisfy some requirements.");
foreach($messages as $m){
echo " - $m" . PHP_EOL;
}
critical_error("PHP binary used: " . $binary);
critical_error("Loaded php.ini: " . (($file = php_ini_loaded_file()) !== false ? $file : "none"));
critical_error("Please recompile PHP with the needed configuration, or refer to the installation instructions at http://pmmp.rtfd.io/en/rtfd/installation.html.");
echo PHP_EOL;
exit(1);
}
unset($messages);
PHP:
function check_platform_dependencies(){
if(version_compare(MIN_PHP_VERSION, PHP_VERSION) > 0){
//If PHP version isn't high enough, anything below might break, so don't bother checking it.
return [
"PHP >= " . MIN_PHP_VERSION . " is required, but you have PHP " . PHP_VERSION . "."
];
}
$messages = [];
if(PHP_INT_SIZE < 8){
$messages[] = "32-bit systems/PHP are no longer supported. Please upgrade to a 64-bit system, or use a 64-bit PHP binary if this is a 64-bit system.";
}
if(php_sapi_name() !== "cli"){
$messages[] = "Only PHP CLI is supported.";
}
$extensions = [
"curl" => "cURL",
"ctype" => "ctype",
"date" => "Date",
"hash" => "Hash",
"json" => "JSON",
"mbstring" => "Multibyte String",
"openssl" => "OpenSSL",
"pcre" => "PCRE",
"phar" => "Phar",
"pthreads" => "pthreads",
"reflection" => "Reflection",
"sockets" => "Sockets",
"spl" => "SPL",
"yaml" => "YAML",
"zip" => "Zip",
"zlib" => "Zlib"
];
foreach($extensions as $ext => $name){
if(!extension_loaded($ext)){
$messages[] = "Unable to find the $name ($ext) extension.";
}
}
if(extension_loaded("pthreads")){
$pthreads_version = phpversion("pthreads");
if(substr_count($pthreads_version, ".") < 2){
$pthreads_version = "0.$pthreads_version";
}
if(version_compare($pthreads_version, "3.2.0") < 0){
$messages[] = "pthreads >= 3.2.0 is required, while you have $pthreads_version.";
}
}
if(extension_loaded("leveldb")){
$leveldb_version = phpversion("leveldb");
if(version_compare($leveldb_version, "0.2.1") < 0){
$messages[] = "php-leveldb >= 0.2.1 is required, while you have $leveldb_version.";
}
}
if(extension_loaded("pocketmine")){
$messages[] = "The native PocketMine extension is no longer supported.";
}
return $messages;
}
我们会看见一开始就又引入了另一个方法 check_platform_dependencies(), 这个方法是用来检测核心所需要的模块是否拥有,如果缺少就会把缺少模块的信息存进 $message 这个数组里,然后在返回这个数组。所以一旦这个数组消息数大于0,便会开始解析 php 的运行版本是否大于 5.4,一旦不符合同样也会报错退出。
我这边有点啰嗦,我觉得还是得要简洁意概,所以接下来我会挑重点的去讲,其他不太重要的大家自行翻阅源码。
========= 第二节 关键的 server.lock =========
在我们开启服务器的瞬间,PocketMine 为了避免同一个服务器重复开启,他们在 server 方法里生成了一个 server.lock 文件,然后每次开启服务器之时就会检测是否有 server.lock 这个文件,如果有就说明之前已经有一个服务器开启了,便会阻止这个服务器的启动,如果没有那就生成一个 server.lock 文件。具体实现代码我就贴在下方,大家可以去感受感受。
PHP:
$lockFile = fopen(\pocketmine\DATA . 'server.lock', "a+b");
if($lockFile === false){
critical_error("Unable to open server.lock file. Please check that the current user has read/write permissions to it.");
exit(1);
}
define('pocketmine\LOCK_FILE', $lockFile);
if(!flock(\pocketmine\LOCK_FILE, LOCK_EX | LOCK_NB)){
//wait for a shared lock to avoid race conditions if two servers started at the same time - this makes sure the
//other server wrote its PID and released exclusive lock before we get our lock
flock(\pocketmine\LOCK_FILE, LOCK_SH);
$pid = stream_get_contents(\pocketmine\LOCK_FILE);
critical_error("Another " . \pocketmine\NAME . " instance (PID $pid) is already using this folder (" . realpath(\pocketmine\DATA) . ").");
critical_error("Please stop the other server first before running a new one.");
exit(1);
}
ftruncate(\pocketmine\LOCK_FILE, 0);
fwrite(\pocketmine\LOCK_FILE, (string) getmypid());
fflush(\pocketmine\LOCK_FILE);
flock(\pocketmine\LOCK_FILE, LOCK_SH); //prevent acquiring an exclusive lock from another process, but allow reading
我回来了,今天下午这段时间真是忙碌死了。那么我们继续接上上次的话题 server.lock 文件能够有限的防止服务器在开启的状态下被误开。判断完服务器是否开启之后,才是真正的重头戏。接下来的代码就直接开始服务器的配置。
PHP:
if(!file_exists(\pocketmine\DATA . "server.properties") and !isset($opts["no-wizard"])){
$installer = new SetupWizard();
if(!$installer->run()){
$exitCode = -1;
break;
}
}
在这里就可以发现,当服务器的配置文件不存在和没启动配置都存在的时候,我们服务器的初始配置系统才会开始启动,所以大家能清楚的看见 SetupWizard 这个类便是 PocketMine 内核初始配置的类,以上内容就为第一章的内容了。我决定把 第二章 服务器的初始配置 放在明天更新。明天再见吧!
最后编辑: