{"id":370972,"date":"2024-05-21T04:37:42","date_gmt":"2024-05-21T04:37:42","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=370972"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=370972","title":{"rendered":"<span>\u0420\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0432\u044b\u0441\u043e\u043a\u043e\u043d\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u0433\u043e \u0438\u0433\u0440\u043e\u0432\u043e\u0433\u043e WebSocket \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043d\u0430 Kotlin, Webflux \u0441 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u043e\u0439 BattleRoyale\/Matchmaking<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412\u0441\u0435\u043c \u0434\u043e\u0431\u0440\u043e\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0441\u0443\u0442\u043e\u043a. \u041d\u0435\u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u043d\u0430\u0437\u0430\u0434 \u043c\u043d\u043e\u044e\u00a0\u0431\u044b\u043b\u0430 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0430 <a href=\"https:\/\/habr.com\/ru\/articles\/774322\/\" rel=\"noopener noreferrer nofollow\">\u0441\u0442\u0430\u0442\u044c\u044f<\/a>, \u0433\u0434\u0435 \u044f \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e \u043e\u043f\u0438\u0441\u0430\u043b \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0434\u0435\u043c\u043e \u0438\u0433\u0440\u043e\u0432\u043e\u0433\u043e \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430. \u041d\u0430\u00a0\u044d\u0442\u043e\u0442 \u0440\u0430\u0437, \u044f \u0445\u043e\u0442\u0435\u043b\u00a0\u0431\u044b \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0431\u043e\u043b\u0435\u0435 \u0443\u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u0441\u0442\u0432\u043e\u0432\u0430\u043d\u043d\u044b\u043c \u0438 \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u043e\u043c \u043d\u0430 <strong>Kotlin<\/strong> \u0438 <strong>\u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u0442\u0435\u043a\u0435<\/strong>. <\/p>\n<h2>\u041d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043a\u0438 \u0438 \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432\u0430 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u044f<\/h2>\n<p>\u0421\u0442\u0430\u0440\u044b\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u0438\u043c\u0435\u0435\u0442 \u0440\u044f\u0434 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0445 \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043a\u043e\u0432 \u0434\u043b\u044f\u00a0\u043a\u043e\u043c\u043c\u0435\u0440\u0447\u0435\u0441\u043a\u043e\u0433\u043e\/\u044d\u043d\u0442\u0435\u0440\u043f\u0440\u0430\u0439\u0437 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f\u00a0\u2014 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u043f\u0440\u044f\u043c\u043e\u0433\u043e \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441\u00a0\u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u0438\u0437\u00a0\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 <strong>SpringBoot<\/strong>. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u0440\u0438\u043a\u0440\u0443\u0442\u0438\u0442\u044c \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u0443\u044e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e(oidc) \u0438\u0437\u00a0\u043a\u043e\u0440\u043e\u0431\u043a\u0438. \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0432\u0435\u0441\u044c\u043c\u0430 \u0443\u0441\u0442\u0430\u0440\u0435\u043b\u043e, \u0432\u0440\u0435\u043c\u044f \u0438 \u043e\u043f\u044b\u0442 \u0442\u043e\u043b\u043a\u0430\u0435\u0442 \u0434\u0432\u0438\u0433\u0430\u0442\u044c\u0441\u044f \u0432\u043f\u0435\u0440\u0435\u0434 \u0438 \u0443\u043b\u0443\u0447\u0448\u0430\u0442\u044c \u043f\u0440\u043e\u0434\u0443\u043a\u0442.<\/p>\n<p>\u0421\u0440\u0435\u0434\u0438 \u043f\u0440\u0435\u043c\u0443\u0449\u0435\u0441\u0442\u0432\u00a0\u2014 \u0432\u044b \u0438\u043c\u0435\u0435\u0442\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u043f\u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f \u0441\u00a0\u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c Netty, \u043f\u043e\u043d\u044f\u0442\u044c \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u044b \u0435\u0433\u043e \u0440\u0430\u0431\u043e\u0442\u044b \u0438 \u0432\u043b\u0438\u044f\u0442\u044c \u043d\u0430\u00a0\u0435\u0433\u043e \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c\u044b.<\/p>\n<h2>\u041a\u043e\u043d\u0446\u0435\u043f\u0442<\/h2>\n<p>\u041a\u0430\u043a\u00a0\u0438 \u0432\u00a0\u043f\u0440\u043e\u0448\u043b\u044b\u0439 \u0440\u0430\u0437 \u043a\u043e\u043d\u0446\u0435\u043f\u0442 \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0435\u0436\u043d\u0438\u043c, \u043d\u043e\u00a0\u0441\u00a0\u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u043c\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u043c\u0438.<\/p>\n<ol>\n<li>\n<p>\u041d\u0430\u00a0\u0432\u0445\u043e\u0434\u0435: Kotlin, Reactor\u2011Netty, Spring, Maven<\/p>\n<\/li>\n<li>\n<p>\u0426\u0435\u043b\u0438 \u0442\u0435\u00a0\u0436\u0435\u00a0\u2014 \u0445\u043e\u0442\u0438\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c\u0441\u044f, \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0442\u044c\u0441\u044f \u043a\u00a0\u0438\u0433\u0440\u043e\u0432\u043e\u0439 \u043a\u043e\u043c\u043d\u0430\u0442\u0435 \u0438 \u043d\u0430\u0447\u0430\u0442\u044c \u0438\u0433\u0440\u0430\u0442\u044c, \u0434\u0432\u0438\u0433\u0430\u044f \u043a\u0440\u0443\u0436\u043e\u043a \u043d\u0430\u00a0\u044d\u043a\u0440\u0430\u043d\u0435.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430\u00a0\u0432\u044b\u0445\u043e\u0434\u0435: \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0440\u0430\u0431\u043e\u0442\u044b \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0438 \u0435\u0433\u043e \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0435\u0439.<\/p>\n<\/li>\n<\/ol>\n<h2>\u0421\u0442\u0435\u043a<\/h2>\n<ul>\n<li>\n<p>Kotlin 1.8<\/p>\n<\/li>\n<li>\n<p>JDK 21<\/p>\n<\/li>\n<li>\n<p>Spring Boot 3.2.2<\/p>\n<\/li>\n<li>\n<p>Spring Webflux (<a href=\"https:\/\/projectreactor.io\/docs\/netty\/release\/reference\/index.html\" rel=\"noopener noreferrer nofollow\">Reactor-netty<\/a>)<\/p>\n<\/li>\n<\/ul>\n<h2>\u0421\u0431\u043e\u0440\u043a\u0430<\/h2>\n<blockquote>\n<p><a href=\"https:\/\/github.com\/tfkfan\/webflux-server-game-demo\" rel=\"noopener noreferrer nofollow\">\u0418\u0441\u0445\u043e\u0434\u043d\u0438\u043a \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0437\u0434\u0435\u0441\u044c<\/a><\/p>\n<\/blockquote>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u0431\u043e\u0440\u0449\u0438\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438 \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f Maven<\/p>\n<p><strong>pom.xml<\/strong> \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"xml\">&lt;?xml version=\"1.0\" encoding=\"ISO-8859-15\"?> &lt;project xmlns=\"http:\/\/maven.apache.org\/POM\/4.0.0\" xmlns:xsi=\"http:\/\/www.w3.org\/2001\/XMLSchema-instance\"          xsi:schemaLocation=\"http:\/\/maven.apache.org\/POM\/4.0.0 http:\/\/maven.apache.org\/maven-v4_0_0.xsd\">     &lt;modelVersion>4.0.0&lt;\/modelVersion>     &lt;name>webflux-game-server&lt;\/name>      &lt;groupId>com.tfkfan&lt;\/groupId>     &lt;artifactId>webflux-game-server&lt;\/artifactId>     &lt;version>1.0.0-SNAPSHOT&lt;\/version>     &lt;packaging>jar&lt;\/packaging>      &lt;parent>         &lt;groupId>org.springframework.boot&lt;\/groupId>         &lt;artifactId>spring-boot-starter-parent&lt;\/artifactId>         &lt;version>3.2.2&lt;\/version>         &lt;relativePath\/>     &lt;\/parent>      &lt;properties>         &lt;kotlin.version>1.8.0&lt;\/kotlin.version>         &lt;kotlin.coroutines.version>1.8.0-RC&lt;\/kotlin.coroutines.version>         &lt;maven.compiler.version>21&lt;\/maven.compiler.version>     &lt;\/properties>      &lt;dependencies>         &lt;dependency>             &lt;groupId>org.springframework.boot&lt;\/groupId>             &lt;artifactId>spring-boot-starter-webflux&lt;\/artifactId>         &lt;\/dependency>         &lt;dependency>             &lt;groupId>org.jetbrains.kotlinx&lt;\/groupId>             &lt;artifactId>kotlinx-coroutines-core&lt;\/artifactId>             &lt;version>${kotlin.coroutines.version}&lt;\/version>         &lt;\/dependency>         &lt;dependency>             &lt;groupId>org.jetbrains.kotlin&lt;\/groupId>             &lt;artifactId>kotlin-stdlib&lt;\/artifactId>             &lt;version>${kotlin.version}&lt;\/version>         &lt;\/dependency>         &lt;dependency>             &lt;groupId>org.jetbrains.kotlin&lt;\/groupId>             &lt;artifactId>kotlin-reflect&lt;\/artifactId>             &lt;version>${kotlin.version}&lt;\/version>         &lt;\/dependency>         &lt;dependency>             &lt;groupId>com.google.code.gson&lt;\/groupId>             &lt;artifactId>gson&lt;\/artifactId>             &lt;version>2.8.9&lt;\/version>         &lt;\/dependency>         &lt;dependency>             &lt;groupId>org.apache.commons&lt;\/groupId>             &lt;artifactId>commons-lang3&lt;\/artifactId>             &lt;version>3.12.0&lt;\/version>         &lt;\/dependency>     &lt;\/dependencies>      &lt;build>         &lt;plugins>             &lt;plugin>                 &lt;artifactId>kotlin-maven-plugin&lt;\/artifactId>                 &lt;groupId>org.jetbrains.kotlin&lt;\/groupId>                 &lt;version>${kotlin.version}&lt;\/version>                 &lt;executions>                     &lt;execution>                         &lt;id>compile&lt;\/id>                         &lt;phase>process-sources&lt;\/phase>                         &lt;goals>                             &lt;goal>compile&lt;\/goal>                         &lt;\/goals>                         &lt;configuration>                             &lt;sourceDirs>                                 &lt;sourceDir>${project.basedir}\/src\/main\/kotlin&lt;\/sourceDir>                             &lt;\/sourceDirs>                         &lt;\/configuration>                     &lt;\/execution>                     &lt;execution>                         &lt;id>test-compile&lt;\/id>                         &lt;goals>                             &lt;goal>test-compile&lt;\/goal>                         &lt;\/goals>                         &lt;configuration>                             &lt;sourceDirs>                                 &lt;sourceDir>${project.basedir}\/src\/test\/kotlin&lt;\/sourceDir>                             &lt;\/sourceDirs>                         &lt;\/configuration>                     &lt;\/execution>                 &lt;\/executions>             &lt;\/plugin>             &lt;plugin>                 &lt;groupId>org.apache.maven.plugins&lt;\/groupId>                 &lt;artifactId>maven-compiler-plugin&lt;\/artifactId>             &lt;\/plugin>         &lt;\/plugins>     &lt;\/build> &lt;\/project> <\/code><\/pre>\n<p>\u0412\u0441\u0435 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e, \u0432\u0441\u0435 \u0447\u0442\u043e\u00a0\u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e\u00a0\u2014 webflux \u0432\u043c\u0435\u0441\u0442\u0435 \u0441\u00a0embedded netty \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c. <\/p>\n<p>\u0421\u0431\u043e\u0440\u043a\u0430 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a<\/p>\n<pre><code class=\"bash\">.\/mvnw clean verify spring-boot:run<\/code><\/pre>\n<h2>\u041a\u043b\u0438\u0435\u043d\u0442<\/h2>\n<p>\u041a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432\u00a0\u0441\u0442\u0430\u0442\u0438\u043a\u0435 <strong>\/src\/main\/resources\/static<\/strong>. \u041a\u0440\u0430\u0442\u043a\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435:<\/p>\n<ul>\n<li>\n<p><strong>index.html<\/strong>\u00a0\u2014 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/p>\n<\/li>\n<li>\n<p><strong>app.js<\/strong>\u00a0\u2014 \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u043a\u0430 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439<\/p>\n<\/li>\n<li>\n<p><strong>network.js<\/strong>\u00a0\u2014 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u0434\u043b\u044f\u00a0\u0440\u0430\u0431\u043e\u0442\u044b \u0441\u00a0\u0441\u0435\u0442\u044c\u044e (websocket)<\/p>\n<\/li>\n<li>\n<p><strong>types.js<\/strong>\u00a0\u2014 \u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a \u0442\u0438\u043f\u043e\u0432 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439<\/p>\n<\/li>\n<li>\n<p><strong>styles.css<\/strong>\u00a0\u2014 \u0442\u0430\u0431\u043b\u0438\u0446\u0430 \u0441\u0442\u0438\u043b\u0435\u0439 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/p>\n<\/li>\n<\/ul>\n<p>\u041d\u0435\u00a0\u0431\u0443\u0434\u0435\u043c \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0430\u00a0\u043a\u043b\u0438\u0435\u043d\u0442\u0435, \u043e\u043d \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u0438\u043c\u0438\u0442\u0438\u0432\u0435\u043d \u0438 \u043f\u0440\u043e\u0441\u0442. \u0412\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u0443\u0434\u0435\u043b\u0438\u043c \u0431\u043e\u043b\u044c\u0448\u0435\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u043e\u0439 \u0447\u0430\u0441\u0442\u0438, \u043f\u0440\u043e\u00a0\u0447\u0442\u043e\u00a0\u0438 \u043f\u0438\u0441\u0430\u043b\u0430\u0441\u044c \u0441\u0442\u0430\u0442\u044c\u044f.<\/p>\n<h3>\u0421\u0435\u0440\u0432\u0435\u0440<\/h3>\n<p>Http\u2011\u043f\u043e\u0440\u0442 \u043f\u043e\u00a0\u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e\u00a0\u2014 8080. \u0414\u043b\u044f\u00a0\u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442\u0430 \u0432\u044b\u0434\u0435\u043b\u0435\u043d \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u043f\u043e\u0434\u00a0\u044d\u0442\u0438\u043c \u043f\u043e\u0440\u0442\u043e\u043c: <strong>\/websocket<\/strong> <\/p>\n<p>\u0421\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d \u043d\u0430\u00a0\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u043f\u0430\u043a\u0435\u0442\u044b:<\/p>\n<ul>\n<li>\n<p><strong>config<\/strong>\u00a0\u2014 \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043f\u0440\u043e\u043f\u0435\u0440\u0442\u044f, \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u044b<\/p>\n<\/li>\n<li>\n<p><strong>event<\/strong>\u00a0\u2014 \u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u0434\u043b\u044f\u00a0\u0440\u0430\u0431\u043e\u0442\u044b \u0441\u00a0\u0432\u043d\u0443\u0442\u0440\u0438\u0438\u0433\u0440\u043e\u0432\u044b\u043c\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\u043c\u0438<\/p>\n<\/li>\n<li>\n<p><strong>game<\/strong>\u00a0\u2014 \u0412\u043d\u0443\u0442\u0440\u0438\u043a\u043e\u043c\u043d\u0430\u0442\u043d\u0430\u044f \u0438\u0433\u0440\u043e\u0432\u0430\u044f \u043b\u043e\u0433\u0438\u043a\u0430, \u0438\u0433\u0440\u043e\u0432\u044b\u0435 \u043c\u043e\u0434\u0435\u043b\u0438, \u0438\u0433\u0440\u043e\u0432\u044b\u0435 \u043f\u043e\u043b\u044f (\u0441\u044e\u0434\u0430\u00a0\u0436\u0435 \u0434\u043e\u043b\u0436\u043d\u044b \u0432\u0445\u043e\u0434\u0438\u0442\u044c \u0438 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u0432 \u0432\u043d\u0443\u0442\u0440\u0438\u0438\u0433\u0440\u043e\u0432\u044b\u0445 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432, \u0442\u0430\u043a\u0438\u0445 \u043a\u0430\u043a <strong>Mesh, QuadTree <\/strong>\u0438\u00a0\u0442.\u00a0\u043f.)<\/p>\n<\/li>\n<li>\n<p><strong>math<\/strong>\u00a0\u2014 \u041c\u0430\u0442\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043c\u0430\u043d\u0438\u043f\u0443\u043b\u044f\u0446\u0438\u0438 \u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u044b<\/p>\n<\/li>\n<li>\n<p><strong>network<\/strong>\u00a0\u2014 \u0420\u0430\u0431\u043e\u0442\u0430 \u0441\u00a0\u0441\u0435\u0442\u044c\u044e, \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043f\u043e\u0434\u043a\u0430\u043f\u043e\u0442\u043d\u044b\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b<\/p>\n<\/li>\n<li>\n<p><strong>service<\/strong>\u00a0\u2014 \u0421\u0435\u0440\u0432\u0438\u0441\u044b, \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0449\u0438\u0435 \u0437\u0430\u00a0\u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0432\u00a0\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f, \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440 \u0441\u0435\u0441\u0441\u0438\u0439, \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440 \u043a\u043e\u043c\u043d\u0430\u0442 (\u043e\u043d\u00a0\u0436\u0435 \u043c\u0430\u0442\u0447\u043c\u0435\u0439\u043a\u0435\u0440) \u0438 \u0434\u0440.<\/p>\n<\/li>\n<li>\n<p><strong>shared <\/strong>\u2014 \u0420\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u043f\u043e\u0432\u0441\u0435\u043c\u0435\u0441\u0442\u043d\u043e \u0432\u00a0\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438<\/p>\n<\/li>\n<\/ul>\n<p>\u041a\u0430\u043a\u00a0\u0432\u0438\u0434\u043d\u043e \u0438\u0437\u00a0\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u0431\u043e\u043b\u044c\u0448\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0438\u0433\u0440\u043e\u0432\u043e\u0439 \u043b\u043e\u0433\u0438\u043a\u0438 \u0432\u00a0\u0438\u0442\u043e\u0433\u0435 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0430 \u043c\u0435\u0436\u0434\u0443 \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c\u0438 \u0438 \u0438\u0433\u0440\u043e\u0432\u044b\u043c\u0438 \u043a\u043e\u043c\u043d\u0430\u0442\u0430\u043c\u0438. \u0411\u043e\u043b\u044c\u0448\u0435 \u043d\u0435\u00a0\u043d\u0443\u0436\u043d\u043e \u043f\u0438\u0441\u0430\u0442\u044c \u0434\u0435\u043a\u043e\u0434\u0435\u0440\u044b, netty\u2011\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0438 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u044b, \u043a\u043e\u0434 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u0437\u043d\u0430\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0449\u0435.<\/p>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0440\u0430\u0431\u043e\u0442\u0443 \u0441\u00a0\u0441\u0435\u0442\u044c\u044e \u0438 \u043f\u0430\u043a\u0435\u0442 <strong><em>network<\/em><\/strong>.<\/p>\n<h3>Network. \u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0441\u0435\u0442\u044c\u044e \u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u0441\u0435\u0440\u0432\u0435\u0440\u0430<\/h3>\n<p>\u0420\u0430\u0431\u043e\u0442\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0440\u0430\u0437\u0443\u043c\u0435\u0435\u0442\u0441\u044f \u0441 <strong>Application.java<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u0442 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 \u0432 webflux <strong>netty-\u0441\u0435\u0440\u0432\u0435\u0440<\/strong>.<\/p>\n<p>\u041d\u0430\u043f\u043e\u043c\u043d\u044e, Netty\u00a0\u2014 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a \u0434\u043b\u044f\u00a0\u0440\u0430\u0431\u043e\u0442\u044b \u0441\u00a0\u0441\u0435\u0442\u044c\u044e, \u0432\u00a0\u043e\u0441\u043d\u043e\u0432\u0435 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043b\u0435\u0436\u0430\u0442 \u043d\u0435\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u044e\u0449\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438. \u0422\u043e\u0435\u0441\u0442\u044c, \u0433\u0440\u0443\u0431\u043e \u0433\u043e\u0432\u043e\u0440\u044f, \u043a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0438\u0439 \u0437\u0430\u043f\u0440\u043e\u0441, \u043f\u043e\u0442\u043e\u043a, \u0432\u00a0\u043f\u043e\u0441\u043b\u0435\u0434\u0441\u0442\u0432\u0438\u0438 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0449\u0438\u0439 \u0435\u0433\u043e, \u043d\u0435\u00a0\u0436\u0434\u0435\u0442 \u0447\u0442\u0435\u043d\u0438\u044f \u0438\u0437\u00a0\u0441\u043e\u043a\u0435\u0442\u0430, \u0432\u00a0\u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442\u00a0\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u044e\u0449\u0438\u0445 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0432\u0432\u043e\u0434\u0430\u2011\u0432\u044b\u0432\u043e\u0434\u0430, \u0430\u00a0\u043c\u043e\u0436\u0435\u0442\u00a0\u0431\u044b\u0442\u044c \u0437\u0430\u043d\u044f\u0442 \u0447\u0435\u043c\u00a0\u043b\u0438\u0431\u043e \u0435\u0449\u0435 \u0438 \u0432\u044b\u0437\u043e\u0432\u0435\u0442\u0441\u044f, \u043a\u043e\u0433\u0434\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043d\u043e \u0438 \u0433\u043e\u0442\u043e\u0432\u043e \u043a\u00a0\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435. <\/p>\n<p>\u0412\u043b\u0438\u044f\u0442\u044c \u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u043a\u0443\u0440\u0430\u0446\u0438\u044e \u0441\u0430\u043c\u043e\u0433\u043e Netty \u0438 \u0435\u0433\u043e \u0440\u0435\u0441\u0443\u0440\u0441\u044b \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f  <\/p>\n<pre><code>-Dreactor.netty.*<\/code><\/pre>\n<p>\u0421\u043c. <a href=\"https:\/\/projectreactor.io\/docs\/netty\/release\/reference\/index.html\" rel=\"noopener noreferrer nofollow\">https:\/\/projectreactor.io\/docs\/netty\/release\/reference\/index.html<\/a><\/p>\n<p>\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a, \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u0439 \u0441 \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438:<\/p>\n<pre><code class=\"kotlin\">@Service class MainWebSocketHandler(     private val webSocketSessionService: WebSocketSessionService ) : WebSocketHandler {     private val objectMapper = Gson()     private lateinit var roomService: RoomService      companion object {         val log: Logger = LogManager.getLogger(this::class.java)     }      override fun handle(webSocketSession: WebSocketSession): Mono&lt;Void> {         val input = webSocketSession.receive().share()         val userSession = UserSession(webSocketSession.id, webSocketSession.handshakeInfo)         val sessionHandler = UserSessionWebSocketHandler(             userSession,             webSocketSessionService, roomService         )          val receive = input             .filter { it.type == WebSocketMessage.Type.TEXT }             .map(this::toMessage)             .doOnNext(sessionHandler::onNext)          val send = webSocketSession.send(webSocketSessionService.onActive(userSession)             .map {                 webSocketSession.textMessage(objectMapper.toJson(it))             }             .doOnError { handleError(webSocketSession, it) })          val security = webSocketSession.handshakeInfo.principal.doOnNext {             webSocketSessionService.onPrincipalInit(userSession, it)         }         return Flux.merge(receive, send, security)             .doOnSubscribe { webSocketSessionService.onSubscribe(userSession, it) }             .doOnTerminate { webSocketSessionService.onInactive(userSession) }             .doOnError { handleError(webSocketSession, it) }             .then()     }      private fun toMessage(webSocketMessage: WebSocketMessage): Message =         objectMapper.fromJson(InputStreamReader(webSocketMessage.payload.asInputStream()), Message::class.java)      private fun handleError(webSocketSession: WebSocketSession, exception: Throwable) {         log.error(\"Error in ${webSocketSession.id} session\", exception)     }      @Autowired     fun setGameRoomManagementService(@Lazy roomService: RoomService) {         this.roomService = roomService     } }<\/code><\/pre>\n<p>\u041a\u0430\u043a\u00a0\u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u0435\u043b upgrade \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0430\u00a0\u2014 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043f\u043e\u043f\u0430\u0434\u0430\u0435\u0442 \u0432\u00a0\u043c\u0435\u0442\u043e\u0434 <strong>handle<\/strong>. \u0412\u00a0\u043d\u0435\u043c \u0441\u0440\u0430\u0437\u0443 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0439 \u0441\u0435\u0441\u0441\u0438\u0438 <strong>UserSession<\/strong>, \u0430\u00a0\u0442\u0430\u043a\u0436\u0435 \u043d\u0430\u0432\u0435\u0448\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0440\u044f\u0434 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0445 \u043b\u044f\u043c\u0431\u0434, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0449\u0438\u0445 \u043a\u0430\u043a\u00a0\u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u0442\u0430\u043a \u0438 \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0435.<\/p>\n<p>\u0421\u0442\u043e\u0438\u0442 \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a handshake:<\/p>\n<pre><code class=\"kotlin\">val security = webSocketSession.handshakeInfo.principal.doOnNext {     webSocketSessionService.onPrincipalInit(userSession, it) }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u0430\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0430\u043b\u043e\u0432, \u0435\u0441\u043b\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0448\u043b\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e, \u043f\u0440\u0438\u0447\u0435\u043c \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0430\u043b\u0430 \u0432\u00a0Mono \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043d\u0430\u00a0\u043c\u043e\u043c\u0435\u043d\u0442 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0437\u0430\u00a0\u043f\u0440\u0435\u0434\u0435\u043b\u0430\u043c\u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430. \u041d\u0430\u00a0\u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442, \u0438\u043d\u043e\u0433\u043e \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c\u0430 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043a\u0440\u0435\u0434\u043e\u0432 \u0438\u0437 <strong>handshakeInfo<\/strong>, \u0432\u00a0\u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u0432\u00a0\u043c\u0435\u0442\u043e\u0434\u0435 handle,\u00a0\u2014 \u043d\u0435\u0442, \u0430\u00a0\u0441\u0432\u0435\u0436\u0438\u0439 \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0439 <strong><em>websocket\u2011security<\/em><\/strong> \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043d\u0430\u00a0\u0441\u0442\u0430\u0434\u0438\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043a\u043e\u043c\u0430\u043d\u0434\u043e\u0439 <strong>spring<\/strong>.<\/p>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043a\u043b\u0430\u0441\u0441 <strong>UserSessionWebSocketHandler<\/strong>:<\/p>\n<pre><code class=\"kotlin\">class UserSessionWebSocketHandler(     private val userSession: UserSession,     private val webSocketSessionService: WebSocketSessionService,     private val roomService: RoomService ) {     private val objectMapper = Gson()      companion object {         val log: Logger = LogManager.getLogger(this::class.java)     }      fun onNext(message: Message) {         val messageData = if (message.data != null) message.data as Map&lt;*, *> else null          when (message.type) {             GAME_ROOM_JOIN -> {                     log.debug(\"Join attempt from {}\", userSession.handshakeInfo.remoteAddress)                     roomService.addPlayerToWait(                         userSession,                         objectMapper.fromJson(messageData.toString(), GameRoomJoinEvent::class.java)                     )             }              INIT -> {                 roomService.getRoomByKey(userSession.roomKey)                     .ifPresent { it.onPlayerInitRequest(userSession) }             }              PLAYER_KEY_DOWN -> {                 roomService.getRoomByKey(userSession.roomKey)                     .ifPresent {                         it.onPlayerKeyDown(                             userSession,                             objectMapper.fromJson(messageData.toString(), KeyDownPlayerEvent::class.java)                         )                     }             }          }     } }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0447\u0438\u0441\u043b\u043e\u0432\u043e\u0433\u043e <strong>message.type<\/strong>. <\/p>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u044b <strong>WebSocketSessionService<\/strong>, <strong>WebSocketMessagePublisher<\/strong> \u0438 \u0438\u0445 \u0438\u043c\u043f\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044e:<\/p>\n<pre><code class=\"kotlin\">interface WebSocketMessagePublisher {     fun send(userSession: UserSession, message: Any)     fun sendFailure(userSession: UserSession, message: Any)     fun sendBroadcast(type: MessageType, message: String)     fun sendBroadcast(message: Any)     fun sendBroadcast(messageFunction: Function&lt;UserSession, Any>)     fun sendBroadcast(userSessions: Collection&lt;UserSession>, message: Any)     fun send(userSession: UserSession, function: Function&lt;UserSession, Any>)     fun sendBroadcast(userSessions: Collection&lt;UserSession>, function: Function&lt;UserSession, Any>) }<\/code><\/pre>\n<pre><code class=\"kotlin\">interface WebSocketSessionService : WebSocketMessagePublisher {     fun onActive(userSession: UserSession): Flux&lt;Any>     fun onSubscribe(userSession: UserSession, subscription: Subscription)     fun onPrincipalInit(userSession: UserSession, principal: Principal)     fun onInactive(userSession: UserSession)     fun close(userSession: UserSession): Mono&lt;Void>     fun close(userSessionId: String): Mono&lt;Void>     fun closeAll():Mono&lt;Void>     fun roomIds():Mono&lt;Collection&lt;String>>     fun sessionIds():Mono&lt;Collection&lt;String>>     fun roomSessionIds(roomId:UUID):Mono&lt;Collection&lt;String>> }<\/code><\/pre>\n<pre><code class=\"kotlin\">@Service open class WebSocketSessionServiceImpl : WebSocketSessionService {     companion object {         val log = LogManager.getLogger(this::class.java)     }      private var sessionPublishers: MutableMap&lt;String, Many&lt;Any>> = HashMap()     private var sessionSubscriptions: MutableMap&lt;String, Subscription> = HashMap()     private var sessions: MutableMap&lt;String, UserSession> = HashMap()     private lateinit var roomService: RoomService      override fun sendBroadcast(message: Any) = sessionPublishers.values.forEach { it.tryEmitNext(message) }     override fun close(userSession: UserSession): Mono&lt;Void> {         if (sessionSubscriptions.containsKey(userSession.id))             sessionSubscriptions[userSession.id]!!.cancel()         return Mono.empty()     }      override fun close(userSessionId: String): Mono&lt;Void> {         if (sessions.containsKey(userSessionId))             return close(sessions[userSessionId]!!)         return Mono.empty()     }      override fun closeAll(): Mono&lt;Void> {         sessions.values.forEach(this::close)         return Mono.empty()     }      override fun roomIds(): Mono&lt;Collection&lt;String>> = Mono.fromCallable {         roomService.getRoomIds()     }      override fun sessionIds(): Mono&lt;Collection&lt;String>> = Mono.fromCallable {         sessions.keys.toList()     }      override fun roomSessionIds(roomId: UUID): Mono&lt;Collection&lt;String>> = Mono.fromCallable {         roomService.getRoomSessionIds(roomId)     }      override fun sendBroadcast(messageFunction: Function&lt;UserSession, Any>) =         sessions.values.stream().forEach { this.send(it, messageFunction) }      override fun sendBroadcast(userSessions: Collection&lt;UserSession>, message: Any) =         userSessions.forEach { send(it, message) }      override fun send(         userSession: UserSession,         function: Function&lt;UserSession, Any>     ) =         send(userSession, function.apply(userSession))      override fun sendBroadcast(         userSessions: Collection&lt;UserSession>,         function: Function&lt;UserSession, Any>     ) = userSessions.forEach { send(it, function.apply(it)) }      override fun send(userSession: UserSession, message: Any) {         val webSocketSessionId = userSession.id         if (sessionPublishers.containsKey(webSocketSessionId)) sessionPublishers[webSocketSessionId]!!             .tryEmitNext(message)     }      override fun sendFailure(userSession: UserSession, message: Any) =         send(userSession, Message(FAILURE, message))      override fun sendBroadcast(type: MessageType, message: String) =         sendBroadcast(Message(MESSAGE, GameMessagePack(type.type, message)))      override fun onActive(userSession: UserSession): Flux&lt;Any> {         log.debug(\"Client ${userSession.id} connected\")         \/*        UriComponentsBuilder builder = UriComponentsBuilder.fromUri(session.getHandshakeInfo().getUri()); Map&lt;String,String> queryParams = builder.build().getQueryParams().toSingleValueMap();*\/         val sink = Sinks.many().unicast().onBackpressureBuffer&lt;Any>()         sessionPublishers[userSession.id] = sink         sessions[userSession.id] = userSession         return sink.asFlux()     }      override fun onSubscribe(userSession: UserSession, subscription: Subscription) {         sessionSubscriptions[userSession.id] = subscription     }      override fun onPrincipalInit(         userSession: UserSession,         principal: Principal     ) {         userSession.principal = principal     }      override fun onInactive(userSession: UserSession) {         log.debug(\"Client ${userSession.id} disconnected\")         if (!sessionPublishers.containsKey(userSession.id)) return         sessionPublishers.remove(userSession.id)         sessions.remove(userSession.id)          if (userSession.roomKey != null)             roomService.getRoomByKey(userSession.roomKey!!)                 .ifPresent { it.onDisconnect(userSession) }     }      @Autowired     fun setGameRoomManagementService(@Lazy roomService: RoomService) {         this.roomService = roomService     } } <\/code><\/pre>\n<p>\u0414\u0430\u043d\u043d\u044b\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u044b \u0441\u043b\u0443\u0436\u0430\u0442, \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u043e, \u0434\u043b\u044f \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f \u0441\u0435\u0441\u0441\u0438\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u0412\u0441\u0435 \u044d\u0442\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u043a\u043b\u0430\u0441\u0441\u044b <strong>Sinks.Many<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043e\u0447\u0435\u0442\u0430\u044e\u0442 \u0432\u043d\u0443\u0442\u0440\u0438 \u0441\u0435\u0431\u044f \u043a\u0430\u043a Publisher \u0442\u0430\u043a \u0438 Subscriber.  \u0412 \u0440\u0430\u043d\u043d\u0438\u0445 \u0432\u0435\u0440\u0441\u0438\u044f\u0445 project reactor \u0430\u043a\u0442\u0438\u0432\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0438\u0441\u044c <strong>Processor&lt;T, R><\/strong>  \u0441 \u0442\u0435\u043c \u0436\u0435 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c, \u0432 \u043f\u043e\u0437\u0434\u043d\u0438\u0445 \u0432\u0435\u0440\u0441\u0438\u044f\u0445 \u044d\u0442\u0438 \u043a\u043b\u0430\u0441\u0441\u044b \u043f\u043e\u043c\u0435\u0447\u0435\u043d\u044b \u043a\u0430\u043a Deprecated.<\/p>\n<h2>\u0421\u0435\u0441\u0441\u0438\u044f<\/h2>\n<pre><code class=\"kotlin\">class UserSession(     override val id: String,     val handshakeInfo: HandshakeInfo ) : AbstractEntity&lt;String>(id) {     var player: Player? = null     var roomKey: UUID? = null     var principal: Principal? = null     override fun toString(): String = \"UserSession [id=\" + id + \"player=\" + player + \", parentGameRoom=\" + roomKey + \"]\" }<\/code><\/pre>\n<p>\u0421\u0435\u0441\u0441\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0445\u0440\u0430\u043d\u0438\u0442 \u0432 \u0441\u0435\u0431\u0435 \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435, \u0432 \u0442.\u0447. Principal, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u0432 \u043c\u043e\u043c\u0435\u043d\u0442 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438. Id \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0439 \u0441\u0435\u0441\u0441\u0438\u0438 \u0431\u0435\u0440\u0435\u0442\u0441\u044f \u0438\u0437 \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442 \u0441\u0435\u0441\u0441\u0438\u0438:<\/p>\n<pre><code class=\"kotlin\">UserSession(webSocketSession.id, webSocketSession.handshakeInfo)<\/code><\/pre>\n<h2>\u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f<\/h2>\n<p>\u0424\u043e\u0440\u043c\u0430\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043e\u0441\u0442\u0430\u043b\u0441\u044f \u043f\u0440\u0435\u0436\u043d\u0438\u043c, \u043f\u043e\u043b\u0435 <strong>type<\/strong> \u0438\u043c\u0435\u0435\u0442 \u043a\u043b\u044e\u0447\u0435\u0432\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435<\/p>\n<pre><code class=\"kotlin\">open class Message {     var type = 0     var data: Any? = null      constructor()     constructor(type: Int) : this(type, null)     constructor(type: Int, data: Any?) {         this.type = type         this.data = data     }      override fun toString(): String = \"Message [type=\" + type + \", source=\" + data.toString() + \"]\" }<\/code><\/pre>\n<p>\u041f\u0430\u043a\u0435\u0442\u044b \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u044b \u043d\u0430:<\/p>\n<ul>\n<li>\n<p><strong>Pack<\/strong>\u00a0\u2014 \u0433\u043b\u0430\u0432\u043d\u044b\u0439 \u043f\u0440\u0435\u0434\u043e\u043a \u0432\u0441\u0435\u0445 \u043f\u0430\u043a\u0435\u0442\u043e\u0432. \u041e\u0431\u044b\u0447\u043d\u043e, \u044d\u0442\u043e \u043f\u0430\u043a\u0435\u0442\u044b, \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0449\u0438\u0435 \u0437\u0430\u00a0\u043a\u0443\u0441\u043e\u043a \u043b\u043e\u0433\u0438\u043a\u0438, \u043d\u0435\u00a0\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0439 \u0441\u00a0\u0438\u0433\u0440\u043e\u0432\u044b\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u043c \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e, \u0432\u00a0\u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u0438 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043e\u0448\u0438\u0431\u043a\u0438 <strong><em>ExceptionPack<\/em><\/strong>.<\/p>\n<\/li>\n<li>\n<p><strong><em>InitPack<\/em><\/strong>\u00a0\u2014 \u043f\u0430\u043a\u0435\u0442\u044b \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438\u0433\u0440\u043e\u0432\u044b\u0445 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u043d\u0430\u00a0\u043f\u043e\u043b\u0435 \u0431\u043e\u044f<\/p>\n<\/li>\n<li>\n<p><strong><em>UpdatePack<\/em><\/strong>\u00a0\u2014 \u043f\u0430\u043a\u0435\u0442\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432, \u0440\u0430\u0441\u0441\u044b\u043b\u0430\u0435\u043c\u044b\u0435 \u0432\u0441\u0435\u043c \u0438\u0433\u0440\u043e\u043a\u0430\u043c<\/p>\n<\/li>\n<li>\n<p><strong><em>RemovePack<\/em><\/strong>\u00a0\u2014 \u043f\u0430\u043a\u0435\u0442\u044b \u043e\u0447\u0438\u0441\u0442\u043a\u0438 \u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0447\u0430\u0441\u0442\u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0438\u0433\u0440\u043e\u0432\u044b\u0445 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432. <\/p>\n<\/li>\n<li>\n<p><strong>PrivateUpdatePack<\/strong>\u00a0\u2014 \u0438\u043d\u0434\u0438\u0432\u0438\u0434\u0443\u0430\u043b\u044c\u043d\u044b\u0435 \u043f\u0430\u043a\u0435\u0442\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432<\/p>\n<\/li>\n<li>\n<p><strong>PrivateRemovePack<\/strong>\u00a0\u2014 \u0438\u043d\u0434\u0438\u0432\u0438\u0434\u0443\u0430\u043b\u044c\u043d\u044b\u0435 \u043f\u0430\u043a\u0435\u0442\u044b \u043e\u0447\u0438\u0441\u0442\u043a\u0438 \u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0447\u0430\u0441\u0442\u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0438\u0433\u0440\u043e\u0432\u044b\u0445 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432. <\/p>\n<\/li>\n<li>\n<p><strong>PrivateInitPack<\/strong>\u00a0\u2014 \u0438\u043d\u0434\u0438\u0432\u0438\u0434\u0443\u0430\u043b\u044c\u043d\u044b\u0435 \u043f\u0430\u043a\u0435\u0442\u044b \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438\u0433\u0440\u043e\u0432\u044b\u0445 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u043d\u0430\u00a0\u043f\u043e\u043b\u0435 \u0431\u043e\u044f<\/p>\n<\/li>\n<\/ul>\n<h3>Game. \u0418\u0433\u0440\u043e\u0432\u0430\u044f \u043b\u043e\u0433\u0438\u043a\u0430 \u0438 \u043a\u043e\u043c\u043d\u0430\u0442\u044b<\/h3>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/e6f\/f4a\/c82\/e6ff4ac82d0798094945c5619916d26d.png\" alt=\"\u0421\u0445\u0435\u043c\u0430 \u0440\u0430\u0431\u043e\u0442\u044b \u0438\u0433\u0440\u043e\u0432\u043e\u0439 \u043a\u043e\u043c\u043d\u0430\u0442\u044b\" title=\"\u0421\u0445\u0435\u043c\u0430 \u0440\u0430\u0431\u043e\u0442\u044b \u0438\u0433\u0440\u043e\u0432\u043e\u0439 \u043a\u043e\u043c\u043d\u0430\u0442\u044b\" width=\"1321\" height=\"1230\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e6f\/f4a\/c82\/e6ff4ac82d0798094945c5619916d26d.png\"\/><\/p>\n<div><figcaption>\u0421\u0445\u0435\u043c\u0430 \u0440\u0430\u0431\u043e\u0442\u044b \u0438\u0433\u0440\u043e\u0432\u043e\u0439 \u043a\u043e\u043c\u043d\u0430\u0442\u044b<\/figcaption><\/div>\n<\/figure>\n<p>\u041e\u0441\u043d\u043e\u0432\u0443 \u0438\u0433\u0440\u043e\u0432\u043e\u0439 \u043b\u043e\u0433\u0438\u043a\u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0438\u0433\u0440\u043e\u0432\u0430\u044f \u043a\u043e\u043c\u043d\u0430\u0442\u0430\u00a0\u2014 <strong><em>GameRoom<\/em><\/strong><\/p>\n<pre><code class=\"kotlin\">interface GameRoom : Runnable, Updatable, WebSocketMessagePublisher {     fun onRoomCreated(userSessions: List&lt;UserSession>)     fun onRoomStarted()     fun onBattleStarted()     fun onDestroy(userSessions: List&lt;UserSession>)     fun onDisconnect(userSession: UserSession): UserSession     fun sessions(): Collection&lt;UserSession>     fun currentPlayersCount(): Int     fun getPlayerSessionBySessionId(userSession: UserSession): Optional&lt;UserSession>     fun key(): UUID     fun key(key: UUID)     fun close(): Collection&lt;UserSession>     fun onClose(userSession: UserSession)     fun schedule(runnable: Runnable, delayMillis: Long):Boolean     fun schedulePeriodically(runnable: Runnable, initDelay:Long, loopRate:Long):Boolean } <\/code><\/pre>\n<pre><code class=\"kotlin\">abstract class AbstractGameRoom protected constructor(     private var gameRoomId: UUID,     private val schedulerService: Scheduler,     protected val roomService: RoomService,     protected val webSocketSessionService: WebSocketSessionService ) : GameRoom {     private val roomFutureList: MutableList&lt;Disposable> = ArrayList()     companion object {         val log: Logger = LogManager.getLogger(this::class.java)     }      private var sessions: MutableMap&lt;String, UserSession> = HashMap()      override fun onRoomCreated(userSessions: List&lt;UserSession>) {         for (playerSession in userSessions) {             this.sessions[playerSession.id] = playerSession             sendBroadcast(Message(MESSAGE, playerSession.player!!.id.toString() + \" successfully joined\"))         }     }      override fun onDestroy(userSessions: List&lt;UserSession>) {         for (playerSession in userSessions) {             this.sessions.remove(playerSession.id)             sendBroadcast(Message(MESSAGE, playerSession.player!!.id.toString() + \" left\"))         }     }      override fun schedule(runnable: Runnable, delayMillis: Long)=         roomFutureList.add(schedulerService.schedule(runnable, delayMillis, TimeUnit.MILLISECONDS))      override fun schedulePeriodically(runnable: Runnable, initDelay: Long, loopRate: Long)=         roomFutureList.add(schedulerService.schedulePeriodically(runnable,initDelay, loopRate,TimeUnit.MILLISECONDS))      override fun onDisconnect(userSession: UserSession): UserSession = sessions.remove(userSession.id)!!     override fun send(userSession: UserSession, message: Any) =         webSocketSessionService.send(userSession, message)      override fun sendBroadcast(message: Any) =         webSocketSessionService.sendBroadcast(sessions.values, message)      override fun sendBroadcast(messageFunction: Function&lt;UserSession, Any>) =         webSocketSessionService.sendBroadcast(sessions.values, messageFunction)      override fun sendFailure(userSession: UserSession, message: Any) {         webSocketSessionService.sendFailure(userSession, message)     }      override fun sendBroadcast(type: MessageType, message: String) {        webSocketSessionService.sendBroadcast(type, message)     }      override fun sendBroadcast(userSessions: Collection&lt;UserSession>, message: Any) {         webSocketSessionService.sendBroadcast(userSessions, message)     }      override fun send(userSession: UserSession, function: Function&lt;UserSession, Any>) {         webSocketSessionService.send(userSession, function)     }      override fun sendBroadcast(userSessions: Collection&lt;UserSession>, function: Function&lt;UserSession, Any>) {         webSocketSessionService.sendBroadcast(userSessions, function)     }      override fun run() {         try {             update()         } catch (e: Exception) {             log.error(\"room update exception\", e)         }     }      override fun close(): Collection&lt;UserSession> {         val result: Collection&lt;UserSession> = sessions.values         sessions.values.forEach { this.onClose(it) }         roomFutureList.forEach { it.dispose() }         log.trace(\"Room {} has been closed\", key())         return result     }      override fun onClose(userSession: UserSession) {}     override fun getPlayerSessionBySessionId(userSession: UserSession): Optional&lt;UserSession> =         if (sessions.containsKey(userSession.id)) Optional.of(sessions[userSession.id]!!)         else Optional.empty()      override fun sessions(): Collection&lt;UserSession> = sessions.values     override fun currentPlayersCount(): Int = sessions.size     override fun key(): UUID = gameRoomId     override fun key(key: UUID) {         this.gameRoomId = key     } }<\/code><\/pre>\n<p>\u0427\u0442\u043e \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u0432 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438 <strong><em>stateful<\/em><\/strong> \u043e\u0431\u0435\u0440\u0442\u043a\u0443 \u0432\u043e\u043a\u0440\u0443\u0433 Runnable-\u0437\u0430\u0434\u0430\u0447\u0438, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0435\u0439 \u0432\u043d\u0443\u0442\u0440\u0438\u0438\u0433\u0440\u043e\u0432\u043e\u0433\u043e \u0446\u0438\u043a\u043b\u0430 (\u0441\u043c. \u043c\u0435\u0442\u043e\u0434 <strong><em>update<\/em><\/strong>). \u042d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u043a\u043e\u043c\u043d\u0430\u0442\u044b \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438:<\/p>\n<ul>\n<li>\n<p>\u0425\u0440\u0430\u043d\u0435\u043d\u0438\u0435, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 \u0443\u0442\u0438\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0441\u0435\u0441\u0441\u0438\u0439 \u0438\u0433\u0440\u043e\u043a\u043e\u0432<\/p>\n<\/li>\n<li>\n<p>\u041a\u043e\u043c\u043c\u0443\u043d\u0438\u043a\u0430\u0446\u0438\u044e \u0441 \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440\u043e\u043c \u043a\u043e\u043c\u043d\u0430\u0442<\/p>\n<\/li>\n<li>\n<p>\u041a\u043e\u043c\u043c\u0443\u043d\u0438\u043a\u0430\u0446\u0438\u044e \u0441 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u043c<\/p>\n<\/li>\n<li>\n<p>\u0412\u044b\u0437\u043e\u0432, \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u0438 \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0432\u043d\u0443\u0442\u0440\u0438\u0438\u0433\u0440\u043e\u0432\u044b\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u0439<\/p>\n<\/li>\n<li>\n<p>\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u0438\u0433\u0440\u043e\u0432\u044b\u0445 \u043c\u043e\u0434\u0435\u043b\u0435\u0439<\/p>\n<\/li>\n<\/ul>\n<p>\u041b\u043e\u0433\u0438\u043a\u0430 \u0440\u0430\u0431\u043e\u0442\u044b \u0438\u0433\u0440\u043e\u0432\u043e\u0439 \u043a\u043e\u043c\u043d\u0430\u0442\u044b \u0443\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u043d\u0430 \u043f\u043e\u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0443\u0437\u043b\u043e\u0432\u044b\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u0439:<\/p>\n<ol>\n<li>\n<p><strong><em>onRoomCreated<\/em><\/strong> &#8212; \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043a\u043e\u043c\u043d\u0430\u0442\u044b \u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u0435\u0441\u0441\u0438\u0439 \u0438\u0433\u0440\u043e\u043a\u043e\u0432 \u0432 \u043d\u0435\u0439.<\/p>\n<\/li>\n<li>\n<p><strong><em>onRoomStarted<\/em><\/strong> &#8212; \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u0441 <strong><em>onRoomCreated<\/em><\/strong>, \u043a\u043e\u0433\u0434\u0430 \u0438\u0433\u0440\u043e\u0432\u0430\u044f \u043a\u043e\u043c\u043d\u0430\u0442\u0430 \u0433\u043e\u0442\u043e\u0432\u0430 \u043a \u0440\u0430\u0431\u043e\u0442\u0435.<\/p>\n<\/li>\n<li>\n<p>\u0421\u0442\u0430\u0440\u0442 \u0438\u0433\u0440\u043e\u0432\u043e\u0433\u043e \u0446\u0438\u043a\u043b\u0430 \u0438 \u0432\u044b\u0437\u043e\u0432 <strong><em>onBattleStarted <\/em><\/strong>&#8212; \u043f\u043e \u0438\u0441\u0442\u0435\u0447\u0435\u043d\u0438\u044e \u043e\u0442\u0441\u0440\u043e\u0447\u043a\u0438 \u0441\u0442\u0430\u0440\u0442\u0430 \u0431\u0438\u0442\u0432\u044b, \u0441\u043b\u0443\u0436\u0430\u0449\u0435\u0439 \u0434\u043b\u044f \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0432\u0441\u0435\u0445 \u0438\u0433\u0440\u043e\u043a\u043e\u0432, \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0439 <strong>Scheduler<\/strong> \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0432\u044b\u0437\u043e\u0432 \u043c\u0435\u0442\u043e\u0434\u0430 <strong><em>onBattleStarted <\/em><\/strong>\u0438<strong><em> <\/em><\/strong>\u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043c\u0435\u0442\u043e\u0434\u0430 <strong><em>run<\/em><\/strong> \u0438\u0433\u0440\u043e\u0432\u043e\u0439 \u043a\u043e\u043c\u043d\u0430\u0442\u044b, \u0430 \u043e\u043d \u0432 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u043e\u0431\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442 \u0432\u044b\u0437\u043e\u0432 \u043c\u0435\u0442\u043e\u0434\u0430 <strong><em>update<\/em><\/strong>. \u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0437\u0430\u0434\u0430\u0435\u0442\u0441\u044f \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0435 <strong><em>application.room.loop-rate. <\/em><\/strong> \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u043e\u0442\u0441\u0440\u043e\u0447\u043a\u0438 \u0441\u0442\u0430\u0440\u0442\u0430 \u0437\u0430\u0434\u0430\u0435\u0442\u0441\u044f \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0435 <strong><em>application.room.start-delay.<\/em><\/strong><\/p>\n<\/li>\n<li>\n<p>\u0417\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u0438\u0433\u0440\u043e\u0432\u043e\u0433\u043e \u0446\u0438\u043a\u043b\u0430 \u0438 \u0432\u044b\u0437\u043e\u0432 <strong><em>onBattleEnded<\/em><\/strong> &#8212; \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u043e \u0438\u0441\u0442\u0435\u0447\u0435\u043d\u0438\u044e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0431\u0438\u0442\u0432\u044b (\u0437\u0430\u0434\u0430\u0435\u0442\u0441\u044f \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0435 <strong><em>application.room.end-delay<\/em><\/strong>).<\/p>\n<p>\u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0440\u0438\u043a\u0440\u0443\u0442\u0438\u0442\u044c \u043b\u044e\u0431\u043e\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0435\u0442\u043e\u0434\u043e\u0432 <strong><em>schedule <\/em><\/strong>\u0438 <strong><em>schedulePeriodically<\/em><\/strong><\/p>\n<p>\u041f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0433\u043e\u0442\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 \u0438\u0433\u0440\u043e\u0432\u043e\u0439 \u043a\u043e\u043c\u043d\u0430\u0442\u044b \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"kotlin\">class DefaultGameRoom(     val map: GameMap,     gameRoomId: UUID,     roomService: RoomService,     webSocketSessionService: WebSocketSessionService,     schedulerService: Scheduler,     val gameProperties: GameProperties,     val roomProperties: RoomProperties ) : AbstractGameRoom(gameRoomId, schedulerService, roomService, webSocketSessionService) {     private val started = AtomicBoolean(false)      override fun onRoomCreated(userSessions: List&lt;UserSession>) {         if (userSessions.isNotEmpty()) {             userSessions.forEach {                 val defaultPlayer = it.player as DefaultPlayer                 defaultPlayer.position = Vector(100.0, 100.0)                 map.addPlayer(defaultPlayer)             }         }          super.onRoomCreated(userSessions)          sendBroadcast {             Message(                 GAME_ROOM_JOIN_SUCCESS,                 GameSettingsPack(                     roomProperties.loopRate                 )             )         }          schedulePeriodically(             this,             roomProperties.initDelay,             roomProperties.loopRate         )          schedule(             { roomService.onBattleEnd(this) },             roomProperties.endDelay + roomProperties.startDelay         )          log.trace(\"Room {} has been created\", key())     }      override fun onRoomStarted() {         started.set(false)         sendBroadcast(             Message(                 GAME_ROOM_START,                 GameRoomPack(                     OffsetDateTime.now().plus(roomProperties.startDelay, ChronoUnit.MILLIS).toInstant().toEpochMilli()                 )             )         )         schedule({ this.onBattleStarted() }, roomProperties.startDelay)         log.trace(\"Room {} has been started\", key())     }      override fun onBattleStarted() {         log.trace(\"Room {}. Battle has been started\", key())         started.set(true)         sendBroadcast(             Message(                 GAME_ROOM_BATTLE_START, GameRoomPack(                     OffsetDateTime.now().plus(roomProperties.endDelay, ChronoUnit.MILLIS).toInstant().toEpochMilli()                 )             )         )     }      \/\/room's game loop     override fun update() {         if (!started.get()) return         for (currentPlayer in map.getPlayers()) {             if (currentPlayer.isAlive) currentPlayer.update()             val updatePack = currentPlayer.getPrivateUpdatePack()             val playerUpdatePackList = map.getPlayers()                 .map { it.getUpdatePack() }              val session = currentPlayer.userSession             send(                 session, Message(                     UPDATE,                     GameUpdatePack(                         updatePack,                         playerUpdatePackList                     )                 )             )         }     }      fun onPlayerKeyDown(userSession: UserSession, event: KeyDownPlayerEvent) {         if (!started.get()) return         val player = userSession.player as DefaultPlayer         if (!player.isAlive) return         val direction = Direction.valueOf(event.inputId)         player.updateState(direction, event.state)     }      fun onPlayerInitRequest(userSession: UserSession) {         send(             userSession, Message(                 INIT,                 GameInitPack(                     (userSession.player as DefaultPlayer).getInitPack(),                     roomProperties.loopRate,                     map.alivePlayers()                 )             )         )     }      override fun onDestroy(userSessions: List&lt;UserSession>) {         userSessions.forEach { userSession: UserSession ->             map.removePlayer(                 userSession.player as DefaultPlayer             )         }         super.onDestroy(userSessions)     }      override fun onClose(userSession: UserSession) {         send(userSession, Message(GAME_ROOM_CLOSE))         super.onClose(userSession)     } }<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u043c\u0435\u0442\u043e\u0434 <strong><em>update<\/em><\/strong>:<\/p>\n<pre><code class=\"kotlin\">\/\/room's game loop     override fun update() {         if (!started.get()) return         for (currentPlayer in map.getPlayers()) {             if (currentPlayer.isAlive) currentPlayer.update()             val updatePack = currentPlayer.getPrivateUpdatePack()             val playerUpdatePackList = map.getPlayers()                 .map { it.getUpdatePack() }              val session = currentPlayer.userSession             send(                 session, Message(                     UPDATE,                     GameUpdatePack(                         updatePack,                         playerUpdatePackList                     )                 )             )         }     }<\/code><\/pre>\n<p>\u0412\u00a0\u043d\u0435\u043c, \u043a\u0430\u043a\u00a0\u0441\u0442\u0430\u043b\u043e \u044f\u0441\u043d\u043e \u0440\u0430\u043d\u0435\u0435, \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0446\u0438\u043a\u043b\u0438\u0447\u043d\u043e\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0432\u043d\u0443\u0442\u0440\u0438\u0438\u0433\u0440\u043e\u0432\u044b\u0445 \u043c\u043e\u0434\u0435\u043b\u0435\u0439. \u0420\u0430\u0437\u0443\u043c\u0435\u0435\u0442\u0441\u044f, \u043f\u043e\u00a0\u0441\u043c\u044b\u0441\u043b\u0443, \u044d\u0442\u043e \u0434\u043e\u043b\u0436\u043d\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442\u044c \u0432 \u043e\u0431\u044a\u0435\u043a\u0442\u0435 <strong><em>GameMap<\/em><\/strong>, \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0449\u0435\u043c \u0437\u0430\u00a0\u0438\u0433\u0440\u043e\u0432\u043e\u0435 \u043f\u043e\u043b\u0435 \u0432 \u0446\u0435\u043b\u043e\u043c, \u043d\u043e\u00a0\u0434\u043b\u044f\u00a0\u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430 \u0432\u043e\u0441\u043f\u0440\u0438\u044f\u0442\u0438\u044f \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u0438 \u0432\u044b\u043d\u0435\u0441\u0435\u043d\u043e \u0432\u00a0\u0438\u0433\u0440\u043e\u0432\u0443\u044e \u043a\u043e\u043c\u043d\u0430\u0442\u0443. \u0412\u00a0\u044d\u0442\u043e\u043c \u0446\u0438\u043a\u043b\u0435 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432\u0441\u0435 \u0447\u0442\u043e\u00a0\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f\u00a0\u0438\u0433\u0440\u044b, \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u0438 NPC, \u0432\u044b\u0441\u0442\u0440\u0435\u043b\u044b(\u043a\u0430\u043a \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438), \u0443\u043c\u0435\u043d\u0438\u044f, \u044d\u0444\u0444\u0435\u043a\u0442\u044b \u0438 \u0442. \u0434. \u0412\u00a0\u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0438\u0433\u0440\u043e\u043a\u043e\u0432 \u0432\u00a0\u043a\u043b\u0430\u0441\u0441\u0435 <strong><em>DefaultPlayer<\/em><\/strong>:<\/p>\n<pre><code class=\"kotlin\">override fun update() {         velocity.x =             if (isMoving &amp;&amp; movingState[Direction.RIGHT] == true) ABS_PLAYER_SPEED else (if (isMoving &amp;&amp; movingState[Direction.LEFT] == true) -ABS_PLAYER_SPEED else 0.0)         velocity.y =             if (isMoving &amp;&amp; movingState[Direction.UP] == true) -ABS_PLAYER_SPEED else (if (isMoving &amp;&amp; movingState[Direction.DOWN] == true) ABS_PLAYER_SPEED else 0.0)          if (isMoving) position.sum(velocity) }<\/code><\/pre>\n<p>\u0417\u0430\u0442\u0435\u043c \u0438\u0437 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0441\u043e\u0431\u0438\u0440\u0430\u044e\u0442\u0441\u044f <strong><em>PlayerUpdatePack<\/em><\/strong>, \u043f\u0435\u0440\u0435\u0441\u044b\u043b\u0430\u0435\u043c\u044b\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u0443 \u0432 \u0446\u0438\u043a\u043b\u0435 \u043d\u0438\u0436\u0435 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 <strong><em>GameUpdatePack<\/em><\/strong> (\u0441\u0432\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 + \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0445 \u0438\u0433\u0440\u043e\u043a\u043e\u0432).<\/p>\n<pre><code class=\"kotlin\">data class PlayerUpdatePack(     val id: Long,     val position: Vector ) : UpdatePack<\/code><\/pre>\n<pre><code class=\"kotlin\">data class PrivatePlayerUpdatePack(     val id: Long ) : PrivateUpdatePack<\/code><\/pre>\n<pre><code class=\"kotlin\">data class GameUpdatePack(     val player: PrivatePlayerUpdatePack,     val players: Collection&lt;PlayerUpdatePack> ) : UpdatePack<\/code><\/pre>\n<h3>Matchmaker<\/h3>\n<p>\u041c\u0430\u0442\u0447\u043c\u0435\u0439\u043a\u0435\u0440\u043e\u043c \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f <strong><em>RoomService<\/em><\/strong>:<\/p>\n<pre><code class=\"kotlin\">interface RoomService{     fun getRoomSessionIds(key: UUID?):Collection&lt;String>     fun getRoomIds():Collection&lt;String>     fun getRooms():Collection&lt;DefaultGameRoom>     fun getRoomByKey(key: UUID?): Optional&lt;DefaultGameRoom>     fun addPlayerToWait(userSession: UserSession, initialData: GameRoomJoinEvent)     fun removePlayerFromWaitQueue(session: UserSession)       fun onBattleEnd(room: DefaultGameRoom)     fun close(key: UUID?):Mono&lt;Void> }<\/code><\/pre>\n<pre><code class=\"kotlin\"> @Service open class RoomServiceImpl(     private val playerFactory: PlayerFactory&lt;GameRoomJoinEvent, DefaultPlayer, DefaultGameRoom>,     private val applicationProperties: ApplicationProperties,     private val schedulerService: Scheduler ) : RoomService {     private lateinit var webSocketSessionService: WebSocketSessionService      companion object {         val log: Logger = LogManager.getLogger(this::class.java)     }      private val gameRoomMap: MutableMap&lt;UUID, DefaultGameRoom> = mutableMapOf()     private val sessionQueue: Queue&lt;WaitingPlayerSession> = ArrayDeque()     override fun getRoomSessionIds(key: UUID?): Collection&lt;String> = getRoomByKey(key).map { room ->         room.sessions().map { it.id }     }.orElse(listOf())      override fun getRoomIds(): Collection&lt;String> = gameRoomMap.keys.map { it.toString() }     override fun getRooms(): Collection&lt;DefaultGameRoom> = gameRoomMap.values.toList()     override fun getRoomByKey(key: UUID?): Optional&lt;DefaultGameRoom> =         if (key == null) Optional.empty() else Optional.ofNullable(gameRoomMap[key])      override fun addPlayerToWait(userSession: UserSession, initialData: GameRoomJoinEvent) {         sessionQueue.add(WaitingPlayerSession(userSession, initialData))         webSocketSessionService.send(userSession, Message(GAME_ROOM_JOIN_WAIT))          if (sessionQueue.size &lt; applicationProperties.room.maxPlayers) return          val gameMap = GameMap()         val room = createRoom(gameMap)         val userSessions: MutableList&lt;UserSession> = ArrayList()         while (userSessions.size.toLong() != applicationProperties.room.maxPlayers) {             val waitingPlayerSession = sessionQueue.remove()             val ps: UserSession = waitingPlayerSession.userSession             val id: GameRoomJoinEvent = waitingPlayerSession.initialData             val player: Player = playerFactory.create(gameMap.nextPlayerId(),  id, room, ps)             ps.roomKey = room.key()             ps.player = player             userSessions.add(ps)         }         launchRoom(room, userSessions)     }      override fun removePlayerFromWaitQueue(session: UserSession) {             sessionQueue.removeIf{waitingPlayerSession -> waitingPlayerSession.userSession == session }     }      private fun createRoom(gameMap: GameMap): DefaultGameRoom {         val room = DefaultGameRoom(gameMap,             UUID.randomUUID(), this, webSocketSessionService,             schedulerService, applicationProperties.game,             applicationProperties.room         )         gameRoomMap[room.key()] = room         return room     }      private fun launchRoom(room: DefaultGameRoom, userSessions: List&lt;UserSession>) {         room.onRoomCreated(userSessions)         room.onRoomStarted()     }      override fun onBattleEnd(room: DefaultGameRoom) {         room.close()         gameRoomMap.remove(room.key())     }      override fun close(key: UUID?): Mono&lt;Void> {         getRoomByKey(key).ifPresent {             onBattleEnd(it)         }         return Mono.empty()     }      @Autowired     fun setGameManager(@Lazy webSocketSessionService: WebSocketSessionService) {         this.webSocketSessionService = webSocketSessionService     } } <\/code><\/pre>\n<p>\u041a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435, \u043c\u0430\u043d\u0438\u043f\u0443\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430\u043c\u0438 \u0438\u0433\u0440\u043e\u0432\u044b\u0445  \u043a\u043e\u043c\u043d\u0430\u0442, \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u0435\u0441\u0441\u0438\u044e \u0438\u0433\u0440\u043e\u043a\u0430 \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u043d\u0430 \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u0435, \u0441\u043e\u0437\u0434\u0430\u0442\u044c  \u0434\u043b\u044f \u0433\u0440\u0443\u043f\u043f\u044b \u0438\u0433\u0440\u043e\u043a\u043e\u0432 \u043a\u043e\u043c\u043d\u0430\u0442\u0443, \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0438\u0433\u0440\u043e\u043a\u0430 \u0438\u0437 \u043e\u0447\u0435\u0440\u0435\u0434\u0438 \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c  \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u043c\u0430\u0442\u0447\u0430.<\/p>\n<h2>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h2>\n<p>\u0412 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0434\u0435\u043c\u043a\u0438 \u0432\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0435 \u043f\u0440\u043e\u0441\u0442\u0435\u043d\u044c\u043a\u043e\u0435 \u0438\u0433\u0440\u043e\u0432\u043e\u0435 \u043f\u043e\u043b\u0435 \u0441 \u0437\u0430\u0434\u0430\u0442\u043a\u0430\u043c\u0438 \u043d\u0430 \u0431\u0443\u0434\u0443\u0449\u0438\u0439 \u043c\u0443\u043b\u044c\u0442\u0438\u043f\u043b\u0435\u0435\u0440 \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 <strong><em>Battle-royale<\/em><\/strong>. <\/p>\n<p>\u041f\u0430\u0440\u0443 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432:<\/p>\n<p>\u0412\u0441\u0435 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438 \u0438 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u044b \u0432 \u0434\u0435\u043c\u043e \u043d\u0435 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f Thread-safe, \u043e\u0434\u043d\u0430\u043a\u043e \u0432 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043e\u0434\u043d\u0438\u043c \u0438\u0437 \u0447\u0438\u0442\u0430\u0442\u0435\u043b\u0435\u0439 \u0431\u044b\u043b\u0438 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0438 \u0432 <strong>RoomService<\/strong>, \u0432\u043b\u0438\u044f\u044e\u0449\u0438\u0435 \u043d\u0430 \u0432\u0441\u0435 \u043a\u043e\u043c\u043d\u0430\u0442\u044b. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 <strong>java.util.concurrent.*<\/strong> \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0430\u043c \u0433\u0434\u0435 \u044d\u0442\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e.<\/p>\n<p>\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0441\u043f\u043e\u043a\u043e\u0439\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u0443\u0442\u0435\u043c \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f spring-starter-security \u0432 \u043f\u0440\u043e\u0435\u043a\u0442 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u043e\u0439 oidc\/oauth. \u0411\u043e\u043b\u0435\u0435 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0432 <strong>generator-jhipster<\/strong>.<\/p>\n<p>\u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f JDK 21. \u041d\u0430\u0447\u0438\u043d\u0430\u044f \u0441 19 \u0432\u0435\u0440\u0441\u0438\u0438 \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0435 \u043f\u043e\u0442\u043e\u043a\u0438, \u0447\u0442\u043e \u0442\u0430\u043a\u0436\u0435 \u0441\u0438\u043b\u044c\u043d\u043e \u043f\u043e\u0432\u044b\u0448\u0430\u0435\u0442 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043d\u0430 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0430\u0445. \u0421\u043c. <strong>ApplicationConfiguration<\/strong><\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/20f\/dad\/aae\/20fdadaae6ef4f92bc4c0ba0ccab6306.png\" alt=\"\u0411\u0440\u0430\u0443\u0437\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u0438\u0435\u043d\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\" title=\"\u0411\u0440\u0430\u0443\u0437\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u0438\u0435\u043d\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\" width=\"1906\" height=\"795\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/20f\/dad\/aae\/20fdadaae6ef4f92bc4c0ba0ccab6306.png\"\/><\/p>\n<div><figcaption>\u0411\u0440\u0430\u0443\u0437\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u0438\u0435\u043d\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/figcaption><\/div>\n<\/figure>\n<p>\u041c\u043d\u043e\u0433\u0438\u043c \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u043a\u043e\u0434\u0430 \u043e\u0447\u0435\u043d\u044c \u043c\u043d\u043e\u0433\u043e \u0434\u043b\u044f \u0442\u0430\u043a\u043e\u0433\u043e \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u043e\u0433\u043e  \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043d\u043e \u044d\u0442\u043e \u0432\u0441\u0435\u0433\u043e \u043b\u0438\u0448\u044c \u043e\u0441\u043d\u043e\u0432\u0430 \u0434\u043b\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430.  \u0411\u043e\u043b\u044c\u0448\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u043f\u043e\u0434\u043a\u0430\u043f\u043e\u0442\u043d\u044b\u0445 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c\u043e\u0432 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0430, \u0432\u0430\u043c \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u043e\u0431\u043e\u0433\u0430\u0442\u0438\u0442\u044c  \u0441\u0432\u043e\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 \u0441\u0443\u0433\u0443\u0431\u043e \u0438\u0433\u0440\u043e\u0432\u043e\u0439 \u043b\u043e\u0433\u0438\u043a\u043e\u0439.<\/p>\n<p>\u0411\u0443\u0434\u044c\u0442\u0435 \u043c\u043e\u0449\u043d\u044b\u043c\u0438 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0441\u0442\u0430\u043c\u0438, \u0447\u0438\u0442\u0430\u0439\u0442\u0435, \u0441\u0442\u0440\u0435\u043c\u0438\u0442\u0435\u0441\u044c \u043a \u0441\u0432\u043e\u0438\u043c \u0446\u0435\u043b\u044f\u043c. \u0421\u043f\u0430\u0441\u0438\u0431\u043e \u0437\u0430 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435.<\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><\/p>\n<div class=\"tm-article-poll-container\"><!--[--><\/p>\n<div class=\"tm-article-poll tm-article-poll_variant-bordered\">\n<div class=\"tm-notice tm-notice_positive tm-article-poll__notice\"><!----><\/p>\n<div class=\"tm-notice__inner\"><!----><\/p>\n<div class=\"tm-notice__content\" data-test-id=\"notice-content\"><!--[--><span>\u0422\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u043c\u043e\u0433\u0443\u0442 \u0443\u0447\u0430\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0432 \u043e\u043f\u0440\u043e\u0441\u0435. <a rel=\"nofollow\" href=\"\/kek\/v1\/auth\/habrahabr\/?back=\/ru\/articles\/800689\/&#038;hl=ru\">\u0412\u043e\u0439\u0434\u0438\u0442\u0435<\/a>, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430.<\/span><!--]--><\/div>\n<\/div>\n<\/div>\n<p><!--[--><\/p>\n<div class=\"tm-article-poll__header\">\u0415\u0441\u0442\u044c \u043b\u0438 \u0441\u0440\u0435\u0434\u0438 \u0447\u0438\u0442\u0430\u0442\u0435\u043b\u0435\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0430 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0443 OpenSource \u0441\u0442\u0430\u0440\u0442\u0435\u0440\u0430 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442 \u0438\u0433\u0440\u043e\u0432\u044b\u0445 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432?<\/div>\n<div class=\"tm-article-poll__answers\"><!--[--><\/p>\n<div class=\"tm-article-poll__answer\">\n<div class=\"tm-article-poll__answer-data\"><span class=\"tm-article-poll__answer-percent tm-article-poll__answer-percent_winning\">56.25% <\/span><span class=\"tm-article-poll__answer-label\">\u0414\u0430<\/span><span class=\"tm-article-poll__answer-votes\">9<\/span><\/div>\n<div class=\"tm-article-poll__answer-bar\">\n<div class=\"tm-article-poll__answer-progress tm-article-poll__answer-progress_winning\" style=\"width: 56.25%\"><\/div>\n<\/div>\n<\/div>\n<div class=\"tm-article-poll__answer\">\n<div class=\"tm-article-poll__answer-data\"><span class=\"tm-article-poll__answer-percent\">43.75% <\/span><span class=\"tm-article-poll__answer-label\">\u041d\u0435\u0442<\/span><span class=\"tm-article-poll__answer-votes\">7<\/span><\/div>\n<div class=\"tm-article-poll__answer-bar\">\n<div class=\"tm-article-poll__answer-progress\" style=\"width: 43.75%\"><\/div>\n<\/div>\n<\/div>\n<p><!--]--><\/div>\n<div class=\"tm-article-poll__stats\"> \u041f\u0440\u043e\u0433\u043e\u043b\u043e\u0441\u043e\u0432\u0430\u043b\u0438 16 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439.   \u0412\u043e\u0437\u0434\u0435\u0440\u0436\u0430\u043b\u0438\u0441\u044c 4 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. <\/div>\n<p><!--]--><\/div>\n<p><!--]--><\/div>\n<p> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/800689\/\"> https:\/\/habr.com\/ru\/articles\/800689\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412\u0441\u0435\u043c \u0434\u043e\u0431\u0440\u043e\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0441\u0443\u0442\u043e\u043a. \u041d\u0435\u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u043d\u0430\u0437\u0430\u0434 \u043c\u043d\u043e\u044e\u00a0\u0431\u044b\u043b\u0430 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0430 <a href=\"https:\/\/habr.com\/ru\/articles\/774322\/\" rel=\"noopener noreferrer nofollow\">\u0441\u0442\u0430\u0442\u044c\u044f<\/a>, \u0433\u0434\u0435 \u044f \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e \u043e\u043f\u0438\u0441\u0430\u043b \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0434\u0435\u043c\u043e \u0438\u0433\u0440\u043e\u0432\u043e\u0433\u043e \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430. \u041d\u0430\u00a0\u044d\u0442\u043e\u0442 \u0440\u0430\u0437, \u044f \u0445\u043e\u0442\u0435\u043b\u00a0\u0431\u044b \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0431\u043e\u043b\u0435\u0435 \u0443\u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u0441\u0442\u0432\u043e\u0432\u0430\u043d\u043d\u044b\u043c \u0438 \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u043e\u043c \u043d\u0430 <strong>Kotlin<\/strong> \u0438 <strong>\u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u0442\u0435\u043a\u0435<\/strong>. <\/p>\n<h2>\u041d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043a\u0438 \u0438 \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432\u0430 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u044f<\/h2>\n<p>\u0421\u0442\u0430\u0440\u044b\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u0438\u043c\u0435\u0435\u0442 \u0440\u044f\u0434 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0445 \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043a\u043e\u0432 \u0434\u043b\u044f\u00a0\u043a\u043e\u043c\u043c\u0435\u0440\u0447\u0435\u0441\u043a\u043e\u0433\u043e\/\u044d\u043d\u0442\u0435\u0440\u043f\u0440\u0430\u0439\u0437 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f\u00a0\u2014 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u043f\u0440\u044f\u043c\u043e\u0433\u043e \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441\u00a0\u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u0438\u0437\u00a0\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 <strong>SpringBoot<\/strong>. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u0440\u0438\u043a\u0440\u0443\u0442\u0438\u0442\u044c \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u0443\u044e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e(oidc) \u0438\u0437\u00a0\u043a\u043e\u0440\u043e\u0431\u043a\u0438. \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0432\u0435\u0441\u044c\u043c\u0430 \u0443\u0441\u0442\u0430\u0440\u0435\u043b\u043e, \u0432\u0440\u0435\u043c\u044f \u0438 \u043e\u043f\u044b\u0442 \u0442\u043e\u043b\u043a\u0430\u0435\u0442 \u0434\u0432\u0438\u0433\u0430\u0442\u044c\u0441\u044f \u0432\u043f\u0435\u0440\u0435\u0434 \u0438 \u0443\u043b\u0443\u0447\u0448\u0430\u0442\u044c \u043f\u0440\u043e\u0434\u0443\u043a\u0442.<\/p>\n<p>\u0421\u0440\u0435\u0434\u0438 \u043f\u0440\u0435\u043c\u0443\u0449\u0435\u0441\u0442\u0432\u00a0\u2014 \u0432\u044b \u0438\u043c\u0435\u0435\u0442\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u043f\u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f \u0441\u00a0\u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c Netty, \u043f\u043e\u043d\u044f\u0442\u044c \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u044b \u0435\u0433\u043e \u0440\u0430\u0431\u043e\u0442\u044b \u0438 \u0432\u043b\u0438\u044f\u0442\u044c \u043d\u0430\u00a0\u0435\u0433\u043e \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c\u044b.<\/p>\n<h2>\u041a\u043e\u043d\u0446\u0435\u043f\u0442<\/h2>\n<p>\u041a\u0430\u043a\u00a0\u0438 \u0432\u00a0\u043f\u0440\u043e\u0448\u043b\u044b\u0439 \u0440\u0430\u0437 \u043a\u043e\u043d\u0446\u0435\u043f\u0442 \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0435\u0436\u043d\u0438\u043c, \u043d\u043e\u00a0\u0441\u00a0\u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u043c\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u043c\u0438.<\/p>\n<ol>\n<li>\n<p>\u041d\u0430\u00a0\u0432\u0445\u043e\u0434\u0435: Kotlin, Reactor\u2011Netty, Spring, Maven<\/p>\n<\/li>\n<li>\n<p>\u0426\u0435\u043b\u0438 \u0442\u0435\u00a0\u0436\u0435\u00a0\u2014 \u0445\u043e\u0442\u0438\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c\u0441\u044f, \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0442\u044c\u0441\u044f \u043a\u00a0\u0438\u0433\u0440\u043e\u0432\u043e\u0439 \u043a\u043e\u043c\u043d\u0430\u0442\u0435 \u0438 \u043d\u0430\u0447\u0430\u0442\u044c \u0438\u0433\u0440\u0430\u0442\u044c, \u0434\u0432\u0438\u0433\u0430\u044f \u043a\u0440\u0443\u0436\u043e\u043a \u043d\u0430\u00a0\u044d\u043a\u0440\u0430\u043d\u0435.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430\u00a0\u0432\u044b\u0445\u043e\u0434\u0435: \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0440\u0430\u0431\u043e\u0442\u044b \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0438 \u0435\u0433\u043e \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0435\u0439.<\/p>\n<\/li>\n<\/ol>\n<h2>\u0421\u0442\u0435\u043a<\/h2>\n<ul>\n<li>\n<p>Kotlin 1.8<\/p>\n<\/li>\n<li>\n<p>JDK 21<\/p>\n<\/li>\n<li>\n<p>Spring Boot 3.2.2<\/p>\n<\/li>\n<li>\n<p>Spring Webflux (<a href=\"https:\/\/projectreactor.io\/docs\/netty\/release\/reference\/index.html\" rel=\"noopener noreferrer nofollow\">Reactor-netty<\/a>)<\/p>\n<\/li>\n<\/ul>\n<h2>\u0421\u0431\u043e\u0440\u043a\u0430<\/h2>\n<blockquote>\n<p><a href=\"https:\/\/github.com\/tfkfan\/webflux-server-game-demo\" rel=\"noopener noreferrer nofollow\">\u0418\u0441\u0445\u043e\u0434\u043d\u0438\u043a \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0437\u0434\u0435\u0441\u044c<\/a><\/p>\n<\/blockquote>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u0431\u043e\u0440\u0449\u0438\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438 \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f Maven<\/p>\n<p><strong>pom.xml<\/strong> \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"xml\">&lt;?xml version=\"1.0\" encoding=\"ISO-8859-15\"?> &lt;project xmlns=\"http:\/\/maven.apache.org\/POM\/4.0.0\" xmlns:xsi=\"http:\/\/www.w3.org\/2001\/XMLSchema-instance\"          xsi:schemaLocation=\"http:\/\/maven.apache.org\/POM\/4.0.0 http:\/\/maven.apache.org\/maven-v4_0_0.xsd\">     &lt;modelVersion>4.0.0&lt;\/modelVersion>     &lt;name>webflux-game-server&lt;\/name>      &lt;groupId>com.tfkfan&lt;\/groupId>     &lt;artifactId>webflux-game-server&lt;\/artifactId>     &lt;version>1.0.0-SNAPSHOT&lt;\/version>     &lt;packaging>jar&lt;\/packaging>      &lt;parent>         &lt;groupId>org.springframework.boot&lt;\/groupId>         &lt;artifactId>spring-boot-starter-parent&lt;\/artifactId>         &lt;version>3.2.2&lt;\/version>         &lt;relativePath\/>     &lt;\/parent>      &lt;properties>         &lt;kotlin.version>1.8.0&lt;\/kotlin.version>         &lt;kotlin.coroutines.version>1.8.0-RC&lt;\/kotlin.coroutines.version>         &lt;maven.compiler.version>21&lt;\/maven.compiler.version>     &lt;\/properties>      &lt;dependencies>         &lt;dependency>             &lt;groupId>org.springframework.boot&lt;\/groupId>             &lt;artifactId>spring-boot-starter-webflux&lt;\/artifactId>         &lt;\/dependency>         &lt;dependency>             &lt;groupId>org.jetbrains.kotlinx&lt;\/groupId>             &lt;artifactId>kotlinx-coroutines-core&lt;\/artifactId>             &lt;version>${kotlin.coroutines.version}&lt;\/version>         &lt;\/dependency>         &lt;dependency>             &lt;groupId>org.jetbrains.kotlin&lt;\/groupId>             &lt;artifactId>kotlin-stdlib&lt;\/artifactId>             &lt;version>${kotlin.version}&lt;\/version>         &lt;\/dependency>         &lt;dependency>             &lt;groupId>org.jetbrains.kotlin&lt;\/groupId>             &lt;artifactId>kotlin-reflect&lt;\/artifactId>             &lt;version>${kotlin.version}&lt;\/version>         &lt;\/dependency>         &lt;dependency>             &lt;groupId>com.google.code.gson&lt;\/groupId>             &lt;artifactId>gson&lt;\/artifactId>             &lt;version>2.8.9&lt;\/version>         &lt;\/dependency>         &lt;dependency>             &lt;groupId>org.apache.commons&lt;\/groupId>             &lt;artifactId>commons-lang3&lt;\/artifactId>             &lt;version>3.12.0&lt;\/version>         &lt;\/dependency>     &lt;\/dependencies>      &lt;build>         &lt;plugins>             &lt;plugin>                 &lt;artifactId>kotlin-maven-plugin&lt;\/artifactId>                 &lt;groupId>org.jetbrains.kotlin&lt;\/groupId>                 &lt;version>${kotlin.version}&lt;\/version>                 &lt;executions>                     &lt;execution>                         &lt;id>compile&lt;\/id>                         &lt;phase>process-sources&lt;\/phase>                         &lt;goals>                             &lt;goal>compile&lt;\/goal>                         &lt;\/goals>                         &lt;configuration>                             &lt;sourceDirs>                                 &lt;sourceDir>${project.basedir}\/src\/main\/kotlin&lt;\/sourceDir>                             &lt;\/sourceDirs>                         &lt;\/configuration>                     &lt;\/execution>                     &lt;execution>                         &lt;id>test-compile&lt;\/id>                         &lt;goals>                             &lt;goal>test-compile&lt;\/goal>                         &lt;\/goals>                         &lt;configuration>                             &lt;sourceDirs>                                 &lt;sourceDir>${project.basedir}\/src\/test\/kotlin&lt;\/sourceDir>                             &lt;\/sourceDirs>                         &lt;\/configuration>                     &lt;\/execution>                 &lt;\/executions>             &lt;\/plugin>             &lt;plugin>                 &lt;groupId>org.apache.maven.plugins&lt;\/groupId>                 &lt;artifactId>maven-compiler-plugin&lt;\/artifactId>             &lt;\/plugin>         &lt;\/plugins>     &lt;\/build> &lt;\/project> <\/code><\/pre>\n<p>\u0412\u0441\u0435 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e, \u0432\u0441\u0435 \u0447\u0442\u043e\u00a0\u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e\u00a0\u2014 webflux \u0432\u043c\u0435\u0441\u0442\u0435 \u0441\u00a0embedded netty \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c. <\/p>\n<p>\u0421\u0431\u043e\u0440\u043a\u0430 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a<\/p>\n<pre><code class=\"bash\">.\/mvnw clean verify spring-boot:run<\/code><\/pre>\n<h2>\u041a\u043b\u0438\u0435\u043d\u0442<\/h2>\n<p>\u041a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432\u00a0\u0441\u0442\u0430\u0442\u0438\u043a\u0435 <strong>\/src\/main\/resources\/static<\/strong>. \u041a\u0440\u0430\u0442\u043a\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435:<\/p>\n<ul>\n<li>\n<p><strong>index.html<\/strong>\u00a0\u2014 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/p>\n<\/li>\n<li>\n<p><strong>app.js<\/strong>\u00a0\u2014 \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u043a\u0430 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439<\/p>\n<\/li>\n<li>\n<p><strong>network.js<\/strong>\u00a0\u2014 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u0434\u043b\u044f\u00a0\u0440\u0430\u0431\u043e\u0442\u044b \u0441\u00a0\u0441\u0435\u0442\u044c\u044e (websocket)<\/p>\n<\/li>\n<li>\n<p><strong>types.js<\/strong>\u00a0\u2014 \u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a \u0442\u0438\u043f\u043e\u0432 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439<\/p>\n<\/li>\n<li>\n<p><strong>styles.css<\/strong>\u00a0\u2014 \u0442\u0430\u0431\u043b\u0438\u0446\u0430 \u0441\u0442\u0438\u043b\u0435\u0439 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/p>\n<\/li>\n<\/ul>\n<p>\u041d\u0435\u00a0\u0431\u0443\u0434\u0435\u043c \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0430\u00a0\u043a\u043b\u0438\u0435\u043d\u0442\u0435, \u043e\u043d \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u0438\u043c\u0438\u0442\u0438\u0432\u0435\u043d \u0438 \u043f\u0440\u043e\u0441\u0442. \u0412\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u0443\u0434\u0435\u043b\u0438\u043c \u0431\u043e\u043b\u044c\u0448\u0435\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u043e\u0439 \u0447\u0430\u0441\u0442\u0438, \u043f\u0440\u043e\u00a0\u0447\u0442\u043e\u00a0\u0438 \u043f\u0438\u0441\u0430\u043b\u0430\u0441\u044c \u0441\u0442\u0430\u0442\u044c\u044f.<\/p>\n<h3>\u0421\u0435\u0440\u0432\u0435\u0440<\/h3>\n<p>Http\u2011\u043f\u043e\u0440\u0442 \u043f\u043e\u00a0\u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e\u00a0\u2014 8080. \u0414\u043b\u044f\u00a0\u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442\u0430 \u0432\u044b\u0434\u0435\u043b\u0435\u043d \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u043f\u043e\u0434\u00a0\u044d\u0442\u0438\u043c \u043f\u043e\u0440\u0442\u043e\u043c: <strong>\/websocket<\/strong> <\/p>\n<p>\u0421\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d \u043d\u0430\u00a0\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u043f\u0430\u043a\u0435\u0442\u044b:<\/p>\n<ul>\n<li>\n<p><strong>config<\/strong>\u00a0\u2014 \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043f\u0440\u043e\u043f\u0435\u0440\u0442\u044f, \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u044b<\/p>\n<\/li>\n<li>\n<p><strong>event<\/strong>\u00a0\u2014 \u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u0434\u043b\u044f\u00a0\u0440\u0430\u0431\u043e\u0442\u044b \u0441\u00a0\u0432\u043d\u0443\u0442\u0440\u0438\u0438\u0433\u0440\u043e\u0432\u044b\u043c\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\u043c\u0438<\/p>\n<\/li>\n<li>\n<p><strong>game<\/strong>\u00a0\u2014 \u0412\u043d\u0443\u0442\u0440\u0438\u043a\u043e\u043c\u043d\u0430\u0442\u043d\u0430\u044f \u0438\u0433\u0440\u043e\u0432\u0430\u044f \u043b\u043e\u0433\u0438\u043a\u0430, \u0438\u0433\u0440\u043e\u0432\u044b\u0435 \u043c\u043e\u0434\u0435\u043b\u0438, \u0438\u0433\u0440\u043e\u0432\u044b\u0435 \u043f\u043e\u043b\u044f (\u0441\u044e\u0434\u0430\u00a0\u0436\u0435 \u0434\u043e\u043b\u0436\u043d\u044b \u0432\u0445\u043e\u0434\u0438\u0442\u044c \u0438 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u0432 \u0432\u043d\u0443\u0442\u0440\u0438\u0438\u0433\u0440\u043e\u0432\u044b\u0445 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432, \u0442\u0430\u043a\u0438\u0445 \u043a\u0430\u043a <strong>Mesh, QuadTree <\/strong>\u0438\u00a0\u0442.\u00a0\u043f.)<\/p>\n<\/li>\n<li>\n<p><strong>math<\/strong>\u00a0\u2014 \u041c\u0430\u0442\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043c\u0430\u043d\u0438\u043f\u0443\u043b\u044f\u0446\u0438\u0438 \u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u044b<\/p>\n<\/li>\n<li>\n<p><strong>network<\/strong>\u00a0\u2014 \u0420\u0430\u0431\u043e\u0442\u0430 \u0441\u00a0\u0441\u0435\u0442\u044c\u044e, \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043f\u043e\u0434\u043a\u0430\u043f\u043e\u0442\u043d\u044b\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b<\/p>\n<\/li>\n<li>\n<p><strong>service<\/strong>\u00a0\u2014 \u0421\u0435\u0440\u0432\u0438\u0441\u044b, \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0449\u0438\u0435 \u0437\u0430\u00a0\u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0432\u00a0\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f, \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440 \u0441\u0435\u0441\u0441\u0438\u0439, \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440 \u043a\u043e\u043c\u043d\u0430\u0442 (\u043e\u043d\u00a0\u0436\u0435 \u043c\u0430\u0442\u0447\u043c\u0435\u0439\u043a\u0435\u0440) \u0438 \u0434\u0440.<\/p>\n<\/li>\n<li>\n<p><strong>shared <\/strong>\u2014 \u0420\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u043f\u043e\u0432\u0441\u0435\u043c\u0435\u0441\u0442\u043d\u043e \u0432\u00a0\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438<\/p>\n<\/li>\n<\/ul>\n<p>\u041a\u0430\u043a\u00a0\u0432\u0438\u0434\u043d\u043e \u0438\u0437\u00a0\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u0431\u043e\u043b\u044c\u0448\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0438\u0433\u0440\u043e\u0432\u043e\u0439 \u043b\u043e\u0433\u0438\u043a\u0438 \u0432\u00a0\u0438\u0442\u043e\u0433\u0435 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0430 \u043c\u0435\u0436\u0434\u0443 \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c\u0438 \u0438 \u0438\u0433\u0440\u043e\u0432\u044b\u043c\u0438 \u043a\u043e\u043c\u043d\u0430\u0442\u0430\u043c\u0438. \u0411\u043e\u043b\u044c\u0448\u0435 \u043d\u0435\u00a0\u043d\u0443\u0436\u043d\u043e \u043f\u0438\u0441\u0430\u0442\u044c \u0434\u0435\u043a\u043e\u0434\u0435\u0440\u044b, netty\u2011\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0438 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u044b, \u043a\u043e\u0434 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u0437\u043d\u0430\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0449\u0435.<\/p>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0440\u0430\u0431\u043e\u0442\u0443 \u0441\u00a0\u0441\u0435\u0442\u044c\u044e \u0438 \u043f\u0430\u043a\u0435\u0442 <strong><em>network<\/em><\/strong>.<\/p>\n<h3>Network. \u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0441\u0435\u0442\u044c\u044e \u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u0441\u0435\u0440\u0432\u0435\u0440\u0430<\/h3>\n<p>\u0420\u0430\u0431\u043e\u0442\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0440\u0430\u0437\u0443\u043c\u0435\u0435\u0442\u0441\u044f \u0441 <strong>Application.java<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u0442 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 \u0432 webflux <strong>netty-\u0441\u0435\u0440\u0432\u0435\u0440<\/strong>.<\/p>\n<p>\u041d\u0430\u043f\u043e\u043c\u043d\u044e, Netty\u00a0\u2014 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a \u0434\u043b\u044f\u00a0\u0440\u0430\u0431\u043e\u0442\u044b \u0441\u00a0\u0441\u0435\u0442\u044c\u044e, \u0432\u00a0\u043e\u0441\u043d\u043e\u0432\u0435 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043b\u0435\u0436\u0430\u0442 \u043d\u0435\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u044e\u0449\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438. \u0422\u043e\u0435\u0441\u0442\u044c, \u0433\u0440\u0443\u0431\u043e \u0433\u043e\u0432\u043e\u0440\u044f, \u043a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0438\u0439 \u0437\u0430\u043f\u0440\u043e\u0441, \u043f\u043e\u0442\u043e\u043a, \u0432\u00a0\u043f\u043e\u0441\u043b\u0435\u0434\u0441\u0442\u0432\u0438\u0438 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0449\u0438\u0439 \u0435\u0433\u043e, \u043d\u0435\u00a0\u0436\u0434\u0435\u0442 \u0447\u0442\u0435\u043d\u0438\u044f \u0438\u0437\u00a0\u0441\u043e\u043a\u0435\u0442\u0430, \u0432\u00a0\u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442\u00a0\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u044e\u0449\u0438\u0445 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0432\u0432\u043e\u0434\u0430\u2011\u0432\u044b\u0432\u043e\u0434\u0430, \u0430\u00a0\u043c\u043e\u0436\u0435\u0442\u00a0\u0431\u044b\u0442\u044c \u0437\u0430\u043d\u044f\u0442 \u0447\u0435\u043c\u00a0\u043b\u0438\u0431\u043e \u0435\u0449\u0435 \u0438 \u0432\u044b\u0437\u043e\u0432\u0435\u0442\u0441\u044f, \u043a\u043e\u0433\u0434\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043d\u043e \u0438 \u0433\u043e\u0442\u043e\u0432\u043e \u043a\u00a0\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435. <\/p>\n<p>\u0412\u043b\u0438\u044f\u0442\u044c \u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u043a\u0443\u0440\u0430\u0446\u0438\u044e \u0441\u0430\u043c\u043e\u0433\u043e Netty \u0438 \u0435\u0433\u043e \u0440\u0435\u0441\u0443\u0440\u0441\u044b \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f  <\/p>\n<pre><code>-Dreactor.netty.*<\/code><\/pre>\n<p>\u0421\u043c. <a href=\"https:\/\/projectreactor.io\/docs\/netty\/release\/reference\/index.html\" rel=\"noopener noreferrer nofollow\">https:\/\/projectreactor.io\/docs\/netty\/release\/reference\/index.html<\/a><\/p>\n<p>\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a, \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u0439 \u0441 \u0432\u0435\u0431\u0441\u043e\u043a\u0435\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438:<\/p>\n<pre><code class=\"kotlin\">@Service class MainWebSocketHandler(     private val webSocketSessionService: WebSocketSessionService ) : WebSocketHandler {     private val objectMapper = Gson()     private lateinit var roomService: RoomService      companion object {         val log: Logger = LogManager.getLogger(this::class.java)     }      override fun handle(webSocketSession: WebSocketSession): Mono&lt;Void> {         val input = webSocketSession.receive().share()         val userSession = UserSession(webSocketSession.id, webSocketSession.handshakeInfo)         val sessionHandler = UserSessionWebSocketHandler(             userSession,             webSocketSessionService, roomService         )          val receive = input             .filter { it.type == WebSocketMessage.Type.TEXT }             .map(this::toMessage)             .doOnNext(sessionHandler::onNext)          val send = webSocketSession.send(webSocketSessionService.onActive(userSession)             .map {                 webSocketSession.textMessage(objectMapper.toJson(it))             }             .doOnError { handleError(webSocketSession, it) })          val security = webSocketSession.handshakeInfo.principal.doOnNext {             webSocketSessionService.onPrincipalInit(userSession, it)         }         return Flux.merge(receive, send, security)             .doOnSubscribe { webSocketSessionService.onSubscribe(userSession, it) }             .doOnTerminate { webSocketSessionService.onInactive(userSession) }             .doOnError { handleError(webSocketSession, it) }             .then()     }      private fun toMessage(webSocketMessage: WebSocketMessage): Message =         objectMapper.fromJson(InputStreamReader(webSocketMessage.payload.asInputStream()), Message::class.java)      private fun handleError(webSocketSession: WebSocketSession, exception: Throwable) {         log.error(\"Error in ${webSocketSession.id} session\", exception)     }      @Autowired     fun setGameRoomManagementService(@Lazy roomService: RoomService) {         this.roomService = roomService     } }<\/code><\/pre>\n<p>\u041a\u0430\u043a\u00a0\u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u0435\u043b upgrade \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0430\u00a0\u2014 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043f\u043e\u043f\u0430\u0434\u0430\u0435\u0442 \u0432\u00a0\u043c\u0435\u0442\u043e\u0434 <strong>handle<\/strong>. \u0412\u00a0\u043d\u0435\u043c \u0441\u0440\u0430\u0437\u0443 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0439 \u0441\u0435\u0441\u0441\u0438\u0438 <strong>UserSession<\/strong>, \u0430\u00a0\u0442\u0430\u043a\u0436\u0435 \u043d\u0430\u0432\u0435\u0448\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0440\u044f\u0434 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0445 \u043b\u044f\u043c\u0431\u0434, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0449\u0438\u0445 \u043a\u0430\u043a\u00a0\u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u0442\u0430\u043a \u0438 \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0435.<\/p>\n<p>\u0421\u0442\u043e\u0438\u0442 \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a handshake:<\/p>\n<pre><code class=\"kotlin\">val security = webSocketSession.handshakeInfo.principal.doOnNext {     webSocketSessionService.onPrincipalInit(userSession, it) }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u0430\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0430\u043b\u043e\u0432, \u0435\u0441\u043b\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0448\u043b\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e, \u043f\u0440\u0438\u0447\u0435\u043c \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0430\u043b\u0430 \u0432\u00a0Mono<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-370972","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/370972","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=370972"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/370972\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=370972"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=370972"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=370972"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}