ч.15 Игровой авторитарный сервер на процессах и архитектура в картинках

В предыдущей статье я рассказывал об архитектуре приложения, где ее отдельные компоненты могли бы работать параллельно не блокируя выполнения друг друга.

В этой статье я расскажу о причинах, по которым пришлось выбрать не многопоточное, а мультипроцессорное взаимодействие для разрабатываемого сервиса создания авторитарных серверов (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 процесса для запуска одной карты. Карт может быть много и они могут быть физически на разных серверах при этом визуально это будет открытый мир онлайн игры (о такой архитектуре я рассказывал в другой статье).

Функциональное наполнение разделов этих сервисов выглядит следующим образом (весь код и примеры разделов доступны по гостевому доступу на сайте проекта)

WebSocket сервер

WebSocket сервер

Игровой (Game) сервер для работы с бд (это и админ панель и выгрузка из нее данных в запущенную локацию)

Игровой (Game) сервер для работы с бд (это и админ панель и выгрузка из нее данных в запущенную локацию)

Игровой (Game) сервер позволяет добавлять код, но на отдельные механики (как обычно, и делают авторы игр для издателей), например: код при изменении свойств существа (например жизней, когда они доходят до 0), код при появлении существа (например разослать всем на карте информацию, что игрок вошел или босс игры повержен), добавить изменить отдельные механики (например скорость или анимацию снаряда)

Игровой (Game) сервер позволяет добавлять код, но на отдельные механики (как обычно, и делают авторы игр для издателей), например: код при изменении свойств существа (например жизней, когда они доходят до 0), код при появлении существа (например разослать всем на карте информацию, что игрок вошел или босс игры повержен), добавить изменить отдельные механики (например скорость или анимацию снаряда)

Песочница (сервер игровых механик)

Песочница (сервер игровых механик)

Причина 3 — шина данных для взаимодействия

Когда вы пишете код на потоках язык программирования предоставляет вам инструмент синхронизации (обмена) данными между этими потоками («субпроцессами»). В C# это происходит между общими статическими свойствами (которые в том коде, где вы делите потоки), в GO и PHP этот инструмент называется Channel (обмен происходит через общую оперативную память Shared Memory). Однако сервис как я писал раньше это независимое от языка приложение (например мы захотим сделать WebSocket сервер не на PHP, а на GO) и нужен уже независимый от языка инструмент обмена данными между приложениями, в т.ч. написанных на разных языках.

Ниже привожу примеры инструментов с замерами скорости обмена данных в секунду на малых пакетах (забегая вперед скажу что был выбран популярны метод обмена IPC очереди)

e89bea3c5242996b790bc43d0e688877.png

Для примера хотел бы поделиться более поздней версией архитектуры игрового авторитарного сервера на потоках которую я разработал (тем не менее многие вещи из этой диаграммы используется и по сей день)

0e896231d0403f8e7217cec28ce0385b.png

В заключении

Для тех кто читает серию статей и следит за проектом я подготовил ответ из предыдущей статьи »как ускорить ваш TCP сервер в 2 раза отключив лишь один стандартный параметр».

Ответ на этот вопрос пришел в Южной Африке с ужасным ping — приходилось отключать все обновления, авто скачивание медиа файлов в мессенджерах … В общем мой компьютер стал очень редко стать какие-то запросы в сеть и я обнаружил, что работая в игровом движке Unity (использует C#) пакеты отправляются не сразу… При этом в версии для WEB (браузерные) такой проблемы нет.

Описание проблемы можно увидеть ниже

Решение на 2:00, а причина параметр TCP взаимодействия в netFramework NoDelay = false. При работе с PHP этот параметр так же выключен, а в JS наоборот включен — он НЕ отправляет пакеты сразу как они приходят в очередь, а ждет еще некоторое время для отправки пакетов пачками далее по стеку в сетевую карту для минимизации времени задержки при работе сетевой карты Frame latency (вы можете почитать об этом в моей предыдущей статье так же на примере разработки онлайн игр).

В настоящее время я продолжаю разрабатывать сервис добавляя новые механики в игру (полагаю в части архитектуры существенных изменений более не будет).

О новых фичах и о том что будет дальше вы можете узнать подписавшись на выпуск новых статей в моем профиле.

По результатам моих исследований на рынке нет коробочных версий для создания Авторитарных игровых серверов, так что полагаю продукт будет полезен многим. Если у вас есть опыт написания подобных сервисов будут рад услышать его в комментариях или по личным контактом со мной с сайта.

© Habrahabr.ru