В предыдущей статье я рассказывал об архитектуре приложения, где ее отдельные компоненты могли бы работать параллельно не блокируя выполнения друг друга.
В этой статье я расскажу о причинах, по которым пришлось выбрать не многопоточное, а мультипроцессорное взаимодействие для разрабатываемого сервиса создания авторитарных серверов (http://my-fantasy.ru) и выделить компоненты в сервисы.
Бонус - архитектура игрового сервера в картинках в конце статьи.
Причина 1 - пользовательский код
Я рассказывал о том, что для вашей игры, где все механики рассчитывает сервер нужна возможность добавлять свой код в каком то toolset, не углубляясь во весь Фреймворк в целом и не изучая его архитектуру. Однако в будущем это может понадобиться и нам понадобиться менять и его.
Так как сервис, в том числе и SAAS (облачное) решение то нужно ограничить возможность пользоваться опасными функциями (например для доступа к файловой и операционной системе машины где работают и другие пользователи сервиса), для этого нужно ограничить вызов таких функций, ограничить потребление CPU и памяти, что и делается для определённого процесса.
Это может быть и код на JS, LUA и в тч на PHP - нужен именно скриптовый язык что позволит просто и быстро добавлять и менять фичи, притом сам сервер может быть написан совершенно на другом компилируемом языке (так устроено во многих онлайн играх с авторитарным сервером, для того что бы НЕ перекомпилировать каждый раз все целиком и часто у издателей, что платят авторам игр ежегодный royalty (авторский гонорар) нет доступа к исходному коду. но есть к таким скриптам).
Ниже приводиться список небезопасных и "нежелательных" встроенных функций для пользовательского кода языка PHP
set_include_path,ini_set,ini_alter,ini_get,ini_get_all,ini_restore,gc_enable,gc_collect_cycles,extension_loaded,dl,get_cfg_var,get_extension_funcs,restore_include_path,get_include_path,get_included_files,include,require,readfile,require_once,include_once,file,goto,set_time_limit,sys_get_temp_dir,php_ini_loaded_file,php_ini_scanned_files,putenv,get_resources,getenv,get_loaded_extensions,get_included_files,get_required_files,libxml_clear_errors,libxml_disable_entity_loader,libxml_get_errors,libxml_get_external_entity_loader,libxml_get_last_error,libxml_set_external_entity_loader,libxml_set_streams_context,libxml_use_internal_errors,basename,chgrp,chmod,chown,clearstatcache,copy,delete,dirname,disk_free_space,disk_total_space,diskfreespace,fclose,fdatasync,feof,fflush,fgetc,fgetcsv,fgets,fgetss,file_exists,file_get_contents,file_put_contents,fileatime,filectime,filegroup,fileinode,filemtime,fileowner,fileperms,filesize,filetype,flock,fnmatch,fopen,fpassthru,fputcsv,fputs,fread,fscanf,fseek,fstat,fsync,ftell,ftruncate,fwrite,glob,is_dir,is_executable,is_file,is_link,is_readable,is_uploaded_file,is_writable,is_writeable,lchgrp,lchown,link,linkinfo,lstat,mkdir,move_uploaded_file,parse_ini_file,parse_ini_string,pathinfo,pclose,popen,readfile,readlink,realpath_cache_get,realpath_cache_size,realpath,rename,rewind,rmdir,set_file_buffer,stat,symlink,tempnam,tmpfile,touch,umask,unlink,spl_autoload_call,spl_autoload_extensions,spl_autoload_functions,spl_autoload_register,spl_autoload_unregister,spl_autoload,escapeshellarg,escapeshellcmd,exec,passthru,proc_close,proc_get_status,proc_nice,proc_open,proc_terminate,shell_exec,system,mail,gc_mem_caches,get_current_user,getlastmod,getmyuid,getmyinode,getmygid,php_uname,phpcredits,phpinfo,phpversion,set_time_limit,zend_thread_id,zend_version,dir,chdir,chroot,closedir,dir,getcwd,opendir,readdir,rewinddir,scandir,session_abort,session_cache_expire,session_cache_limiter,session_commit,session_create_id,session_decode,session_destroy,session_encode,session_gc,session_get_cookie_params,session_id,session_module_name,session_name,session_regenerate_id,session_register_shutdown,session_reset,session_save_path,session_set_cookie_params,session_set_save_handler,session_start,session_status,session_unset,session_write_close,die,exit,__halt_compiler,highlight_file,highlight_string,ignore_user_abort,php_strip_whitespace,show_source,sleep,time_sleep_until,time_nanosleep,usleep,stream_bucket_append,stream_bucket_make_writeable,stream_bucket_new,stream_bucket_prepend,stream_context_create,stream_context_get_default,stream_context_get_options,stream_context_get_params,stream_context_set_default,stream_context_set_option,stream_context_set_params,stream_copy_to_stream,stream_filter_append,stream_filter_prepend,stream_filter_register,stream_filter_remove,stream_get_contents,stream_get_filters,stream_get_line,stream_get_meta_data,stream_get_transports,stream_get_wrappers,stream_is_local,stream_isatty,stream_notification_callback,stream_register_wrapper,stream_resolve_include_path,stream_select,stream_set_blocking,stream_set_chunk_size,stream_set_read_buffer,stream_set_timeout,stream_set_write_buffer,stream_socket_accept,stream_socket_client,stream_socket_enable_crypto,stream_socket_get_name,stream_socket_pair,stream_socket_recvfrom,stream_socket_sendto,stream_socket_server,stream_socket_shutdown,stream_supports_lock,stream_wrapper_register,stream_wrapper_restore,stream_wrapper_unregister,checkdnsrr,closelog,dns_check_record,dns_get_mx,dns_get_record,fsockopen,gethostbyaddr,gethostbyname,gethostbynamel,gethostname,getmxrr,getprotobyname,getprotobynumber,getservbyname,getservbyport,header_register_callback,header_remove,header,headers_list,headers_sent,http_response_code,inet_ntop,inet_pton,ip2long,long2ip,net_get_interfaces,openlog,pfsockopen,setcookie,setrawcookie,socket_get_status,socket_set_blocking,socket_set_timeout,syslog,md5_file,hash_file,hash_hmac_file,hash_update_file,msg_remove_queue,msg_set_queue,ftok,getmypid
а так же классы
FilesystemIterator,RecursiveDirectoryIterator,SplFileInfo,SplFileObject,SplTempFileObject,Directory,SessionHandler,SessionHandlerInterface,SessionIdInterface,SessionUpdateTimestampHandlerInterface,php_user_filter,streamWrapper,Reflection,ReflectionClass,ReflectionZendExtension,ReflectionExtension,ReflectionClassConstant,Reflection,ReflectionEnum,ReflectionEnumUnitCase,ReflectionEnumBackedCase,ReflectionFunction,ReflectionFunctionAbstract,ReflectionMethod,ReflectionNamedType,ReflectionObject,ReflectionParameter,ReflectionProperty,ReflectionType,ReflectionUnionType,ReflectionGenerator,ReflectionFiber,ReflectionIntersectionType,ReflectionReference,ReflectionAttribute,Reflector,ReflectionException
Причина 2 - сервисная архитектура
По определению сервис - это часть какого то приложения которая может быть написано на любом языке (в т.ч. отличного от языка приложения) и работать с ним в паре (как например различные GO сервисы с вебсайтами, например для обменов с 1с и т.п). Когда пишется приложение на потоках (thread) то оно тесно повязано на языке (вы разветвляете свой код как бы на 2 под процесса).
В игровом сервере есть несколько основных компонентов (у разных проектов свой, приведу на базе http://my-fantasy.ru)
WebSocket сервер - принимает и отправляет пакеты, про игру он не знает ничего
Игровой сервер - загружает из БД данные введенные в админ панели (загруженные карты, данные игроков, npc, анимации) для отправки в сервис WebSocket на отправку игрока и сохраняя изменения и запуская игровой кадр сервиса ниже...
Песочница (сервер Game механик) - это тот процесс в котором выполняется пользовательский код добавленный через админ панель. Здесь происходят расчеты текущего кадра: физики, поиска путей, что какой npc в этом кадре делает (двигается, регенерирует, атакует и т.д.)
Итого 3 процесса для запуска одной карты. Карт может быть много и они могут быть физически на разных серверах при этом визуально это будет открытый мир онлайн игры (о такой архитектуре я рассказывал в другой статье).
Функциональное наполнение разделов этих сервисов выглядит следующим образом (весь код и примеры разделов доступны по гостевому доступу на сайте проекта)
Причина 3 - шина данных для взаимодействия
Когда вы пишете код на потоках язык программирования предоставляет вам инструмент синхронизации (обмена) данными между этими потоками ("субпроцессами"). В C# это происходит между общими статическими свойствами (которые в том коде, где вы делите потоки), в GO и PHP этот инструмент называется Channel (обмен происходит через общую оперативную память Shared Memory). Однако сервис как я писал раньше это независимое от языка приложение (например мы захотим сделать WebSocket сервер не на PHP а на GO) и нужен уже независимый от языка инструмент обмена данными между приложениями, в т.ч. написанных на разных языках.
Ниже привожу примеры инструментов с замерами скорости обмена данных в секунду на малых пакетах (забегая вперед скажу что был выбран популярны метод обмена IPC очереди)
Для примера хотел бы поделиться более поздней версией архитектуры игрового авторитарного сервера на потоках которую я разработал (тем не менее многие вещи из этой диаграммы используется и по сей день)
В заключении
Для тех кто читает серию статей и следит за проектом я подготовил ответ из предыдущей статьи "как ускорить ваш TCP сервер в 2 раза отключив лишь один стандартный параметр".
Ответ на этот вопрос пришел в Южной Африке с ужасным ping - приходилось отключать все обновления, авто скачивание медиа файлов в мессенджерах ... В общем мой компьютер стал очень редко стать какие-то запросы в сеть и я обнаружил, что работая в игровом движке Unity (использует C#) пакеты отправляются не сразу... При этом в версии для WEB (браузерные) такой проблемы нет.
Описание проблемы можно увидеть ниже
Решение на 2:00, а причина параметр TCP взаимодействия в netFramework NoDelay = false
. При работе с PHP этот параметр так же выключен, а в JS наоборот включен - он НЕ отправляет пакеты сразу как они приходят в очередь, а ждет еще некоторое время для отправки пакетов пачками далее по стеку в сетевую карту для минимизации времени задержки при работе сетевой карты Frame latency (вы можете почитать об этом в моей предыдущей статье так же на примере разработки онлайн игр).
В настоящее время я продолжаю разрабатывать сервис добавляя новые механики в игру (полагаю в части архитектуры существенных изменений более не будет).
О новых фичах и о том что будет дальше вы можете узнать подписавшись на выпуск новых статей в моем профиле.
По результатам моих исследований на рынке нет коробочных версий для создания Авторитарных игровых серверов, так что полагаю продукт будет полезен многим. Если у вас есть опыт написания подобных сервисов будут рад услышать его в комментариях или по личным контактом со мной с сайта.