├── .github └── workflows │ ├── gradle.yml │ └── publish.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── STYLEGUIDE.md ├── build.gradle ├── examples ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── tlf │ └── monkeynetty │ └── test │ ├── JmeClient.java │ ├── JmeServer.java │ └── messages │ ├── TestSerializableDataA.java │ ├── TestSerializableDataB.java │ ├── TestTCPBigMessageA.java │ ├── TestTCPBigMessageB.java │ ├── TestTCPMessage.java │ ├── TestUDPBigMessageA.java │ ├── TestUDPBigMessageB.java │ └── TestUDPMessage.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── monkey-netty ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── tlf │ └── monkeynetty │ ├── ConnectionListener.java │ ├── MessageListener.java │ ├── NetworkClient.java │ ├── NetworkMessageDecoder.java │ ├── NetworkMessageEncoder.java │ ├── NetworkMessageException.java │ ├── NetworkObjectInputStream.java │ ├── NetworkObjectOutputStream.java │ ├── NetworkProtocol.java │ ├── NetworkRegistrar.java │ ├── NetworkServer.java │ ├── client │ ├── DatagramPacketObjectDecoder.java │ ├── MessageCacheMode.java │ └── NettyClient.java │ ├── msg │ ├── ConnectionEstablishedMessage.java │ ├── NetworkMessage.java │ ├── PingMessage.java │ └── UdpConHashMessage.java │ └── server │ ├── NettyConnection.java │ ├── NettyServer.java │ ├── UdpChannel.java │ └── UdpServerChannel.java ├── publishing └── publishing.gradle └── settings.gradle /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Java CI with Gradle 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up JDK 1.8 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: 1.8 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x gradlew 25 | - name: Build with Gradle 26 | run: ./gradlew build 27 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish package to GitHub Packages 2 | on: 3 | release: 4 | types: [created] 5 | jobs: 6 | publish: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions/setup-java@v1 11 | with: 12 | java-version: 1.8 13 | - name: Publish package 14 | run: gradle publish 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .shelf/ 3 | /.gradle/ 4 | /.gradle/* 5 | /.nb-gradle/ 6 | /.nb-gradle/* 7 | /build/* 8 | /build/**/ 9 | .nb-* 10 | .class 11 | nbproject/private/ 12 | bin/ 13 | libs/ 14 | build/ 15 | .gradle 16 | .gradletasknamecache 17 | /publishing/publishing.properties 18 | *.asc 19 | *.gpg 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #### v0.1.2 2 | * Artifacts now published to Maven Central (#35) 3 | * Update Netty.IO to v4.1.82.Final 4 | * Update jMonkeyEngine to v3.5.2 5 | 6 | #### v0.1.1 7 | * Fixed concurrency issues with Collections (#31) 8 | * Updated Netty.IO to version 4.1.58 (#29) 9 | * Updated Gradle to version 6.8.1 (#30) 10 | 11 | #### v0.1.0 12 | * SSL Support for TCP channel (#5, #6) 13 | * Optimize network message transport (#4, #11) 14 | * Better logging and enhanced test examples (#9) 15 | * Better error reporting on non-serializable objects in a message (#14, #16) 16 | * Separation of examples and library, and corresponding dependencies (#22) 17 | * Fixed issue where messages would be transferred before connection listeners were notified (#20, #21) 18 | * Fixed issue where dropped sockets would not be detected (#10, #23) 19 | 20 | #### V0.0.0 21 | Initial Release 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Liquid Crystal Studios Contribution Guide 2 | 3 | ## Code Style 4 | [Style Guide](STYLEGUIDE.md) 5 | 6 | ## Branches 7 | When developing, one must create a branch and then submit a merge request when finished with the branch. 8 | There are two types of branches that are acceptable. 9 | 10 | ### Working Branches 11 | A branch labeled `working-[username]` can be used when making changed that are not related to an issue. An example of such branch is: `working-trevorflynn`, this would indicate that Trevor Flynn has created this branch. 12 | Not all working branches will get accepted when a merge request is accepted. 13 | A branch will not be accepted when: 14 | * Conflicts with master branch 15 | * Does not pass pipeline build 16 | * Does not provide any contribution 17 | * Contains improperly formatted code 18 | * Contains a feature or fix that was not approved. 19 | 20 | ### Issue Branches 21 | When working on an issue or feature that is documented in issues, a branch must be created and its name must start with the ID of the issue. 22 | An example would be if someone was fixing issue #45, an error. Then the branch name must start with 45. i.e. `45-fix-for-some-error` 23 | A merge request must be made before the branch will be merged with the master branch. 24 | A branch will not be accepted when: 25 | * Conflicts with master branch 26 | * Does not pass pipeline build 27 | * Does not provide any contribution 28 | * Contains improperly formatted code 29 | * Contains a feature or fix that was not approved. 30 | 31 | ### The Master Branch 32 | The master branch is the current development branch that all work will be merged into. 33 | 34 | ### Production Branches 35 | A production branch will deploy the application. 36 | The branch must be named: `[release issue #]-release-v[version]` 37 | Example: `33-release-v0.1.1` 38 | 39 | ## Issues 40 | 41 | ### Tagging 42 | If you find a bug, security exploit, or feature. Please create an issue with the appropriate tags. 43 | Do not attach the `issue` tag to something that is not a bug or security exploit. 44 | Do not attach the `feature` tag to something that is not a possible feature or enhancement. 45 | Do not attach a group tage such as `front-end` or `back-end` to an issue that does not pertain to the group. 46 | Do not attach a `review` or `second-opinion` tag to issues that do not need feedback from Trevor Flynn or Jayce Miller respectively. 47 | Attach a code tag if the issue pertains to a type of code. For example, if there is an issue inside of the database, attach the `database` tag. 48 | 49 | ### Assigning 50 | Do not assign an issue to someone other than yourself. 51 | Do not assign on issue to yourself if you are not going to work on it. 52 | (The above rules do not seem to apply to Trevor Flynn...) 53 | 54 | ### Closing 55 | Do not close an issue unless it is agreed that it should be closed, or until the issue is resolved. 56 | 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Trevor Flynn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # monkey-netty 2 | ![Build](https://github.com/tlf30/monkey-netty/workflows/Java%20CI%20with%20Gradle/badge.svg) 3 | An implementation of a server-client communication system for jMonkeyEngine using Netty.IO that utilizes 4 | both TCP and UDP communication. 5 | 6 | **Checkout our [Wiki](https://github.com/tlf30/monkey-netty/wiki) for getting started.** 7 | 8 | **See example for server and client in `examples` module.** 9 | 10 | ## Installing with Gradle 11 | Note: We will no longer be publishing packages to GitHub, future packages will be in Maven Central. 12 | In your `build.gradle` you will need to: 13 | 14 | ```groovy 15 | dependencies { 16 | ... 17 | implementation 'io.tlf.monkeynetty:monkey-netty:0.1.2-SNAPSHOT' 18 | } 19 | ``` 20 | 21 | ## Installing with Maven 22 | Note: We will no longer be publishing packages to GitHub, future packages will be in Maven Central. 23 | In your pom.xml you will need to: 24 | 25 | ```xml 26 | 27 | ... 28 | 29 | io.tlf.monkeynetty 30 | monkey-netty 31 | 0.1.2-SNAPSHOT 32 | 33 | 34 | ``` 35 | 36 | -------------------------------------------------------------------------------- /STYLEGUIDE.md: -------------------------------------------------------------------------------- 1 | Liquid Crystal Studios' Java Style Guide 2 | 3 | ## Coding style 4 | 5 | ### Formatting 6 | 7 | #### Use line breaks wisely 8 | There are generally two reasons to insert a line break: 9 | 10 | 1. Your statement exceeds the column limit. 11 | 12 | 2. You want to logically separate a thought.
13 | Writing code is like telling a story. Written language constructs like chapters, paragraphs, 14 | and punctuation (e.g. semicolons, commas, periods, hyphens) convey thought hierarchy and 15 | separation. We have similar constructs in programming languages; you should use them to your 16 | advantage to effectively tell the story to those reading the code. 17 | 18 | #### Indent style 19 | We use the "one true brace style" ([1TBS](http://en.wikipedia.org/wiki/Indent_style#Variant:_1TBS)). 20 | Indent size is 4 columns. 21 | 22 | ``` 23 | // Like this. 24 | if (x < 0) { 25 | negative(x); 26 | } else { 27 | nonnegative(x); 28 | } 29 | 30 | // Not like this. 31 | if (x < 0) 32 | negative(x); 33 | 34 | // Also not like this. 35 | if (x < 0) negative(x); 36 | ``` 37 | 38 | Continuation indent is 8 columns. Nested continuations may add 8 columns or 4 at each level. 39 | 40 | ``` 41 | // Bad. 42 | // - Line breaks are arbitrary. 43 | // - Scanning the code makes it difficult to piece the message together. 44 | throw new IllegalStateException("Failed to process request" + request.getId() 45 | + " for user " + user.getId() + " query: '" + query.getText() 46 | + "'"); 47 | 48 | // Good. 49 | // - Each component of the message is separate and self-contained. 50 | // - Adding or removing a component of the message requires minimal reformatting. 51 | throw new IllegalStateException("Failed to process" 52 | + " request " + request.getId() 53 | + " for user " + user.getId() 54 | + " query: '" + query.getText() + "'"); 55 | ``` 56 | 57 | Don't break up a statement unnecessarily. 58 | 59 | ``` 60 | // Bad. 61 | final String value = 62 | otherValue; 63 | 64 | // Good. 65 | final String value = otherValue; 66 | ``` 67 | 68 | Method declaration continuations. 69 | 70 | ``` 71 | // Sub-optimal since line breaks are arbitrary and only filling lines. 72 | String downloadAnInternet(Internet internet, Tubes tubes, 73 | Blogosphere blogs, Amount bandwidth) { 74 | tubes.download(internet); 75 | ... 76 | } 77 | 78 | // Acceptable. 79 | String downloadAnInternet(Internet internet, Tubes tubes, Blogosphere blogs, 80 | Amount bandwidth) { 81 | tubes.download(internet); 82 | ... 83 | } 84 | 85 | // Nicer, as the extra newline gives visual separation to the method body. 86 | String downloadAnInternet(Internet internet, Tubes tubes, Blogosphere blogs, 87 | Amount bandwidth) { 88 | 89 | tubes.download(internet); 90 | ... 91 | } 92 | 93 | // Preferred for easy scanning and extra column space. 94 | public String downloadAnInternet( 95 | Internet internet, 96 | Tubes tubes, 97 | Blogosphere blogs, 98 | Amount bandwidth) { 99 | 100 | tubes.download(internet); 101 | ... 102 | } 103 | 104 | ``` 105 | 106 | ##### Chained method calls 107 | 108 | ``` 109 | // Bad. 110 | // - Line breaks are based on line length, not logic. 111 | Iterable modules = ImmutableList.builder().add(new LifecycleModule()) 112 | .add(new AppLauncherModule()).addAll(application.getModules()).build(); 113 | 114 | // Better. 115 | // - Calls are logically separated. 116 | // - However, the trailing period logically splits a statement across two lines. 117 | Iterable modules = ImmutableList.builder(). 118 | add(new LifecycleModule()). 119 | add(new AppLauncherModule()). 120 | addAll(application.getModules()). 121 | build(); 122 | 123 | // Good. 124 | // - Method calls are isolated to a line. 125 | // - The proper location for a new method call is unambiguous. 126 | Iterable modules = ImmutableList.builder() 127 | .add(new LifecycleModule()) 128 | .add(new AppLauncherModule()) 129 | .addAll(application.getModules()) 130 | .build(); 131 | ``` 132 | 133 | #### Only tabs 134 | All indentation must be done with tabs. 135 | 136 | #### CamelCase for types and classes, camelCase for variables, UPPER_SNAKE for constants, lowercase for packages. 137 | 138 | #### No trailing whitespace 139 | Trailing whitespace characters, while logically benign, add nothing to the program. 140 | However, they do serve to frustrate developers when using keyboard shortcuts to navigate code. 141 | 142 | ### Field, class, and method declarations 143 | 144 | ##### Modifier order 145 | 146 | We follow the [Java Language Specification](http://docs.oracle.com/javase/specs/) for modifier 147 | ordering (sections 148 | [8.1.1](http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.1.1), 149 | [8.3.1](http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1) and 150 | [8.4.3](http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.4.3)). 151 | 152 | ``` 153 | // Bad. 154 | final volatile private String value; 155 | 156 | // Good. 157 | private final volatile String value; 158 | ``` 159 | 160 | ### Variable naming 161 | 162 | #### Extremely short variable names should be reserved for instances like loop indices. 163 | 164 | ``` 165 | // Bad. 166 | // - Field names give little insight into what fields are used for. 167 | class User { 168 | private final int a; 169 | private final String m; 170 | 171 | ... 172 | } 173 | 174 | // Good. 175 | class User { 176 | private final int ageInYears; 177 | private final String maidenName; 178 | 179 | ... 180 | } 181 | ``` 182 | 183 | #### Don't embed metadata in variable names 184 | A variable name should describe the variable's purpose. Adding extra information like scope and 185 | type is generally a sign of a bad variable name. 186 | 187 | Avoid embedding the field type in the field name. 188 | 189 | ``` 190 | // Bad. 191 | Map idToUserMap; 192 | String valueString; 193 | 194 | // Good. 195 | Map usersById; 196 | String value; 197 | ``` 198 | 199 | Also avoid embedding scope information in a variable. Hierarchy-based naming suggests that a class 200 | is too complex and should be broken apart. 201 | 202 | ``` 203 | // Bad. 204 | String _value; 205 | String mValue; 206 | 207 | // Good. 208 | String value; 209 | ``` 210 | 211 | ### Space pad operators and equals. 212 | 213 | ``` 214 | // Bad. 215 | // - This offers poor visual separation of operations. 216 | int foo=a+b+1; 217 | 218 | // Good. 219 | int foo = a + b + 1; 220 | ``` 221 | 222 | ### Be explicit about operator precedence 223 | Don't make your reader open the 224 | [spec](http://docs.oracle.com/javase/tutorial/java/nutsandbolts/operators.html) to confirm, 225 | if you expect a specific operation ordering, make it obvious with parenthesis. 226 | 227 | ``` 228 | // Bad. 229 | return a << 8 * n + 1 | 0xFF; 230 | 231 | // Good. 232 | return (a << (8 * n) + 1) | 0xFF; 233 | ``` 234 | 235 | It's even good to be *really* obvious. 236 | 237 | ``` 238 | if ((values != null) && (10 > values.size())) { 239 | ... 240 | } 241 | ``` 242 | 243 | ### Always include scope 244 | ``` 245 | // Bad. 246 | class MyClass { 247 | ... 248 | } 249 | 250 | // Good. 251 | protected MyClass { 252 | ... 253 | } 254 | 255 | ``` 256 | ### Documentation 257 | 258 | The more visible a piece of code is (and by extension - the farther away consumers might be), 259 | the more documentation is needed. 260 | 261 | #### "I'm writing a report about..." 262 | Your elementary school teacher was right - you should never start a statement this way. 263 | Likewise, you shouldn't write documentation this way. 264 | 265 | ``` 266 | // Bad. 267 | /** 268 | * This is a class that implements a cache. It does caching for you. 269 | */ 270 | class Cache { 271 | ... 272 | } 273 | 274 | // Good. 275 | /** 276 | * A volatile storage for objects based on a key, which may be invalidated and discarded. 277 | */ 278 | class Cache { 279 | ... 280 | } 281 | ``` 282 | 283 | #### Documenting a class 284 | Documentation for a class may range from a single sentence 285 | to paragraphs with code examples. Documentation should serve to disambiguate any conceptual 286 | blanks in the API, and make it easier to quickly and *correctly* use your API. 287 | A thorough class doc usually has a one sentence summary and, if necessary, 288 | a more detailed explanation. 289 | 290 | ``` 291 | /** 292 | * An RPC equivalent of a unix pipe tee. Any RPC sent to the tee input is guaranteed to have 293 | * been sent to both tee outputs before the call returns. 294 | * 295 | * @param The type of the tee'd service. 296 | */ 297 | public class RpcTee { 298 | ... 299 | } 300 | ``` 301 | 302 | #### Documenting a method 303 | A method doc should tell what the method *does*. Depending on the argument types, it may 304 | also be important to document input format. 305 | 306 | ``` 307 | // Bad. 308 | // - The doc tells nothing that the method declaration didn't. 309 | // - This is the 'filler doc'. It would pass style checks, but doesn't help anybody. 310 | /** 311 | * Splits a string. 312 | * 313 | * @param s A string. 314 | * @return A list of strings. 315 | */ 316 | List split(String s); 317 | 318 | // Better. 319 | // - We know what the method splits on. 320 | // - Still some undefined behavior. 321 | /** 322 | * Splits a string on whitespace. 323 | * 324 | * @param s The string to split. An {@code null} string is treated as an empty string. 325 | * @return A list of the whitespace-delimited parts of the input. 326 | */ 327 | List split(String s); 328 | 329 | // Great. 330 | // - Covers yet another edge case. 331 | /** 332 | * Splits a string on whitespace. Repeated whitespace characters are collapsed. 333 | * 334 | * @param s The string to split. An {@code null} string is treated as an empty string. 335 | * @return A list of the whitespace-delimited parts of the input. 336 | */ 337 | List split(String s); 338 | ``` 339 | 340 | #### Be professional 341 | We've all encountered frustration when dealing with other libraries, but ranting about it doesn't 342 | do you any favors. Suppress the expletives and get to the point. 343 | 344 | ``` 345 | // Bad. 346 | // I hate xml/soap so much, why can't it do this for me!? 347 | try { 348 | userId = Integer.parseInt(xml.getField("id")); 349 | } catch (NumberFormatException e) { 350 | ... 351 | } 352 | 353 | // Good. 354 | // TODO: Tuck field validation away in a library. 355 | try { 356 | userId = Integer.parseInt(xml.getField("id")); 357 | } catch (NumberFormatException e) { 358 | ... 359 | } 360 | ``` 361 | 362 | #### Don't document overriding methods (usually) 363 | 364 | ``` 365 | public interface Database { 366 | /** 367 | * Gets the installed version of the database. 368 | * 369 | * @return The database version identifier. 370 | */ 371 | String getVersion(); 372 | } 373 | 374 | // Bad. 375 | // - Overriding method doc doesn't add anything. 376 | public class PostgresDatabase implements Database { 377 | /** 378 | * Gets the installed version of the database. 379 | * 380 | * @return The database version identifier. 381 | */ 382 | @Override 383 | public String getVersion() { 384 | ... 385 | } 386 | } 387 | 388 | // Good. 389 | public class PostgresDatabase implements Database { 390 | @Override 391 | public int getVersion(); 392 | } 393 | 394 | // Great. 395 | // - The doc explains how it differs from or adds to the interface doc. 396 | public class TwitterDatabase implements Database { 397 | /** 398 | * Semantic version number. 399 | * 400 | * @return The database version in semver format. 401 | */ 402 | @Override 403 | public String getVersion() { 404 | ... 405 | } 406 | } 407 | ``` 408 | 409 | #### Use javadoc features 410 | 411 | ##### No author tags 412 | Code can change hands numerous times in its lifetime, and quite often the original author of a 413 | source file is irrelevant after several iterations. We find it's better to trust commit 414 | history. 415 | 416 | An exception to this is usually found in classes or functions written specificly by Trevor Flynn or Jayce Miller. 417 | 418 | ### Imports 419 | 420 | #### Import ordering 421 | Imports are grouped by top-level package, with blank lines separating groups. 422 | 423 | ``` 424 | import java.* 425 | import javax.* 426 | 427 | import scala.* 428 | 429 | import com.* 430 | 431 | import net.* 432 | 433 | import org.* 434 | 435 | import com.twitter.* 436 | 437 | import static * 438 | ``` 439 | 440 | #### No wildcard imports 441 | Wildcard imports make the source of an imported class less clear. They also tend to hide a high 442 | class [fan-out](http://en.wikipedia.org/wiki/Coupling_(computer_programming)#Module_coupling).
443 | *See also [texas imports](#stay-out-of-texas)* 444 | 445 | ``` 446 | // Bad. 447 | // - Where did Foo come from? 448 | import com.twitter.baz.foo.*; 449 | import com.twitter.*; 450 | 451 | interface Bar extends Foo { 452 | ... 453 | } 454 | 455 | // Good. 456 | import com.twitter.baz.foo.BazFoo; 457 | import com.twitter.Foo; 458 | 459 | interface Bar extends Foo { 460 | ... 461 | } 462 | ``` 463 | The exception to this is when working with junit assertions (static imports), awt or swing classes. 464 | 465 | ### Use annotations wisely 466 | 467 | #### @Nullable 468 | This only applies to projects that are using null protection. 469 | By default - disallow `null`. When a variable, parameter, or method return value may be `null`, 470 | be explicit about it by marking 471 | [@Nullable](http://code.google.com/p/jsr-305/source/browse/trunk/ri/src/main/java/javax/annotation/Nullable.java?r=24). 472 | This is advisable even for fields/methods with private visibility. 473 | 474 | ``` 475 | class Database { 476 | @Nullable private Connection connection; 477 | 478 | @Nullable 479 | Connection getConnection() { 480 | return connection; 481 | } 482 | 483 | void setConnection(@Nullable Connection connection) { 484 | this.connection = connection; 485 | } 486 | } 487 | ``` 488 | 489 | ### TODOs 490 | 491 | #### Leave TODOs early and often 492 | A TODO isn't a bad thing - it's signaling a future developer (possibly yourself) that a 493 | consideration was made, but omitted for various reasons. It can also serve as a useful signal when 494 | debugging. 495 | 496 | ### Comments 497 | 498 | #### Leave comments often 499 | It often is much more clear what a snippet of code is doing when a short comment explains it. 500 | 501 | ``` 502 | // Good. 503 | //Add mesh to world 504 | world.getApplication().enqueue(() -> { 505 | if (markedForRebuild.get()) { 506 | destroyGeometry(this.chunkGeometry); 507 | this.chunkGeometry = newGeom; 508 | this.chunkNode.attachChild(chunkGeometry); 509 | world.fireChunkRebuilt(this, edited); 510 | this.edited = false; 511 | } else { 512 | this.chunkGeometry = newGeom; 513 | this.chunkNode.attachChild(newGeom); 514 | } 515 | if (Outside.IS_CLIENT) { 516 | chunkGeometry.addControl(new ChunkLod(this)); 517 | } 518 | }); 519 | ``` 520 | 521 | #### Don't leave dead code commented out 522 | Leaving dead code commented out can offten confuse other programmers who go to work on the code latter. 523 | ``` 524 | // Bad. 525 | HashMap data = componentImporter.importComponent("world_" + worldName); 526 | if (data == null) { 527 | return new HashSet<>(); 528 | } 529 | long id = Long.parseLong(data.get("id").toString()); 530 | //Script[] scriptData = result.getObject("scripts", Script[].class); 531 | Chunk[] sqlChunks = ((ArrayList) data.get("chunks")).toArray(new Chunk[0]); 532 | ChunkMeta[] chunks = new ChunkMeta[sqlChunks.length]; 533 | ``` 534 | One exception to this is when a todo is left to indicate to another programmer what needs to happen with the code: 535 | ``` 536 | // Good. 537 | HashMap data = componentImporter.importComponent("world_" + worldName); 538 | if (data == null) { 539 | return new HashSet<>(); 540 | } 541 | long id = Long.parseLong(data.get("id").toString()); 542 | //Script[] scriptData = result.getObject("scripts", Script[].class); //TODO: Currently we have no way of attaching scripts to the world 543 | Chunk[] sqlChunks = ((ArrayList) data.get("chunks")).toArray(new Chunk[0]); 544 | ChunkMeta[] chunks = new ChunkMeta[sqlChunks.length]; 545 | `` 546 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'io.github.gradle-nexus.publish-plugin' version '1.1.0' 3 | } 4 | 5 | allprojects { 6 | version = '0.1.2-SNAPSHOT' 7 | group = "io.tlf.monkeynetty" 8 | 9 | ext { 10 | //Dependency Versions 11 | jmeVersion = "3.5.2-stable" 12 | jmeGroup = "org.jmonkeyengine" 13 | nettyioVersion = "4.1.82.Final" 14 | } 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | } 20 | 21 | apply from: 'publishing/publishing.gradle' 22 | -------------------------------------------------------------------------------- /examples/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | dependencies { 6 | implementation project(":monkey-netty") 7 | implementation jmeGroup + ':jme3-desktop:' + jmeVersion 8 | implementation jmeGroup + ':jme3-lwjgl3:' + jmeVersion 9 | } 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/src/main/java/io/tlf/monkeynetty/test/JmeClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty.test; 26 | 27 | import io.netty.handler.logging.LogLevel; 28 | import io.tlf.monkeynetty.ConnectionListener; 29 | import io.tlf.monkeynetty.test.messages.TestUDPBigMessageA; 30 | import io.tlf.monkeynetty.test.messages.TestTCPBigMessageA; 31 | import io.tlf.monkeynetty.test.messages.TestTCPMessage; 32 | import io.tlf.monkeynetty.test.messages.TestUDPMessage; 33 | import com.jme3.app.SimpleApplication; 34 | import com.jme3.input.KeyInput; 35 | import com.jme3.input.controls.ActionListener; 36 | import com.jme3.input.controls.KeyTrigger; 37 | import io.tlf.monkeynetty.MessageListener; 38 | import io.tlf.monkeynetty.NetworkClient; 39 | import io.tlf.monkeynetty.NetworkServer; 40 | import io.tlf.monkeynetty.client.NettyClient; 41 | import io.tlf.monkeynetty.msg.NetworkMessage; 42 | import io.tlf.monkeynetty.test.messages.TestTCPBigMessageB; 43 | import io.tlf.monkeynetty.test.messages.TestUDPBigMessageB; 44 | 45 | /** 46 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 47 | */ 48 | public class JmeClient extends SimpleApplication { 49 | 50 | NettyClient client; 51 | 52 | @Override 53 | public void simpleInitApp() { 54 | client = new NettyClient("test", true, 10000, "localhost"); 55 | stateManager.attach(client); 56 | client.setLogLevel(LogLevel.INFO); 57 | client.registerListener(new MessageListener() { 58 | @Override 59 | public void onMessage(NetworkMessage msg, NetworkServer server, NetworkClient client) { 60 | System.out.println("Got message " + msg); 61 | } 62 | 63 | @Override 64 | public Class[] getSupportedMessages() { 65 | return new Class[]{TestTCPMessage.class, TestUDPMessage.class, TestTCPBigMessageA.class, TestTCPBigMessageB.class, TestUDPBigMessageA.class, TestUDPBigMessageB.class}; 66 | } 67 | }); 68 | 69 | client.registerListener(new ConnectionListener() { 70 | @Override 71 | public void onConnect(NetworkClient client) { 72 | client.send(new TestTCPMessage()); 73 | } 74 | 75 | @Override 76 | public void onDisconnect(NetworkClient client) { 77 | 78 | } 79 | }); 80 | inputManager.addMapping("enter", new KeyTrigger(KeyInput.KEY_RETURN)); 81 | inputManager.addListener((ActionListener) (name, isPressed, tpf) -> { 82 | if (isPressed) { 83 | client.send(new TestTCPMessage()); 84 | } 85 | }, "enter"); 86 | inputManager.addMapping("space", new KeyTrigger(KeyInput.KEY_SPACE)); 87 | inputManager.addListener((ActionListener) (name, isPressed, tpf) -> { 88 | if (isPressed) { 89 | client.send(new TestUDPMessage()); 90 | } 91 | }, "space"); 92 | //todo: replace with setup protocol, when feature https://github.com/tlf30/monkey-netty/issues/7 will be solved 93 | inputManager.addMapping("key1", new KeyTrigger(KeyInput.KEY_1)); 94 | inputManager.addListener((ActionListener) (name, isPressed, tpf) -> { 95 | if (isPressed) { 96 | client.send(new TestTCPBigMessageA()); 97 | } 98 | }, "key1"); 99 | inputManager.addMapping("key2", new KeyTrigger(KeyInput.KEY_2)); 100 | inputManager.addListener((ActionListener) (name, isPressed, tpf) -> { 101 | if (isPressed) { 102 | client.send(new TestUDPBigMessageA()); 103 | } 104 | }, "key2"); 105 | inputManager.addMapping("key3", new KeyTrigger(KeyInput.KEY_3)); 106 | inputManager.addListener((ActionListener) (name, isPressed, tpf) -> { 107 | if (isPressed) { 108 | client.send(new TestTCPBigMessageB()); 109 | } 110 | }, "key3"); 111 | inputManager.addMapping("key4", new KeyTrigger(KeyInput.KEY_4)); 112 | inputManager.addListener((ActionListener) (name, isPressed, tpf) -> { 113 | if (isPressed) { 114 | client.send(new TestUDPBigMessageB()); 115 | } 116 | }, "key4"); 117 | } 118 | 119 | int frame = 0; 120 | 121 | @Override 122 | public void simpleUpdate(float tpf) { 123 | if (frame < 10) { 124 | client.send(new TestTCPMessage()); 125 | frame++; 126 | } 127 | } 128 | 129 | public static void main(String[] args) { 130 | 131 | JmeClient client = new JmeClient(); 132 | client.start(); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /examples/src/main/java/io/tlf/monkeynetty/test/JmeServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty.test; 26 | 27 | import io.tlf.monkeynetty.test.messages.TestTCPMessage; 28 | import io.tlf.monkeynetty.test.messages.TestUDPMessage; 29 | import com.jme3.app.SimpleApplication; 30 | import com.jme3.system.JmeContext; 31 | import io.netty.handler.logging.LogLevel; 32 | import io.tlf.monkeynetty.ConnectionListener; 33 | import io.tlf.monkeynetty.MessageListener; 34 | import io.tlf.monkeynetty.NetworkClient; 35 | import io.tlf.monkeynetty.NetworkServer; 36 | import io.tlf.monkeynetty.msg.NetworkMessage; 37 | import io.tlf.monkeynetty.server.NettyServer; 38 | import io.tlf.monkeynetty.test.messages.TestTCPBigMessageA; 39 | import io.tlf.monkeynetty.test.messages.TestTCPBigMessageB; 40 | import io.tlf.monkeynetty.test.messages.TestUDPBigMessageA; 41 | import io.tlf.monkeynetty.test.messages.TestUDPBigMessageB; 42 | 43 | /** 44 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 45 | */ 46 | public class JmeServer extends SimpleApplication { 47 | 48 | @Override 49 | public void simpleInitApp() { 50 | NettyServer server = new NettyServer("test", true, 10000); 51 | server.setLogLevel(LogLevel.INFO); 52 | stateManager.attach(server); 53 | server.registerListener(new ConnectionListener() { 54 | @Override 55 | public void onConnect(NetworkClient client) { 56 | System.out.println("Client connected: " + client.getAddress()); 57 | } 58 | 59 | @Override 60 | public void onDisconnect(NetworkClient client) { 61 | System.out.println("Client disconnected: " + client.getAddress()); 62 | } 63 | }); 64 | server.registerListener(new MessageListener() { 65 | @Override 66 | public void onMessage(NetworkMessage msg, NetworkServer server, NetworkClient client) { 67 | System.out.println("Got message " + msg.getName() + " from client " + client.getAddress()); 68 | System.out.println(msg.toString()); 69 | client.send(msg); 70 | } 71 | 72 | @Override 73 | public Class[] getSupportedMessages() { 74 | return new Class[] {TestUDPMessage.class, TestTCPMessage.class, TestTCPBigMessageA.class, TestTCPBigMessageB.class, TestUDPBigMessageA.class, TestUDPBigMessageB.class}; 75 | } 76 | }); 77 | } 78 | 79 | public static void main(String[] args) { 80 | JmeServer server = new JmeServer(); 81 | server.start(JmeContext.Type.Headless); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /examples/src/main/java/io/tlf/monkeynetty/test/messages/TestSerializableDataA.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Trevor Flynn 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package io.tlf.monkeynetty.test.messages; 25 | 26 | import java.io.Serializable; 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | 30 | /** 31 | * @author Radosław K 32 | * 33 | * Class used for message testing purpose, it will check: 34 | * - if Serializable Object within message will work correctly 35 | * - if default Java Serializable class will not cause issue 36 | */ 37 | public class TestSerializableDataA implements Serializable { 38 | 39 | public List list; 40 | 41 | public TestSerializableDataA() { 42 | list = new ArrayList<>(); 43 | for (long i = 0; i < 10; i++) { 44 | list.add(i); 45 | } 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | return list.toString(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/src/main/java/io/tlf/monkeynetty/test/messages/TestSerializableDataB.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Trevor Flynn 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package io.tlf.monkeynetty.test.messages; 25 | 26 | import java.io.Serializable; 27 | import java.util.ArrayList; 28 | import java.util.Arrays; 29 | import java.util.HashMap; 30 | import java.util.Random; 31 | 32 | /** 33 | * @author Radosław K 34 | * 35 | * Class used for message testing purpose, it will check: 36 | * - if Serializable Object within message will work correctly 37 | * - if private fields will not cause issues 38 | * - if multiple depth primitives/Classes will work correctly. 39 | */ 40 | public class TestSerializableDataB implements Serializable { 41 | 42 | private HashMap map = new HashMap<>(); 43 | 44 | public TestSerializableDataB() { 45 | map.put("test1", 12); 46 | map.put("test2", "TestString"); 47 | map.put("test2", "TestVal" + (new Random().nextFloat() * 1000)); 48 | map.put("test3", new int[]{34,3245,534543,2233}); 49 | map.put("test4", new ArrayList<>(Arrays.asList("TestValue1", "TestValue2", "TestValue3"))); 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return map.toString(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/src/main/java/io/tlf/monkeynetty/test/messages/TestTCPBigMessageA.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Trevor Flynn 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package io.tlf.monkeynetty.test.messages; 25 | 26 | import io.tlf.monkeynetty.NetworkProtocol; 27 | import io.tlf.monkeynetty.msg.NetworkMessage; 28 | 29 | /** 30 | * @author Radosław K 31 | * 32 | * Message used for testing nested Serializable objects within message 33 | */ 34 | public class TestTCPBigMessageA implements NetworkMessage { 35 | 36 | private final TestSerializableDataA objectA; 37 | 38 | public TestTCPBigMessageA() { 39 | objectA = new TestSerializableDataA(); 40 | } 41 | 42 | @Override 43 | public String getName() { 44 | return "Test TCP Big Message A"; 45 | } 46 | 47 | @Override 48 | public NetworkProtocol getProtocol() { 49 | return NetworkProtocol.TCP; 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return objectA.toString(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/src/main/java/io/tlf/monkeynetty/test/messages/TestTCPBigMessageB.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Trevor Flynn 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package io.tlf.monkeynetty.test.messages; 25 | 26 | import io.tlf.monkeynetty.NetworkProtocol; 27 | import io.tlf.monkeynetty.msg.NetworkMessage; 28 | 29 | /** 30 | * @author Radosław K 31 | * 32 | * Message used for testing nested Serializable objects within message 33 | */ 34 | public class TestTCPBigMessageB implements NetworkMessage { 35 | 36 | private final TestSerializableDataB objectB; 37 | 38 | public TestTCPBigMessageB() { 39 | objectB = new TestSerializableDataB(); 40 | } 41 | 42 | @Override 43 | public String getName() { 44 | return "Test TCP Big Message B"; 45 | } 46 | 47 | @Override 48 | public NetworkProtocol getProtocol() { 49 | return NetworkProtocol.TCP; 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return objectB.toString(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/src/main/java/io/tlf/monkeynetty/test/messages/TestTCPMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty.test.messages; 26 | 27 | import io.tlf.monkeynetty.NetworkProtocol; 28 | import io.tlf.monkeynetty.msg.NetworkMessage; 29 | 30 | /** 31 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 32 | */ 33 | public class TestTCPMessage implements NetworkMessage { 34 | 35 | private int someValue; 36 | 37 | @Override 38 | public String getName() { 39 | return "Test TCP Message"; 40 | } 41 | 42 | @Override 43 | public NetworkProtocol getProtocol() { 44 | return NetworkProtocol.TCP; 45 | } 46 | 47 | public int getSomeValue() { 48 | return someValue; 49 | } 50 | 51 | public void setSomeValue(int someValue) { 52 | this.someValue = someValue; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/src/main/java/io/tlf/monkeynetty/test/messages/TestUDPBigMessageA.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Trevor Flynn 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package io.tlf.monkeynetty.test.messages; 25 | 26 | import io.tlf.monkeynetty.NetworkProtocol; 27 | import io.tlf.monkeynetty.msg.NetworkMessage; 28 | 29 | /** 30 | * @author Radosław K 31 | * 32 | * Message used for testing nested Serializable objects within message 33 | */ 34 | public class TestUDPBigMessageA implements NetworkMessage { 35 | 36 | private final TestSerializableDataA objectA; 37 | 38 | public TestUDPBigMessageA() { 39 | objectA = new TestSerializableDataA(); 40 | } 41 | 42 | @Override 43 | public String getName() { 44 | return "Test UDP Big Message A"; 45 | } 46 | 47 | @Override 48 | public NetworkProtocol getProtocol() { 49 | return NetworkProtocol.UDP; 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return objectA.toString(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/src/main/java/io/tlf/monkeynetty/test/messages/TestUDPBigMessageB.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Trevor Flynn 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package io.tlf.monkeynetty.test.messages; 25 | 26 | import io.tlf.monkeynetty.NetworkProtocol; 27 | import io.tlf.monkeynetty.msg.NetworkMessage; 28 | 29 | /** 30 | * @author Radosław K 31 | * 32 | * Message used for testing nested Serializable objects within message 33 | */ 34 | public class TestUDPBigMessageB implements NetworkMessage { 35 | 36 | private final TestSerializableDataB objectB; 37 | 38 | public TestUDPBigMessageB() { 39 | objectB = new TestSerializableDataB(); 40 | } 41 | 42 | @Override 43 | public String getName() { 44 | return "Test UDP Big Message B"; 45 | } 46 | 47 | @Override 48 | public NetworkProtocol getProtocol() { 49 | return NetworkProtocol.UDP; 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return objectB.toString(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/src/main/java/io/tlf/monkeynetty/test/messages/TestUDPMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty.test.messages; 26 | 27 | import io.tlf.monkeynetty.NetworkProtocol; 28 | import io.tlf.monkeynetty.msg.NetworkMessage; 29 | 30 | /** 31 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 32 | */ 33 | public class TestUDPMessage implements NetworkMessage { 34 | 35 | private String someValue; 36 | 37 | @Override 38 | public String getName() { 39 | return "Test UDP Message"; 40 | } 41 | 42 | @Override 43 | public NetworkProtocol getProtocol() { 44 | return NetworkProtocol.UDP; 45 | } 46 | 47 | public String getSomeValue() { 48 | return someValue; 49 | } 50 | 51 | public void setSomeValue(String someValue) { 52 | this.someValue = someValue; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tlf30/monkey-netty/318598262566551e21960855f07fe09cce4dad89/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | 88 | @rem Execute Gradle 89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 90 | 91 | :end 92 | @rem End local scope for the variables with windows NT shell 93 | if "%ERRORLEVEL%"=="0" goto mainEnd 94 | 95 | :fail 96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 97 | rem the _cmd.exe /c_ return code! 98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 99 | exit /b 1 100 | 101 | :mainEnd 102 | if "%OS%"=="Windows_NT" endlocal 103 | 104 | :omega 105 | -------------------------------------------------------------------------------- /monkey-netty/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | id 'maven-publish' 4 | } 5 | 6 | ext { 7 | PUBLISH_GROUP_ID = group 8 | PUBLISH_VERSION = version 9 | PUBLISH_ARTIFACT_ID = name 10 | } 11 | 12 | task javadocJar(type: Jar) { 13 | classifier = 'javadoc' 14 | from javadoc 15 | } 16 | 17 | task sourcesJar(type: Jar) { 18 | classifier = 'sources' 19 | from sourceSets.main.allSource 20 | } 21 | 22 | artifacts { 23 | archives javadocJar, sourcesJar 24 | } 25 | 26 | dependencies { 27 | //Jme3 28 | api jmeGroup + ':jme3-core:' + jmeVersion 29 | //Netty.IO 30 | api group: 'io.netty', name: 'netty-all', version: nettyioVersion 31 | api group: 'io.netty', name: 'netty-transport', version: nettyioVersion 32 | // io.tlf.monkeynetty.test 33 | testImplementation jmeGroup + ':jme3-desktop:' + jmeVersion 34 | testImplementation jmeGroup + ':jme3-lwjgl3:' + jmeVersion 35 | } 36 | 37 | 38 | afterEvaluate { 39 | publishing { 40 | publications { 41 | release(MavenPublication) { 42 | groupId PUBLISH_GROUP_ID 43 | artifactId PUBLISH_ARTIFACT_ID 44 | version PUBLISH_VERSION 45 | 46 | from components.java 47 | 48 | artifact sourcesJar 49 | artifact javadocJar 50 | 51 | pom { 52 | name = PUBLISH_ARTIFACT_ID 53 | description = 'A implementation of a server-client communication system for jMonkeyEngine using Netty.IO that utilizes both TCP and UDP communication.' 54 | url = 'https://github.com/tlf30/monkey-netty' 55 | licenses { 56 | license { 57 | name = 'MIT License' 58 | url = 'https://opensource.org/licenses/MIT' 59 | } 60 | } 61 | developers { 62 | developer { 63 | id = 'tlf30' 64 | name = 'Trevor Flynn' 65 | email = 'trevorflynn@liquidcrystalstudios.com' 66 | } 67 | } 68 | scm { 69 | connection = 'scm:git:git://github.com/tlf30/monkey-netty.git' 70 | developerConnection = 'scm:git:ssh://tlf30/monkey-netty.git' 71 | url = 'https://github.com/tlf30/monkey-netty/' 72 | } 73 | } 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/ConnectionListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty; 26 | 27 | /** 28 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 29 | */ 30 | public interface ConnectionListener { 31 | 32 | /** 33 | * Called when a client is connected 34 | * 35 | * @param client The client connected. 36 | */ 37 | public void onConnect(NetworkClient client); 38 | 39 | /** 40 | * Called when a client disconnects 41 | * 42 | * @param client The client that disconnected. 43 | */ 44 | public void onDisconnect(NetworkClient client); 45 | } 46 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/MessageListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty; 26 | 27 | import io.tlf.monkeynetty.msg.NetworkMessage; 28 | 29 | /** 30 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 31 | */ 32 | public interface MessageListener { 33 | 34 | /** 35 | * When the server/client receives a message, this will be called. 36 | * This is to be implemented by the user code. 37 | * 38 | * @param msg The message received 39 | * @param server The server that sent the message, will be null on client side application 40 | * @param client The client that received the message 41 | */ 42 | public void onMessage(NetworkMessage msg, NetworkServer server, NetworkClient client); 43 | 44 | /** 45 | * The listener onMessage will only get called if the message received 46 | * is within this returned list. 47 | * 48 | * @return A list of supported messages by this listener 49 | */ 50 | public Class[] getSupportedMessages(); 51 | } 52 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/NetworkClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package io.tlf.monkeynetty; 25 | 26 | import io.tlf.monkeynetty.msg.NetworkMessage; 27 | 28 | /** 29 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 30 | *

31 | * Used as a basis for building a custom network client. 32 | */ 33 | public interface NetworkClient { 34 | 35 | /** 36 | * @return if the client is connected to the server 37 | */ 38 | public boolean isConnected(); 39 | 40 | /** 41 | * Send a message from the client to the server 42 | * 43 | * @param message The message to send 44 | */ 45 | public void send(NetworkMessage message); 46 | 47 | /** 48 | * Internal Use Only 49 | * Called by the server when the server receives a message for the server side connection client. 50 | * 51 | * @param message The message received 52 | */ 53 | public void receive(NetworkMessage message); 54 | 55 | /** 56 | * Disconnects the client from the server 57 | */ 58 | public void disconnect(); 59 | 60 | /** 61 | * @return If the client is connected to the server using SSL 62 | */ 63 | public boolean isSsl(); 64 | 65 | /** 66 | * @return The address of the server the client is connecting to 67 | */ 68 | public String getAddress(); 69 | 70 | /** 71 | * @return The port of the server the client is connecting to 72 | */ 73 | public int getPort(); 74 | 75 | /** 76 | * The service string should be unique to each server. 77 | * This is entirely for user use, and is not validated between server and client. 78 | * 79 | * @return The service the client is connecting to. 80 | */ 81 | public String getService(); 82 | 83 | /** 84 | * @return Which network protocols the client supports 85 | */ 86 | public NetworkProtocol[] getProtocol(); 87 | 88 | /** 89 | * Register a message listener with the client. 90 | * 91 | * @param handler The message listener to register 92 | */ 93 | public void registerListener(MessageListener handler); 94 | 95 | /** 96 | * Unregister a message listener with the client. 97 | * 98 | * @param handler The message listener to unregister 99 | */ 100 | public void unregisterListener(MessageListener handler); 101 | 102 | /** 103 | * Register a connection listener with the client. 104 | * 105 | * @param listener The connection listener to register 106 | */ 107 | public void registerListener(ConnectionListener listener); 108 | 109 | /** 110 | * Unregister a connection listener with the client. 111 | * 112 | * @param listener The connection listener to unregister 113 | */ 114 | public void unregisterListener(ConnectionListener listener); 115 | 116 | /** 117 | * Set obj value attribute for key param 118 | * 119 | * @param key key for attribute 120 | * @param obj value object for attribute 121 | */ 122 | public void setUserData(String key, Object obj); 123 | 124 | /** 125 | * Return attribute stored under key param 126 | * 127 | * @param type 128 | * @param key key for attribute 129 | * @return object casted to T type 130 | */ 131 | public T getUserData(String key); 132 | 133 | } 134 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/NetworkMessageDecoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty; 26 | 27 | import io.netty.buffer.ByteBuf; 28 | import io.netty.buffer.ByteBufInputStream; 29 | import io.netty.channel.ChannelHandlerContext; 30 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 31 | import io.netty.handler.codec.serialization.ClassResolver; 32 | 33 | import java.io.ObjectInputStream; 34 | import java.io.StreamCorruptedException; 35 | 36 | /** 37 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 38 | *

39 | * Decodes a NetworkMessage from a binary stream for recieving to remote side. 40 | * Utilizes NetworkObjectInputtream 41 | *

42 | * Based from: io.netty.handler.codec.serialization.ObjectDecoder 43 | */ 44 | public class NetworkMessageDecoder extends LengthFieldBasedFrameDecoder { 45 | 46 | private final ClassResolver classResolver; 47 | 48 | private NetworkRegistrar registrar = new NetworkRegistrar(); 49 | 50 | /** 51 | * Creates a new decoder whose maximum object size is {@code 1048576} 52 | * bytes. If the size of the received object is greater than 53 | * {@code 1048576} bytes, a {@link StreamCorruptedException} will be 54 | * raised. 55 | * 56 | * @param classResolver the {@link ClassResolver} to use for this decoder 57 | */ 58 | public NetworkMessageDecoder(ClassResolver classResolver) { 59 | this(1048576, classResolver); 60 | } 61 | 62 | /** 63 | * Creates a new decoder with the specified maximum object size. 64 | * 65 | * @param maxObjectSize the maximum byte length of the serialized object. 66 | * if the length of the received object is greater 67 | * than this value, {@link StreamCorruptedException} 68 | * will be raised. 69 | * @param classResolver the {@link ClassResolver} which will load the class 70 | * of the serialized object 71 | */ 72 | public NetworkMessageDecoder(int maxObjectSize, ClassResolver classResolver) { 73 | super(maxObjectSize, 0, 4, 0, 4); 74 | this.classResolver = classResolver; 75 | } 76 | 77 | @Override 78 | public Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { 79 | ByteBuf frame = (ByteBuf) super.decode(ctx, in); 80 | if (frame == null) { 81 | return null; 82 | } 83 | 84 | ObjectInputStream ois = new NetworkObjectInputStream(new ByteBufInputStream(frame, true), classResolver, registrar); 85 | try { 86 | return ois.readObject(); 87 | } finally { 88 | ois.close(); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/NetworkMessageEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty; 26 | 27 | import io.netty.buffer.ByteBuf; 28 | import io.netty.buffer.ByteBufOutputStream; 29 | import io.netty.channel.ChannelHandlerContext; 30 | import io.netty.handler.codec.MessageToByteEncoder; 31 | 32 | import java.io.NotSerializableException; 33 | import java.io.ObjectOutputStream; 34 | import java.io.Serializable; 35 | 36 | /** 37 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 38 | *

39 | * Encodes a NetworkMessage into a binary stream for sending to remote side. 40 | * Utilizes NetworkObjectOutputStream 41 | *

42 | * Based from: io.netty.handler.codec.serialization.ObjectEncoder 43 | */ 44 | public class NetworkMessageEncoder extends MessageToByteEncoder { 45 | private static final byte[] LENGTH_PLACEHOLDER = new byte[4]; 46 | 47 | private NetworkRegistrar registrar = new NetworkRegistrar(); 48 | 49 | @Override 50 | protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) throws Exception { 51 | int startIdx = out.writerIndex(); 52 | 53 | ByteBufOutputStream bout = new ByteBufOutputStream(out); 54 | ObjectOutputStream oout = null; 55 | try { 56 | bout.write(LENGTH_PLACEHOLDER); 57 | oout = new NetworkObjectOutputStream(bout, registrar); 58 | oout.writeObject(msg); 59 | oout.flush(); 60 | } catch (NotSerializableException nsex) { 61 | throw new NetworkMessageException("Non-Serializable object " + nsex.getMessage() + " found in message " + msg.getClass().getName(), nsex); 62 | } finally { 63 | if (oout != null) { 64 | oout.close(); 65 | } else { 66 | bout.close(); 67 | } 68 | } 69 | 70 | int endIdx = out.writerIndex(); 71 | 72 | out.setInt(startIdx, endIdx - startIdx - 4); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/NetworkMessageException.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty; 26 | 27 | /** 28 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.comm 29 | */ 30 | public class NetworkMessageException extends RuntimeException { 31 | public NetworkMessageException() { 32 | super(); 33 | } 34 | 35 | public NetworkMessageException(String msg) { 36 | super(msg); 37 | } 38 | 39 | public NetworkMessageException(String msg, Throwable cause) { 40 | super(msg, cause); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/NetworkObjectInputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty; 26 | 27 | import io.netty.handler.codec.serialization.ClassResolver; 28 | 29 | import java.io.*; 30 | 31 | /** 32 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 33 | *

34 | * Converts a binary stream into an Object. 35 | * If a class name and UID are sent with the object, this will remember the class to UID relationship. 36 | * All future instances of the UID will be related to the correct class for deserialization. 37 | * Based from: io.netty.handler.codec.serialization.CompactObjectInputStream 38 | */ 39 | public class NetworkObjectInputStream extends ObjectInputStream { 40 | 41 | private final ClassResolver classResolver; 42 | private final NetworkRegistrar registrar; 43 | 44 | NetworkObjectInputStream(InputStream in, ClassResolver classResolver, NetworkRegistrar registrar) throws IOException { 45 | super(in); 46 | this.classResolver = classResolver; 47 | this.registrar = registrar; 48 | } 49 | 50 | @Override 51 | protected void readStreamHeader() throws IOException { 52 | int version = readByte() & 0xFF; 53 | if (version != STREAM_VERSION) { 54 | throw new StreamCorruptedException("Unsupported version: " + version); 55 | } 56 | } 57 | 58 | @Override 59 | protected ObjectStreamClass readClassDescriptor() 60 | throws IOException, ClassNotFoundException { 61 | int type = read(); 62 | if (type < 0) { 63 | throw new EOFException(); 64 | } 65 | switch (type) { 66 | case NetworkObjectOutputStream.TYPE_FAT_DESCRIPTOR: 67 | return super.readClassDescriptor(); 68 | case NetworkObjectOutputStream.TYPE_THIN_DESCRIPTOR: 69 | int id = readInt(); 70 | String className = registrar.getUidRegistry().get(id); 71 | if (className == null) { 72 | throw new NetworkMessageException("Unregistered type received for decoding: " + id); 73 | } 74 | Class clazz = classResolver.resolve(className); 75 | return ObjectStreamClass.lookupAny(clazz); 76 | case NetworkObjectOutputStream.TYPE_NEW_DESCRIPTOR: 77 | String newName = readUTF(); 78 | int newId = readInt(); 79 | registrar.register(newName, newId); 80 | Class newClazz = classResolver.resolve(newName); 81 | return ObjectStreamClass.lookupAny(newClazz); 82 | default: 83 | throw new StreamCorruptedException("Unexpected class descriptor type: " + type); 84 | } 85 | } 86 | 87 | @Override 88 | protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { 89 | Class clazz; 90 | try { 91 | clazz = classResolver.resolve(desc.getName()); 92 | } catch (ClassNotFoundException ignored) { 93 | clazz = super.resolveClass(desc); 94 | } 95 | 96 | return clazz; 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/NetworkObjectOutputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty; 26 | 27 | import java.io.*; 28 | 29 | /** 30 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 31 | *

32 | * Converts a Object into a binary stream. 33 | * The first instance of a class sent will send a UID and class name to remote side. 34 | * All future instances of the class sent will only send UID. 35 | * Based from: io.netty.handler.codec.serialization.CompactObjectOutputStream 36 | */ 37 | public class NetworkObjectOutputStream extends ObjectOutputStream { 38 | 39 | static final int TYPE_FAT_DESCRIPTOR = 0; 40 | static final int TYPE_THIN_DESCRIPTOR = 1; 41 | static final int TYPE_NEW_DESCRIPTOR = 2; 42 | 43 | private NetworkRegistrar registrar; 44 | 45 | NetworkObjectOutputStream(OutputStream out, NetworkRegistrar registrar) throws IOException { 46 | super(out); 47 | this.registrar = registrar; 48 | } 49 | 50 | @Override 51 | protected void writeStreamHeader() throws IOException { 52 | writeByte(STREAM_VERSION); 53 | } 54 | 55 | @Override 56 | protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException { 57 | Class clazz = desc.forClass(); 58 | if (!Serializable.class.isAssignableFrom(clazz)) { 59 | throw new NetworkMessageException("Non-Serializable object found: " + clazz.getName()); 60 | } 61 | if (clazz.isPrimitive() || clazz.isArray() || clazz.isInterface() || 62 | desc.getSerialVersionUID() == 0) { 63 | write(TYPE_FAT_DESCRIPTOR); 64 | super.writeClassDescriptor(desc); 65 | } else { 66 | Integer id = registrar.getClassRegistry().get(clazz.getName()); 67 | if (id != null) { 68 | write(TYPE_THIN_DESCRIPTOR); 69 | writeInt(id); 70 | } else { 71 | registrar.register(clazz.getName()); 72 | write(TYPE_NEW_DESCRIPTOR); 73 | writeUTF(clazz.getName()); 74 | writeInt(registrar.getClassRegistry().get(clazz.getName())); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/NetworkProtocol.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty; 26 | 27 | /** 28 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 29 | */ 30 | public enum NetworkProtocol { 31 | UDP, TCP 32 | } 33 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/NetworkRegistrar.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty; 26 | 27 | import java.util.HashMap; 28 | 29 | /** 30 | * NetworkRegistrar keeps a record of class names to UIDs. 31 | * This is used by monkey-netty for transporting objects by using UIDs for each object. 32 | */ 33 | public class NetworkRegistrar { 34 | 35 | private HashMap classUID = new HashMap<>(); 36 | private HashMap uidClass = new HashMap<>(); 37 | private volatile int uid = 0; 38 | 39 | /** 40 | * Register a class with the registrar. 41 | * If attempting to register the same class multiple times, the additional attempts 42 | * to register will be silently ignored. 43 | * 44 | * @param className The fully qualified class name to register 45 | */ 46 | public void register(String className) { 47 | if (!classUID.containsKey(className)) { 48 | int newId = uid++; 49 | register(className, newId); 50 | } 51 | } 52 | 53 | /** 54 | * Internal Use Only 55 | * Force a classname and ID pair 56 | * 57 | * @param className The fully qualified class name to register 58 | * @param id The UID of the class 59 | */ 60 | public void register(String className, int id) { 61 | classUID.put(className, id); 62 | uidClass.put(id, className); 63 | } 64 | 65 | /** 66 | * @return The registry relating UID to class name 67 | */ 68 | public HashMap getUidRegistry() { 69 | return uidClass; 70 | } 71 | 72 | /** 73 | * @return The registry relating class name to UID 74 | */ 75 | public HashMap getClassRegistry() { 76 | return classUID; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/NetworkServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty; 26 | 27 | import io.tlf.monkeynetty.msg.NetworkMessage; 28 | 29 | /** 30 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 31 | *

32 | * Used as a basis for building a custom network server. 33 | */ 34 | public interface NetworkServer { 35 | 36 | /** 37 | * @return The number of active connections to the server 38 | */ 39 | public int getConnections(); 40 | 41 | /** 42 | * @return The port number the server is listening on 43 | */ 44 | public int getPort(); 45 | 46 | /** 47 | * @return If the server is using SSL for TCP transport 48 | */ 49 | public boolean isSsl(); 50 | 51 | /** 52 | * The service string should be unique to each server. 53 | * This is entirely for user use, and is not validated between server and client. 54 | * 55 | * @return The service the server is running. 56 | */ 57 | public String getService(); 58 | 59 | /** 60 | * @return Which network protocols the server supports 61 | */ 62 | public NetworkProtocol[] getProtocol(); 63 | 64 | /** 65 | * @return true if the server is currently blocking new connections 66 | */ 67 | public boolean isBlocking(); 68 | 69 | /** 70 | * Set if the server should block incoming connections. If true 71 | * the server will close all incoming connections immediately without 72 | * performing a handshake after establishing the connection. 73 | * 74 | * @param blocking If the server should block incoming connections. 75 | */ 76 | public void setBlocking(boolean blocking); 77 | 78 | /** 79 | * @return The maximum number of connections the server will allow. 80 | */ 81 | public int getMaxConnections(); 82 | 83 | /** 84 | * This sets the maximum number of connections the server will be allowed to have at any given time. 85 | * If a connection is attempted to the server and the server currently has the maximum number of 86 | * connections, it will immediately close the connection without performing a handshake after establishing 87 | * the connection. 88 | * 89 | * @param maxConnections The maximum number of connections the server will allow. 90 | */ 91 | public void setMaxConnections(int maxConnections); 92 | 93 | /** 94 | * Send a message to all clients connected to the server. 95 | * 96 | * @param message The message to send 97 | */ 98 | public void send(NetworkMessage message); 99 | 100 | /** 101 | * Send a message to the provided client 102 | * 103 | * @param message The message to send 104 | * @param client The client to send the message to 105 | */ 106 | public void send(NetworkMessage message, NetworkClient client); 107 | 108 | /** 109 | * Register a message listener with the server. 110 | * 111 | * @param handler The message listener to register 112 | */ 113 | public void registerListener(MessageListener handler); 114 | 115 | /** 116 | * Unregister a message listener with the server. 117 | * 118 | * @param handler The message listener to unregister 119 | */ 120 | public void unregisterListener(MessageListener handler); 121 | 122 | /** 123 | * Register a connection listener with the server. 124 | * 125 | * @param listener The connection listener to register 126 | */ 127 | public void registerListener(ConnectionListener listener); 128 | 129 | /** 130 | * Unregister a connection listener with the server. 131 | * 132 | * @param listener The connection listener to unregister 133 | */ 134 | public void unregisterListener(ConnectionListener listener); 135 | } 136 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/client/DatagramPacketObjectDecoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Trevor Flynn 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty.client; 26 | 27 | import io.netty.buffer.ByteBuf; 28 | import io.netty.channel.AddressedEnvelope; 29 | import io.netty.channel.ChannelHandlerContext; 30 | import io.netty.channel.DefaultAddressedEnvelope; 31 | import io.netty.handler.codec.MessageToMessageDecoder; 32 | import io.netty.handler.codec.serialization.ClassResolver; 33 | import io.tlf.monkeynetty.NetworkMessageDecoder; 34 | 35 | import java.net.InetSocketAddress; 36 | import java.util.List; 37 | 38 | public class DatagramPacketObjectDecoder extends MessageToMessageDecoder> { 39 | 40 | private final NetworkMessageDecoder delegateDecoder; 41 | 42 | public DatagramPacketObjectDecoder(ClassResolver resolver) { 43 | this(resolver, Integer.MAX_VALUE); 44 | } 45 | 46 | public DatagramPacketObjectDecoder(ClassResolver resolver, int maxObjectSize) { 47 | delegateDecoder = new NetworkMessageDecoder(maxObjectSize, resolver); 48 | } 49 | 50 | @Override 51 | protected void decode(ChannelHandlerContext ctx, AddressedEnvelope msg, List out) throws Exception { 52 | if (msg.content() instanceof ByteBuf) { 53 | ByteBuf payload = (ByteBuf) msg.content(); 54 | Object result = delegateDecoder.decode(ctx, payload); 55 | AddressedEnvelope addressedEnvelop = new DefaultAddressedEnvelope<>(result, msg.recipient(), msg.sender()); 56 | out.add(addressedEnvelop); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/client/MessageCacheMode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Trevor Flynn 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty.client; 26 | 27 | /** 28 | * Message Cache Mode is used on the client to determine how the client will cache messages 29 | * when not connected to the server. 30 | */ 31 | public enum MessageCacheMode { 32 | /** 33 | * Do not cache messages 34 | */ 35 | DISABLED, 36 | 37 | /** 38 | * Cache both TCP and UDP messages 39 | */ 40 | ENABLED, 41 | 42 | /** 43 | * Cache only TCP messages 44 | */ 45 | TCP_ENABLED, 46 | 47 | /** 48 | * Cache only UDP messages 49 | */ 50 | UDP_ENABLED 51 | } 52 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/client/NettyClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty.client; 26 | 27 | import com.jme3.app.Application; 28 | import com.jme3.app.state.BaseAppState; 29 | import io.netty.bootstrap.Bootstrap; 30 | import io.netty.channel.*; 31 | import io.netty.channel.nio.NioEventLoopGroup; 32 | import io.netty.channel.socket.DatagramChannel; 33 | import io.netty.channel.socket.DatagramChannelConfig; 34 | import io.netty.channel.socket.SocketChannel; 35 | import io.netty.channel.socket.SocketChannelConfig; 36 | import io.netty.channel.socket.nio.NioDatagramChannel; 37 | import io.netty.channel.socket.nio.NioSocketChannel; 38 | import io.netty.handler.codec.serialization.ClassResolvers; 39 | import io.netty.handler.logging.LogLevel; 40 | import io.netty.handler.logging.LoggingHandler; 41 | import io.netty.handler.ssl.SslContext; 42 | import io.netty.handler.ssl.SslContextBuilder; 43 | import io.netty.handler.ssl.util.InsecureTrustManagerFactory; 44 | import io.netty.handler.timeout.IdleState; 45 | import io.netty.handler.timeout.IdleStateEvent; 46 | import io.netty.handler.timeout.IdleStateHandler; 47 | import io.tlf.monkeynetty.*; 48 | import io.tlf.monkeynetty.msg.ConnectionEstablishedMessage; 49 | import io.tlf.monkeynetty.msg.NetworkMessage; 50 | import io.tlf.monkeynetty.msg.PingMessage; 51 | import io.tlf.monkeynetty.msg.UdpConHashMessage; 52 | 53 | import java.net.InetSocketAddress; 54 | import java.util.*; 55 | import java.util.concurrent.ConcurrentHashMap; 56 | import java.util.concurrent.ConcurrentLinkedQueue; 57 | import java.util.logging.Level; 58 | import java.util.logging.Logger; 59 | 60 | import static io.netty.channel.ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE; 61 | 62 | /** 63 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 64 | */ 65 | public class NettyClient extends BaseAppState implements NetworkClient { 66 | 67 | private final static Logger LOGGER = Logger.getLogger(NettyClient.class.getName()); 68 | 69 | protected String service; 70 | protected int port; 71 | protected String server; 72 | protected boolean ssl; 73 | protected boolean sslSelfSigned; 74 | protected volatile boolean reconnect = false; 75 | protected volatile boolean disconnecting = false; 76 | private volatile boolean udpHandshakeComplete = false; 77 | private volatile boolean pendingEstablish = true; 78 | 79 | /* 80 | * Connection timeout in milliseconds used when client is unable connect to server 81 | * Note: Currently it do not apply when server is "off" 82 | */ 83 | protected int connectionTimeout = 10000; 84 | private MessageCacheMode cacheMode = MessageCacheMode.TCP_ENABLED; 85 | private LogLevel logLevel; 86 | 87 | //Netty 88 | private EventLoopGroup tcpGroup = new NioEventLoopGroup(); 89 | private Bootstrap tcpClientBootstrap = new Bootstrap(); 90 | private ChannelFuture tcpChannelFuture; 91 | private SocketChannel tcpChannel; 92 | private EventLoopGroup udpGroup = new NioEventLoopGroup(); 93 | private Bootstrap udpClientBootstrap = new Bootstrap(); 94 | private ChannelFuture udpChannelFuture; 95 | private DatagramChannel udpChannel; 96 | private SslContext sslContext; 97 | 98 | private final Set handlers = ConcurrentHashMap.newKeySet(); 99 | private final Set listeners = ConcurrentHashMap.newKeySet(); 100 | private final ConcurrentLinkedQueue messageCache = new ConcurrentLinkedQueue<>(); 101 | private final Map atts = new ConcurrentHashMap<>(); 102 | 103 | /** 104 | * Creates a new client configured to connect to the server. 105 | * This connection will have SSL disabled. 106 | * 107 | * @param service The name of the service running on this client 108 | * @param port The port the server is listening on 109 | * @param server The host/ip of the server 110 | */ 111 | public NettyClient(String service, int port, String server) { 112 | this(service, false, false, port, server); 113 | } 114 | 115 | /** 116 | * Creates a new client configured to connect to the server. 117 | * 118 | * @param service The name of the service running on this client 119 | * @param ssl If the client should attempt to connect with ssl 120 | * @param port The port the server is listening on 121 | * @param server The host/ip of the server 122 | */ 123 | public NettyClient(String service, boolean ssl, int port, String server) { 124 | this(service, ssl, true, port, server); 125 | } 126 | 127 | /** 128 | * Creates a new client configured to connect to the server. 129 | * 130 | * @param service The name of the service running on this client 131 | * @param ssl If the client should attempt to connect with ssl 132 | * @param sslSelfSigned If the client will allow the server to use a self signed ssl certificate 133 | * @param port The port the server is listening on 134 | * @param server The host/ip of the server 135 | */ 136 | public NettyClient(String service, boolean ssl, boolean sslSelfSigned, int port, String server) { 137 | this.service = service; 138 | this.port = port; 139 | this.server = server; 140 | this.ssl = ssl; 141 | this.sslSelfSigned = sslSelfSigned; 142 | } 143 | 144 | @Override 145 | public void initialize(Application app) { 146 | 147 | } 148 | 149 | @Override 150 | public void cleanup(Application app) { 151 | 152 | } 153 | 154 | @Override 155 | public void onEnable() { 156 | LOGGER.fine("Loading Netty.IO network client"); 157 | setupTcp(); 158 | } 159 | 160 | @Override 161 | public void onDisable() { 162 | disconnect(); 163 | } 164 | 165 | /** 166 | * Set the timeout duration in milliseconds for creating a new connection from the client to the server. 167 | * This does not effect the read/write timeouts for messages after the connection has been established. 168 | * 169 | * @param connectionTimeout The timeout in milliseconds for creating a new connection. 170 | */ 171 | public void setConnectionTimeout(int connectionTimeout) { 172 | this.connectionTimeout = connectionTimeout; 173 | } 174 | 175 | /** 176 | * @return The timeout in milliseconds for creating a new connection 177 | */ 178 | public int getConnectionTimeout() { 179 | return connectionTimeout; 180 | } 181 | 182 | /** 183 | * Sets the Netty.IO internal log level. 184 | * This will not change the java.util.logger Logger for Monkey-Netty. 185 | * 186 | * @param logLevel The internal Netty.IO log level 187 | */ 188 | public void setLogLevel(LogLevel logLevel) { 189 | this.logLevel = logLevel; 190 | } 191 | 192 | /** 193 | * @return The internal Netty.IO log level 194 | */ 195 | public LogLevel getLogLevel() { 196 | return logLevel; 197 | } 198 | 199 | /** 200 | * Sets the message cache mode. By default the mode is MessageCacheMode.ENABLE_TCP 201 | * See MessageCacheMode for more information about the supported mode options. 202 | * 203 | * @param mode The desired message cache mode. 204 | */ 205 | public void setMessageCacheMode(MessageCacheMode mode) { 206 | this.cacheMode = mode; 207 | } 208 | 209 | /** 210 | * @return The current message cache mode. 211 | */ 212 | public MessageCacheMode getMessageCacheMode() { 213 | return cacheMode; 214 | } 215 | 216 | /** 217 | * Internal use only 218 | * Setup the TCP netty.io pipeline. 219 | * This will create a dedicated TCP channel to the server. 220 | * The pipeline is setup to handle NetworkMessage message types. 221 | * The pipeline will also send/receive a ping to/from the server to ensure the connection is still active. 222 | * If the connection becomes inactive, the client will attempt a new connection. 223 | * The pipeline will be configured with SSL if SSL parameters have been passed to the client. 224 | */ 225 | private void setupTcp() { 226 | LOGGER.fine("Setting up tcp"); 227 | if (ssl) { 228 | try { 229 | if (sslSelfSigned) { 230 | sslContext = SslContextBuilder.forClient() 231 | .trustManager(InsecureTrustManagerFactory.INSTANCE).build(); 232 | } else { 233 | sslContext = SslContextBuilder.forClient().build(); 234 | } 235 | } catch (Exception ex) { 236 | LOGGER.log(Level.WARNING, "Failed to load ssl, failing back to no ssl", ex); 237 | ssl = false; 238 | } 239 | } 240 | //Setup TCP 241 | tcpGroup = new NioEventLoopGroup(); 242 | tcpClientBootstrap = new Bootstrap(); 243 | tcpClientBootstrap.group(tcpGroup); 244 | tcpClientBootstrap.channel(NioSocketChannel.class); 245 | tcpClientBootstrap.remoteAddress(new InetSocketAddress(server, port)); 246 | tcpClientBootstrap.handler(new ChannelInitializer() { 247 | protected void initChannel(SocketChannel socketChannel) { 248 | tcpChannel = socketChannel; 249 | SocketChannelConfig cfg = tcpChannel.config(); 250 | cfg.setConnectTimeoutMillis(connectionTimeout); 251 | 252 | ChannelPipeline p = socketChannel.pipeline(); 253 | //Setup ssl 254 | if (ssl) { 255 | p.addLast(sslContext.newHandler(socketChannel.alloc(), server, port)); 256 | } 257 | //Set log level 258 | if (logLevel != null) { 259 | p.addLast(new LoggingHandler(logLevel)); 260 | } 261 | //Setup pipeline 262 | p.addLast( 263 | new NetworkMessageEncoder(), 264 | new NetworkMessageDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)), 265 | new ChannelInboundHandlerAdapter() { 266 | @Override 267 | public void channelRead(ChannelHandlerContext ctx, Object msg) { 268 | if (!udpHandshakeComplete && msg instanceof UdpConHashMessage) { 269 | String hash = ((UdpConHashMessage) msg).getUdpHash(); 270 | setupUdp(hash); 271 | } else if (pendingEstablish && msg instanceof ConnectionEstablishedMessage) { 272 | completeConnection(); 273 | } else if (msg instanceof NetworkMessage) { 274 | receive((NetworkMessage) msg); 275 | } else { 276 | LOGGER.log(Level.SEVERE, "Received message that was not a NetworkMessage object"); 277 | } 278 | ctx.fireChannelRead(msg); 279 | } 280 | 281 | @Override 282 | public void channelReadComplete(ChannelHandlerContext ctx) { 283 | ctx.flush(); 284 | } 285 | 286 | @Override 287 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 288 | catchNetworkError(cause); 289 | ctx.close(); 290 | } 291 | }, 292 | new IdleStateHandler(30, 10, 0), 293 | new ChannelDuplexHandler() { 294 | @Override 295 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { 296 | if (evt instanceof IdleStateEvent) { 297 | IdleStateEvent e = (IdleStateEvent) evt; 298 | if (e.state() == IdleState.READER_IDLE) { 299 | handleInactiveConnection(); 300 | } else if (e.state() == IdleState.WRITER_IDLE) { 301 | send(new PingMessage()); 302 | } 303 | } 304 | } 305 | 306 | public void channelInactive(ChannelHandlerContext ctx) { 307 | handleInactiveConnection(); 308 | } 309 | } 310 | ); 311 | } 312 | }); 313 | try { 314 | LOGGER.fine("Making tcp connection"); 315 | tcpChannelFuture = tcpClientBootstrap.connect().sync(); 316 | LOGGER.fine("Tcp future synced"); 317 | } catch (Exception e) { 318 | LOGGER.log(Level.SEVERE, "Failed to setup tcp connection"); 319 | catchNetworkError(e); 320 | } 321 | } 322 | 323 | /** 324 | * Internal use only 325 | * Setup the UDP netty.io pipeline. 326 | * This will create a dedicated UDP channel to the server. 327 | * The pipeline is setup to handle NetworkMessage message types. 328 | * 329 | * @param hash The hash that will be used to establish the UDP channel with the server. 330 | */ 331 | private void setupUdp(String hash) { 332 | LOGGER.fine("Setting up udp"); 333 | udpGroup = new NioEventLoopGroup(); 334 | udpClientBootstrap = new Bootstrap(); 335 | udpClientBootstrap.group(udpGroup); 336 | udpClientBootstrap.channel(NioDatagramChannel.class); 337 | udpClientBootstrap.option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(65535)); 338 | udpClientBootstrap.remoteAddress(new InetSocketAddress(server, port)); 339 | udpClientBootstrap.handler(new ChannelInitializer() { 340 | protected void initChannel(DatagramChannel socketChannel) { 341 | udpChannel = socketChannel; 342 | DatagramChannelConfig cfg = udpChannel.config(); 343 | cfg.setConnectTimeoutMillis(connectionTimeout); 344 | //Setup pipeline 345 | ChannelPipeline p = socketChannel.pipeline(); 346 | //Setup pipeline 347 | if (logLevel != null) { 348 | p.addLast(new LoggingHandler(logLevel)); 349 | } 350 | p.addLast( 351 | new NetworkMessageEncoder(), 352 | new DatagramPacketObjectDecoder(ClassResolvers.cacheDisabled(null), 65507), 353 | new ChannelInboundHandlerAdapter() { 354 | @Override 355 | public void channelRead(ChannelHandlerContext ctx, Object netObj) { 356 | if (netObj instanceof AddressedEnvelope) { 357 | //We don't care about the envelope type, only the object within if it is a NetworkMessage 358 | AddressedEnvelope envelope = (AddressedEnvelope) netObj; 359 | Object msg = envelope.content(); 360 | if (msg instanceof NetworkMessage) { 361 | receive((NetworkMessage) msg); 362 | } else { 363 | LOGGER.log(Level.SEVERE, "Received message that was not a NetworkMessage object"); 364 | } 365 | } 366 | ctx.fireChannelRead(netObj); 367 | } 368 | 369 | @Override 370 | public void channelReadComplete(ChannelHandlerContext ctx) { 371 | ctx.flush(); 372 | } 373 | 374 | @Override 375 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 376 | catchNetworkError(cause); 377 | ctx.close(); 378 | } 379 | }); 380 | } 381 | }); 382 | try { 383 | LOGGER.fine("Making udp connection"); 384 | udpChannelFuture = udpClientBootstrap.connect().sync(); 385 | LOGGER.fine("Udp future synced"); 386 | send(new UdpConHashMessage(hash, false), false); 387 | udpHandshakeComplete = true; 388 | } catch (Exception e) { 389 | LOGGER.log(Level.SEVERE, "Failed to setup udp connection"); 390 | catchNetworkError(e); 391 | } 392 | } 393 | 394 | /** 395 | * Internal use only 396 | * Completes the connection and notifies all connection listeners 397 | * that the client has connected. 398 | */ 399 | protected void completeConnection() { 400 | pendingEstablish = false; 401 | LOGGER.log(Level.FINEST, "Connection established"); 402 | //Notify that we have completed the connection process 403 | for (ConnectionListener listener : listeners) { 404 | try { 405 | listener.onConnect(NettyClient.this); 406 | } catch (Exception ex) { 407 | Logger.getLogger(NettyClient.class.getName()).log(Level.SEVERE, null, ex); 408 | } 409 | } 410 | } 411 | 412 | /** 413 | * Internal use only 414 | * If the client is disconnecting from the server, then this function will not perform 415 | * any action. Otherwise, we will attempt to reconnect. 416 | */ 417 | private void handleInactiveConnection() { 418 | if (disconnecting) { 419 | return; //We will ignore inactive connections when disconnecting as this will get triggered 420 | } 421 | LOGGER.info("Connection inactive"); 422 | LOGGER.fine("Queuing reconnect"); 423 | reconnect = true; 424 | } 425 | 426 | /** 427 | * Internal use only 428 | * Catch a network error. This will cause the error to be sent to the logger. 429 | * Catching the exception will cause the client to attempt to reconnect to the server. 430 | * 431 | * @param cause The error to catch 432 | */ 433 | private void catchNetworkError(Throwable cause) { 434 | //if (cause instanceof java.net.SocketException) { 435 | //The server disconnected unexpectedly, we will not log it. 436 | //} else { 437 | LOGGER.log(Level.SEVERE, "Network Client Error", cause); 438 | //} 439 | 440 | LOGGER.fine("Queuing reconnect"); 441 | reconnect = true; 442 | } 443 | 444 | @Override 445 | public void update(float tpf) { 446 | if (reconnect) { 447 | 448 | reconnect = false; 449 | 450 | LOGGER.info("Attempting to reconnect on loss of connection to server"); 451 | 452 | //Attempt to reconnect 453 | disconnecting = true; //Ignore channel inactive events 454 | try { 455 | tcpChannel.close(); 456 | } catch (Exception e) { 457 | //Don't care 458 | } 459 | try { 460 | udpChannel.close(); 461 | } catch (Exception e) { 462 | //Don't care 463 | } 464 | disconnecting = false; 465 | 466 | try { 467 | LOGGER.fine("Making tcp connection"); 468 | tcpChannelFuture = tcpClientBootstrap.connect().sync(); 469 | LOGGER.fine("Tcp future synced"); 470 | } catch (Exception e) { 471 | LOGGER.log(Level.SEVERE, "Failed to setup tcp connection", e); 472 | reconnect = true; 473 | pendingEstablish = true; 474 | udpHandshakeComplete = false; 475 | } 476 | 477 | if (isConnected()) { 478 | LOGGER.info("Network client reconnected to server"); 479 | } 480 | } 481 | if (messageCache.size() > 0) { 482 | LOGGER.finest("Sending cached messages"); 483 | while (messageCache.size() > 0 && isConnected()) { 484 | send(messageCache.poll()); 485 | } 486 | LOGGER.finest("Done sending cached messages"); 487 | } 488 | } 489 | 490 | @Override 491 | public boolean isConnected() { 492 | return tcpChannel != null && tcpChannel.isOpen() && udpHandshakeComplete && !pendingEstablish; 493 | } 494 | 495 | @Override 496 | public void send(NetworkMessage message) { 497 | send(message, true); 498 | } 499 | 500 | /** 501 | * Internal use only 502 | * Send a message from the client to the server. 503 | * If caching is enabled on the client, and @param enabledCache is true 504 | * then the cache will be used if the client is not currently connected to the server. 505 | * Otherwise, the client will attempt to send the message without the cache. 506 | *

507 | * The client will select the appropriate TCP or UDP channel to send the message 508 | * to the server on, depending on the protocol specified in the message. 509 | * 510 | * @param message The message to send to the client 511 | * @param enableCache If the client should attempt to use the message cache if required 512 | */ 513 | private void send(NetworkMessage message, boolean enableCache) { 514 | if (!isConnected() && enableCache) { 515 | if (cacheMode == MessageCacheMode.ENABLED) { 516 | messageCache.add(message); 517 | } else if (cacheMode == MessageCacheMode.TCP_ENABLED && message.getProtocol() == NetworkProtocol.TCP) { 518 | messageCache.add(message); 519 | } else if (cacheMode == MessageCacheMode.UDP_ENABLED && message.getProtocol() == NetworkProtocol.UDP) { 520 | messageCache.add(message); 521 | } 522 | return; 523 | } 524 | try { 525 | if (message.getProtocol() == NetworkProtocol.TCP) { 526 | ChannelFuture future = tcpChannel.writeAndFlush(message); 527 | future.addListener(FIRE_EXCEPTION_ON_FAILURE); 528 | } else { 529 | ChannelFuture future = udpChannel.writeAndFlush(message); 530 | future.addListener(FIRE_EXCEPTION_ON_FAILURE); 531 | } 532 | } catch (Exception ex) { 533 | LOGGER.log(Level.SEVERE, "Failed to send message to server", ex); 534 | } 535 | } 536 | 537 | @Override 538 | public void disconnect() { 539 | disconnecting = true; 540 | try { 541 | for (ConnectionListener listener : listeners) { 542 | listener.onDisconnect(this); 543 | } 544 | } catch (Exception ex) { 545 | LOGGER.log(Level.SEVERE, null, ex); 546 | } 547 | try { 548 | tcpGroup.shutdownGracefully(); 549 | udpGroup.shutdownGracefully(); 550 | } catch (Exception ex) { 551 | LOGGER.log(Level.SEVERE, null, ex); 552 | } 553 | disconnecting = false; 554 | } 555 | 556 | @Override 557 | public String getAddress() { 558 | return server; 559 | } 560 | 561 | @Override 562 | public int getPort() { 563 | return port; 564 | } 565 | 566 | @Override 567 | public String getService() { 568 | return service; 569 | } 570 | 571 | @Override 572 | public boolean isSsl() { 573 | return ssl; 574 | } 575 | 576 | @Override 577 | public NetworkProtocol[] getProtocol() { 578 | return new NetworkProtocol[]{NetworkProtocol.TCP, NetworkProtocol.UDP}; 579 | } 580 | 581 | @Override 582 | public void receive(NetworkMessage message) { 583 | LOGGER.finest("Got message: " + message.getName()); 584 | //Handlers 585 | try { 586 | for (MessageListener handler : handlers) { 587 | for (Class a : handler.getSupportedMessages()) { 588 | if (a.isInstance(message)) { 589 | handler.onMessage(message, null, this); 590 | } 591 | } 592 | } 593 | } catch (Exception ex) { 594 | LOGGER.log(Level.SEVERE, "An error occurred handling message " + message.getName(), ex); 595 | } 596 | } 597 | 598 | @Override 599 | public void registerListener(MessageListener handler) { 600 | handlers.add(handler); 601 | } 602 | 603 | @Override 604 | public void unregisterListener(MessageListener handler) { 605 | handlers.remove(handler); 606 | } 607 | 608 | @Override 609 | public void registerListener(ConnectionListener listener) { 610 | listeners.add(listener); 611 | } 612 | 613 | @Override 614 | public void unregisterListener(ConnectionListener listener) { 615 | listeners.remove(listener); 616 | } 617 | 618 | @Override 619 | public void setUserData(String key, Object obj) { 620 | atts.put(key, obj); 621 | } 622 | 623 | @Override 624 | public T getUserData(String key) { 625 | return (T) atts.get(key); 626 | } 627 | } 628 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/msg/ConnectionEstablishedMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Trevor Flynn 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty.msg; 26 | 27 | import io.tlf.monkeynetty.NetworkProtocol; 28 | 29 | /** 30 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 31 | * 32 | * This message is sent from the server to the client to nifty the 33 | * client that the server is ready to receive messages. 34 | */ 35 | public class ConnectionEstablishedMessage implements NetworkMessage { 36 | 37 | @Override 38 | public String getName() { 39 | return "connection-established-message"; 40 | } 41 | 42 | @Override 43 | public NetworkProtocol getProtocol() { 44 | return NetworkProtocol.TCP; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/msg/NetworkMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty.msg; 26 | 27 | import io.tlf.monkeynetty.NetworkProtocol; 28 | 29 | import java.io.Serializable; 30 | 31 | /** 32 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 33 | *

34 | * NetworkMessage interface provides a base for all monkey-netty messages. 35 | * When implementing this interface you user defined messages, monkey-netty 36 | * will optimize message transport, and enable the user to use the monkey-netty 37 | * interface. 38 | */ 39 | public interface NetworkMessage extends Serializable { 40 | 41 | /** 42 | * @return A unique name of the message, this is not used during message transport. 43 | */ 44 | public String getName(); 45 | 46 | /** 47 | * @return The communication protocol for which monkey-netty should use when transporting this message. 48 | */ 49 | public NetworkProtocol getProtocol(); 50 | } 51 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/msg/PingMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty.msg; 26 | 27 | import io.tlf.monkeynetty.NetworkProtocol; 28 | 29 | /** 30 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 31 | *

32 | * Internal Use Onlu 33 | * This message is sent periodically between the server and client to ensure communication is still active. 34 | */ 35 | public class PingMessage implements NetworkMessage { 36 | @Override 37 | public String getName() { 38 | return "ping-message"; 39 | } 40 | 41 | @Override 42 | public NetworkProtocol getProtocol() { 43 | return NetworkProtocol.TCP; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/msg/UdpConHashMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty.msg; 26 | 27 | import io.tlf.monkeynetty.NetworkProtocol; 28 | 29 | /** 30 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 31 | *

32 | * Internal Use Onlu 33 | * UdpConHashMessage is used internally to establish a UDP channel with the client 34 | * from a tcp channel. 35 | */ 36 | public class UdpConHashMessage implements NetworkMessage { 37 | 38 | /** 39 | * Internal Use Only 40 | * The Base64 hash that the server will provide to the client, 41 | * for which the client must correctly respond with when connecting the UDP channel. 42 | */ 43 | private String udpHash; 44 | 45 | /** 46 | * Internal Use Only 47 | * A flag that indicated which side sent the message 48 | */ 49 | private boolean isServer; 50 | 51 | /** 52 | * Internal Use Only 53 | * 54 | * @param hash The Base64 hash used to authenticate the client's UDP channel 55 | * @param isServer The side that sent the message 56 | */ 57 | public UdpConHashMessage(String hash, boolean isServer) { 58 | udpHash = hash; 59 | this.isServer = isServer; 60 | } 61 | 62 | @Override 63 | public String getName() { 64 | return "udp-con-string"; 65 | } 66 | 67 | /** 68 | * @return Base64 hash 69 | */ 70 | public String getUdpHash() { 71 | return udpHash; 72 | } 73 | 74 | @Override 75 | public NetworkProtocol getProtocol() { 76 | return isServer ? NetworkProtocol.TCP : NetworkProtocol.UDP; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/server/NettyConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty.server; 26 | 27 | import io.netty.channel.ChannelFuture; 28 | import io.netty.channel.ChannelFutureListener; 29 | import io.netty.channel.socket.SocketChannel; 30 | import io.tlf.monkeynetty.ConnectionListener; 31 | import io.tlf.monkeynetty.NetworkClient; 32 | import io.tlf.monkeynetty.NetworkServer; 33 | import io.tlf.monkeynetty.MessageListener; 34 | import io.tlf.monkeynetty.msg.NetworkMessage; 35 | import io.tlf.monkeynetty.NetworkProtocol; 36 | 37 | import java.nio.channels.ClosedChannelException; 38 | import java.util.Collections; 39 | import java.util.HashMap; 40 | import java.util.HashSet; 41 | import java.util.Set; 42 | import java.util.logging.Level; 43 | import java.util.logging.Logger; 44 | 45 | import static io.netty.channel.ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE; 46 | 47 | /** 48 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 49 | */ 50 | public class NettyConnection implements NetworkClient { 51 | 52 | private final static Logger LOGGER = Logger.getLogger(NettyConnection.class.getName()); 53 | private SocketChannel tcpConn; 54 | private UdpChannel udpConn; 55 | private final NetworkServer server; 56 | private boolean connected = false; 57 | private final HashSet handlers = new HashSet<>(); 58 | private final Object handlerLock = new Object(); 59 | private final Set listeners = Collections.synchronizedSet(new HashSet<>()); 60 | 61 | private final HashMap atts = new HashMap<>(); 62 | 63 | public NettyConnection(NetworkServer server) { 64 | this.server = server; 65 | } 66 | 67 | public void setUdp(UdpChannel conn) { 68 | udpConn = conn; 69 | } 70 | 71 | public void setTcp(SocketChannel conn) { 72 | tcpConn = conn; 73 | } 74 | 75 | /** 76 | * Run all connection listeners on client. 77 | * The client will be flagged as connected upon the completion 78 | * of running all connection listeners. 79 | */ 80 | protected void connect() { 81 | for (ConnectionListener listener : listeners) { 82 | listener.onConnect(this); 83 | } 84 | connected = true; 85 | } 86 | 87 | /** 88 | * The client will be connected once all connection listeners 89 | * have been run specific to the client, and the client is connected 90 | * to the remote client endpoint. 91 | * @return If the client is connected 92 | */ 93 | @Override 94 | public boolean isConnected() { 95 | return connected; 96 | } 97 | 98 | @Override 99 | public void disconnect() { 100 | for (ConnectionListener listener : listeners) { 101 | listener.onDisconnect(this); 102 | } 103 | //Close connection 104 | tcpConn.close(); 105 | connected = false; 106 | } 107 | 108 | @Override 109 | public int getPort() { 110 | return server.getPort(); 111 | } 112 | 113 | public NetworkServer getServer() { 114 | return server; 115 | } 116 | 117 | @Override 118 | public String getService() { 119 | return server.getService(); 120 | } 121 | 122 | public boolean isSsl() { 123 | return server.isSsl(); 124 | } 125 | 126 | @Override 127 | public NetworkProtocol[] getProtocol() { 128 | return server.getProtocol(); 129 | } 130 | 131 | @Override 132 | public void send(NetworkMessage message) { 133 | ChannelFuture future; 134 | try { 135 | if (message.getProtocol() == NetworkProtocol.TCP) { 136 | future = tcpConn.writeAndFlush(message); 137 | } else { 138 | future = udpConn.writeAndFlush(message); 139 | } 140 | future.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); 141 | future.addListener((ChannelFutureListener) future1 -> { 142 | if (!future1.isSuccess()) { 143 | if (!(future1.cause() instanceof ClosedChannelException)) { 144 | LOGGER.log(Level.WARNING, "Error on " + (message.getProtocol() == NetworkProtocol.TCP ? "TCP" : "UDP") + " channel"); 145 | LOGGER.log(Level.WARNING, "Error sending " + message.getName() + " to " + (message.getProtocol() == NetworkProtocol.TCP ? tcpConn.localAddress().toString() : udpConn.localAddress0().toString())); 146 | LOGGER.log(Level.WARNING, "Failed to send message to client", future1.cause()); 147 | } 148 | } 149 | }); 150 | } catch (Exception ex) { 151 | LOGGER.log(Level.SEVERE, "Failed to send message to client", ex); 152 | } 153 | } 154 | 155 | @Override 156 | public void receive(NetworkMessage message) { 157 | //Handlers 158 | synchronized (handlerLock) { 159 | for (MessageListener handler : handlers) { 160 | for (Class a : handler.getSupportedMessages()) { 161 | if (a.isInstance(message)) { 162 | handler.onMessage(message, null, this); 163 | } 164 | } 165 | } 166 | } 167 | } 168 | 169 | @Override 170 | public String getAddress() { 171 | return getUserData("address").toString(); 172 | } 173 | 174 | @Override 175 | public void setUserData(String key, Object obj) { 176 | atts.put(key, obj); 177 | } 178 | 179 | @Override 180 | public T getUserData(String key) { 181 | return (T) atts.get(key); 182 | } 183 | 184 | @Override 185 | public void registerListener(MessageListener handler) { 186 | synchronized (handlerLock) { 187 | handlers.add(handler); 188 | } 189 | } 190 | 191 | @Override 192 | public void unregisterListener(MessageListener handler) { 193 | synchronized (handlerLock) { 194 | handlers.remove(handler); 195 | } 196 | } 197 | 198 | @Override 199 | public void registerListener(ConnectionListener listener) { 200 | listeners.add(listener); 201 | } 202 | 203 | @Override 204 | public void unregisterListener(ConnectionListener listener) { 205 | listeners.remove(listener); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/server/NettyServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Trevor Flynn 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty.server; 26 | 27 | import com.jme3.app.Application; 28 | import com.jme3.app.state.BaseAppState; 29 | import io.netty.bootstrap.ServerBootstrap; 30 | import io.netty.channel.*; 31 | import io.netty.channel.nio.NioEventLoopGroup; 32 | import io.netty.channel.socket.SocketChannel; 33 | import io.netty.channel.socket.nio.NioServerSocketChannel; 34 | import io.netty.handler.codec.serialization.ClassResolvers; 35 | import io.netty.handler.logging.LogLevel; 36 | import io.netty.handler.logging.LoggingHandler; 37 | import io.netty.handler.ssl.SslContext; 38 | import io.netty.handler.ssl.SslContextBuilder; 39 | import io.netty.handler.ssl.util.SelfSignedCertificate; 40 | import io.netty.handler.timeout.IdleState; 41 | import io.netty.handler.timeout.IdleStateEvent; 42 | import io.netty.handler.timeout.IdleStateHandler; 43 | import io.tlf.monkeynetty.*; 44 | import io.tlf.monkeynetty.msg.ConnectionEstablishedMessage; 45 | import io.tlf.monkeynetty.msg.NetworkMessage; 46 | import io.tlf.monkeynetty.msg.PingMessage; 47 | import io.tlf.monkeynetty.msg.UdpConHashMessage; 48 | 49 | import java.io.File; 50 | import java.security.SecureRandom; 51 | import java.util.*; 52 | import java.util.concurrent.ConcurrentHashMap; 53 | import java.util.logging.Level; 54 | import java.util.logging.Logger; 55 | 56 | public class NettyServer extends BaseAppState implements NetworkServer { 57 | 58 | private final static Logger LOGGER = Logger.getLogger(NettyServer.class.getName()); 59 | private final Set messageListeners = ConcurrentHashMap.newKeySet(); 60 | private final Set connectionListeners = ConcurrentHashMap.newKeySet(); 61 | private final Map tcpClients = new ConcurrentHashMap<>(); 62 | private final Map udpClients = new ConcurrentHashMap<>(); 63 | private final Map secrets = new ConcurrentHashMap<>(); 64 | private final Set pendingConnections = ConcurrentHashMap.newKeySet(); 65 | 66 | private int maxConnections = 10; 67 | private boolean blocking = false; 68 | private LogLevel logLevel; 69 | 70 | //Netty objects 71 | private EventLoopGroup tcpConGroup; 72 | private EventLoopGroup tcpMsgGroup; 73 | private ServerBootstrap tcpServer; 74 | private ChannelFuture tcpFuture; 75 | private EventLoopGroup udpConGroup; 76 | private EventLoopGroup udpMsgGroup; 77 | private ServerBootstrap udpServer; 78 | private ChannelFuture udpFuture; 79 | private SslContext sslContext; 80 | 81 | private final String service; 82 | private final int port; 83 | private boolean ssl; 84 | private boolean selfGenCert; 85 | private File cert; 86 | private File key; 87 | 88 | /** 89 | * Create a new UDP/TCP server. 90 | * The server will be created without SSL 91 | * 92 | * @param service The name of the service the server is running 93 | * @param port The port the TCP/UDP server will listen on 94 | */ 95 | public NettyServer(String service, int port) { 96 | this(service, false, port); 97 | } 98 | 99 | /** 100 | * Create a new UDP/TCP server. 101 | * If ssl is enabled, the TCP server will generate a self signed certificate and use ssl. 102 | * 103 | * @param service The name of the service the server is running 104 | * @param ssl If ssl should be used on the TCP server 105 | * @param port The port the TCP/UDP server will listen on 106 | */ 107 | public NettyServer(String service, boolean ssl, int port) { 108 | this(service, ssl, true, null, null, port); 109 | } 110 | 111 | /** 112 | * Create a new UDP/TCP server. 113 | * If ssl is enabled, and a certificate key pair are provided, the TCP server will use ssl. 114 | * If the server failes to load the cert key pair, or they are null, it will fail back to non-ssl. 115 | * 116 | * @param service The name of the service the server is running 117 | * @param ssl If ssl should be used on the TCP server 118 | * @param cert The certificate file, or null 119 | * @param key The certificate key, or null 120 | * @param port The port the TCP/UDP server will listen on 121 | */ 122 | public NettyServer(String service, boolean ssl, File cert, File key, int port) { 123 | this(service, ssl, false, cert, key, port); 124 | } 125 | 126 | /** 127 | * Create a new UDP/TCP server. 128 | * If ssl is enabled, and a certificate key pair are provided, the TCP server will use ssl. 129 | * If the server failes to load the cert key pair, or they are null, it will fail back to 130 | * a self signed certificate if enabled, otherwise will fail back to non-ssl. 131 | * 132 | * @param service The name of the service the server is running 133 | * @param ssl If ssl should be used on the TCP server 134 | * @param selfGenCert If a self signed certificate can be used. 135 | * @param cert The certificate file, or null to use self signed cert 136 | * @param key The certificate key, or null to use self signed cert 137 | * @param port The port the TCP/UDP server will listen on 138 | */ 139 | private NettyServer(String service, boolean ssl, boolean selfGenCert, File cert, File key, int port) { 140 | this.service = service; 141 | this.port = port; 142 | this.ssl = ssl; 143 | this.cert = cert; 144 | this.key = key; 145 | this.selfGenCert = selfGenCert; 146 | } 147 | 148 | @Override 149 | protected void initialize(Application app) { 150 | 151 | } 152 | 153 | @Override 154 | protected void cleanup(Application app) { 155 | 156 | } 157 | 158 | @Override 159 | public void onEnable() { 160 | LOGGER.log(Level.INFO, "Loading Netty.IO Server {0} on port {1,number,#}", new Object[]{getService(), getPort()}); 161 | setupTcp(); 162 | setupUdp(); 163 | LOGGER.log(Level.INFO, "Server {0} running on port {1,number,#}", new Object[]{getService(), getPort()}); 164 | } 165 | 166 | @Override 167 | public void onDisable() { 168 | LOGGER.log(Level.INFO, "Unloading Netty.IO Server {0} on port {1,number,#}", new Object[]{getService(), getPort()}); 169 | 170 | try { 171 | tcpConGroup.shutdownGracefully(); 172 | tcpMsgGroup.shutdownGracefully(); 173 | tcpFuture.channel().closeFuture().sync(); 174 | udpConGroup.shutdownGracefully(); 175 | udpMsgGroup.shutdownGracefully(); 176 | ((UdpServerChannel) udpFuture.channel()).doClose(); 177 | } catch (Exception ex) { 178 | LOGGER.log(Level.SEVERE, "Failed to stop server", ex); 179 | } 180 | 181 | LOGGER.log(Level.INFO, "Server {0} stopped on port {1,number,#}", new Object[]{getService(), getPort()}); 182 | } 183 | 184 | @Override 185 | public int getConnections() { 186 | return tcpClients.size(); 187 | } 188 | 189 | @Override 190 | public boolean isBlocking() { 191 | return blocking; 192 | } 193 | 194 | @Override 195 | public void setBlocking(boolean blocking) { 196 | this.blocking = blocking; 197 | } 198 | 199 | @Override 200 | public int getMaxConnections() { 201 | return maxConnections; 202 | } 203 | 204 | @Override 205 | public void setMaxConnections(int maxConnections) { 206 | this.maxConnections = maxConnections; 207 | } 208 | 209 | /** 210 | * Internal use only 211 | * Process an incoming client connection. 212 | * Will handle max connections and blocking mode. 213 | * Will fire connection listeners. 214 | * @param client The client making the connection 215 | */ 216 | private void receive(NetworkClient client) { 217 | if (isBlocking() || getConnections() >= getMaxConnections() || !(client instanceof NettyConnection)) { 218 | client.disconnect(); 219 | LOGGER.log(Level.INFO, "Server rejected connection from {0}", client.getAddress()); 220 | } else { 221 | if (udpClients.containsValue(client)) { 222 | //Run listeners 223 | ((NettyConnection) client).connect(); 224 | LOGGER.log(Level.INFO, "Connection received from {0}", client.getAddress()); 225 | try { 226 | for (ConnectionListener listener : connectionListeners) { 227 | listener.onConnect(client); 228 | } 229 | client.send(new ConnectionEstablishedMessage()); 230 | pendingConnections.remove(client); 231 | } catch (Exception ex) { 232 | LOGGER.log(Level.WARNING, "Exception thrown running connection listeners", ex); 233 | } 234 | } else { 235 | //We don't have the client on udp yet 236 | //Send them the hand-shake 237 | String hash = getUdpHash(128); 238 | secrets.put(hash, (NettyConnection) client); 239 | UdpConHashMessage str = new UdpConHashMessage(hash, true); 240 | client.send(str); 241 | } 242 | } 243 | } 244 | 245 | /** 246 | * Internal use only 247 | * Process an incoming message from a client. 248 | * Will notify message listeners. 249 | * 250 | * @param client The client the message was from 251 | * @param message The message sent 252 | */ 253 | private void receive(NetworkClient client, NetworkMessage message) { 254 | client.receive(message); 255 | for (MessageListener handler : messageListeners) { 256 | for (Class a : handler.getSupportedMessages()) { 257 | if (a.isInstance(message)) { 258 | try { 259 | handler.onMessage(message, this, client); 260 | } catch (Exception ex) { 261 | LOGGER.log(Level.SEVERE, "Message handler failed to handle message", ex); 262 | } 263 | } 264 | } 265 | } 266 | } 267 | 268 | @Override 269 | public void send(NetworkMessage message) { 270 | Collection cs = tcpClients.values(); 271 | cs.forEach(c -> c.send(message)); 272 | } 273 | 274 | @Override 275 | public void send(NetworkMessage message, NetworkClient client) { 276 | client.send(message); 277 | } 278 | 279 | @Override 280 | public int getPort() { 281 | return port; 282 | } 283 | 284 | @Override 285 | public String getService() { 286 | return service; 287 | } 288 | 289 | @Override 290 | public boolean isSsl() { 291 | return ssl; 292 | } 293 | 294 | @Override 295 | public NetworkProtocol[] getProtocol() { 296 | return new NetworkProtocol[]{NetworkProtocol.UDP, NetworkProtocol.TCP}; 297 | } 298 | 299 | /** 300 | * Sets the Netty.IO internal log level. 301 | * This will not change the java.util.logger Logger for Monkey-Netty. 302 | * @param logLevel The internal Netty.IO log level 303 | */ 304 | public void setLogLevel(LogLevel logLevel) { 305 | this.logLevel = logLevel; 306 | } 307 | 308 | /** 309 | * @return The internal Netty.IO log level 310 | */ 311 | public LogLevel getLogLevel() { 312 | return logLevel; 313 | } 314 | 315 | /** 316 | * Internal use only 317 | * Setup the TCP netty.io server pipeline. 318 | * This will create a dedicated TCP channel for each client. 319 | * The pipeline is setup to handle NetworkMessage message types. 320 | * The pipeline will also send/receive a ping to/from the client to ensure the connection is still active. 321 | * If the connection becomes inactive, the server will disconnect the client. 322 | * The pipeline will be configured with SSL if SSL parameters have been passed to the server. 323 | */ 324 | private void setupTcp() { 325 | //Setup ssl 326 | if (ssl) { 327 | try { 328 | if (selfGenCert) { 329 | LOGGER.log(Level.WARNING, "No SSL cert or key provided, using self signed certificate"); 330 | SelfSignedCertificate ssc = new SelfSignedCertificate(); 331 | cert = ssc.certificate(); 332 | key = ssc.privateKey(); 333 | } 334 | sslContext = SslContextBuilder.forServer(cert, key).build(); 335 | } catch (Exception ex) { 336 | LOGGER.log(Level.WARNING, "Failed to load ssl, failing back to no ssl", ex); 337 | ssl = false; 338 | } 339 | } 340 | //Setup tcp socket 341 | try { 342 | tcpConGroup = new NioEventLoopGroup(); 343 | tcpMsgGroup = new NioEventLoopGroup(); 344 | tcpServer = new ServerBootstrap(); 345 | tcpServer.group(tcpConGroup, tcpMsgGroup) 346 | .channel(NioServerSocketChannel.class) 347 | .childHandler(new ChannelInitializer() { 348 | @Override 349 | public void initChannel(SocketChannel ch) { 350 | ChannelPipeline p = ch.pipeline(); 351 | NettyConnection client = new NettyConnection(NettyServer.this); 352 | client.setTcp(ch); 353 | client.setUserData("address", ch.remoteAddress()); 354 | tcpClients.put(ch, client); 355 | pendingConnections.add(client); 356 | 357 | //Disconnect client listener 358 | ch.closeFuture().addListener((ChannelFutureListener) future -> { 359 | NettyConnection connection = tcpClients.get(future.channel()); 360 | if (connection == null) { 361 | return; //No client on this connection 362 | } 363 | 364 | //find and remove secret for client if one exists 365 | String secret = null; 366 | for (String key : Collections.unmodifiableCollection(secrets.keySet())) { 367 | if (secrets.get(key).equals(connection)) { 368 | secret = key; 369 | } 370 | } 371 | if (secret != null) { 372 | secrets.remove(secret); 373 | } 374 | 375 | tcpClients.remove(future.channel()); 376 | 377 | try { 378 | for (ConnectionListener listener : connectionListeners) { 379 | listener.onDisconnect(client); 380 | } 381 | } catch (Exception ex) { 382 | LOGGER.log(Level.WARNING, "Exception thrown running connection listeners", ex); 383 | } 384 | }); 385 | 386 | //Setup ssl 387 | if (ssl) { 388 | p.addLast(sslContext.newHandler(ch.alloc())); 389 | } 390 | 391 | //Setup pipeline 392 | if (logLevel != null) { 393 | p.addLast(new LoggingHandler(logLevel)); 394 | } 395 | p.addLast( 396 | new NetworkMessageEncoder(), 397 | new NetworkMessageDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)), 398 | new ChannelInboundHandlerAdapter() { 399 | @Override 400 | public void channelRead(ChannelHandlerContext ctx, Object msg) { 401 | if (msg instanceof NetworkMessage) { 402 | NettyConnection conn = tcpClients.get(ctx.channel()); 403 | if (!pendingConnections.contains(conn)) { 404 | receive(conn, (NetworkMessage) msg); 405 | } else { 406 | LOGGER.fine("Rejected message " + ((NetworkMessage) msg).getName() + " from " + conn.getAddress() + ". Connection not fully established"); 407 | } 408 | } else { 409 | LOGGER.log(Level.SEVERE, "Received message that was not a NetworkMessage object"); 410 | } 411 | ctx.fireChannelRead(msg); 412 | } 413 | 414 | @Override 415 | public void channelReadComplete(ChannelHandlerContext ctx) { 416 | ctx.flush(); 417 | } 418 | 419 | @Override 420 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 421 | catchNetworkError(cause); 422 | ctx.close(); 423 | } 424 | }, 425 | new IdleStateHandler(30, 10, 0), 426 | new ChannelDuplexHandler() { 427 | @Override 428 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { 429 | if (evt instanceof IdleStateEvent) { 430 | IdleStateEvent e = (IdleStateEvent) evt; 431 | if (e.state() == IdleState.READER_IDLE) { 432 | ctx.close(); 433 | } else if (e.state() == IdleState.WRITER_IDLE) { 434 | NettyConnection conn = tcpClients.get(ctx.channel()); 435 | conn.send(new PingMessage()); 436 | } 437 | } 438 | } 439 | } 440 | ); 441 | //Receive the client after comms are setup 442 | receive(client); 443 | } 444 | }); 445 | tcpFuture = tcpServer.bind(port).sync(); 446 | } catch (Exception ex) { 447 | LOGGER.log(Level.SEVERE, "Outside TCP server crash", ex); 448 | } 449 | } 450 | 451 | /** 452 | * Internal use only 453 | * Setup the UDP netty.io server pipeline. 454 | * This will create a dedicated UDP channel for each client. 455 | * The pipeline is setup to handle NetworkMessage message types. 456 | */ 457 | private void setupUdp() { 458 | try { 459 | udpConGroup = new NioEventLoopGroup(); 460 | udpMsgGroup = new NioEventLoopGroup(); 461 | udpServer = new ServerBootstrap(); 462 | udpServer.group(udpConGroup, udpMsgGroup) 463 | .channel(UdpServerChannel.class) 464 | .option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(65535)) 465 | .childHandler(new ChannelInitializer() { 466 | @Override 467 | public void initChannel(UdpChannel ch) { 468 | ChannelPipeline p = ch.pipeline(); 469 | //Disconnect client listener 470 | ch.closeFuture().addListener((ChannelFutureListener) future -> { 471 | if (udpClients.get(future.channel()) == null) { 472 | return; //No client on this connection 473 | } 474 | udpClients.remove(future.channel()); 475 | }); 476 | 477 | //Setup pipeline 478 | if (logLevel != null) { 479 | p.addLast(new LoggingHandler(logLevel)); 480 | } 481 | p.addLast( 482 | new NetworkMessageEncoder(), 483 | new NetworkMessageDecoder(65507, ClassResolvers.cacheDisabled(null)), 484 | new ChannelInboundHandlerAdapter() { 485 | @Override 486 | public void channelRead(ChannelHandlerContext ctx, Object msg) { 487 | //Connect udp client when requested 488 | if (msg instanceof UdpConHashMessage) { 489 | NettyConnection client = secrets.get(((UdpConHashMessage) msg).getUdpHash()); 490 | if (client == null) { 491 | ctx.close(); 492 | //Do not pass the message on, we are forcibly disconnecting the client 493 | return; 494 | } 495 | secrets.remove(((UdpConHashMessage) msg).getUdpHash()); 496 | client.setUdp((UdpChannel) ctx.channel()); 497 | udpClients.put(ctx.channel(), client); 498 | receive(client); 499 | ctx.fireChannelRead(msg); 500 | return; 501 | } 502 | if (msg instanceof NetworkMessage) { 503 | NettyConnection conn = udpClients.get(ctx.channel()); 504 | if (!pendingConnections.contains(conn)) { 505 | receive(conn, (NetworkMessage) msg); 506 | } else { 507 | LOGGER.fine("Rejected message " + ((NetworkMessage) msg).getName() + " from " + conn.getAddress() + ". Connection not fully established"); 508 | } 509 | } else { 510 | LOGGER.log(Level.SEVERE, "Received message that was not a NetworkMessage object"); 511 | } 512 | ctx.fireChannelRead(msg); 513 | } 514 | 515 | @Override 516 | public void channelReadComplete(ChannelHandlerContext ctx) { 517 | ctx.flush(); 518 | } 519 | 520 | @Override 521 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 522 | catchNetworkError(cause); 523 | ctx.close(); 524 | } 525 | }); 526 | } 527 | }); 528 | udpFuture = udpServer.bind(port).sync(); 529 | } catch (Exception ex) { 530 | LOGGER.log(Level.SEVERE, "Outside UDP server crash", ex); 531 | } 532 | } 533 | 534 | /** 535 | * Internal use only 536 | * Catch a network error. This will cause the error to be sent to the logger. 537 | * @param cause The error to catch 538 | */ 539 | private void catchNetworkError(Throwable cause) { 540 | if (!(cause instanceof java.net.SocketException)) { 541 | LOGGER.log(Level.WARNING, "Network Server Error", cause); 542 | } 543 | //The client disconnected unexpectedly, we can ignore. 544 | } 545 | 546 | @Override 547 | public void registerListener(MessageListener handler) { 548 | messageListeners.add(handler); 549 | } 550 | 551 | @Override 552 | public void unregisterListener(MessageListener handler) { 553 | messageListeners.remove(handler); 554 | } 555 | 556 | @Override 557 | public void registerListener(ConnectionListener listener) { 558 | connectionListeners.add(listener); 559 | } 560 | 561 | @Override 562 | public void unregisterListener(ConnectionListener listener) { 563 | connectionListeners.remove(listener); 564 | } 565 | 566 | /** 567 | * Internal use only 568 | * Generates a base64 like hash 569 | * 570 | * @param len The number of characters to generate in the hash 571 | * @return The generated base64 like hash 572 | */ 573 | private String getUdpHash(int len) { 574 | String SALTCHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_+-=[]{};':\",.<>/?\\"; 575 | StringBuilder salt = new StringBuilder(); 576 | SecureRandom rnd = new SecureRandom(); 577 | while (salt.length() < len) { // length of the random string. 578 | int index = (int) (rnd.nextFloat() * SALTCHARS.length()); 579 | salt.append(SALTCHARS.charAt(index)); 580 | } 581 | return salt.toString(); 582 | 583 | } 584 | } 585 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/server/UdpChannel.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty.server; 26 | 27 | import io.netty.buffer.ByteBuf; 28 | import io.netty.channel.*; 29 | import io.netty.util.ReferenceCountUtil; 30 | import io.netty.util.internal.RecyclableArrayList; 31 | 32 | import java.net.InetSocketAddress; 33 | import java.net.SocketAddress; 34 | import java.util.concurrent.ConcurrentLinkedQueue; 35 | import java.util.concurrent.atomic.AtomicBoolean; 36 | 37 | /** 38 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 39 | */ 40 | public class UdpChannel extends AbstractChannel { 41 | 42 | protected final ChannelMetadata metadata = new ChannelMetadata(false); 43 | protected final DefaultChannelConfig config = new DefaultChannelConfig(this); 44 | protected final UdpServerChannel serverChannel; 45 | protected final InetSocketAddress remote; 46 | protected final ConcurrentLinkedQueue buffers = new ConcurrentLinkedQueue<>(); 47 | 48 | protected AtomicBoolean isNew = new AtomicBoolean(true); 49 | protected volatile boolean open = true; 50 | protected boolean reading = false; 51 | 52 | protected UdpChannel(UdpServerChannel serverchannel, InetSocketAddress remote) { 53 | super(serverchannel); 54 | this.serverChannel = serverchannel; 55 | this.remote = remote; 56 | } 57 | 58 | protected boolean getIsNew() { 59 | return isNew.compareAndSet(true, false); 60 | } 61 | 62 | @Override 63 | public ChannelMetadata metadata() { 64 | return metadata; 65 | } 66 | 67 | @Override 68 | public ChannelConfig config() { 69 | return config; 70 | } 71 | 72 | @Override 73 | public boolean isActive() { 74 | return open; 75 | } 76 | 77 | @Override 78 | public boolean isOpen() { 79 | return isActive(); 80 | } 81 | 82 | @Override 83 | protected void doClose() { 84 | open = false; 85 | serverChannel.doUserChannelRemove(this); 86 | } 87 | 88 | @Override 89 | protected void doDisconnect() { 90 | doClose(); 91 | } 92 | 93 | protected void addBuffer(ByteBuf buffer) { 94 | this.buffers.add(buffer); 95 | } 96 | 97 | @Override 98 | protected void doBeginRead() { 99 | if (!reading) { 100 | reading = true; 101 | try { 102 | ByteBuf buffer; 103 | while ((buffer = buffers.poll()) != null) { 104 | pipeline().fireChannelRead(buffer); 105 | } 106 | pipeline().fireChannelReadComplete(); 107 | } finally { 108 | reading = false; 109 | } 110 | } 111 | } 112 | 113 | @Override 114 | protected void doWrite(ChannelOutboundBuffer buffer) { 115 | final RecyclableArrayList list = RecyclableArrayList.newInstance(); 116 | boolean freeList = true; 117 | try { 118 | ByteBuf buf; 119 | while ((buf = (ByteBuf) buffer.current()) != null) { 120 | list.add(buf.retain()); 121 | buffer.remove(); 122 | } 123 | freeList = false; 124 | } finally { 125 | if (freeList) { 126 | for (Object obj : list) { 127 | ReferenceCountUtil.safeRelease(obj); 128 | } 129 | list.recycle(); 130 | } 131 | } 132 | serverChannel.doWrite(list, remote); 133 | } 134 | 135 | @Override 136 | protected boolean isCompatible(EventLoop eventloop) { 137 | return true; 138 | } 139 | 140 | @Override 141 | protected AbstractUnsafe newUnsafe() { 142 | return new AbstractUnsafe() { 143 | @Override 144 | public void connect(SocketAddress addr1, SocketAddress addr2, ChannelPromise pr) { 145 | throw new UnsupportedOperationException(); 146 | } 147 | }; 148 | } 149 | 150 | @Override 151 | protected SocketAddress localAddress0() { 152 | return serverChannel.localAddress0(); 153 | } 154 | 155 | @Override 156 | protected SocketAddress remoteAddress0() { 157 | return remote; 158 | } 159 | 160 | @Override 161 | protected void doBind(SocketAddress addr) { 162 | throw new UnsupportedOperationException(); 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /monkey-netty/src/main/java/io/tlf/monkeynetty/server/UdpServerChannel.java: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Trevor Flynn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package io.tlf.monkeynetty.server; 26 | 27 | import io.netty.bootstrap.Bootstrap; 28 | import io.netty.buffer.ByteBuf; 29 | import io.netty.channel.*; 30 | import io.netty.channel.ChannelHandler.Sharable; 31 | import io.netty.channel.epoll.Epoll; 32 | import io.netty.channel.epoll.EpollDatagramChannel; 33 | import io.netty.channel.epoll.EpollEventLoopGroup; 34 | import io.netty.channel.nio.NioEventLoopGroup; 35 | import io.netty.channel.socket.DatagramChannel; 36 | import io.netty.channel.socket.DatagramPacket; 37 | import io.netty.channel.socket.nio.NioDatagramChannel; 38 | import io.netty.channel.unix.UnixChannelOption; 39 | import io.netty.util.internal.RecyclableArrayList; 40 | 41 | import java.net.InetSocketAddress; 42 | import java.net.SocketAddress; 43 | import java.util.ArrayList; 44 | import java.util.List; 45 | import java.util.concurrent.ConcurrentHashMap; 46 | 47 | /** 48 | * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com 49 | */ 50 | public class UdpServerChannel extends AbstractServerChannel { 51 | 52 | protected final static int BUFFER_ALLOC = 2048; 53 | protected final static int DEFAULT_THREADS = 1; 54 | 55 | protected volatile boolean open = true; 56 | 57 | protected final EventLoopGroup group; 58 | protected final List ioBootstraps = new ArrayList<>(); 59 | protected final List ioChannels = new ArrayList<>(); 60 | protected final ConcurrentHashMap userChannels = new ConcurrentHashMap<>(); 61 | protected final DefaultChannelConfig config = new DefaultChannelConfig(this) { 62 | { 63 | setRecvByteBufAllocator(new FixedRecvByteBufAllocator(BUFFER_ALLOC)); 64 | } 65 | 66 | @Override 67 | public boolean isAutoRead() { 68 | return true; 69 | } 70 | 71 | @Override 72 | public ChannelConfig setAutoRead(boolean autoRead) { 73 | return this; 74 | } 75 | }; 76 | 77 | public UdpServerChannel() { 78 | this(DEFAULT_THREADS); 79 | } 80 | 81 | public UdpServerChannel(int threads) { 82 | if (threads < 1) { 83 | throw new IllegalArgumentException("Must have at least 1 thread"); 84 | } 85 | boolean ebolaAvailable = Epoll.isAvailable(); 86 | if (!ebolaAvailable) { 87 | threads = DEFAULT_THREADS; 88 | } 89 | group = ebolaAvailable ? new EpollEventLoopGroup(threads) : new NioEventLoopGroup(threads); 90 | Class channel = ebolaAvailable ? EpollDatagramChannel.class : NioDatagramChannel.class; 91 | ChannelInitializer initializer = new ChannelInitializer() { 92 | final ReadRouteChannelHandler ioReadRoute = new ReadRouteChannelHandler(); 93 | 94 | @Override 95 | protected void initChannel(Channel ioChannel) throws Exception { 96 | ioChannel.pipeline().addLast(ioReadRoute); 97 | } 98 | }; 99 | while (threads-- > 0) { 100 | Bootstrap ioBootstrap = new Bootstrap().group(group).channel(channel).handler(initializer); 101 | if (ebolaAvailable) { 102 | ioBootstrap.option(UnixChannelOption.SO_REUSEPORT, true); 103 | } 104 | ioBootstraps.add(ioBootstrap); 105 | } 106 | } 107 | 108 | protected void doWrite(RecyclableArrayList list, InetSocketAddress remote) { 109 | Channel ioChannel = ioChannels.get(remote.hashCode() & (ioChannels.size() - 1)); 110 | ioChannel.eventLoop().execute(() -> { 111 | try { 112 | for (Object buf : list) { 113 | ioChannel.write(new DatagramPacket((ByteBuf) buf, remote)); 114 | } 115 | ioChannel.flush(); 116 | } finally { 117 | list.recycle(); 118 | } 119 | }); 120 | } 121 | 122 | protected void doUserChannelRemove(UdpChannel userChannel) { 123 | userChannels.compute((InetSocketAddress) userChannel.remoteAddress(), (lAddr, lChannel) -> lChannel == userChannel ? null : lChannel); 124 | } 125 | 126 | @Override 127 | public boolean isOpen() { 128 | return open; 129 | } 130 | 131 | @Override 132 | public boolean isActive() { 133 | return isOpen(); 134 | } 135 | 136 | @Override 137 | protected void doClose() throws Exception { 138 | open = false; 139 | new ArrayList<>(userChannels.values()).forEach(Channel::close); 140 | ioChannels.forEach(Channel::close); 141 | group.shutdownGracefully().sync(); 142 | } 143 | 144 | @Override 145 | protected SocketAddress localAddress0() { 146 | return ioChannels.size() > 0 ? ioChannels.get(0).localAddress() : null; 147 | } 148 | 149 | @Override 150 | public InetSocketAddress localAddress() { 151 | return ioChannels.size() > 0 ? (InetSocketAddress) ioChannels.get(0).localAddress() : null; 152 | } 153 | 154 | @Override 155 | protected void doBind(SocketAddress local) throws Exception { 156 | for (Bootstrap bootstrap : ioBootstraps) { 157 | ioChannels.add(bootstrap.bind(local).sync().channel()); 158 | } 159 | ioBootstraps.clear(); 160 | } 161 | 162 | @Override 163 | public DefaultChannelConfig config() { 164 | return config; 165 | } 166 | 167 | @Override 168 | public InetSocketAddress remoteAddress() { 169 | return null; 170 | } 171 | 172 | @Override 173 | protected boolean isCompatible(EventLoop loop) { 174 | return true; 175 | } 176 | 177 | @Override 178 | protected void doBeginRead() throws Exception { 179 | } 180 | 181 | @Sharable 182 | protected class ReadRouteChannelHandler extends SimpleChannelInboundHandler { 183 | @Override 184 | protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket p) throws Exception { 185 | UdpChannel channel = userChannels.compute(p.sender(), (lAddr, lChannel) -> ((lChannel == null) || !lChannel.isOpen()) ? new UdpChannel(UdpServerChannel.this, lAddr) : lChannel); 186 | channel.buffers.add(p.content().retain()); 187 | if (channel.getIsNew()) { 188 | ChannelPipeline serverPipeline = UdpServerChannel.this.pipeline(); 189 | serverPipeline.fireChannelRead(channel); 190 | serverPipeline.fireChannelReadComplete(); 191 | } else { 192 | if (channel.isRegistered()) { 193 | channel.read(); 194 | } 195 | } 196 | } 197 | } 198 | 199 | } 200 | -------------------------------------------------------------------------------- /publishing/publishing.gradle: -------------------------------------------------------------------------------- 1 | // Create variables with empty default values 2 | 3 | apply plugin: 'maven-publish' 4 | apply plugin: 'signing' 5 | 6 | ext["ossrhUsername"] = '' 7 | ext["ossrhPassword"] = '' 8 | ext["sonatypeStagingProfileId"] = '' 9 | ext["signing.keyId"] = '' 10 | ext["signing.password"] = '' 11 | ext["signing.secretKeyRingFile"] = '' 12 | 13 | File secretPropsFile = project.rootProject.file('publishing/publishing.properties') 14 | if (secretPropsFile.exists()) { 15 | // Read local.properties file first if it exists 16 | Properties p = new Properties() 17 | new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) } 18 | p.each { name, value -> ext[name] = value } 19 | } else { 20 | // Use system environment variables 21 | ext["ossrhUsername"] = System.getenv('OSSRH_USERNAME') 22 | ext["ossrhPassword"] = System.getenv('OSSRH_PASSWORD') 23 | ext["sonatypeStagingProfileId"] = System.getenv('SONATYPE_STAGING_PROFILE_ID') 24 | ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID') 25 | ext["signing.password"] = System.getenv('SIGNING_PASSWORD') 26 | ext["signing.secretKeyRingFile"] = System.getenv('SIGNING_SECRET_KEY_RING_FILE') 27 | } 28 | 29 | // Set up Sonatype repository 30 | nexusPublishing { 31 | repositories { 32 | sonatype { 33 | stagingProfileId = sonatypeStagingProfileId 34 | username = ossrhUsername 35 | password = ossrhPassword 36 | nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) 37 | snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) 38 | } 39 | } 40 | } 41 | 42 | signing { 43 | sign publishing.publications 44 | } 45 | 46 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'monkey-netty-root' 2 | include 'examples' 3 | include 'monkey-netty' 4 | --------------------------------------------------------------------------------