├── .github └── FUNDING.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DONATIONS.md ├── LICENSE.md ├── README.md ├── TestDebug.bat ├── assets ├── icon.ico └── icon.png ├── cavemantcp.crt ├── cavemantcp.key ├── cavemantcp.pfx ├── certificate-instructions.txt └── src ├── CavemanTcp.sln ├── CavemanTcp ├── CavemanTcp.csproj ├── CavemanTcp.xml ├── CavemanTcpClient.cs ├── CavemanTcpClientEvents.cs ├── CavemanTcpClientSettings.cs ├── CavemanTcpKeepaliveSettings.cs ├── CavemanTcpServer.cs ├── CavemanTcpServerCallbacks.cs ├── CavemanTcpServerEvents.cs ├── CavemanTcpServerSettings.cs ├── CavemanTcpStatistics.cs ├── ClientConnectedEventArgs.cs ├── ClientDeclinedEventArgs.cs ├── ClientDisconnectedEventArgs.cs ├── ClientMetadata.cs ├── Common.cs ├── DisconnectReason.cs ├── ExceptionEventArgs.cs ├── LICENSE.md ├── ReadResult.cs ├── WriteResult.cs ├── assets │ ├── icon.ico │ └── icon.png └── icon.ico ├── Test.Client ├── Program.cs ├── Test.Client.csproj ├── cavemantcp.crt ├── cavemantcp.key └── cavemantcp.pfx ├── Test.ClientAsync ├── Program.cs ├── Test.ClientAsync.csproj ├── cavemantcp.crt ├── cavemantcp.key └── cavemantcp.pfx ├── Test.Disconnect ├── Program.cs └── Test.Disconnect.csproj ├── Test.HttpLoopback ├── Program.cs └── Test.HttpLoopback.csproj ├── Test.Server ├── Program.cs ├── Test.Server.csproj ├── cavemantcp.crt ├── cavemantcp.key └── cavemantcp.pfx ├── Test.ServerAsync ├── Program.cs ├── Test.ServerAsync.csproj ├── cavemantcp.crt ├── cavemantcp.key └── cavemantcp.pfx ├── Test.SslClient ├── Program.cs ├── Test.SslClient.csproj ├── cavemantcp.crt ├── cavemantcp.key └── cavemantcp.pfx └── Test.SslServer ├── Program.cs ├── Test.SslServer.csproj ├── cavemantcp.crt ├── cavemantcp.key └── cavemantcp.pfx /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [jchristn] 2 | custom: ["https://paypal.me/joelchristner"] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### 2 | ### 3 | ### Node 4 | ### 5 | ### 6 | 7 | # Node modules: 8 | node_modules 9 | node_modules/ 10 | 11 | ### 12 | ### 13 | ### JetBrains 14 | ### 15 | ### 16 | 17 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 18 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 19 | 20 | # User-specific stuff: 21 | .idea 22 | .idea/ 23 | .idea/**/workspace.xml 24 | .idea/**/tasks.xml 25 | .idea/dictionaries 26 | 27 | # Sensitive or high-churn files: 28 | .idea/**/dataSources/ 29 | .idea/**/dataSources.ids 30 | .idea/**/dataSources.xml 31 | .idea/**/dataSources.local.xml 32 | .idea/**/sqlDataSources.xml 33 | .idea/**/dynamic.xml 34 | .idea/**/uiDesigner.xml 35 | 36 | # Gradle: 37 | .idea/**/gradle.xml 38 | .idea/**/libraries 39 | 40 | # CMake 41 | cmake-build-debug/ 42 | cmake-build-release/ 43 | 44 | # Mongo Explorer plugin: 45 | .idea/**/mongoSettings.xml 46 | 47 | ## File-based project format: 48 | *.iws 49 | 50 | ## Plugin-specific files: 51 | 52 | # IntelliJ 53 | out/ 54 | 55 | # mpeltonen/sbt-idea plugin 56 | .idea_modules/ 57 | 58 | # JIRA plugin 59 | atlassian-ide-plugin.xml 60 | 61 | # Cursive Clojure plugin 62 | .idea/replstate.xml 63 | 64 | # Crashlytics plugin (for Android Studio and IntelliJ) 65 | com_crashlytics_export_strings.xml 66 | crashlytics.properties 67 | crashlytics-build.properties 68 | fabric.properties 69 | 70 | ### 71 | ### 72 | ### Visual Studio 73 | ### 74 | ### 75 | 76 | ## Ignore Visual Studio temporary files, build results, and 77 | ## files generated by popular Visual Studio add-ons. 78 | 79 | # User-specific files 80 | *.suo 81 | *.user 82 | *.userosscache 83 | *.sln.docstates 84 | 85 | # User-specific files (MonoDevelop/Xamarin Studio) 86 | *.userprefs 87 | 88 | # Build results 89 | [Dd]ebug/ 90 | [Dd]ebugPublic/ 91 | [Rr]elease/ 92 | [Rr]eleases/ 93 | x64/ 94 | x86/ 95 | bld/ 96 | [Bb]in/ 97 | [Oo]bj/ 98 | [Ll]og/ 99 | 100 | # Visual Studio 2015 cache/options directory 101 | .vs/ 102 | # Uncomment if you have tasks that create the project's static files in wwwroot 103 | #wwwroot/ 104 | 105 | # MSTest test Results 106 | [Tt]est[Rr]esult*/ 107 | [Bb]uild[Ll]og.* 108 | 109 | # NUNIT 110 | *.VisualState.xml 111 | TestResult.xml 112 | 113 | # Build Results of an ATL Project 114 | [Dd]ebugPS/ 115 | [Rr]eleasePS/ 116 | dlldata.c 117 | 118 | # DNX 119 | project.lock.json 120 | project.fragment.lock.json 121 | artifacts/ 122 | 123 | *_i.c 124 | *_p.c 125 | *_i.h 126 | *.ilk 127 | *.meta 128 | *.obj 129 | *.pch 130 | *.pdb 131 | *.pgc 132 | *.pgd 133 | *.rsp 134 | *.sbr 135 | *.tlb 136 | *.tli 137 | *.tlh 138 | *.tmp 139 | *.tmp_proj 140 | *.log 141 | *.vspscc 142 | *.vssscc 143 | .builds 144 | *.pidb 145 | *.svclog 146 | *.scc 147 | 148 | # Chutzpah Test files 149 | _Chutzpah* 150 | 151 | # Visual C++ cache files 152 | ipch/ 153 | *.aps 154 | *.ncb 155 | *.opendb 156 | *.opensdf 157 | *.sdf 158 | *.cachefile 159 | *.VC.db 160 | *.VC.VC.opendb 161 | 162 | # Visual Studio profiler 163 | *.psess 164 | *.vsp 165 | *.vspx 166 | *.sap 167 | 168 | # Strong Naming 169 | *.snk 170 | 171 | # TFS 2012 Local Workspace 172 | $tf/ 173 | 174 | # Guidance Automation Toolkit 175 | *.gpState 176 | 177 | # ReSharper is a .NET coding add-in 178 | _ReSharper*/ 179 | *.[Rr]e[Ss]harper 180 | *.DotSettings.user 181 | 182 | # JustCode is a .NET coding add-in 183 | .JustCode 184 | 185 | # TeamCity is a build add-in 186 | _TeamCity* 187 | 188 | # DotCover is a Code Coverage Tool 189 | *.dotCover 190 | 191 | # Visual Studio code coverage results 192 | *.coverage 193 | *.coveragexml 194 | 195 | # NCrunch 196 | _NCrunch_* 197 | .*crunch*.local.xml 198 | nCrunchTemp_* 199 | 200 | # MightyMoose 201 | *.mm.* 202 | AutoTest.Net/ 203 | 204 | # Web workbench (sass) 205 | .sass-cache/ 206 | 207 | # Installshield output folder 208 | [Ee]xpress/ 209 | 210 | # DocProject is a documentation generator add-in 211 | DocProject/buildhelp/ 212 | DocProject/Help/*.HxT 213 | DocProject/Help/*.HxC 214 | DocProject/Help/*.hhc 215 | DocProject/Help/*.hhk 216 | DocProject/Help/*.hhp 217 | DocProject/Help/Html2 218 | DocProject/Help/html 219 | 220 | # Click-Once directory 221 | publish/ 222 | 223 | # Publish Web Output 224 | *.[Pp]ublish.xml 225 | *.azurePubxml 226 | # TODO: Comment the next line if you want to checkin your web deploy settings 227 | # but database connection strings (with potential passwords) will be unencrypted 228 | *.pubxml 229 | *.publishproj 230 | 231 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 232 | # checkin your Azure Web App publish settings, but sensitive information contained 233 | # in these scripts will be unencrypted 234 | PublishScripts/ 235 | 236 | # NuGet Packages 237 | *.nupkg 238 | # The packages folder can be ignored because of Package Restore 239 | **/packages/* 240 | # except build/, which is used as an MSBuild target. 241 | !**/packages/build/ 242 | # Uncomment if necessary however generally it will be regenerated when needed 243 | #!**/packages/repositories.config 244 | # NuGet v3's project.json files produces more ignoreable files 245 | *.nuget.props 246 | *.nuget.targets 247 | 248 | # Microsoft Azure Build Output 249 | csx/ 250 | *.build.csdef 251 | 252 | # Microsoft Azure Emulator 253 | ecf/ 254 | rcf/ 255 | 256 | # Windows Store app package directories and files 257 | AppPackages/ 258 | BundleArtifacts/ 259 | Package.StoreAssociation.xml 260 | _pkginfo.txt 261 | 262 | # Visual Studio cache files 263 | # files ending in .cache can be ignored 264 | *.[Cc]ache 265 | # but keep track of directories ending in .cache 266 | !*.[Cc]ache/ 267 | 268 | # Others 269 | ClientBin/ 270 | ~$* 271 | *~ 272 | *.dbmdl 273 | *.dbproj.schemaview 274 | *.jfm 275 | *.publishsettings 276 | node_modules/ 277 | orleans.codegen.cs 278 | 279 | # Since there are multiple workflows, uncomment next line to ignore bower_components 280 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 281 | #bower_components/ 282 | 283 | # RIA/Silverlight projects 284 | Generated_Code/ 285 | 286 | # Backup & report files from converting an old project file 287 | # to a newer Visual Studio version. Backup files are not needed, 288 | # because we have git ;-) 289 | _UpgradeReport_Files/ 290 | Backup*/ 291 | UpgradeLog*.XML 292 | UpgradeLog*.htm 293 | 294 | # SQL Server files 295 | *.mdf 296 | *.ldf 297 | 298 | # Business Intelligence projects 299 | *.rdl.data 300 | *.bim.layout 301 | *.bim_*.settings 302 | 303 | # Microsoft Fakes 304 | FakesAssemblies/ 305 | 306 | # GhostDoc plugin setting file 307 | *.GhostDoc.xml 308 | 309 | # Node.js Tools for Visual Studio 310 | .ntvs_analysis.dat 311 | 312 | # Visual Studio 6 build log 313 | *.plg 314 | 315 | # Visual Studio 6 workspace options file 316 | *.opt 317 | 318 | # Visual Studio LightSwitch build output 319 | **/*.HTMLClient/GeneratedArtifacts 320 | **/*.DesktopClient/GeneratedArtifacts 321 | **/*.DesktopClient/ModelManifest.xml 322 | **/*.Server/GeneratedArtifacts 323 | **/*.Server/ModelManifest.xml 324 | _Pvt_Extensions 325 | 326 | # Paket dependency manager 327 | .paket/paket.exe 328 | paket-files/ 329 | 330 | # FAKE - F# Make 331 | .fake/ 332 | 333 | # JetBrains Rider 334 | .idea/ 335 | *.sln.iml 336 | 337 | # CodeRush 338 | .cr/ 339 | 340 | # Python Tools for Visual Studio (PTVS) 341 | __pycache__/ 342 | *.pyc 343 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## Current Version 4 | 5 | v2.0.x 6 | 7 | - Breaking changes 8 | - Clients now referenced by `Guid` instead of `string ipPort` 9 | - `ListClients` now returns an enumeration of `ClientMetadata` 10 | - `Send` and `Read` methods using `string ipPort` are marked obsolete 11 | - `AddClient` moved closer to connection acceptance 12 | - Target `net461` `net472` `net48` `net6.0` `net7.0` and `net8.0` 13 | - Better detection of disconnects 14 | - Disable Nagle's algorithm by default 15 | 16 | ## Previous Versions 17 | 18 | v1.3.3 19 | 20 | - Breaking change, disabled TCP keepalives by default due to incompatibility in certain platforms 21 | 22 | v1.3.2 23 | 24 | - .NET 5.0 support 25 | - Better async handling and cancellation 26 | 27 | v1.3.0 28 | 29 | - Breaking changes 30 | - Retarget to include .NET Core 3.1 (in addition to .NET Standard and .NET Framework) 31 | - Consolidated settings and events into their own separate classes 32 | - Added support for TCP keepalives 33 | 34 | v1.2.1 35 | 36 | - Better threading for scalability 37 | 38 | v1.2.0 39 | 40 | - `SendWithTimeout`, `SendWithTimeoutAsync`, `ReadWithTimeout`, and `ReadWithTimeoutAsync` APIs 41 | - Async test client and server 42 | - Disable MutuallyAuthenticate for SSL by default on the client 43 | 44 | v1.1.1 45 | 46 | - Disable MutuallyAuthenticate for SSL by default 47 | 48 | v1.1.0 49 | 50 | - Breaking changes; read now returns ReadResult and write now returns WriteResult 51 | 52 | v1.0.3 53 | 54 | - Dispose fix 55 | 56 | v1.0.2 57 | 58 | - IsListening (server) and IsConnected (client) properties 59 | 60 | v1.0.1 61 | 62 | - Async APIs 63 | - Minor refactor 64 | - Dispose bugfixes 65 | 66 | v1.0.0 67 | 68 | - Initial release -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | - Using welcoming and inclusive language 12 | - Being respectful of differing viewpoints and experiences 13 | - Gracefully accepting constructive criticism 14 | - Focusing on what is best for the community 15 | - Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | - Trolling, insulting/derogatory comments, and personal or political attacks 21 | - Public or private harassment 22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | - Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 44 | 45 | For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq 46 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for your interest in contributing! 4 | 5 | The following is a set of guidelines for contributing to our project on Github. These are mostly guidelines, not rules. 6 | 7 | ## Code of Conduct 8 | 9 | This project and everyone participating in it is governed by the Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to project moderators. 10 | 11 | ## Pull Requests 12 | 13 | Please follow these guidelines when submitting pull requests (PRs): 14 | 15 | - PRs should be manageable in size to make it easy for us to validate and integrate 16 | - Each PR should be contained to a single fix or a single feature 17 | - Describe the motivation for the PR 18 | - Describe a methodology to test and validate, if appropriate 19 | 20 | Please ensure that the code in your PR follows a style similar to that of the project. If you find a material discrepancy between the style followed by the project and a de facto standard style given the project language and framework, please let us know so we can amend and make our code more maintainable. 21 | 22 | ## Asking Questions 23 | 24 | Prior to asking questions, please review closed issues and wiki pages. If your question is not answered in either of those places, please feel free to file an issue! This will also help us to build out documentation. 25 | 26 | ## Reporting Bugs 27 | 28 | If you encounter an issue, please let us know! We kindly ask that you supply the following information with your bug report when you file an issue. Feel free to copy/paste from the below and use as a template. 29 | 30 | --- Bug Report --- 31 | 32 | Operating system and version: Windows 10 33 | Framework and runtime: .NET Core 2.0 34 | Issue encountered: The widget shifted left 35 | Expected behavior: The widget should have shifted right 36 | Steps to reproduce: Instantiate the widget and call .ShiftRight() 37 | Sample code encapsulating the problem: 38 | ``` 39 | Widget widget = new Widget(); 40 | widget.ShiftRight(); 41 | ``` 42 | Exception details: [insert exception output here] 43 | Stack trace: [if appropriate] 44 | 45 | --- End --- 46 | 47 | ## Suggesting Enhancements 48 | 49 | Should there be a way that you feel we could improve upon this project, please feel free to file an issue and use the template below to provide the necessary details to support the request. 50 | 51 | Some basic guidelines for suggesting enhancements: 52 | 53 | - Use a clear and descriptive title for the issue to identify the suggestion. 54 | - Provide a step-by-step description of the suggested enhancement in as many details as possible. 55 | - Provide specific examples to demonstrate the steps including copy/pasteable snippets where possible 56 | - Describe the current behavior and the behavior you would like to see 57 | - Describe the usefulness of the enhancement to yourself and potentially to others 58 | 59 | --- Enhancement Request --- 60 | 61 | Enhancement request title: Widgets should have a color attribute 62 | Use case: I want to specify what color a widget is 63 | Current behavior: Widgets don't have a color 64 | Requested behavior: Allow me to specify a widget's color 65 | Recommended implementation: Add a Color attribute to the Widget class 66 | Usefulness of the enhancement: All widgets have color, and everyone has to build their own implementation to set this 67 | 68 | --- End --- 69 | -------------------------------------------------------------------------------- /DONATIONS.md: -------------------------------------------------------------------------------- 1 | ## Donations 2 | 3 | If you're interested in financially supporting this work on other open source projects I manage, first of all, thank you! It brings me delight to know that this software has helped you in some way. Please find below address details for donations using 4 | 5 | ### Traditional 6 | 7 | | Method | Address | 8 | |--------|---------| 9 | | PayPal | @joelchristner - https://paypal.me/joelchristner?country.x=US&locale.x=en_US | 10 | | Venmo | @Joel-Christner - https://account.venmo.com/u/Joel-Christner | 11 | 12 | ### Cryptocurrency (Mainstream) 13 | 14 | | Method | Address | 15 | |----------|---------| 16 | | Bitcoin | 3HRgnvEWQBDdWDp75CFDsz3PirCYmftDwU | 17 | | Ethereum | 0xE064dC84270e17e2Ac34b2552461d672BdBC5e36 | 18 | 19 | ### Cryptocurrency (Altcoins) 20 | 21 | | Method | Address | 22 | |--------|---------| 23 | | XRP | Tag 1765608084 Address rw2ciyaNshpHe7bCHo4bRWq6pqqynnWKQg | 24 | | Shiba Inu SHIB | 0xdA58D4ba0d5823d80a0C42C69E139124B889c69a | 25 | | Algorand ALGO | FFKA23KC4BHEU5HM4OQHLEILVIYDLRXYK6WD6UI573JPUGHZR43JVHAF7A | 26 | | Stellar Lumens XML | Memo 2264929895 Address GDQP2KPQGKIHYJGXNUIYOMHARUARCA7DJT5FO2FFOOKY3B2WSQHG4W37 | 27 | | Dogecoin DOGE | DQ2dn4UifpYA8RyuNF1t112Y1XH5L42Rxv | 28 | | Cardano ADA | addr1vxrxyrv0phgr2xcl08dj0sfy425mhqehuu4dy99ja4nhtwqfsvgjk | 29 | 30 | If you have a coin you prefer to use, please let me know! 31 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![alt tag](https://github.com/jchristn/cavemantcp/blob/master/assets/icon.ico) 2 | 3 | # CavemanTcp 4 | 5 | [![NuGet Version](https://img.shields.io/nuget/v/CavemanTcp.svg?style=flat)](https://www.nuget.org/packages/CavemanTcp/) [![NuGet](https://img.shields.io/nuget/dt/CavemanTcp.svg)](https://www.nuget.org/packages/CavemanTcp) 6 | 7 | CavemanTcp gives you the ultimate control in building TCP-based applications involving clients and servers. 8 | 9 | With CavemanTcp, you have full control over reading and writing data. CavemanTcp is designed for those that want explicit control over when data is read or written or want to build a state machine on top of TCP. 10 | 11 | Important: 12 | - If you are looking for a package that will continually read data and raise events when data is received, see SimpleTcp: https://github.com/jchristn/simpletcp 13 | - If you are looking for an all-in-one package that handles delivery of well-formed application-layer messages, see WatsonTcp: https://github.com/jchristn/watsontcp 14 | 15 | ## Disconnection Handling 16 | 17 | Since CavemanTcp relies on the consuming application to specify when to read or write, there are no background threads continually monitoring the state of the TCP connection (unlike SimpleTcp and WatsonTcp). Thus, you should build your apps on the expectation that an exception may be thrown while in the middle of a read or write. 18 | 19 | As of v1.3.0, TCP keepalive support was added for .NET Core and .NET Framework; unfortunately .NET Standard does not offer this support, so it is not present for apps using CavemanTcp targeted to .NET Standard. 20 | 21 | ## New in v2.0.x 22 | 23 | - Breaking changes 24 | - Clients now referenced by `Guid` instead of `string ipPort` 25 | - `ListClients` now returns an enumeration of `ClientMetadata` 26 | - `Send` and `Read` methods using `string ipPort` are marked obsolete 27 | - `AddClient` moved closer to connection acceptance 28 | - Target `net461` `net472` `net48` `net6.0` `net7.0` and `net8.0` 29 | - Better detection of disconnects 30 | - Disable Nagle's algorithm by default 31 | 32 | ## Examples 33 | 34 | ### Server Example 35 | ```csharp 36 | using CavemanTcp; 37 | 38 | // Instantiate 39 | TcpServer server = new TcpServer("127.0.0.1", 8000, false, null, null); 40 | server.Logger = Logger; 41 | 42 | // Set callbacks 43 | server.Events.ClientConnected += (s, e) => 44 | { 45 | Console.WriteLine("Client " + e.Client.ToString() + " connected to server"); 46 | }; 47 | server.Events.ClientDisconnected += (s, e) => 48 | { 49 | Console.WriteLine("Client " + e.Client.ToString() + " disconnected from server"); 50 | }; 51 | 52 | // Start server 53 | server.Start(); 54 | 55 | // Send [Data] to client at [guid] 56 | Guid guid = Guid.Parse("00001111-2222-3333-4444-555566667777"); 57 | WriteResult wr = null; 58 | wr = server.Send(guid, "[Data]"); 59 | wr = server.SendWithTimeout([ms], guid, "[Data]"); 60 | wr = await server.SendAsync(guid, "[Data]"); 61 | wr = await server.SendWithTimeoutAsync([ms], guid, "[Data]"); 62 | 63 | // Receive [count] bytes of data from client at [guid] 64 | ReadResult rr = null; 65 | rr = server.Read(guid, [count]); 66 | rr = server.ReadWithTimeout([ms], guid, count); 67 | rr = await server.ReadAsync(guid, [count]); 68 | rr = await server.ReadWithTimeoutAsync([ms], guid, [count]); 69 | 70 | // List clients 71 | List clients = server.GetClients().ToList(); 72 | 73 | // Disconnect a client 74 | server.DisconnectClient(guid); 75 | ``` 76 | 77 | ### Client Example 78 | ```csharp 79 | using CavemanTcp; 80 | 81 | // Instantiate 82 | TcpClient client = new TcpClient("127.0.0.1", 8000, false, null, null); 83 | client.Logger = Logger; 84 | 85 | // Set callbacks 86 | client.Events.ClientConnected += (s, e) => 87 | { 88 | Console.WriteLine("Connected to server"); 89 | }; 90 | 91 | client.Events.ClientDisconnected += (s, e) => 92 | { 93 | Console.WriteLine("Disconnected from server"); 94 | }; 95 | 96 | // Connect to server 97 | client.Connect(10); 98 | 99 | // Send data to server 100 | WriteResult wr = null; 101 | wr = client.Send("[Data]"); 102 | wr = client.SendWithTimeout([ms], "[Data]"); 103 | wr = await client.SendAsync("[Data]"); 104 | wr = await client.SendWithTimeoutAsync([ms], "[Data]"); 105 | 106 | // Read [count] bytes of data from server 107 | ReadResult rr = null; 108 | rr = client.Read([count]); 109 | rr = client.ReadWithTimeout([ms], count); 110 | rr = await client.ReadAsync([count]); 111 | rr = await client.ReadWithTimeoutAsync([ms], [count]); 112 | ``` 113 | 114 | ## WriteResult and ReadResult 115 | 116 | `WriteResult` and `ReadResult` contains a `Status` property that indicates one of the following: 117 | 118 | - `ClientNotFound` - only applicable for server read and write operations 119 | - `Success` - the operation was successful 120 | - `Timeout` - the operation timed out (reserved for future use) 121 | - `Disconnected` - the peer disconnected 122 | 123 | `WriteResult` also includes: 124 | 125 | - `BytesWritten` - the number of bytes written to the socket. 126 | 127 | `ReadResult` also includes: 128 | 129 | - `BytesRead` - the number of bytes read from the socket. 130 | - `DataStream` - a `MemoryStream` containing the requested data. 131 | - `Data` - a `byte[]` representation of `DataStream`. Using this property will fully read `DataStream` to the end. 132 | 133 | ## Local vs External Connections 134 | 135 | **IMPORTANT** 136 | * If you specify `127.0.0.1` as the listener IP address, it will only be able to accept connections from within the local host. 137 | * To accept connections from other machines: 138 | * Use a specific interface IP address, or 139 | * Use `null`, `*`, `+`, or `0.0.0.0` for the listener IP address (requires admin privileges to listen on any IP address) 140 | * Make sure you create a permit rule on your firewall to allow inbound connections on that port 141 | * If you use a port number under 1024, admin privileges will be required 142 | 143 | ## Operations with Timeouts 144 | 145 | When using any of the APIs that allow you to specify a timeout (i.e. `SendWithTimeout`, `SendWithTimeoutAsync`, `ReadWithTimeout`, and `ReadWithTimeoutAsync`), the resultant `WriteResult` and `ReadResult` as mentioned above will indicate if the operation timed out. 146 | 147 | It is important to understand what a timeout indicates and more important what it doesn't. 148 | 149 | - A timeout on a write operation has **nothing to do with whether or not the recipient read the data**. Rather it is whether or not CavemanTcp was able to write the data to the underlying `NetworkStream` or `SslStream` 150 | - A timeout on a read operation will occur if CavemanTcp is unable to read the specified number of bytes from the underlying `NetworkStream` or `SslStream` in the allotted number of milliseconds 151 | - Valid values for `timeoutMs` are `-1` or any positive integer. `-1` indicates no timeout and is the same as using an API that doesn't specify a timeout 152 | - Pay close attention to either `BytesRead` or `BytesWritten` (if you were reading or writing) in the event of a timeout. The timeout may have occurred mid-operation and therefore it will be important to recover from the failure. 153 | - For example, server sends client 50,000 bytes 154 | - On the client, a `ReadWithTimeout` was initiated with a 10 second timeout, attempting to read 50,000 bytes 155 | - In that 10 seconds, the client was only able to read 30,000 bytes 156 | - A `ReadResult` with `Status == ReadResultStatus.Timeout` is returned, and the `BytesRead` property is set to 30,000 157 | - In this case, **there are still 20,000 bytes from the server waiting in the client's underlying `NetworkStream` or `SslStream`** 158 | - As such, it is recommended that, upon timeout, you reset the connection (but this is your choice) 159 | 160 | ## TCP Keepalives 161 | 162 | As of v1.3.0, support for TCP keepalives has been added to CavemanTcp, primarily to address the issue of a network interface being shut down, the cable unplugged, or the media otherwise becoming unavailable. It is important to note that keepalives are supported in .NET Core and .NET Framework, but NOT .NET Standard. As of this release, .NET Standard provides no facilities for TCP keepalives. 163 | 164 | TCP keepalives are enabled by default. 165 | ```csharp 166 | server.Keepalive.EnableTcpKeepAlives = true; 167 | server.Keepalive.TcpKeepAliveInterval = 5; // seconds to wait before sending subsequent keepalive 168 | server.Keepalive.TcpKeepAliveTime = 5; // seconds to wait before sending a keepalive 169 | server.Keepalive.TcpKeepAliveRetryCount = 5; // number of failed keepalive probes before terminating connection 170 | ``` 171 | 172 | Some important notes about TCP keepalives: 173 | 174 | - This capability is enabled by the underlying framework and operating system, not provided by this library 175 | - Keepalives only work in .NET Core and .NET Framework 176 | - Keepalives can be enabled on either client or server, but generally only work on server (being investigated) 177 | - ```Keepalive.TcpKeepAliveRetryCount``` is only applicable to .NET Core; for .NET Framework, this value is forced to 10 178 | 179 | ## Special Thanks 180 | 181 | A special thanks to those that have helped improve the library thus far! 182 | 183 | @LeaT113 @Kliodna @zzampong @SaintedPsycho @samisil @eatyouroats @CetinOzdil @akselatom @wtarr 184 | 185 | ## Help or Feedback 186 | 187 | Need help or have feedback? Please file an issue here! 188 | 189 | ## Version History 190 | 191 | Please refer to CHANGELOG.md. 192 | 193 | ## Thanks 194 | 195 | Special thanks to VektorPicker for the free Caveman icon: http://www.vectorpicker.com/caveman-icon_490587_47.html 196 | -------------------------------------------------------------------------------- /TestDebug.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | IF [%1] == [] GOTO Usage 3 | cd src\Test.Server\bin\debug\net7.0 4 | start Test.Server.exe 5 | TIMEOUT 4 > NUL 6 | cd ..\..\..\..\.. 7 | 8 | cd src\Test.Client\bin\debug\net7.0 9 | FOR /L %%i IN (1,1,%1) DO ( 10 | ECHO Starting client %%i 11 | start Test.Client.exe 12 | TIMEOUT 2 > NUL 13 | cd ..\..\..\..\.. 14 | @echo on 15 | EXIT /b 16 | 17 | :Usage 18 | ECHO Specify the number of client nodes to start. 19 | @echo on 20 | EXIT /b -------------------------------------------------------------------------------- /assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchristn/CavemanTcp/3b36f917dca9422c88e544e85bf249f468efb804/assets/icon.ico -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchristn/CavemanTcp/3b36f917dca9422c88e544e85bf249f468efb804/assets/icon.png -------------------------------------------------------------------------------- /cavemantcp.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFqTCCA5GgAwIBAgIUALJzgmmtlYlKi/7qIP3KlaC3YN0wDQYJKoZIhvcNAQEL 3 | BQAwYzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFzAVBgNVBAcM 4 | DlNpbGljb24gVmFsbGV5MRIwEAYDVQQKDAlTaW1wbGVUY3AxEjAQBgNVBAMMCXNp 5 | bXBsZXRjcDAgFw0xODExMjkxODA2MTVaGA8yMTE4MTEwNTE4MDYxNVowYzELMAkG 6 | A1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFzAVBgNVBAcMDlNpbGljb24g 7 | VmFsbGV5MRIwEAYDVQQKDAlTaW1wbGVUY3AxEjAQBgNVBAMMCXNpbXBsZXRjcDCC 8 | AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMn8DVfN1MVCq/BfOLgTosY7 9 | jSBFU7W2//0ha0BsWrciGL9rQ80Pvpfno1twpUX3OIs0jniooKDAUNKeHJvj2fle 10 | 5lACDOOgaYslXfpiKIOskTcqj4zpOdjhgo4Itpa+KnMaEq0DXj0y45WI2Z4E4Ruj 11 | IZkVMNSsfF3OVa8ZT2R/SurFlNsy5IWqWSeodP9YBV91Lc+7AZ/GbYKy1Qu/5mS6 12 | ++z7ZA8lCr4Nn2wnnoQqdZMU28zeHCTCT/ywlse4T5LTTwDfs4P/YuN4It1lxmSm 13 | qjo4yqpxoOdlXn4CKxRMaDpz29AspU0/g7bRn5keEnPqymYQ/i03FgHbDG9mJizM 14 | 0uw4EnvuWGoilCRz1CkvpKnpZ/Tq8FTy1JIqC7wKIYa4lRLXdhPvs7L4a3jSpWTj 15 | CiXeXKc4hRrAjTNw+OyaviPJW2Bc+lZTDO24SxweGnVIJ6fdeV0SZAPKgynpXKT5 16 | KJvSsxmsCGfh937tasD5wnu6DB9gUQLvIYpDKCc8lZTYv55Exv5JEKZ/Gn8FJU5D 17 | WTlvW1UVQdYdYeppNv5KvAvSOLr08GtzxKOY2Y+k8PI1eUmBYkuo+0r1SORCvj3U 18 | Dcd3njjcFOTv2Agb4Ohbh9FY96yukQ3yxwIQT6ad6LZahr6HeKHKiP5DQ/2xDBJp 19 | I/olGBB59cRVRFgt+7zTAgMBAAGjUzBRMB0GA1UdDgQWBBSpz8QPxENL4/qbHYTV 20 | 5u2uYf8p4TAfBgNVHSMEGDAWgBSpz8QPxENL4/qbHYTV5u2uYf8p4TAPBgNVHRMB 21 | Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCU5B0o9+Vimk7bbgklFSbX2L2/ 22 | ml+oVTIvyi0mQThnXr348ddv0zROVhQnPQUcdnlGdCPiYl+Jpkyr14bnOunvjjhQ 23 | wOer3r+mF4aU/kW618uXhHIsIkiKwCBwXGfOCqd/Phnptknjf6teq/R8OgGBMUY0 24 | twqWT4y7epyA0dFm+4bQzOu1FGHIvgfpv6Hjb0+jWPId8TP2bvgU674w6fm0KKDe 25 | 7ubYVi/Q9i494WbFY5PGY2MBu82gcMSIuVasVLJAaaj6vxsrK31dYkRGWGm6YOjB 26 | 38g4TanmiK0AjVkxhhMuiOJYMFe8R2dOHJBTd6wnnnPKa+IgUk9RJEdPLXV4Pkqh 27 | 2QdI9APAjxlJ5RMZfLa1ADPOP+NBENkIo43W7Cmx+z2BLU4/9OB0doHk7MBiTKFO 28 | V58ZkJOrApY9uFvSdJELWcfQlzn0XDAP12lEnJoLEorQK3mi4peXPWZRs1Iw5dAE 29 | pFz89Qy7Bc3i8MRkMWdu1eD17iiXSAd9G12wHlgaoR4GT2vDG870ce8/aefWInmj 30 | xs2RQTlx9g7DGi4WmMozUJh1ENrgI+ppL7yJiVUCsbDejOHAh1qurvZbgIA7qiHm 31 | SZMu7dm8BPlVhLtdkhR+9pdFWm+TjEhuIsyc6sxP5SqwI4xhGxSsMDQ2DkD+laYP 32 | shLs1ionTO/MVOisLA== 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /cavemantcp.key: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | MIIJnDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIzyT8bR+zRwcCAggA 3 | MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECOSHMlux3KUYBIIJSFzHVcFK9B39 4 | 7ctTAIye62oiyvI8crMgRRmu/7FShgXn2X4s4ozM/uMLlAE3m9Fa9EQ7hp8YotUB 5 | +jnugODVmr4lNZ7CiAHmUbixvzBJU+SoWn3b8ivBLrG8D/IT6vLgyrjQNvZExOeN 6 | 2s9Bz1nu3SLORklC1yKB80n/GmPMMtb4XmygFSnQ/wx07wMIep6JKsODMJplVWPl 7 | r07bh9ndlv8oc2RZ54Tt/ApZi5lpqGbCQEgNoXheyHnabiVFiAOlXu58IvHXSOav 8 | qMjp+dK3IwqiamZNCR/8jAq6EltGzv1E/hAWZ8YgTA0s0474ESP6o5uAzVeb1yMJ 9 | PHRm4lOD4HgaYElHgL6hHN4Q4R1DSKtaMIigod6GzMYI9ZRhUiK30x9xJgDlwhjo 10 | IgkoiHZ3LzFR+dUawrUvRInyPyL5tprNx6FhYXe50CS2U99neKw/rDi7lTIoF29P 11 | jdm/JN1PLN78gBZqnjtJNFJEg7FaAtmi0/QF133JHURwN+NfZln9sCMXzmc5+jjD 12 | k7kfKgLsNHHH11V5ZXARTrQQ+r2FM3c8okj7yJXA56gQXi7MsqFI4dqUmSsHllkI 13 | DWCc0HKAo63TOw+2YCU0SRHt2MNt6oHfIVr2HxIPEmsOD2rwDGRIfgzhS+1le+hS 14 | bj8iMCq9hPhpkI2o4yQLu6Mf7FSH8N8ZwHOR32Euwwv50OES/UMa4/I2Ld1qnPa7 15 | hcllwziOAiRJSlqWTL6B75H5Vvx1+gGD3oDnNHpRBM0ld8QzYEvRM4i9BHZcXKEQ 16 | GlLU4H+Fg3WexcoEvmt7jLGTlIbZfibDtQs/IpnfAuYLFO87VGRDMHpEpDX0dzdU 17 | hPa5WGcZ+hF7GUh4KViFwdcPErYpGpNXYOhmhkyJoL/a3WJ4MuFeb2djzdEoNEth 18 | XGGFrKcojAZGynQNvR1ZT8Az7GvUrjSj3fffLMIHRjPSSugLOzDldaBD+sA/Kmaz 19 | CcO/Ufq2pav/BPoVNGgTLzaI8d3moXdRyGKf9CAIANhaxhS1M8JRlrVa2JncfbkF 20 | NB2hh/C28o90ZULBCv3nqdjBeqxWYFgCrH84uhezJNkc+TEJIwNIFur0JB4hYl4d 21 | PiqOzAdDVNhmxKzpdc0grDtxr/wWOa9+lPT4aHoUd8BmgmCTCw8yBp2+VAfQ7qOv 22 | k1x0+mlUxSJNgvU39xCm8oD/c8Sx5RYHjOIJ8FPn/tbVsr2/WuSLkZHpW3w6DXFB 23 | AoEav3A9/LcLhbkraPdwTmUH4RgGjZxS/n+hvR3mIH7IdPQMojybzuWba9EHc6a6 24 | Uf+QmwPGyOZl+DdBNqzlUSSTuRIkbKuri2p2sfT/0EsbC5bQTjR/782NzxgQ/+mJ 25 | 7nh5ThZVWZyvl/wUenq3Kze9gDstGZF+yKk+I1elaZouvds3/ead1LA+6P1dntwo 26 | zcKfYdjix6Pcn/c0HrFzvq1OWn0Gwki5+DWj4NTrUEc15Jf5OqdNYj3lv4a8muQu 27 | zwZW3VubqmATGQ1XjIvqgmteqcKNWg5KaHU7TDccBy2g5WCo5fbeQlXLWum17+OG 28 | Ffi77c0/8t72ywB4ufud+o49KzhHoAs0ylVl6CJw+3TIiZEEre8QMq2VppJdJmTD 29 | UFI4qXT/446ylN6OFUuAc1GHSIe95E/AGkoe9pDNN3QVqOJNQvqlGCWQ2myvIT7M 30 | b0TJtyyOyi1SRoaHkqmciIiALv3NG60xJ7K/CXb5aUCraoodtxxSxfVCDOEXLdTe 31 | W+PXVvVuXGLUiPWv8DuLZdml0S4ID1ZgYUjuf3VznJJcbXQQAbDgyW2Trdg+s94k 32 | Iyhyb213KpeXjEw9GcTdX2VDo/MUWUSGBG1eu3dnxJzJJK9vKNa6V8opI3KSnB9R 33 | 6TAX2p6V8nZsgzOMwB750NTE5c/V2AxZ2pjjZk2QfqNEf0nPwbCv2xeHQN7Wexja 34 | wqALP4rdJvGIs0ONVpvmvux2CTH3JantIs2r+if2V2Vd6qRSf4U4RZZG+LPsZy2a 35 | efafnQV+XsCcSaT6rK7qTvHqNpBEc6I2YL7AkrT9m0Nu0grGSrbuFNkzOqnOS9Le 36 | E24YZxn0fQGQu37Mntp2umDX6ixnPTA2gRExdxiSoyMs3pUAHPthxJiFN6gcyNI9 37 | i3tE21hSHpcEeEzArukdhM8wn8Utph2bmP+AgTWfkotCrFHr5clOiawjIkHcUT/w 38 | yeIDUIT5Ruc0x+4STDAwJTSUQ+DcDeM3yHnmSaint3ih55BnIO3uCwlaY+dsXRkN 39 | 4ZTlFFHHQoZV7Ok/3DP2nS8AeNNWPnqKtf/s0IX5zKZUG671G5lCJUJxLShktCKY 40 | y54m+ZRlJXfuCTthO/Htgio/hWnUXodLefZt4IwiRuBMdYbXjC67gVqpEuoiOpsj 41 | q9I4QXT0PTvFkusE/CxcKQkdF9LHwoV2Dz5xV9adjIDJqoAehX2qI8vOe0+81jkC 42 | Qo1sjSp35C59oT5BAgyebuDgWfcH247wr6RxH4rOuBGCK4IXWv9slYA9lQz/D0IM 43 | LiISwo6g6xpn0PW3jnKXzvk5Avio8/Wg+2eCzQT4KWg56M4lkDKc6b7+njm4d4mA 44 | qNN+wm+Xw7Vq+6ulRnwrU4jy22jHQ10jbWupro1QQ4eHwTvHrj8v+jKPhIUTD9xJ 45 | h3eFizCY31jgUzlV4GNPJ+JophZh0F4PWDFVfRq3rh7LVRbYz+dFcDeIJuuQQv70 46 | WLpdSyQszq2lKvCVUvG985E5fD3l2tHRK/njc7eiQFPLcK0D5RBfI5mzD9cCchFK 47 | WhJOQATzKH8/i6toP0f5yd0w8vjO/0WKpQfG7tbeyt8YQVGolRwGQJmEFpydRiT9 48 | VAYfWS2hUCRbdYmB1hoFrY0tPeRlOhRoRmcIiCXmb4joioFiAZ3q2UnswbnfWKiK 49 | oEBK+KtulXttImREuWFOE4hwvv0B6Dm3TPagMEymn4Ko/ql2Vj9VaXeEj1JAuM4A 50 | NyE5lWW/rfmi9Xbi4Xs/buNWlnG3nn5rVn/XXQK71ip5cjrm8uGI5Rkhmk4r6pXC 51 | cRKM6XjZeRu++6Q2p7UnAU2mjU8ps7HzsFtWMq1UySkJ4JaLsyOOAt338SQw928r 52 | VL2AhrS20lEbrBxZbQGiyz5sTBahWJ127ploEo4C6cJuLwAQmzaIyS3GQ9XHn9Aj 53 | n7xqA1MJhdSXXrOzUzs1VQ== 54 | -----END ENCRYPTED PRIVATE KEY----- 55 | -------------------------------------------------------------------------------- /cavemantcp.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchristn/CavemanTcp/3b36f917dca9422c88e544e85bf249f468efb804/cavemantcp.pfx -------------------------------------------------------------------------------- /certificate-instructions.txt: -------------------------------------------------------------------------------- 1 | TcpWrapper Certificate Details 2 | Created using OpenSSL. This is a self-signed certificate and should NOT be used in production. 3 | 4 | openssl req -x509 -newkey rsa:4096 -keyout cavemantcp.key -out cavemantcp.crt -days 36500 5 | openssl pkcs12 -export -out cavemantcp.pfx -inkey cavemantcp.key -in cavemantcp.crt 6 | 7 | Export password is simpletcp 8 | -------------------------------------------------------------------------------- /src/CavemanTcp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29411.108 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CavemanTcp", "CavemanTcp\CavemanTcp.csproj", "{AEE995D8-F936-4AF0-9DCB-F60EE36DBFE4}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test.Client", "Test.Client\Test.Client.csproj", "{B60431D5-8EC8-4ECE-AD5A-E564674F27A5}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test.Server", "Test.Server\Test.Server.csproj", "{21CF3C62-8F45-4166-BF45-7C48307C8889}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test.SslClient", "Test.SslClient\Test.SslClient.csproj", "{6822B83D-0FBA-4A48-8D51-0628C6CF53A3}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test.SslServer", "Test.SslServer\Test.SslServer.csproj", "{A44C2D24-AD79-45F2-8121-4B0DA10FC939}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test.ClientAsync", "Test.ClientAsync\Test.ClientAsync.csproj", "{2F5406DC-EB8C-4043-B209-99604391DBD1}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test.ServerAsync", "Test.ServerAsync\Test.ServerAsync.csproj", "{0D4DACA2-77D0-4742-A733-FD90E3B9B175}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test.Disconnect", "Test.Disconnect\Test.Disconnect.csproj", "{D94B4C24-C9C7-4BA8-8430-8A49328AC68C}" 21 | EndProject 22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.HttpLoopback", "Test.HttpLoopback\Test.HttpLoopback.csproj", "{589815FB-5847-4288-8986-D45925AA1DDC}" 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {AEE995D8-F936-4AF0-9DCB-F60EE36DBFE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {AEE995D8-F936-4AF0-9DCB-F60EE36DBFE4}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {AEE995D8-F936-4AF0-9DCB-F60EE36DBFE4}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {AEE995D8-F936-4AF0-9DCB-F60EE36DBFE4}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {B60431D5-8EC8-4ECE-AD5A-E564674F27A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {B60431D5-8EC8-4ECE-AD5A-E564674F27A5}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {B60431D5-8EC8-4ECE-AD5A-E564674F27A5}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {B60431D5-8EC8-4ECE-AD5A-E564674F27A5}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {21CF3C62-8F45-4166-BF45-7C48307C8889}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {21CF3C62-8F45-4166-BF45-7C48307C8889}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {21CF3C62-8F45-4166-BF45-7C48307C8889}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {21CF3C62-8F45-4166-BF45-7C48307C8889}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {6822B83D-0FBA-4A48-8D51-0628C6CF53A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {6822B83D-0FBA-4A48-8D51-0628C6CF53A3}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {6822B83D-0FBA-4A48-8D51-0628C6CF53A3}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {6822B83D-0FBA-4A48-8D51-0628C6CF53A3}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {A44C2D24-AD79-45F2-8121-4B0DA10FC939}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {A44C2D24-AD79-45F2-8121-4B0DA10FC939}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {A44C2D24-AD79-45F2-8121-4B0DA10FC939}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {A44C2D24-AD79-45F2-8121-4B0DA10FC939}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {2F5406DC-EB8C-4043-B209-99604391DBD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {2F5406DC-EB8C-4043-B209-99604391DBD1}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {2F5406DC-EB8C-4043-B209-99604391DBD1}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {2F5406DC-EB8C-4043-B209-99604391DBD1}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {0D4DACA2-77D0-4742-A733-FD90E3B9B175}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {0D4DACA2-77D0-4742-A733-FD90E3B9B175}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {0D4DACA2-77D0-4742-A733-FD90E3B9B175}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {0D4DACA2-77D0-4742-A733-FD90E3B9B175}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {D94B4C24-C9C7-4BA8-8430-8A49328AC68C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {D94B4C24-C9C7-4BA8-8430-8A49328AC68C}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {D94B4C24-C9C7-4BA8-8430-8A49328AC68C}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {D94B4C24-C9C7-4BA8-8430-8A49328AC68C}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {589815FB-5847-4288-8986-D45925AA1DDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {589815FB-5847-4288-8986-D45925AA1DDC}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {589815FB-5847-4288-8986-D45925AA1DDC}.Release|Any CPU.ActiveCfg = Release|Any CPU 65 | {589815FB-5847-4288-8986-D45925AA1DDC}.Release|Any CPU.Build.0 = Release|Any CPU 66 | EndGlobalSection 67 | GlobalSection(SolutionProperties) = preSolution 68 | HideSolutionNode = FALSE 69 | EndGlobalSection 70 | GlobalSection(ExtensibilityGlobals) = postSolution 71 | SolutionGuid = {F66769D1-8F26-4307-A737-E27BB893E901} 72 | EndGlobalSection 73 | EndGlobal 74 | -------------------------------------------------------------------------------- /src/CavemanTcp/CavemanTcp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;netstandard2.1;net462;net472;net48;net6.0;net8.0 5 | 2.0.9 6 | Joel Christner 7 | CavemanTcp is a simple TCP client and server providing easy integration and full control over network reads and writes, allowing you to build your own state machines with ease. 8 | Fix client-side memory leak 9 | 10 | (c)2025 Joel Christner 11 | 12 | https://github.com/jchristn/cavemantcp 13 | https://github.com/jchristn/cavemantcp 14 | Github 15 | tcp messaging socket message sockets api rpc 16 | CavemanTcp 17 | CavemanTcp 18 | 19 | LICENSE.md 20 | icon.png 21 | README.md 22 | true 23 | true 24 | CavemanTcp.xml 25 | icon.ico 26 | CavemanTcp 27 | True 28 | snupkg 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | True 38 | \ 39 | 40 | 41 | True 42 | 43 | 44 | 45 | True 46 | 47 | Always 48 | 49 | 50 | 51 | 52 | 53 | Always 54 | 55 | 56 | Always 57 | 58 | 59 | Always 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/CavemanTcp/CavemanTcpClientEvents.cs: -------------------------------------------------------------------------------- 1 | namespace CavemanTcp 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | /// 8 | /// CavemanTcp client events. 9 | /// 10 | public class CavemanTcpClientEvents 11 | { 12 | #region Public-Members 13 | 14 | /// 15 | /// Event to fire when the client connects. 16 | /// 17 | public event EventHandler ClientConnected; 18 | 19 | /// 20 | /// Event to fire when the client disconnects. 21 | /// 22 | public event EventHandler ClientDisconnected; 23 | 24 | /// 25 | /// Event to fire when an exception is encountered. 26 | /// 27 | public event EventHandler ExceptionEncountered; 28 | 29 | #endregion 30 | 31 | #region Private-Members 32 | 33 | private bool _ClientConnectedFiring = false; 34 | private bool _ClientDisconnectedFiring = false; 35 | 36 | #endregion 37 | 38 | #region Constructors-and-Factories 39 | 40 | /// 41 | /// Instantiate the object. 42 | /// 43 | public CavemanTcpClientEvents() 44 | { 45 | 46 | } 47 | 48 | #endregion 49 | 50 | #region Public-Methods 51 | 52 | #endregion 53 | 54 | #region Internal-Methods 55 | 56 | internal void HandleClientConnected(object sender) 57 | { 58 | if (_ClientConnectedFiring) return; 59 | 60 | _ClientConnectedFiring = true; 61 | WrappedEventHandler(() => ClientConnected?.Invoke(sender, EventArgs.Empty), "ClientConnected", sender); 62 | _ClientConnectedFiring = false; 63 | } 64 | 65 | internal void HandleClientDisconnected(object sender) 66 | { 67 | if (_ClientDisconnectedFiring) return; 68 | 69 | _ClientDisconnectedFiring = true; 70 | WrappedEventHandler(() => ClientDisconnected?.Invoke(sender, EventArgs.Empty), "ClientDisconnected", sender); 71 | _ClientDisconnectedFiring = false; 72 | } 73 | 74 | internal void HandleExceptionEncountered(object sender, Exception e) 75 | { 76 | ExceptionEventArgs args = new ExceptionEventArgs(e); 77 | WrappedEventHandler(() => ExceptionEncountered?.Invoke(sender, args), "ExceptionEncountered", sender); 78 | } 79 | 80 | #endregion 81 | 82 | #region Private-Methods 83 | 84 | private void WrappedEventHandler(Action action, string handler, object sender) 85 | { 86 | if (action == null) return; 87 | 88 | Action logger = ((CavemanTcpClient)sender).Logger; 89 | 90 | try 91 | { 92 | action.Invoke(); 93 | } 94 | catch (Exception e) 95 | { 96 | logger?.Invoke("Event handler exception in " + handler + ": " + e.Message); 97 | } 98 | } 99 | 100 | #endregion 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/CavemanTcp/CavemanTcpClientSettings.cs: -------------------------------------------------------------------------------- 1 | namespace CavemanTcp 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | /// 8 | /// CavemanTcp client settings. 9 | /// 10 | public class CavemanTcpClientSettings 11 | { 12 | #region Public-Members 13 | 14 | /// 15 | /// Buffer size to use while interacting with streams. 16 | /// 17 | public int StreamBufferSize 18 | { 19 | get 20 | { 21 | return _StreamBufferSize; 22 | } 23 | set 24 | { 25 | if (value < 1) throw new ArgumentException("StreamBufferSize must be greater than zero."); 26 | _StreamBufferSize = value; 27 | } 28 | } 29 | 30 | /// 31 | /// Enable or disable acceptance of invalid SSL certificates. 32 | /// 33 | public bool AcceptInvalidCertificates { get; set; } = true; 34 | 35 | /// 36 | /// Enable or disable this to tell the TcpClient perform certificate revocation checks. This is not to be confused 37 | /// with AcceptInvalidCertificates setting which decides whether to ignore issues that arise in the call back during the 38 | /// authentication phase. 39 | /// 40 | /// With this setting on, in addition to the certificate validation the client performs a revocation check by using either 41 | /// the Certificate Revocation List (CRL) or the Online Certificate Status Protocol (OCSP). 42 | /// This may not always be needed, which is why this option is exposed here. 43 | /// 44 | /// By default this value is set to false. If AcceptInvalidCertificates is set to true, then this value will be 45 | /// set to false at runtime. 46 | /// 47 | public bool CheckCertificateRevocation { get; set; } = false; 48 | 49 | /// 50 | /// Enable or disable mutual authentication of SSL client and server. 51 | /// 52 | public bool MutuallyAuthenticate { get; set; } = false; 53 | 54 | /// 55 | /// Enable or disable connection monitor. 56 | /// 57 | public bool EnableConnectionMonitor { get; set; } = true; 58 | 59 | /// 60 | /// Connection monitor polling interval, in microseconds. 61 | /// 62 | public int PollIntervalMicroSeconds 63 | { 64 | get 65 | { 66 | return _PollIntervalMicroseconds; 67 | } 68 | set 69 | { 70 | if (value > -1 || value < 1) throw new ArgumentException("Poll interval microseconds must be -1 (indefinite) or greater than zero."); 71 | _PollIntervalMicroseconds = value; 72 | } 73 | } 74 | 75 | #endregion 76 | 77 | #region Private-Members 78 | 79 | private int _StreamBufferSize = 65536; 80 | private int _PollIntervalMicroseconds = -1; 81 | 82 | #endregion 83 | 84 | #region Constructors-and-Factories 85 | 86 | /// 87 | /// Instantiate the object. 88 | /// 89 | public CavemanTcpClientSettings() 90 | { 91 | 92 | } 93 | 94 | #endregion 95 | 96 | #region Public-Methods 97 | 98 | #endregion 99 | 100 | #region Private-Methods 101 | 102 | #endregion 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/CavemanTcp/CavemanTcpKeepaliveSettings.cs: -------------------------------------------------------------------------------- 1 | namespace CavemanTcp 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | /// 8 | /// CavemanTcp keepalive settings. 9 | /// Keepalive probes are sent after an idle period defined by TcpKeepAliveTime (seconds). 10 | /// Should a keepalive response not be received within TcpKeepAliveInterval (seconds), a subsequent keepalive probe will be sent. 11 | /// For .NET Framework, should 10 keepalive probes fail, the connection will terminate. 12 | /// For .NET Core, should a number of probes fail as specified in TcpKeepAliveRetryCount, the connection will terminate. 13 | /// TCP keepalives are not supported in .NET Standard. 14 | /// 15 | public class CavemanTcpKeepaliveSettings 16 | { 17 | #region Public-Members 18 | 19 | /// 20 | /// Enable or disable TCP-based keepalive probes. 21 | /// TCP keepalives are only supported in .NET Core and .NET Framework projects. .NET Standard does not provide facilities to support TCP keepalives. 22 | /// 23 | public bool EnableTcpKeepAlives = false; 24 | 25 | /// 26 | /// TCP keepalive interval, i.e. the number of seconds a TCP connection will wait for a keepalive response before sending another keepalive probe. 27 | /// Default is 5 seconds. Value must be greater than zero. 28 | /// 29 | public int TcpKeepAliveInterval 30 | { 31 | get 32 | { 33 | return _TcpKeepAliveInterval; 34 | } 35 | set 36 | { 37 | if (value < 1) throw new ArgumentException("TcpKeepAliveInterval must be greater than zero."); 38 | _TcpKeepAliveInterval = value; 39 | } 40 | } 41 | 42 | /// 43 | /// TCP keepalive time, i.e. the number of seconds a TCP connection will remain alive/idle before keepalive probes are sent to the remote. 44 | /// Default is 5 seconds. Value must be greater than zero. 45 | /// 46 | public int TcpKeepAliveTime 47 | { 48 | get 49 | { 50 | return _TcpKeepAliveTime; 51 | } 52 | set 53 | { 54 | if (value < 1) throw new ArgumentException("TcpKeepAliveTime must be greater than zero."); 55 | _TcpKeepAliveTime = value; 56 | } 57 | } 58 | 59 | /// 60 | /// TCP keepalive retry count, i.e. the number of times a TCP probe will be sent in effort to verify the connection. 61 | /// After the specified number of probes fail, the connection will be terminated. 62 | /// 63 | public int TcpKeepAliveRetryCount 64 | { 65 | get 66 | { 67 | return _TcpKeepAliveRetryCount; 68 | } 69 | set 70 | { 71 | if (value < 1) throw new ArgumentException("TcpKeepAliveRetryCount must be greater than zero."); 72 | _TcpKeepAliveRetryCount = value; 73 | } 74 | } 75 | 76 | #endregion 77 | 78 | #region Private-Members 79 | 80 | private int _TcpKeepAliveInterval = 2; 81 | private int _TcpKeepAliveTime = 2; 82 | private int _TcpKeepAliveRetryCount = 3; 83 | 84 | #endregion 85 | 86 | #region Constructors-and-Factories 87 | 88 | /// 89 | /// Instantiate the object. 90 | /// 91 | public CavemanTcpKeepaliveSettings() 92 | { 93 | 94 | } 95 | 96 | #endregion 97 | 98 | #region Public-Methods 99 | 100 | #endregion 101 | 102 | #region Private-Methods 103 | 104 | #endregion 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/CavemanTcp/CavemanTcpServerCallbacks.cs: -------------------------------------------------------------------------------- 1 | namespace CavemanTcp 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | /// 8 | /// CavemanTcp server callbacks. 9 | /// 10 | public class CavemanTcpServerCallbacks 11 | { 12 | #region Public-Members 13 | 14 | /// 15 | /// Callback to invoke when a connection is received to permit or deny the connection. 16 | /// 17 | public Func AuthorizeConnection = null; 18 | 19 | #endregion 20 | 21 | #region Private-Members 22 | 23 | #endregion 24 | 25 | #region Constructors-and-Factories 26 | 27 | /// 28 | /// Instantiate the object. 29 | /// 30 | public CavemanTcpServerCallbacks() 31 | { 32 | 33 | } 34 | 35 | #endregion 36 | 37 | #region Public-Methods 38 | 39 | #endregion 40 | 41 | #region Internal-Methods 42 | 43 | #endregion 44 | 45 | #region Private-Methods 46 | 47 | #endregion 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/CavemanTcp/CavemanTcpServerEvents.cs: -------------------------------------------------------------------------------- 1 | namespace CavemanTcp 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | /// 8 | /// CavemanTcp server events. 9 | /// 10 | public class CavemanTcpServerEvents 11 | { 12 | #region Public-Members 13 | 14 | /// 15 | /// Event to fire when a client connects. A string containing the client IP:port will be passed. 16 | /// 17 | public event EventHandler ClientConnected; 18 | 19 | /// 20 | /// Event to fire when a client disconnects. A string containing the client IP:port will be passed. 21 | /// 22 | public event EventHandler ClientDisconnected; 23 | 24 | /// 25 | /// Event to fire when an exception is encountered. 26 | /// 27 | public event EventHandler ExceptionEncountered; 28 | 29 | #endregion 30 | 31 | #region Private-Members 32 | 33 | #endregion 34 | 35 | #region Constructors-and-Factories 36 | 37 | /// 38 | /// Instantiate the object. 39 | /// 40 | public CavemanTcpServerEvents() 41 | { 42 | 43 | } 44 | 45 | #endregion 46 | 47 | #region Public-Methods 48 | 49 | #endregion 50 | 51 | #region Internal-Methods 52 | 53 | internal void HandleClientConnected(object sender, ClientConnectedEventArgs args) 54 | { 55 | WrappedEventHandler(() => ClientConnected?.Invoke(sender, args), "ClientConnected", sender); 56 | } 57 | 58 | internal void HandleClientDisconnected(object sender, ClientDisconnectedEventArgs args) 59 | { 60 | WrappedEventHandler(() => ClientDisconnected?.Invoke(sender, args), "ClientDisconnected", sender); 61 | } 62 | 63 | internal void HandleExceptionEncountered(object sender, Exception e) 64 | { 65 | ExceptionEventArgs args = new ExceptionEventArgs(e); 66 | WrappedEventHandler(() => ExceptionEncountered?.Invoke(sender, args), "ExceptionEncountered", sender); 67 | } 68 | 69 | #endregion 70 | 71 | #region Private-Methods 72 | 73 | private void WrappedEventHandler(Action action, string handler, object sender) 74 | { 75 | if (action == null) return; 76 | 77 | Action logger = ((CavemanTcpServer)sender).Logger; 78 | 79 | try 80 | { 81 | action.Invoke(); 82 | } 83 | catch (Exception e) 84 | { 85 | logger?.Invoke("Event handler exception in " + handler + ": " + e.Message); 86 | } 87 | } 88 | 89 | #endregion 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/CavemanTcp/CavemanTcpServerSettings.cs: -------------------------------------------------------------------------------- 1 | namespace CavemanTcp 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | /// 8 | /// CavemanTcp server settings. 9 | /// 10 | public class CavemanTcpServerSettings 11 | { 12 | #region Public-Members 13 | 14 | /// 15 | /// Buffer size to use while interacting with streams. 16 | /// 17 | public int StreamBufferSize 18 | { 19 | get 20 | { 21 | return _StreamBufferSize; 22 | } 23 | set 24 | { 25 | if (value < 1) throw new ArgumentException("StreamBufferSize must be greater than zero."); 26 | _StreamBufferSize = value; 27 | } 28 | } 29 | 30 | /// 31 | /// Enable client connection monitoring, which checks connectivity every second. 32 | /// 33 | public bool MonitorClientConnections = true; 34 | 35 | /// 36 | /// Enable or disable acceptance of invalid SSL certificates. 37 | /// 38 | public bool AcceptInvalidCertificates = true; 39 | 40 | /// 41 | /// Enable or disable mutual authentication of SSL client and server. 42 | /// 43 | public bool MutuallyAuthenticate = false; 44 | 45 | /// 46 | /// Maximum number of connections the server will accept. 47 | /// Default is 4096. Value must be greater than zero. 48 | /// 49 | public int MaxConnections 50 | { 51 | get 52 | { 53 | return _MaxConnections; 54 | } 55 | set 56 | { 57 | if (value < 1) throw new ArgumentException("Max connections must be greater than zero."); 58 | _MaxConnections = value; 59 | } 60 | } 61 | 62 | /// 63 | /// The list of permitted IP addresses from which connections can be received. 64 | /// 65 | public List PermittedIPs 66 | { 67 | get 68 | { 69 | return _PermittedIPs; 70 | } 71 | set 72 | { 73 | if (value == null) _PermittedIPs = new List(); 74 | else _PermittedIPs = value; 75 | } 76 | } 77 | 78 | /// 79 | /// The list of blocked IP addresses from which connections will be declined. 80 | /// 81 | public List BlockedIPs 82 | { 83 | get 84 | { 85 | return _BlockedIPs; 86 | } 87 | set 88 | { 89 | if (value == null) _BlockedIPs = new List(); 90 | else _BlockedIPs = value; 91 | } 92 | } 93 | 94 | #endregion 95 | 96 | #region Private-Members 97 | 98 | private int _StreamBufferSize = 65536; 99 | private int _MaxConnections = 4096; 100 | private List _PermittedIPs = new List(); 101 | private List _BlockedIPs = new List(); 102 | 103 | #endregion 104 | 105 | #region Constructors-and-Factories 106 | 107 | /// 108 | /// Instantiate the object. 109 | /// 110 | public CavemanTcpServerSettings() 111 | { 112 | 113 | } 114 | 115 | #endregion 116 | 117 | #region Public-Methods 118 | 119 | #endregion 120 | 121 | #region Private-Methods 122 | 123 | #endregion 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/CavemanTcp/CavemanTcpStatistics.cs: -------------------------------------------------------------------------------- 1 | namespace CavemanTcp 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading; 7 | 8 | /// 9 | /// CavemanTcp statistics. 10 | /// 11 | public class CavemanTcpStatistics 12 | { 13 | #region Public-Members 14 | 15 | /// 16 | /// The time at which the client or server was started. 17 | /// 18 | public DateTime StartTime 19 | { 20 | get 21 | { 22 | return _StartTime; 23 | } 24 | } 25 | 26 | /// 27 | /// The amount of time which the client or server has been up. 28 | /// 29 | public TimeSpan UpTime 30 | { 31 | get 32 | { 33 | return DateTime.Now.ToUniversalTime() - _StartTime; 34 | } 35 | } 36 | 37 | /// 38 | /// The number of bytes received. 39 | /// 40 | public long ReceivedBytes 41 | { 42 | get 43 | { 44 | return _ReceivedBytes; 45 | } 46 | internal set 47 | { 48 | _ReceivedBytes = value; 49 | } 50 | } 51 | 52 | /// 53 | /// The number of bytes sent. 54 | /// 55 | public long SentBytes 56 | { 57 | get 58 | { 59 | return _SentBytes; 60 | } 61 | internal set 62 | { 63 | _SentBytes = value; 64 | } 65 | } 66 | 67 | #endregion 68 | 69 | #region Private-Members 70 | 71 | private DateTime _StartTime = DateTime.Now.ToUniversalTime(); 72 | private long _ReceivedBytes = 0; 73 | private long _SentBytes = 0; 74 | 75 | #endregion 76 | 77 | #region Constructors-and-Factories 78 | 79 | /// 80 | /// Initialize the statistics object. 81 | /// 82 | public CavemanTcpStatistics() 83 | { 84 | 85 | } 86 | 87 | #endregion 88 | 89 | #region Public-Methods 90 | 91 | /// 92 | /// Return human-readable version of the object. 93 | /// 94 | /// 95 | public override string ToString() 96 | { 97 | string ret = 98 | "--- Statistics ---" + Environment.NewLine + 99 | " Started : " + _StartTime.ToString() + Environment.NewLine + 100 | " Uptime : " + UpTime.ToString() + Environment.NewLine + 101 | " Received bytes : " + ReceivedBytes + Environment.NewLine + 102 | " Sent bytes : " + SentBytes + Environment.NewLine; 103 | return ret; 104 | } 105 | 106 | /// 107 | /// Reset statistics other than StartTime and UpTime. 108 | /// 109 | public void Reset() 110 | { 111 | _ReceivedBytes = 0; 112 | _SentBytes = 0; 113 | } 114 | 115 | internal void AddReceivedBytes(long bytes) 116 | { 117 | _ReceivedBytes = Interlocked.Add(ref _ReceivedBytes, bytes); 118 | } 119 | 120 | internal void AddSentBytes(long bytes) 121 | { 122 | _SentBytes = Interlocked.Add(ref _SentBytes, bytes); 123 | } 124 | 125 | #endregion 126 | 127 | #region Private-Methods 128 | 129 | #endregion 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/CavemanTcp/ClientConnectedEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace CavemanTcp 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Net; 6 | using System.Text; 7 | 8 | /// 9 | /// Arguments for client connection events. 10 | /// 11 | public class ClientConnectedEventArgs : EventArgs 12 | { 13 | #region Public-Members 14 | 15 | 16 | /// 17 | /// Client metadata. 18 | /// 19 | public ClientMetadata Client { get; } 20 | 21 | /// 22 | /// Local endpoint. 23 | /// 24 | public EndPoint LocalEndpoint { get; } 25 | 26 | #endregion 27 | 28 | #region Private-Members 29 | 30 | #endregion 31 | 32 | #region Constructors-and-Factories 33 | 34 | internal ClientConnectedEventArgs(ClientMetadata client, EndPoint localEndpoint) 35 | { 36 | Client = client; 37 | LocalEndpoint = localEndpoint; 38 | } 39 | 40 | #endregion 41 | 42 | #region Public-Methods 43 | 44 | #endregion 45 | 46 | #region Private-Methods 47 | 48 | #endregion 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/CavemanTcp/ClientDeclinedEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace CavemanTcp 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | /// 8 | /// Arguments for situations where client connections are declined. 9 | /// 10 | public class ClientDeclinedEventArgs : EventArgs 11 | { 12 | internal ClientDeclinedEventArgs(string ipPort, DisconnectReason reason = DisconnectReason.ConnectionDeclined) 13 | { 14 | IpPort = ipPort; 15 | Reason = reason; 16 | } 17 | 18 | /// 19 | /// The IP address and port number of the disconnected client socket. 20 | /// 21 | public string IpPort { get; } 22 | 23 | /// 24 | /// The reason for the disconnection. 25 | /// 26 | public DisconnectReason Reason { get; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/CavemanTcp/ClientDisconnectedEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace CavemanTcp 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Net; 6 | using System.Text; 7 | 8 | /// 9 | /// Arguments for client disconnection events. 10 | /// 11 | public class ClientDisconnectedEventArgs : EventArgs 12 | { 13 | #region Public-Members 14 | 15 | /// 16 | /// Client metadata. 17 | /// 18 | public ClientMetadata Client { get; } 19 | 20 | /// 21 | /// The reason for the disconnection. 22 | /// 23 | public DisconnectReason Reason { get; } 24 | 25 | #endregion 26 | 27 | #region Private-Members 28 | 29 | #endregion 30 | 31 | #region Constructors-and-Factories 32 | 33 | internal ClientDisconnectedEventArgs(ClientMetadata client, DisconnectReason reason) 34 | { 35 | Client = client; 36 | Reason = reason; 37 | } 38 | 39 | #endregion 40 | 41 | #region Public-Methods 42 | 43 | #endregion 44 | 45 | #region Private-Methods 46 | 47 | #endregion 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/CavemanTcp/ClientMetadata.cs: -------------------------------------------------------------------------------- 1 | namespace CavemanTcp 2 | { 3 | using System; 4 | using System.Net; 5 | using System.Net.Security; 6 | using System.Net.Sockets; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | /// 11 | /// Client metadata. 12 | /// 13 | public class ClientMetadata : IDisposable 14 | { 15 | #region Public-Members 16 | 17 | /// 18 | /// Globally-unique identifier for the connection. 19 | /// 20 | public Guid Guid { get; } = Guid.NewGuid(); 21 | 22 | /// 23 | /// IP:port. 24 | /// 25 | public string IpPort 26 | { 27 | get { return _IpPort; } 28 | } 29 | 30 | /// 31 | /// Name for the client, managed by the developer (you). 32 | /// 33 | public string Name { get; set; } = null; 34 | 35 | /// 36 | /// Metadata for the client, managed by the developer (you). 37 | /// 38 | public object Metadata { get; set; } = null; 39 | 40 | #endregion 41 | 42 | #region Internal-Members 43 | 44 | internal System.Net.Sockets.TcpClient Client 45 | { 46 | get { return _TcpClient; } 47 | } 48 | 49 | internal NetworkStream NetworkStream 50 | { 51 | get { return _NetworkStream; } 52 | } 53 | 54 | internal SslStream SslStream 55 | { 56 | get { return _SslStream; } 57 | set { _SslStream = value; } 58 | } 59 | 60 | internal SemaphoreSlim ReadSemaphore 61 | { 62 | get { return _ReadSemaphore; } 63 | } 64 | 65 | internal SemaphoreSlim WriteSemaphore 66 | { 67 | get { return _WriteSemaphore; } 68 | } 69 | 70 | internal CancellationTokenSource TokenSource { get; set; } 71 | 72 | internal CancellationToken Token { get; set; } 73 | 74 | #endregion 75 | 76 | #region Private-Members 77 | 78 | private System.Net.Sockets.TcpClient _TcpClient = null; 79 | private NetworkStream _NetworkStream = null; 80 | private SslStream _SslStream = null; 81 | private string _IpPort = null; 82 | private SemaphoreSlim _ReadSemaphore = new SemaphoreSlim(1, 1); 83 | private SemaphoreSlim _WriteSemaphore = new SemaphoreSlim(1, 1); 84 | 85 | #endregion 86 | 87 | #region Constructors-and-Factories 88 | 89 | internal ClientMetadata(System.Net.Sockets.TcpClient tcp) 90 | { 91 | if (tcp == null) throw new ArgumentNullException(nameof(tcp)); 92 | 93 | _TcpClient = tcp; 94 | _NetworkStream = tcp.GetStream(); 95 | _IpPort = tcp.Client.RemoteEndPoint.ToString(); 96 | 97 | TokenSource = new CancellationTokenSource(); 98 | Token = TokenSource.Token; 99 | } 100 | 101 | #endregion 102 | 103 | #region Public-Methods 104 | 105 | /// 106 | /// Tear down the object and dispose of resources. 107 | /// 108 | public void Dispose() 109 | { 110 | if (TokenSource != null) 111 | { 112 | if (!TokenSource.IsCancellationRequested) TokenSource.Cancel(); 113 | TokenSource.Dispose(); 114 | TokenSource = null; 115 | } 116 | 117 | if (_SslStream != null) 118 | { 119 | try 120 | { 121 | _SslStream.Close(); 122 | } 123 | catch (Exception) 124 | { 125 | 126 | } 127 | } 128 | 129 | if (_NetworkStream != null) 130 | { 131 | try 132 | { 133 | _NetworkStream.Close(); 134 | } 135 | catch (Exception) 136 | { 137 | 138 | } 139 | } 140 | 141 | if (_TcpClient != null) 142 | { 143 | try 144 | { 145 | _TcpClient.Close(); 146 | _TcpClient.Dispose(); 147 | _TcpClient = null; 148 | } 149 | catch (Exception) 150 | { 151 | 152 | } 153 | } 154 | } 155 | 156 | /// 157 | /// Human-readable representation of the object. 158 | /// 159 | /// 160 | public override string ToString() 161 | { 162 | string ret = "["; 163 | ret += Guid.ToString() + "|" + IpPort; 164 | if (!String.IsNullOrEmpty(Name)) ret += "|" + Name; 165 | ret += "]"; 166 | return ret; 167 | } 168 | 169 | #endregion 170 | 171 | #region Private-Methods 172 | 173 | #endregion 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/CavemanTcp/Common.cs: -------------------------------------------------------------------------------- 1 | namespace CavemanTcp 2 | { 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Net; 9 | using System.Net.Security; 10 | using System.Net.Sockets; 11 | using System.Security.Authentication; 12 | using System.Security.Cryptography.X509Certificates; 13 | using System.Text; 14 | using System.Threading; 15 | using System.Threading.Tasks; 16 | 17 | internal static class Common 18 | { 19 | internal static SslProtocols GetSslProtocol 20 | { 21 | get 22 | { 23 | SslProtocols protocols = SslProtocols.Tls12; 24 | 25 | #if NET5_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER 26 | 27 | protocols |= SslProtocols.Tls13; 28 | 29 | #endif 30 | 31 | return protocols; 32 | } 33 | } 34 | 35 | internal static byte[] StreamToBytes(Stream input) 36 | { 37 | if (input == null) throw new ArgumentNullException(nameof(input)); 38 | if (!input.CanRead) throw new InvalidOperationException("Input stream is not readable"); 39 | 40 | byte[] buffer = new byte[16 * 1024]; 41 | using (MemoryStream ms = new MemoryStream()) 42 | { 43 | int read; 44 | 45 | while ((read = input.Read(buffer, 0, buffer.Length)) > 0) 46 | { 47 | ms.Write(buffer, 0, read); 48 | } 49 | 50 | return ms.ToArray(); 51 | } 52 | } 53 | 54 | internal static void ParseIpPort(string ipPort, out string ip, out int port) 55 | { 56 | if (String.IsNullOrEmpty(ipPort)) throw new ArgumentNullException(nameof(ipPort)); 57 | 58 | ip = null; 59 | port = -1; 60 | 61 | int colonIndex = ipPort.LastIndexOf(':'); 62 | if (colonIndex != -1) 63 | { 64 | ip = ipPort.Substring(0, colonIndex); 65 | port = Convert.ToInt32(ipPort.Substring(colonIndex + 1)); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/CavemanTcp/DisconnectReason.cs: -------------------------------------------------------------------------------- 1 | namespace CavemanTcp 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | /// 8 | /// Reason why a client disconnected. 9 | /// 10 | public enum DisconnectReason 11 | { 12 | /// 13 | /// Normal disconnection. 14 | /// 15 | Normal = 0, 16 | /// 17 | /// Client connection was intentionally terminated programmatically or by the server. 18 | /// 19 | Kicked = 1, 20 | /// 21 | /// Client connection timed out; server did not receive data within the timeout window. 22 | /// 23 | Timeout = 2, 24 | /// 25 | /// The connection was declined. 26 | /// 27 | ConnectionDeclined = 3 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/CavemanTcp/ExceptionEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace CavemanTcp 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | /// 8 | /// Event arguments for when an exception is encountered. 9 | /// 10 | public class ExceptionEventArgs 11 | { 12 | internal ExceptionEventArgs(Exception e) 13 | { 14 | if (e == null) throw new ArgumentNullException(nameof(e)); 15 | 16 | Exception = e; 17 | } 18 | 19 | /// 20 | /// Exception. 21 | /// 22 | public Exception Exception { get; } = null; 23 | } 24 | } -------------------------------------------------------------------------------- /src/CavemanTcp/LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/CavemanTcp/ReadResult.cs: -------------------------------------------------------------------------------- 1 | namespace CavemanTcp 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text; 7 | 8 | /// 9 | /// Result of a read operation. 10 | /// 11 | public class ReadResult 12 | { 13 | /// 14 | /// Status of the read operation. 15 | /// 16 | public ReadResultStatus Status = ReadResultStatus.Success; 17 | 18 | /// 19 | /// Number of bytes read. 20 | /// 21 | public long BytesRead; 22 | 23 | /// 24 | /// Stream containing data. 25 | /// 26 | public MemoryStream DataStream; 27 | 28 | /// 29 | /// Byte data from the stream. Using this property will fully read the data stream and it will no longer be readable. 30 | /// 31 | public byte[] Data 32 | { 33 | get 34 | { 35 | if (_Data != null) 36 | { 37 | return _Data; 38 | } 39 | else 40 | { 41 | if (BytesRead > 0 && DataStream != null && DataStream.CanRead) 42 | { 43 | _Data = Common.StreamToBytes(DataStream); 44 | return _Data; 45 | } 46 | 47 | return null; 48 | } 49 | } 50 | } 51 | 52 | private byte[] _Data = null; 53 | 54 | /// 55 | /// Instantiate the object. 56 | /// 57 | public ReadResult() 58 | { 59 | 60 | } 61 | 62 | /// 63 | /// Instantiate the object. 64 | /// 65 | /// Status of the read operation. 66 | /// Number of bytes read. 67 | /// Stream containing data. 68 | public ReadResult(ReadResultStatus status, long bytesRead, MemoryStream data) 69 | { 70 | Status = status; 71 | BytesRead = bytesRead; 72 | DataStream = data; 73 | } 74 | } 75 | 76 | /// 77 | /// Read result status. 78 | /// 79 | public enum ReadResultStatus 80 | { 81 | /// 82 | /// The requested client was not found (only applicable for server read requests). 83 | /// 84 | ClientNotFound, 85 | /// 86 | /// The read operation was successful. 87 | /// 88 | Success, 89 | /// 90 | /// The operation timed out (reserved for future use). 91 | /// 92 | Timeout, 93 | /// 94 | /// The connection was lost. 95 | /// 96 | Disconnected, 97 | /// 98 | /// The request was canceled. 99 | /// 100 | Canceled 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/CavemanTcp/WriteResult.cs: -------------------------------------------------------------------------------- 1 | namespace CavemanTcp 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text; 7 | 8 | /// 9 | /// Result of a write operation. 10 | /// 11 | public class WriteResult 12 | { 13 | /// 14 | /// Status of the write operation. 15 | /// 16 | public WriteResultStatus Status = WriteResultStatus.Success; 17 | 18 | /// 19 | /// Number of bytes written. 20 | /// 21 | public long BytesWritten; 22 | 23 | /// 24 | /// Instantiate the object. 25 | /// 26 | public WriteResult() 27 | { 28 | 29 | } 30 | 31 | /// 32 | /// Instantiate the object. 33 | /// 34 | /// Status of the write operation. 35 | /// Number of bytes written. 36 | public WriteResult(WriteResultStatus status, long bytesWritten) 37 | { 38 | Status = status; 39 | BytesWritten = bytesWritten; 40 | } 41 | } 42 | 43 | /// 44 | /// Write result status. 45 | /// 46 | public enum WriteResultStatus 47 | { 48 | /// 49 | /// The requested client was not found (only applicable for server read requests). 50 | /// 51 | ClientNotFound, 52 | /// 53 | /// The write operation was successful. 54 | /// 55 | Success, 56 | /// 57 | /// The operation timed out (reserved for future use). 58 | /// 59 | Timeout, 60 | /// 61 | /// The connection was lost. 62 | /// 63 | Disconnected, 64 | /// 65 | /// The request was canceled. 66 | /// 67 | Canceled 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/CavemanTcp/assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchristn/CavemanTcp/3b36f917dca9422c88e544e85bf249f468efb804/src/CavemanTcp/assets/icon.ico -------------------------------------------------------------------------------- /src/CavemanTcp/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchristn/CavemanTcp/3b36f917dca9422c88e544e85bf249f468efb804/src/CavemanTcp/assets/icon.png -------------------------------------------------------------------------------- /src/CavemanTcp/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchristn/CavemanTcp/3b36f917dca9422c88e544e85bf249f468efb804/src/CavemanTcp/icon.ico -------------------------------------------------------------------------------- /src/Test.Client/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using CavemanTcp; 4 | 5 | namespace Test.Client 6 | { 7 | class Program 8 | { 9 | static bool _RunForever = true; 10 | static string _Hostname = "127.0.0.1"; 11 | static int _Port = 9000; 12 | static bool _Ssl = false; 13 | static CavemanTcpClient _Client = null; 14 | 15 | static void Main(string[] args) 16 | { 17 | InitializeClient(); 18 | Console.WriteLine("Connecting to tcp://" +_Hostname + ":" + _Port); 19 | _Client.Connect(10); 20 | 21 | while (_RunForever) 22 | { 23 | Console.Write("Command [? for help]: "); 24 | string userInput = Console.ReadLine(); 25 | if (String.IsNullOrEmpty(userInput)) continue; 26 | 27 | if (userInput.Equals("?")) 28 | { 29 | Console.WriteLine("-- Available Commands --"); 30 | Console.WriteLine(""); 31 | Console.WriteLine(" cls Clear the screen"); 32 | Console.WriteLine(" q Quit the program"); 33 | Console.WriteLine(" send [data] Send data to the server"); 34 | Console.WriteLine(" sendt [ms] [data] Send data to the server with specified timeout"); 35 | Console.WriteLine(" read [count] Read [count] bytes from the server"); 36 | Console.WriteLine(" readt [ms] [count] Read [count] bytes from the server with specified timeout"); 37 | Console.WriteLine(" dispose Dispose of the client"); 38 | Console.WriteLine(" start Start the client (connected: " + (_Client != null ? _Client.IsConnected.ToString() : "false") + ")"); 39 | Console.WriteLine(" connect Connect to the server"); 40 | Console.WriteLine(" disconnect Disconnect from the server"); 41 | Console.WriteLine(" stats Retrieve statistics"); 42 | Console.WriteLine(""); 43 | continue; 44 | } 45 | 46 | if (userInput.Equals("c") || userInput.Equals("cls")) 47 | { 48 | Console.Clear(); 49 | continue; 50 | } 51 | 52 | if (userInput.Equals("q")) 53 | { 54 | _RunForever = false; 55 | break; 56 | } 57 | 58 | if (userInput.StartsWith("send ")) 59 | { 60 | string[] parts = userInput.Split(new char[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries); 61 | string data = parts[1]; 62 | 63 | WriteResult wr = _Client.Send(data); 64 | if (wr.Status == WriteResultStatus.Success) 65 | Console.WriteLine("Success"); 66 | else 67 | Console.WriteLine("Non-success status: " + wr.Status); 68 | } 69 | 70 | if (userInput.StartsWith("sendt ")) 71 | { 72 | string[] parts = userInput.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries); 73 | int timeoutMs = Convert.ToInt32(parts[1]); 74 | string data = parts[2]; 75 | 76 | WriteResult wr = _Client.SendWithTimeout(timeoutMs, data); 77 | if (wr.Status == WriteResultStatus.Success) 78 | Console.WriteLine("Success"); 79 | else 80 | Console.WriteLine("Non-success status: " + wr.Status); 81 | } 82 | 83 | if (userInput.StartsWith("read ")) 84 | { 85 | string[] parts = userInput.Split(new char[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries); 86 | int count = Convert.ToInt32(parts[1]); 87 | 88 | ReadResult rr = _Client.Read(count); 89 | if (rr.Status == ReadResultStatus.Success) 90 | Console.WriteLine("Retrieved " + rr.BytesRead + " bytes: " + Encoding.UTF8.GetString(rr.Data)); 91 | else 92 | Console.WriteLine("Non-success status: " + rr.Status.ToString()); 93 | } 94 | 95 | if (userInput.StartsWith("readt ")) 96 | { 97 | string[] parts = userInput.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries); 98 | int timeoutMs = Convert.ToInt32(parts[1]); 99 | int count = Convert.ToInt32(parts[2]); 100 | 101 | ReadResult rr = _Client.ReadWithTimeout(timeoutMs, count); 102 | if (rr.Status == ReadResultStatus.Success) 103 | Console.WriteLine("Retrieved " + rr.BytesRead + " bytes: " + Encoding.UTF8.GetString(rr.Data)); 104 | else 105 | Console.WriteLine("Non-success status: " + rr.Status.ToString()); 106 | } 107 | 108 | if (userInput.Equals("dispose")) 109 | { 110 | _Client.Dispose(); 111 | } 112 | 113 | if (userInput.Equals("start")) 114 | { 115 | InitializeClient(); 116 | _Client.Connect(10); 117 | } 118 | 119 | if (userInput.Equals("connect")) 120 | { 121 | _Client.Connect(10); 122 | } 123 | 124 | if (userInput.Equals("disconnect")) 125 | { 126 | _Client.Disconnect(); 127 | } 128 | 129 | if (userInput.Equals("stats")) 130 | { 131 | Console.WriteLine(_Client.Statistics); 132 | } 133 | } 134 | } 135 | 136 | static void InitializeClient() 137 | { 138 | _Client = new CavemanTcpClient(_Hostname, _Port, _Ssl); 139 | _Client.Logger = Logger; 140 | 141 | _Client.Events.ClientConnected += (s, e) => 142 | { 143 | Console.WriteLine("Connected to server"); 144 | }; 145 | 146 | _Client.Events.ClientDisconnected += (s, e) => 147 | { 148 | Console.WriteLine("Disconnected from server"); 149 | }; 150 | } 151 | 152 | static void Logger(string msg) 153 | { 154 | Console.WriteLine(msg); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/Test.Client/Test.Client.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net461;net472;net48;net6.0;net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Always 15 | 16 | 17 | Always 18 | 19 | 20 | Always 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Test.Client/cavemantcp.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFqTCCA5GgAwIBAgIUALJzgmmtlYlKi/7qIP3KlaC3YN0wDQYJKoZIhvcNAQEL 3 | BQAwYzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFzAVBgNVBAcM 4 | DlNpbGljb24gVmFsbGV5MRIwEAYDVQQKDAlTaW1wbGVUY3AxEjAQBgNVBAMMCXNp 5 | bXBsZXRjcDAgFw0xODExMjkxODA2MTVaGA8yMTE4MTEwNTE4MDYxNVowYzELMAkG 6 | A1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFzAVBgNVBAcMDlNpbGljb24g 7 | VmFsbGV5MRIwEAYDVQQKDAlTaW1wbGVUY3AxEjAQBgNVBAMMCXNpbXBsZXRjcDCC 8 | AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMn8DVfN1MVCq/BfOLgTosY7 9 | jSBFU7W2//0ha0BsWrciGL9rQ80Pvpfno1twpUX3OIs0jniooKDAUNKeHJvj2fle 10 | 5lACDOOgaYslXfpiKIOskTcqj4zpOdjhgo4Itpa+KnMaEq0DXj0y45WI2Z4E4Ruj 11 | IZkVMNSsfF3OVa8ZT2R/SurFlNsy5IWqWSeodP9YBV91Lc+7AZ/GbYKy1Qu/5mS6 12 | ++z7ZA8lCr4Nn2wnnoQqdZMU28zeHCTCT/ywlse4T5LTTwDfs4P/YuN4It1lxmSm 13 | qjo4yqpxoOdlXn4CKxRMaDpz29AspU0/g7bRn5keEnPqymYQ/i03FgHbDG9mJizM 14 | 0uw4EnvuWGoilCRz1CkvpKnpZ/Tq8FTy1JIqC7wKIYa4lRLXdhPvs7L4a3jSpWTj 15 | CiXeXKc4hRrAjTNw+OyaviPJW2Bc+lZTDO24SxweGnVIJ6fdeV0SZAPKgynpXKT5 16 | KJvSsxmsCGfh937tasD5wnu6DB9gUQLvIYpDKCc8lZTYv55Exv5JEKZ/Gn8FJU5D 17 | WTlvW1UVQdYdYeppNv5KvAvSOLr08GtzxKOY2Y+k8PI1eUmBYkuo+0r1SORCvj3U 18 | Dcd3njjcFOTv2Agb4Ohbh9FY96yukQ3yxwIQT6ad6LZahr6HeKHKiP5DQ/2xDBJp 19 | I/olGBB59cRVRFgt+7zTAgMBAAGjUzBRMB0GA1UdDgQWBBSpz8QPxENL4/qbHYTV 20 | 5u2uYf8p4TAfBgNVHSMEGDAWgBSpz8QPxENL4/qbHYTV5u2uYf8p4TAPBgNVHRMB 21 | Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCU5B0o9+Vimk7bbgklFSbX2L2/ 22 | ml+oVTIvyi0mQThnXr348ddv0zROVhQnPQUcdnlGdCPiYl+Jpkyr14bnOunvjjhQ 23 | wOer3r+mF4aU/kW618uXhHIsIkiKwCBwXGfOCqd/Phnptknjf6teq/R8OgGBMUY0 24 | twqWT4y7epyA0dFm+4bQzOu1FGHIvgfpv6Hjb0+jWPId8TP2bvgU674w6fm0KKDe 25 | 7ubYVi/Q9i494WbFY5PGY2MBu82gcMSIuVasVLJAaaj6vxsrK31dYkRGWGm6YOjB 26 | 38g4TanmiK0AjVkxhhMuiOJYMFe8R2dOHJBTd6wnnnPKa+IgUk9RJEdPLXV4Pkqh 27 | 2QdI9APAjxlJ5RMZfLa1ADPOP+NBENkIo43W7Cmx+z2BLU4/9OB0doHk7MBiTKFO 28 | V58ZkJOrApY9uFvSdJELWcfQlzn0XDAP12lEnJoLEorQK3mi4peXPWZRs1Iw5dAE 29 | pFz89Qy7Bc3i8MRkMWdu1eD17iiXSAd9G12wHlgaoR4GT2vDG870ce8/aefWInmj 30 | xs2RQTlx9g7DGi4WmMozUJh1ENrgI+ppL7yJiVUCsbDejOHAh1qurvZbgIA7qiHm 31 | SZMu7dm8BPlVhLtdkhR+9pdFWm+TjEhuIsyc6sxP5SqwI4xhGxSsMDQ2DkD+laYP 32 | shLs1ionTO/MVOisLA== 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /src/Test.Client/cavemantcp.key: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | 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 53 | n7xqA1MJhdSXXrOzUzs1VQ== 54 | -----END ENCRYPTED PRIVATE KEY----- 55 | -------------------------------------------------------------------------------- /src/Test.Client/cavemantcp.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchristn/CavemanTcp/3b36f917dca9422c88e544e85bf249f468efb804/src/Test.Client/cavemantcp.pfx -------------------------------------------------------------------------------- /src/Test.ClientAsync/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using CavemanTcp; 4 | 5 | namespace Test.ClientAsync 6 | { 7 | class Program 8 | { 9 | static bool _RunForever = true; 10 | static string _Hostname = "127.0.0.1"; 11 | static int _Port = 9000; 12 | static bool _Ssl = false; 13 | static CavemanTcpClient _Client = null; 14 | 15 | static void Main(string[] args) 16 | { 17 | InitializeClient(); 18 | Console.WriteLine("Connecting to tcp://" + _Hostname + ":" + _Port); 19 | _Client.Connect(10); 20 | 21 | while (_RunForever) 22 | { 23 | Console.Write("Command [? for help]: "); 24 | string userInput = Console.ReadLine(); 25 | if (String.IsNullOrEmpty(userInput)) continue; 26 | 27 | if (userInput.Equals("?")) 28 | { 29 | Console.WriteLine("-- Available Commands --"); 30 | Console.WriteLine(""); 31 | Console.WriteLine(" cls Clear the screen"); 32 | Console.WriteLine(" q Quit the program"); 33 | Console.WriteLine(" send [data] Send data to the server"); 34 | Console.WriteLine(" sendt [ms] [data] Send data to the server with specified timeout"); 35 | Console.WriteLine(" read [count] Read [count] bytes from the server"); 36 | Console.WriteLine(" readt [ms] [count] Read [count] bytes from the server with specified timeout"); 37 | Console.WriteLine(" dispose Dispose of the client"); 38 | Console.WriteLine(" start Start the client (connected: " + (_Client != null ? _Client.IsConnected.ToString() : "false") + ")"); 39 | Console.WriteLine(" stats Retrieve statistics"); 40 | Console.WriteLine(""); 41 | continue; 42 | } 43 | 44 | if (userInput.Equals("c") || userInput.Equals("cls")) 45 | { 46 | Console.Clear(); 47 | continue; 48 | } 49 | 50 | if (userInput.Equals("q")) 51 | { 52 | _RunForever = false; 53 | break; 54 | } 55 | 56 | if (userInput.StartsWith("send ")) 57 | { 58 | string[] parts = userInput.Split(new char[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries); 59 | string data = parts[1]; 60 | 61 | WriteResult wr = _Client.SendAsync(data).Result; 62 | if (wr.Status == WriteResultStatus.Success) 63 | Console.WriteLine("Success"); 64 | else 65 | Console.WriteLine("Non-success status: " + wr.Status); 66 | } 67 | 68 | if (userInput.StartsWith("sendt ")) 69 | { 70 | string[] parts = userInput.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries); 71 | int timeoutMs = Convert.ToInt32(parts[1]); 72 | string data = parts[2]; 73 | 74 | WriteResult wr = _Client.SendWithTimeoutAsync(timeoutMs, data).Result; 75 | if (wr.Status == WriteResultStatus.Success) 76 | Console.WriteLine("Success"); 77 | else 78 | Console.WriteLine("Non-success status: " + wr.Status); 79 | } 80 | 81 | if (userInput.StartsWith("read ")) 82 | { 83 | string[] parts = userInput.Split(new char[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries); 84 | int count = Convert.ToInt32(parts[1]); 85 | 86 | ReadResult rr = _Client.ReadAsync(count).Result; 87 | if (rr.Status == ReadResultStatus.Success) 88 | Console.WriteLine("Retrieved " + rr.BytesRead + " bytes: " + Encoding.UTF8.GetString(rr.Data)); 89 | else 90 | Console.WriteLine("Non-success status: " + rr.Status.ToString()); 91 | } 92 | 93 | if (userInput.StartsWith("readt ")) 94 | { 95 | string[] parts = userInput.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries); 96 | int timeoutMs = Convert.ToInt32(parts[1]); 97 | int count = Convert.ToInt32(parts[2]); 98 | 99 | ReadResult rr = _Client.ReadWithTimeoutAsync(timeoutMs, count).Result; 100 | if (rr.Status == ReadResultStatus.Success) 101 | Console.WriteLine("Retrieved " + rr.BytesRead + " bytes: " + Encoding.UTF8.GetString(rr.Data)); 102 | else 103 | Console.WriteLine("Non-success status: " + rr.Status.ToString()); 104 | } 105 | 106 | if (userInput.Equals("dispose")) 107 | { 108 | _Client.Dispose(); 109 | } 110 | 111 | if (userInput.Equals("start")) 112 | { 113 | InitializeClient(); 114 | _Client.Connect(10); 115 | } 116 | 117 | if (userInput.Equals("stats")) 118 | { 119 | Console.WriteLine(_Client.Statistics); 120 | } 121 | } 122 | } 123 | 124 | static void InitializeClient() 125 | { 126 | _Client = new CavemanTcpClient(_Hostname, _Port, _Ssl); 127 | _Client.Logger = Logger; 128 | 129 | _Client.Events.ClientConnected += (s, e) => 130 | { 131 | Console.WriteLine("Connected to server"); 132 | }; 133 | 134 | _Client.Events.ClientDisconnected += (s, e) => 135 | { 136 | Console.WriteLine("Disconnected from server"); 137 | }; 138 | } 139 | 140 | static void Logger(string msg) 141 | { 142 | Console.WriteLine(msg); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/Test.ClientAsync/Test.ClientAsync.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net461;net472;net48;net6.0;net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Always 15 | 16 | 17 | Always 18 | 19 | 20 | Always 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Test.ClientAsync/cavemantcp.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFqTCCA5GgAwIBAgIUALJzgmmtlYlKi/7qIP3KlaC3YN0wDQYJKoZIhvcNAQEL 3 | BQAwYzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFzAVBgNVBAcM 4 | DlNpbGljb24gVmFsbGV5MRIwEAYDVQQKDAlTaW1wbGVUY3AxEjAQBgNVBAMMCXNp 5 | bXBsZXRjcDAgFw0xODExMjkxODA2MTVaGA8yMTE4MTEwNTE4MDYxNVowYzELMAkG 6 | A1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFzAVBgNVBAcMDlNpbGljb24g 7 | VmFsbGV5MRIwEAYDVQQKDAlTaW1wbGVUY3AxEjAQBgNVBAMMCXNpbXBsZXRjcDCC 8 | AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMn8DVfN1MVCq/BfOLgTosY7 9 | jSBFU7W2//0ha0BsWrciGL9rQ80Pvpfno1twpUX3OIs0jniooKDAUNKeHJvj2fle 10 | 5lACDOOgaYslXfpiKIOskTcqj4zpOdjhgo4Itpa+KnMaEq0DXj0y45WI2Z4E4Ruj 11 | IZkVMNSsfF3OVa8ZT2R/SurFlNsy5IWqWSeodP9YBV91Lc+7AZ/GbYKy1Qu/5mS6 12 | ++z7ZA8lCr4Nn2wnnoQqdZMU28zeHCTCT/ywlse4T5LTTwDfs4P/YuN4It1lxmSm 13 | qjo4yqpxoOdlXn4CKxRMaDpz29AspU0/g7bRn5keEnPqymYQ/i03FgHbDG9mJizM 14 | 0uw4EnvuWGoilCRz1CkvpKnpZ/Tq8FTy1JIqC7wKIYa4lRLXdhPvs7L4a3jSpWTj 15 | CiXeXKc4hRrAjTNw+OyaviPJW2Bc+lZTDO24SxweGnVIJ6fdeV0SZAPKgynpXKT5 16 | KJvSsxmsCGfh937tasD5wnu6DB9gUQLvIYpDKCc8lZTYv55Exv5JEKZ/Gn8FJU5D 17 | WTlvW1UVQdYdYeppNv5KvAvSOLr08GtzxKOY2Y+k8PI1eUmBYkuo+0r1SORCvj3U 18 | Dcd3njjcFOTv2Agb4Ohbh9FY96yukQ3yxwIQT6ad6LZahr6HeKHKiP5DQ/2xDBJp 19 | I/olGBB59cRVRFgt+7zTAgMBAAGjUzBRMB0GA1UdDgQWBBSpz8QPxENL4/qbHYTV 20 | 5u2uYf8p4TAfBgNVHSMEGDAWgBSpz8QPxENL4/qbHYTV5u2uYf8p4TAPBgNVHRMB 21 | Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCU5B0o9+Vimk7bbgklFSbX2L2/ 22 | ml+oVTIvyi0mQThnXr348ddv0zROVhQnPQUcdnlGdCPiYl+Jpkyr14bnOunvjjhQ 23 | wOer3r+mF4aU/kW618uXhHIsIkiKwCBwXGfOCqd/Phnptknjf6teq/R8OgGBMUY0 24 | twqWT4y7epyA0dFm+4bQzOu1FGHIvgfpv6Hjb0+jWPId8TP2bvgU674w6fm0KKDe 25 | 7ubYVi/Q9i494WbFY5PGY2MBu82gcMSIuVasVLJAaaj6vxsrK31dYkRGWGm6YOjB 26 | 38g4TanmiK0AjVkxhhMuiOJYMFe8R2dOHJBTd6wnnnPKa+IgUk9RJEdPLXV4Pkqh 27 | 2QdI9APAjxlJ5RMZfLa1ADPOP+NBENkIo43W7Cmx+z2BLU4/9OB0doHk7MBiTKFO 28 | V58ZkJOrApY9uFvSdJELWcfQlzn0XDAP12lEnJoLEorQK3mi4peXPWZRs1Iw5dAE 29 | pFz89Qy7Bc3i8MRkMWdu1eD17iiXSAd9G12wHlgaoR4GT2vDG870ce8/aefWInmj 30 | xs2RQTlx9g7DGi4WmMozUJh1ENrgI+ppL7yJiVUCsbDejOHAh1qurvZbgIA7qiHm 31 | SZMu7dm8BPlVhLtdkhR+9pdFWm+TjEhuIsyc6sxP5SqwI4xhGxSsMDQ2DkD+laYP 32 | shLs1ionTO/MVOisLA== 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /src/Test.ClientAsync/cavemantcp.key: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | MIIJnDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIzyT8bR+zRwcCAggA 3 | MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECOSHMlux3KUYBIIJSFzHVcFK9B39 4 | 7ctTAIye62oiyvI8crMgRRmu/7FShgXn2X4s4ozM/uMLlAE3m9Fa9EQ7hp8YotUB 5 | +jnugODVmr4lNZ7CiAHmUbixvzBJU+SoWn3b8ivBLrG8D/IT6vLgyrjQNvZExOeN 6 | 2s9Bz1nu3SLORklC1yKB80n/GmPMMtb4XmygFSnQ/wx07wMIep6JKsODMJplVWPl 7 | r07bh9ndlv8oc2RZ54Tt/ApZi5lpqGbCQEgNoXheyHnabiVFiAOlXu58IvHXSOav 8 | qMjp+dK3IwqiamZNCR/8jAq6EltGzv1E/hAWZ8YgTA0s0474ESP6o5uAzVeb1yMJ 9 | PHRm4lOD4HgaYElHgL6hHN4Q4R1DSKtaMIigod6GzMYI9ZRhUiK30x9xJgDlwhjo 10 | IgkoiHZ3LzFR+dUawrUvRInyPyL5tprNx6FhYXe50CS2U99neKw/rDi7lTIoF29P 11 | jdm/JN1PLN78gBZqnjtJNFJEg7FaAtmi0/QF133JHURwN+NfZln9sCMXzmc5+jjD 12 | k7kfKgLsNHHH11V5ZXARTrQQ+r2FM3c8okj7yJXA56gQXi7MsqFI4dqUmSsHllkI 13 | DWCc0HKAo63TOw+2YCU0SRHt2MNt6oHfIVr2HxIPEmsOD2rwDGRIfgzhS+1le+hS 14 | bj8iMCq9hPhpkI2o4yQLu6Mf7FSH8N8ZwHOR32Euwwv50OES/UMa4/I2Ld1qnPa7 15 | hcllwziOAiRJSlqWTL6B75H5Vvx1+gGD3oDnNHpRBM0ld8QzYEvRM4i9BHZcXKEQ 16 | GlLU4H+Fg3WexcoEvmt7jLGTlIbZfibDtQs/IpnfAuYLFO87VGRDMHpEpDX0dzdU 17 | hPa5WGcZ+hF7GUh4KViFwdcPErYpGpNXYOhmhkyJoL/a3WJ4MuFeb2djzdEoNEth 18 | XGGFrKcojAZGynQNvR1ZT8Az7GvUrjSj3fffLMIHRjPSSugLOzDldaBD+sA/Kmaz 19 | CcO/Ufq2pav/BPoVNGgTLzaI8d3moXdRyGKf9CAIANhaxhS1M8JRlrVa2JncfbkF 20 | NB2hh/C28o90ZULBCv3nqdjBeqxWYFgCrH84uhezJNkc+TEJIwNIFur0JB4hYl4d 21 | PiqOzAdDVNhmxKzpdc0grDtxr/wWOa9+lPT4aHoUd8BmgmCTCw8yBp2+VAfQ7qOv 22 | k1x0+mlUxSJNgvU39xCm8oD/c8Sx5RYHjOIJ8FPn/tbVsr2/WuSLkZHpW3w6DXFB 23 | AoEav3A9/LcLhbkraPdwTmUH4RgGjZxS/n+hvR3mIH7IdPQMojybzuWba9EHc6a6 24 | Uf+QmwPGyOZl+DdBNqzlUSSTuRIkbKuri2p2sfT/0EsbC5bQTjR/782NzxgQ/+mJ 25 | 7nh5ThZVWZyvl/wUenq3Kze9gDstGZF+yKk+I1elaZouvds3/ead1LA+6P1dntwo 26 | zcKfYdjix6Pcn/c0HrFzvq1OWn0Gwki5+DWj4NTrUEc15Jf5OqdNYj3lv4a8muQu 27 | zwZW3VubqmATGQ1XjIvqgmteqcKNWg5KaHU7TDccBy2g5WCo5fbeQlXLWum17+OG 28 | Ffi77c0/8t72ywB4ufud+o49KzhHoAs0ylVl6CJw+3TIiZEEre8QMq2VppJdJmTD 29 | UFI4qXT/446ylN6OFUuAc1GHSIe95E/AGkoe9pDNN3QVqOJNQvqlGCWQ2myvIT7M 30 | b0TJtyyOyi1SRoaHkqmciIiALv3NG60xJ7K/CXb5aUCraoodtxxSxfVCDOEXLdTe 31 | W+PXVvVuXGLUiPWv8DuLZdml0S4ID1ZgYUjuf3VznJJcbXQQAbDgyW2Trdg+s94k 32 | Iyhyb213KpeXjEw9GcTdX2VDo/MUWUSGBG1eu3dnxJzJJK9vKNa6V8opI3KSnB9R 33 | 6TAX2p6V8nZsgzOMwB750NTE5c/V2AxZ2pjjZk2QfqNEf0nPwbCv2xeHQN7Wexja 34 | wqALP4rdJvGIs0ONVpvmvux2CTH3JantIs2r+if2V2Vd6qRSf4U4RZZG+LPsZy2a 35 | efafnQV+XsCcSaT6rK7qTvHqNpBEc6I2YL7AkrT9m0Nu0grGSrbuFNkzOqnOS9Le 36 | E24YZxn0fQGQu37Mntp2umDX6ixnPTA2gRExdxiSoyMs3pUAHPthxJiFN6gcyNI9 37 | i3tE21hSHpcEeEzArukdhM8wn8Utph2bmP+AgTWfkotCrFHr5clOiawjIkHcUT/w 38 | yeIDUIT5Ruc0x+4STDAwJTSUQ+DcDeM3yHnmSaint3ih55BnIO3uCwlaY+dsXRkN 39 | 4ZTlFFHHQoZV7Ok/3DP2nS8AeNNWPnqKtf/s0IX5zKZUG671G5lCJUJxLShktCKY 40 | y54m+ZRlJXfuCTthO/Htgio/hWnUXodLefZt4IwiRuBMdYbXjC67gVqpEuoiOpsj 41 | q9I4QXT0PTvFkusE/CxcKQkdF9LHwoV2Dz5xV9adjIDJqoAehX2qI8vOe0+81jkC 42 | Qo1sjSp35C59oT5BAgyebuDgWfcH247wr6RxH4rOuBGCK4IXWv9slYA9lQz/D0IM 43 | LiISwo6g6xpn0PW3jnKXzvk5Avio8/Wg+2eCzQT4KWg56M4lkDKc6b7+njm4d4mA 44 | qNN+wm+Xw7Vq+6ulRnwrU4jy22jHQ10jbWupro1QQ4eHwTvHrj8v+jKPhIUTD9xJ 45 | h3eFizCY31jgUzlV4GNPJ+JophZh0F4PWDFVfRq3rh7LVRbYz+dFcDeIJuuQQv70 46 | WLpdSyQszq2lKvCVUvG985E5fD3l2tHRK/njc7eiQFPLcK0D5RBfI5mzD9cCchFK 47 | WhJOQATzKH8/i6toP0f5yd0w8vjO/0WKpQfG7tbeyt8YQVGolRwGQJmEFpydRiT9 48 | VAYfWS2hUCRbdYmB1hoFrY0tPeRlOhRoRmcIiCXmb4joioFiAZ3q2UnswbnfWKiK 49 | oEBK+KtulXttImREuWFOE4hwvv0B6Dm3TPagMEymn4Ko/ql2Vj9VaXeEj1JAuM4A 50 | NyE5lWW/rfmi9Xbi4Xs/buNWlnG3nn5rVn/XXQK71ip5cjrm8uGI5Rkhmk4r6pXC 51 | cRKM6XjZeRu++6Q2p7UnAU2mjU8ps7HzsFtWMq1UySkJ4JaLsyOOAt338SQw928r 52 | VL2AhrS20lEbrBxZbQGiyz5sTBahWJ127ploEo4C6cJuLwAQmzaIyS3GQ9XHn9Aj 53 | n7xqA1MJhdSXXrOzUzs1VQ== 54 | -----END ENCRYPTED PRIVATE KEY----- 55 | -------------------------------------------------------------------------------- /src/Test.ClientAsync/cavemantcp.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchristn/CavemanTcp/3b36f917dca9422c88e544e85bf249f468efb804/src/Test.ClientAsync/cavemantcp.pfx -------------------------------------------------------------------------------- /src/Test.Disconnect/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using CavemanTcp; 6 | 7 | namespace Test.Disconnect 8 | { 9 | class Program 10 | { 11 | static CancellationTokenSource _TokenSource = new CancellationTokenSource(); 12 | static CancellationToken _Token; 13 | 14 | static CavemanTcpServer _Server = null; 15 | static Guid _LastGuid = Guid.Empty; 16 | 17 | static CavemanTcpClient _Client = null; 18 | static bool _RunForever = true; 19 | 20 | static void Main(string[] args) 21 | { 22 | _Token = _TokenSource.Token; 23 | 24 | Task.Run(() => StartEchoServer(), _Token); 25 | 26 | _Client = new CavemanTcpClient("127.0.0.1", 9000, false, null, null); 27 | _Client.Events.ClientConnected += (s, e) => 28 | { 29 | Console.WriteLine("[Client] connected"); 30 | }; 31 | 32 | _Client.Events.ClientDisconnected += (s, e) => 33 | { 34 | Console.WriteLine("[Client] disconnected"); 35 | }; 36 | 37 | _Client.Logger = Console.WriteLine; 38 | 39 | while (_RunForever) 40 | { 41 | Console.Write("Command [? for help]: "); 42 | string userInput = Console.ReadLine(); 43 | if (String.IsNullOrEmpty(userInput)) continue; 44 | 45 | switch (userInput) 46 | { 47 | case "?": 48 | Console.WriteLine("Available commands:"); 49 | Console.WriteLine("q quit"); 50 | Console.WriteLine("cls clear the screen"); 51 | Console.WriteLine("connect connect to server"); 52 | Console.WriteLine("send send clientecho to the server"); 53 | Console.WriteLine("read read data from the server"); 54 | Console.WriteLine(""); 55 | break; 56 | case "q": 57 | _RunForever = false; 58 | break; 59 | case "cls": 60 | Console.Clear(); 61 | break; 62 | case "connect": 63 | _Client.Connect(5000); 64 | break; 65 | case "send": 66 | _Client.Send("clientecho"); 67 | break; 68 | case "read": 69 | ReadResult rr = _Client.Read(10); 70 | if (rr.Status == ReadResultStatus.Success) 71 | { 72 | Console.WriteLine("[Client] read 10 bytes: " + Encoding.UTF8.GetString(rr.Data)); 73 | } 74 | else 75 | { 76 | Console.WriteLine("*** [Client] read status: " + rr.Status.ToString()); 77 | } 78 | break; 79 | } 80 | } 81 | } 82 | 83 | static void StartEchoServer() 84 | { 85 | Console.WriteLine("[Server] starting TCP/9000"); 86 | 87 | _Server = new CavemanTcpServer("127.0.0.01", 9000, false, null, null); 88 | _Server.Events.ClientConnected += (s, e) => 89 | { 90 | _LastGuid = e.Client.Guid; 91 | Console.WriteLine("[Server] " + e.Client.ToString() + " connected"); 92 | }; 93 | 94 | _Server.Events.ClientDisconnected += (s, e) => 95 | { 96 | _LastGuid = Guid.Empty; 97 | Console.WriteLine("[Server] " + e.Client.ToString() + " disconnected"); 98 | }; 99 | 100 | _Server.Logger = Console.WriteLine; 101 | 102 | _Server.Start(); 103 | 104 | Console.WriteLine("[Server] started TCP/9000"); 105 | 106 | while (true) 107 | { 108 | if (_LastGuid == Guid.Empty) 109 | { 110 | Task.Delay(100).Wait(); 111 | continue; 112 | } 113 | 114 | ReadResult rr = _Server.Read(_LastGuid, 10); 115 | if (rr.Status == ReadResultStatus.Success) 116 | { 117 | Console.WriteLine("[Server] received " + Encoding.UTF8.GetString(rr.Data)); 118 | WriteResult wr = _Server.Send(_LastGuid, "serverecho"); 119 | } 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Test.Disconnect/Test.Disconnect.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net461;net472;net48;net6.0;net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Test.HttpLoopback/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using CavemanTcp; 9 | 10 | namespace Test.HttpLoopback 11 | { 12 | class Program 13 | { 14 | static string _Hostname = "localhost"; 15 | static int _Port = 9090; 16 | static CavemanTcpServer _Server = null; 17 | 18 | static string _HttpResponse = 19 | "HTTP/1.1 200 OK\r\n" + 20 | "Content-Length: 11\r\n" + 21 | "Server: CavemanTcp\r\n" + 22 | "Access-Control-Allow-Origin: *\r\n" + 23 | "Date: " + DateTime.Now.ToString("ddd, dd MMM yyy HH’:’mm’:’ss ‘GMT’") + "\r\n" + 24 | "\r\n" + 25 | "Hello world"; 26 | 27 | static void Main(string[] args) 28 | { 29 | ThreadPool.SetMinThreads(500, 500); 30 | ThreadPool.SetMaxThreads(2500, 2500); 31 | 32 | InitializeServer(); 33 | _Server.Start(); 34 | Console.WriteLine("CavemanTcp listening on http://" + _Hostname + ":" + _Port + "/"); 35 | Console.WriteLine("ENTER to exit"); 36 | Console.ReadLine(); 37 | } 38 | 39 | static void InitializeServer() 40 | { 41 | _Server = new CavemanTcpServer(_Hostname, _Port); 42 | _Server.Settings.MonitorClientConnections = false; 43 | _Server.Events.ClientConnected += ClientConnected; 44 | } 45 | 46 | static async void ClientConnected(object sender, ClientConnectedEventArgs args) 47 | { 48 | try 49 | { 50 | string data = await ReadFully(args.Client.Guid); 51 | await _Server.SendAsync(args.Client.Guid, _HttpResponse); 52 | _Server.DisconnectClient(args.Client.Guid); 53 | } 54 | catch (Exception e) 55 | { 56 | Console.WriteLine(e.ToString()); 57 | } 58 | } 59 | 60 | static async Task ReadFully(Guid guid) 61 | { 62 | StringBuilder sb = new StringBuilder(); 63 | 64 | ReadResult readInitial = await _Server.ReadAsync(guid, 18); 65 | if (readInitial.Status != ReadResultStatus.Success) 66 | { 67 | throw new IOException("Unable to read data"); 68 | } 69 | 70 | sb.Append(Encoding.ASCII.GetString(readInitial.Data)); 71 | while (true) 72 | { 73 | string delimCheck = sb.ToString((sb.Length - 4), 4); 74 | if (delimCheck.EndsWith("\r\n\r\n")) 75 | { 76 | break; 77 | } 78 | else 79 | { 80 | ReadResult readSubsequent = await _Server.ReadAsync(guid, 1); 81 | if (readSubsequent.Status != ReadResultStatus.Success) 82 | { 83 | throw new IOException("Unable to read data"); 84 | } 85 | 86 | sb.Append((char)(readSubsequent.Data[0])); 87 | } 88 | } 89 | 90 | return sb.ToString(); 91 | } 92 | 93 | static byte[] ByteArrayShiftLeft(byte[] bytes) 94 | { 95 | byte[] ret = new byte[bytes.Length]; 96 | 97 | for (int i = 1; i < bytes.Length; i++) 98 | { 99 | ret[(i - 1)] = bytes[i]; 100 | } 101 | 102 | ret[(bytes.Length - 1)] = 0x00; 103 | 104 | return ret; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Test.HttpLoopback/Test.HttpLoopback.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net461;net472;net48;net6.0;net8.0 6 | 7 | 8 | 9 | full 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Test.Server/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using CavemanTcp; 6 | 7 | namespace Test.Server 8 | { 9 | class Program 10 | { 11 | static bool _RunForever = true; 12 | static string _Hostname = "*"; 13 | static int _Port = 9000; 14 | static bool _Ssl = false; 15 | static CavemanTcpServer _Server = null; 16 | 17 | static void Main(string[] args) 18 | { 19 | InitializeServer(); 20 | _Server.Start(); 21 | 22 | Console.WriteLine("Listening on " + (_Ssl ? "ssl://" : "tcp://") + _Hostname + ":" + _Port); 23 | if (_Hostname.Equals("*") 24 | || _Hostname.Equals("+") 25 | || _Hostname.Equals("0.0.0.0")) 26 | { 27 | Console.WriteLine("This program must be run with administrative privileges due to the specified hostname"); 28 | } 29 | 30 | while (_RunForever) 31 | { 32 | Console.Write("Command [? for help]: "); 33 | string userInput = Console.ReadLine(); 34 | if (String.IsNullOrEmpty(userInput)) continue; 35 | 36 | if (userInput.Equals("?")) 37 | { 38 | Console.WriteLine("-- Available Commands --"); 39 | Console.WriteLine(""); 40 | Console.WriteLine(" cls Clear the screen"); 41 | Console.WriteLine(" q Quit the program"); 42 | Console.WriteLine(" start Start listening for connections (listening: " + (_Server != null ? _Server.IsListening.ToString() : "false") + ")"); 43 | Console.WriteLine(" stop Stop listening for connections (listening: " + (_Server != null ? _Server.IsListening.ToString() : "false") + ")"); 44 | Console.WriteLine(" list List connected clients"); 45 | Console.WriteLine(" send [guid] [data] Send data to a specific client"); 46 | Console.WriteLine(" sendt [ms] [guid] [data] Send data to a specific client with specified timeout"); 47 | Console.WriteLine(" read [guid] [count] Read [count] bytes from a specific client"); 48 | Console.WriteLine(" readt [ms] [guid] [count] Read [count] bytes from a specific client with specified timeout"); 49 | Console.WriteLine(" kick [guid] Disconnect a specific client from the server"); 50 | Console.WriteLine(" dispose Dispose of the server"); 51 | Console.WriteLine(" stats Retrieve statistics"); 52 | Console.WriteLine(""); 53 | continue; 54 | } 55 | 56 | if (userInput.Equals("c") || userInput.Equals("cls")) 57 | { 58 | Console.Clear(); 59 | continue; 60 | } 61 | 62 | if (userInput.Equals("q")) 63 | { 64 | _RunForever = false; 65 | break; 66 | } 67 | 68 | if (userInput.Equals("start")) 69 | { 70 | _Server.Start(); 71 | } 72 | 73 | if (userInput.Equals("stop")) 74 | { 75 | _Server.Stop(); 76 | } 77 | 78 | if (userInput.Equals("list")) 79 | { 80 | List clients = _Server.GetClients().ToList(); 81 | if (clients != null) 82 | { 83 | Console.WriteLine("Clients: " + clients.Count); 84 | foreach (ClientMetadata curr in clients) 85 | { 86 | Console.WriteLine(" " + curr.ToString()); 87 | } 88 | } 89 | else 90 | { 91 | Console.WriteLine("(null)"); 92 | } 93 | } 94 | 95 | if (userInput.StartsWith("send ")) 96 | { 97 | string[] parts = userInput.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries); 98 | Guid guid = Guid.Parse(parts[1]); 99 | string data = parts[2]; 100 | 101 | WriteResult wr = _Server.Send(guid, data); 102 | if (wr.Status == WriteResultStatus.Success) 103 | Console.WriteLine("Success"); 104 | else 105 | Console.WriteLine("Non-success status: " + wr.Status); 106 | } 107 | 108 | if (userInput.StartsWith("sendt ")) 109 | { 110 | string[] parts = userInput.Split(new char[] { ' ' }, 4, StringSplitOptions.RemoveEmptyEntries); 111 | int timeoutMs = Convert.ToInt32(parts[1]); 112 | Guid guid = Guid.Parse(parts[2]); 113 | string data = parts[3]; 114 | 115 | WriteResult wr = _Server.SendWithTimeout(timeoutMs, guid, data); 116 | if (wr.Status == WriteResultStatus.Success) 117 | Console.WriteLine("Success"); 118 | else 119 | Console.WriteLine("Non-success status: " + wr.Status); 120 | } 121 | 122 | if (userInput.StartsWith("read ")) 123 | { 124 | string[] parts = userInput.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries); 125 | Guid guid = Guid.Parse(parts[1]); 126 | int count = Convert.ToInt32(parts[2]); 127 | 128 | ReadResult rr = _Server.Read(guid, count); 129 | if (rr.Status == ReadResultStatus.Success) 130 | Console.WriteLine("Retrieved " + rr.BytesRead + " bytes: " + Encoding.UTF8.GetString(rr.Data)); 131 | else 132 | Console.WriteLine("Non-success status: " + rr.Status.ToString()); 133 | } 134 | 135 | if (userInput.StartsWith("readt ")) 136 | { 137 | string[] parts = userInput.Split(new char[] { ' ' }, 4, StringSplitOptions.RemoveEmptyEntries); 138 | int timeoutMs = Convert.ToInt32(parts[1]); 139 | Guid guid = Guid.Parse(parts[2]); 140 | int count = Convert.ToInt32(parts[3]); 141 | 142 | ReadResult rr = _Server.ReadWithTimeout(timeoutMs, guid, count); 143 | if (rr.Status == ReadResultStatus.Success) 144 | Console.WriteLine("Retrieved " + rr.BytesRead + " bytes: " + Encoding.UTF8.GetString(rr.Data)); 145 | else 146 | Console.WriteLine("Non-success status: " + rr.Status.ToString()); 147 | } 148 | 149 | if (userInput.StartsWith("kick ")) 150 | { 151 | string[] parts = userInput.Split(new char[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries); 152 | Guid guid = Guid.Parse(parts[1]); 153 | 154 | _Server.DisconnectClient(guid); 155 | } 156 | 157 | if (userInput.Equals("dispose")) 158 | { 159 | _Server.Dispose(); 160 | } 161 | 162 | if (userInput.Equals("stats")) 163 | { 164 | Console.WriteLine(_Server.Statistics); 165 | } 166 | } 167 | } 168 | 169 | static void InitializeServer() 170 | { 171 | _Server = new CavemanTcpServer(_Hostname, _Port, _Ssl, null, null); 172 | _Server.Logger = Logger; 173 | _Server.Settings.MonitorClientConnections = true; 174 | 175 | _Server.Events.ClientConnected += (s, e) => 176 | { 177 | Console.WriteLine( 178 | "Client " + e.Client.ToString() + " " + 179 | "connected to server on " + (e.LocalEndpoint != null ? e.LocalEndpoint.ToString() : "(unknown)")); 180 | }; 181 | 182 | _Server.Events.ClientDisconnected += (s, e) => 183 | { 184 | Console.WriteLine("Client " + e.Client.ToString() + " disconnected from server"); 185 | }; 186 | } 187 | 188 | static void Logger(string msg) 189 | { 190 | Console.WriteLine(msg); 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/Test.Server/Test.Server.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net461;net472;net48;net6.0;net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Always 15 | 16 | 17 | Always 18 | 19 | 20 | Always 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Test.Server/cavemantcp.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFqTCCA5GgAwIBAgIUALJzgmmtlYlKi/7qIP3KlaC3YN0wDQYJKoZIhvcNAQEL 3 | BQAwYzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFzAVBgNVBAcM 4 | DlNpbGljb24gVmFsbGV5MRIwEAYDVQQKDAlTaW1wbGVUY3AxEjAQBgNVBAMMCXNp 5 | bXBsZXRjcDAgFw0xODExMjkxODA2MTVaGA8yMTE4MTEwNTE4MDYxNVowYzELMAkG 6 | A1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFzAVBgNVBAcMDlNpbGljb24g 7 | VmFsbGV5MRIwEAYDVQQKDAlTaW1wbGVUY3AxEjAQBgNVBAMMCXNpbXBsZXRjcDCC 8 | AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMn8DVfN1MVCq/BfOLgTosY7 9 | jSBFU7W2//0ha0BsWrciGL9rQ80Pvpfno1twpUX3OIs0jniooKDAUNKeHJvj2fle 10 | 5lACDOOgaYslXfpiKIOskTcqj4zpOdjhgo4Itpa+KnMaEq0DXj0y45WI2Z4E4Ruj 11 | IZkVMNSsfF3OVa8ZT2R/SurFlNsy5IWqWSeodP9YBV91Lc+7AZ/GbYKy1Qu/5mS6 12 | ++z7ZA8lCr4Nn2wnnoQqdZMU28zeHCTCT/ywlse4T5LTTwDfs4P/YuN4It1lxmSm 13 | qjo4yqpxoOdlXn4CKxRMaDpz29AspU0/g7bRn5keEnPqymYQ/i03FgHbDG9mJizM 14 | 0uw4EnvuWGoilCRz1CkvpKnpZ/Tq8FTy1JIqC7wKIYa4lRLXdhPvs7L4a3jSpWTj 15 | CiXeXKc4hRrAjTNw+OyaviPJW2Bc+lZTDO24SxweGnVIJ6fdeV0SZAPKgynpXKT5 16 | KJvSsxmsCGfh937tasD5wnu6DB9gUQLvIYpDKCc8lZTYv55Exv5JEKZ/Gn8FJU5D 17 | WTlvW1UVQdYdYeppNv5KvAvSOLr08GtzxKOY2Y+k8PI1eUmBYkuo+0r1SORCvj3U 18 | Dcd3njjcFOTv2Agb4Ohbh9FY96yukQ3yxwIQT6ad6LZahr6HeKHKiP5DQ/2xDBJp 19 | I/olGBB59cRVRFgt+7zTAgMBAAGjUzBRMB0GA1UdDgQWBBSpz8QPxENL4/qbHYTV 20 | 5u2uYf8p4TAfBgNVHSMEGDAWgBSpz8QPxENL4/qbHYTV5u2uYf8p4TAPBgNVHRMB 21 | Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCU5B0o9+Vimk7bbgklFSbX2L2/ 22 | ml+oVTIvyi0mQThnXr348ddv0zROVhQnPQUcdnlGdCPiYl+Jpkyr14bnOunvjjhQ 23 | wOer3r+mF4aU/kW618uXhHIsIkiKwCBwXGfOCqd/Phnptknjf6teq/R8OgGBMUY0 24 | twqWT4y7epyA0dFm+4bQzOu1FGHIvgfpv6Hjb0+jWPId8TP2bvgU674w6fm0KKDe 25 | 7ubYVi/Q9i494WbFY5PGY2MBu82gcMSIuVasVLJAaaj6vxsrK31dYkRGWGm6YOjB 26 | 38g4TanmiK0AjVkxhhMuiOJYMFe8R2dOHJBTd6wnnnPKa+IgUk9RJEdPLXV4Pkqh 27 | 2QdI9APAjxlJ5RMZfLa1ADPOP+NBENkIo43W7Cmx+z2BLU4/9OB0doHk7MBiTKFO 28 | V58ZkJOrApY9uFvSdJELWcfQlzn0XDAP12lEnJoLEorQK3mi4peXPWZRs1Iw5dAE 29 | pFz89Qy7Bc3i8MRkMWdu1eD17iiXSAd9G12wHlgaoR4GT2vDG870ce8/aefWInmj 30 | xs2RQTlx9g7DGi4WmMozUJh1ENrgI+ppL7yJiVUCsbDejOHAh1qurvZbgIA7qiHm 31 | SZMu7dm8BPlVhLtdkhR+9pdFWm+TjEhuIsyc6sxP5SqwI4xhGxSsMDQ2DkD+laYP 32 | shLs1ionTO/MVOisLA== 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /src/Test.Server/cavemantcp.key: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | 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 53 | n7xqA1MJhdSXXrOzUzs1VQ== 54 | -----END ENCRYPTED PRIVATE KEY----- 55 | -------------------------------------------------------------------------------- /src/Test.Server/cavemantcp.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchristn/CavemanTcp/3b36f917dca9422c88e544e85bf249f468efb804/src/Test.Server/cavemantcp.pfx -------------------------------------------------------------------------------- /src/Test.ServerAsync/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using CavemanTcp; 6 | 7 | namespace Test.ServerAsync 8 | { 9 | class Program 10 | { 11 | static bool _RunForever = true; 12 | static string _Hostname = "*"; 13 | static int _Port = 9000; 14 | static bool _Ssl = false; 15 | static CavemanTcpServer _Server = null; 16 | 17 | static void Main(string[] args) 18 | { 19 | InitializeServer(); 20 | _Server.Start(); 21 | 22 | Console.WriteLine("Listening on " + (_Ssl ? "ssl://" : "tcp://") + _Hostname + ":" + _Port); 23 | if (_Hostname.Equals("*") 24 | || _Hostname.Equals("+") 25 | || _Hostname.Equals("0.0.0.0")) 26 | { 27 | Console.WriteLine("This program must be run with administrative privileges due to the specified hostname"); 28 | } 29 | 30 | while (_RunForever) 31 | { 32 | Console.Write("Command [? for help]: "); 33 | string userInput = Console.ReadLine(); 34 | if (String.IsNullOrEmpty(userInput)) continue; 35 | 36 | if (userInput.Equals("?")) 37 | { 38 | Console.WriteLine("-- Available Commands --"); 39 | Console.WriteLine(""); 40 | Console.WriteLine(" cls Clear the screen"); 41 | Console.WriteLine(" q Quit the program"); 42 | Console.WriteLine(" start Start listening for connections (listening: " + (_Server != null ? _Server.IsListening.ToString() : "false") + ")"); 43 | Console.WriteLine(" stop Stop listening for connections (listening: " + (_Server != null ? _Server.IsListening.ToString() : "false") + ")"); 44 | Console.WriteLine(" list List connected clients"); 45 | Console.WriteLine(" send [guid] [data] Send data to a specific client"); 46 | Console.WriteLine(" sendt [ms] [guid] [data] Send data to a specific client with specified timeout"); 47 | Console.WriteLine(" read [guid] [count] Read [count] bytes from a specific client"); 48 | Console.WriteLine(" readt [ms] [guid] [count] Read [count] bytes from a specific client with specified timeout"); 49 | Console.WriteLine(" kick [guid] Disconnect a specific client from the server"); 50 | Console.WriteLine(" dispose Dispose of the server"); 51 | Console.WriteLine(" stats Retrieve statistics"); 52 | Console.WriteLine(""); 53 | continue; 54 | } 55 | 56 | if (userInput.Equals("c") || userInput.Equals("cls")) 57 | { 58 | Console.Clear(); 59 | continue; 60 | } 61 | 62 | if (userInput.Equals("q")) 63 | { 64 | _RunForever = false; 65 | break; 66 | } 67 | 68 | if (userInput.Equals("start")) 69 | { 70 | _Server.Start(); 71 | } 72 | 73 | if (userInput.Equals("stop")) 74 | { 75 | _Server.Stop(); 76 | } 77 | 78 | if (userInput.Equals("list")) 79 | { 80 | List clients = _Server.GetClients().ToList(); 81 | if (clients != null) 82 | { 83 | Console.WriteLine("Clients: " + clients.Count); 84 | foreach (ClientMetadata curr in clients) 85 | { 86 | Console.WriteLine(" " + curr.ToString()); 87 | } 88 | } 89 | else 90 | { 91 | Console.WriteLine("(null)"); 92 | } 93 | } 94 | 95 | if (userInput.StartsWith("send ")) 96 | { 97 | string[] parts = userInput.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries); 98 | Guid guid = Guid.Parse(parts[1]); 99 | string data = parts[2]; 100 | 101 | WriteResult wr = _Server.SendAsync(guid, data).Result; 102 | if (wr.Status == WriteResultStatus.Success) 103 | Console.WriteLine("Success"); 104 | else 105 | Console.WriteLine("Non-success status: " + wr.Status); 106 | } 107 | 108 | if (userInput.StartsWith("sendt ")) 109 | { 110 | string[] parts = userInput.Split(new char[] { ' ' }, 4, StringSplitOptions.RemoveEmptyEntries); 111 | int timeoutMs = Convert.ToInt32(parts[1]); 112 | Guid guid = Guid.Parse(parts[2]); 113 | string data = parts[3]; 114 | 115 | WriteResult wr = _Server.SendWithTimeoutAsync(timeoutMs, guid , data).Result; 116 | if (wr.Status == WriteResultStatus.Success) 117 | Console.WriteLine("Success"); 118 | else 119 | Console.WriteLine("Non-success status: " + wr.Status); 120 | } 121 | 122 | if (userInput.StartsWith("read ")) 123 | { 124 | string[] parts = userInput.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries); 125 | Guid guid = Guid.Parse(parts[1]); 126 | int count = Convert.ToInt32(parts[2]); 127 | 128 | ReadResult rr = _Server.ReadAsync(guid, count).Result; 129 | if (rr.Status == ReadResultStatus.Success) 130 | Console.WriteLine("Retrieved " + rr.BytesRead + " bytes: " + Encoding.UTF8.GetString(rr.Data)); 131 | else 132 | Console.WriteLine("Non-success status: " + rr.Status.ToString()); 133 | } 134 | 135 | if (userInput.StartsWith("readt ")) 136 | { 137 | string[] parts = userInput.Split(new char[] { ' ' }, 4, StringSplitOptions.RemoveEmptyEntries); 138 | int timeoutMs = Convert.ToInt32(parts[1]); 139 | Guid guid = Guid.Parse(parts[2]); 140 | int count = Convert.ToInt32(parts[3]); 141 | 142 | ReadResult rr = _Server.ReadWithTimeoutAsync(timeoutMs, guid, count).Result; 143 | if (rr.Status == ReadResultStatus.Success) 144 | Console.WriteLine("Retrieved " + rr.BytesRead + " bytes: " + Encoding.UTF8.GetString(rr.Data)); 145 | else 146 | Console.WriteLine("Non-success status: " + rr.Status.ToString()); 147 | } 148 | 149 | if (userInput.StartsWith("kick ")) 150 | { 151 | string[] parts = userInput.Split(new char[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries); 152 | Guid guid = Guid.Parse(parts[1]); 153 | 154 | _Server.DisconnectClient(guid); 155 | } 156 | 157 | if (userInput.Equals("dispose")) 158 | { 159 | _Server.Dispose(); 160 | } 161 | 162 | if (userInput.Equals("stats")) 163 | { 164 | Console.WriteLine(_Server.Statistics); 165 | } 166 | } 167 | } 168 | 169 | static void InitializeServer() 170 | { 171 | _Server = new CavemanTcpServer(_Hostname, _Port, _Ssl, null, null); 172 | _Server.Logger = Logger; 173 | 174 | _Server.Events.ClientConnected += (s, e) => 175 | { 176 | Console.WriteLine("Client " + e.Client.ToString() + " connected to server"); 177 | }; 178 | 179 | _Server.Events.ClientDisconnected += (s, e) => 180 | { 181 | Console.WriteLine("Client " + e.Client.ToString() + " disconnected from server"); 182 | }; 183 | } 184 | 185 | static void Logger(string msg) 186 | { 187 | Console.WriteLine(msg); 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/Test.ServerAsync/Test.ServerAsync.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net461;net472;net48;net6.0;net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Always 15 | 16 | 17 | Always 18 | 19 | 20 | Always 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Test.ServerAsync/cavemantcp.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFqTCCA5GgAwIBAgIUALJzgmmtlYlKi/7qIP3KlaC3YN0wDQYJKoZIhvcNAQEL 3 | BQAwYzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFzAVBgNVBAcM 4 | DlNpbGljb24gVmFsbGV5MRIwEAYDVQQKDAlTaW1wbGVUY3AxEjAQBgNVBAMMCXNp 5 | bXBsZXRjcDAgFw0xODExMjkxODA2MTVaGA8yMTE4MTEwNTE4MDYxNVowYzELMAkG 6 | A1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFzAVBgNVBAcMDlNpbGljb24g 7 | VmFsbGV5MRIwEAYDVQQKDAlTaW1wbGVUY3AxEjAQBgNVBAMMCXNpbXBsZXRjcDCC 8 | AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMn8DVfN1MVCq/BfOLgTosY7 9 | jSBFU7W2//0ha0BsWrciGL9rQ80Pvpfno1twpUX3OIs0jniooKDAUNKeHJvj2fle 10 | 5lACDOOgaYslXfpiKIOskTcqj4zpOdjhgo4Itpa+KnMaEq0DXj0y45WI2Z4E4Ruj 11 | IZkVMNSsfF3OVa8ZT2R/SurFlNsy5IWqWSeodP9YBV91Lc+7AZ/GbYKy1Qu/5mS6 12 | ++z7ZA8lCr4Nn2wnnoQqdZMU28zeHCTCT/ywlse4T5LTTwDfs4P/YuN4It1lxmSm 13 | qjo4yqpxoOdlXn4CKxRMaDpz29AspU0/g7bRn5keEnPqymYQ/i03FgHbDG9mJizM 14 | 0uw4EnvuWGoilCRz1CkvpKnpZ/Tq8FTy1JIqC7wKIYa4lRLXdhPvs7L4a3jSpWTj 15 | CiXeXKc4hRrAjTNw+OyaviPJW2Bc+lZTDO24SxweGnVIJ6fdeV0SZAPKgynpXKT5 16 | KJvSsxmsCGfh937tasD5wnu6DB9gUQLvIYpDKCc8lZTYv55Exv5JEKZ/Gn8FJU5D 17 | WTlvW1UVQdYdYeppNv5KvAvSOLr08GtzxKOY2Y+k8PI1eUmBYkuo+0r1SORCvj3U 18 | Dcd3njjcFOTv2Agb4Ohbh9FY96yukQ3yxwIQT6ad6LZahr6HeKHKiP5DQ/2xDBJp 19 | I/olGBB59cRVRFgt+7zTAgMBAAGjUzBRMB0GA1UdDgQWBBSpz8QPxENL4/qbHYTV 20 | 5u2uYf8p4TAfBgNVHSMEGDAWgBSpz8QPxENL4/qbHYTV5u2uYf8p4TAPBgNVHRMB 21 | Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCU5B0o9+Vimk7bbgklFSbX2L2/ 22 | ml+oVTIvyi0mQThnXr348ddv0zROVhQnPQUcdnlGdCPiYl+Jpkyr14bnOunvjjhQ 23 | wOer3r+mF4aU/kW618uXhHIsIkiKwCBwXGfOCqd/Phnptknjf6teq/R8OgGBMUY0 24 | twqWT4y7epyA0dFm+4bQzOu1FGHIvgfpv6Hjb0+jWPId8TP2bvgU674w6fm0KKDe 25 | 7ubYVi/Q9i494WbFY5PGY2MBu82gcMSIuVasVLJAaaj6vxsrK31dYkRGWGm6YOjB 26 | 38g4TanmiK0AjVkxhhMuiOJYMFe8R2dOHJBTd6wnnnPKa+IgUk9RJEdPLXV4Pkqh 27 | 2QdI9APAjxlJ5RMZfLa1ADPOP+NBENkIo43W7Cmx+z2BLU4/9OB0doHk7MBiTKFO 28 | V58ZkJOrApY9uFvSdJELWcfQlzn0XDAP12lEnJoLEorQK3mi4peXPWZRs1Iw5dAE 29 | pFz89Qy7Bc3i8MRkMWdu1eD17iiXSAd9G12wHlgaoR4GT2vDG870ce8/aefWInmj 30 | xs2RQTlx9g7DGi4WmMozUJh1ENrgI+ppL7yJiVUCsbDejOHAh1qurvZbgIA7qiHm 31 | SZMu7dm8BPlVhLtdkhR+9pdFWm+TjEhuIsyc6sxP5SqwI4xhGxSsMDQ2DkD+laYP 32 | shLs1ionTO/MVOisLA== 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /src/Test.ServerAsync/cavemantcp.key: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | 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 53 | n7xqA1MJhdSXXrOzUzs1VQ== 54 | -----END ENCRYPTED PRIVATE KEY----- 55 | -------------------------------------------------------------------------------- /src/Test.ServerAsync/cavemantcp.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchristn/CavemanTcp/3b36f917dca9422c88e544e85bf249f468efb804/src/Test.ServerAsync/cavemantcp.pfx -------------------------------------------------------------------------------- /src/Test.SslClient/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using CavemanTcp; 4 | 5 | namespace Test.SslClient 6 | { 7 | class Program 8 | { 9 | static bool _RunForever = true; 10 | static CavemanTcpClient _Client = null; 11 | 12 | static void Main(string[] args) 13 | { 14 | InitializeClient(); 15 | Console.WriteLine("Connecting to ssl://127.0.0.1:8000"); 16 | _Client.Connect(10); 17 | 18 | while (_RunForever) 19 | { 20 | Console.Write("Command [? for help]: "); 21 | string userInput = Console.ReadLine(); 22 | if (String.IsNullOrEmpty(userInput)) continue; 23 | 24 | if (userInput.Equals("?")) 25 | { 26 | Console.WriteLine("-- Available Commands --"); 27 | Console.WriteLine(""); 28 | Console.WriteLine(" cls Clear the screen"); 29 | Console.WriteLine(" q Quit the program"); 30 | Console.WriteLine(" send [data] Send data to the server"); 31 | Console.WriteLine(" sendt [ms] [data] Send data to the server with specified timeout"); 32 | Console.WriteLine(" read [count] Read [count] bytes from the server"); 33 | Console.WriteLine(" readt [ms] [count] Read [count] bytes from the server with specified timeout"); 34 | Console.WriteLine(" dispose Dispose of the client"); 35 | Console.WriteLine(" start Start the client (connected: " + (_Client != null ? _Client.IsConnected.ToString() : "false") + ")"); 36 | Console.WriteLine(" stats Retrieve statistics"); 37 | Console.WriteLine(""); 38 | continue; 39 | } 40 | 41 | if (userInput.Equals("c") || userInput.Equals("cls")) 42 | { 43 | Console.Clear(); 44 | continue; 45 | } 46 | 47 | if (userInput.Equals("q")) 48 | { 49 | _RunForever = false; 50 | break; 51 | } 52 | 53 | if (userInput.StartsWith("send ")) 54 | { 55 | string[] parts = userInput.Split(new char[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries); 56 | string data = parts[1]; 57 | 58 | WriteResult wr = _Client.Send(data); 59 | if (wr.Status == WriteResultStatus.Success) 60 | Console.WriteLine("Success"); 61 | else 62 | Console.WriteLine("Non-success status: " + wr.Status); 63 | } 64 | 65 | if (userInput.StartsWith("sendt ")) 66 | { 67 | string[] parts = userInput.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries); 68 | int timeoutMs = Convert.ToInt32(parts[1]); 69 | string data = parts[2]; 70 | 71 | WriteResult wr = _Client.SendWithTimeout(timeoutMs, data); 72 | if (wr.Status == WriteResultStatus.Success) 73 | Console.WriteLine("Success"); 74 | else 75 | Console.WriteLine("Non-success status: " + wr.Status); 76 | } 77 | 78 | if (userInput.StartsWith("read ")) 79 | { 80 | string[] parts = userInput.Split(new char[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries); 81 | int count = Convert.ToInt32(parts[1]); 82 | 83 | ReadResult rr = _Client.Read(count); 84 | if (rr.Status == ReadResultStatus.Success) 85 | Console.WriteLine("Retrieved " + rr.BytesRead + " bytes: " + Encoding.UTF8.GetString(rr.Data)); 86 | else 87 | Console.WriteLine("Non-success status: " + rr.Status.ToString()); 88 | } 89 | 90 | if (userInput.StartsWith("readt ")) 91 | { 92 | string[] parts = userInput.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries); 93 | int timeoutMs = Convert.ToInt32(parts[1]); 94 | int count = Convert.ToInt32(parts[2]); 95 | 96 | ReadResult rr = _Client.ReadWithTimeout(timeoutMs, count); 97 | if (rr.Status == ReadResultStatus.Success) 98 | Console.WriteLine("Retrieved " + rr.BytesRead + " bytes: " + Encoding.UTF8.GetString(rr.Data)); 99 | else 100 | Console.WriteLine("Non-success status: " + rr.Status.ToString()); 101 | } 102 | 103 | if (userInput.Equals("dispose")) 104 | { 105 | _Client.Dispose(); 106 | } 107 | 108 | if (userInput.Equals("start")) 109 | { 110 | InitializeClient(); 111 | _Client.Connect(10); 112 | } 113 | 114 | if (userInput.Equals("stats")) 115 | { 116 | Console.WriteLine(_Client.Statistics); 117 | } 118 | } 119 | } 120 | 121 | static void InitializeClient() 122 | { 123 | _Client = new CavemanTcpClient("127.0.0.1", 8000, true, "cavemantcp.pfx", "simpletcp"); 124 | _Client.Logger = Logger; 125 | 126 | _Client.Events.ClientConnected += (s, e) => 127 | { 128 | Console.WriteLine("Connected to server"); 129 | }; 130 | 131 | _Client.Events.ClientDisconnected += (s, e) => 132 | { 133 | Console.WriteLine("Disconnected from server"); 134 | }; 135 | } 136 | 137 | static void Logger(string msg) 138 | { 139 | Console.WriteLine(msg); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Test.SslClient/Test.SslClient.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net461;net472;net48;net6.0;net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Always 15 | 16 | 17 | Always 18 | 19 | 20 | Always 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Test.SslClient/cavemantcp.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFqTCCA5GgAwIBAgIUALJzgmmtlYlKi/7qIP3KlaC3YN0wDQYJKoZIhvcNAQEL 3 | BQAwYzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFzAVBgNVBAcM 4 | DlNpbGljb24gVmFsbGV5MRIwEAYDVQQKDAlTaW1wbGVUY3AxEjAQBgNVBAMMCXNp 5 | bXBsZXRjcDAgFw0xODExMjkxODA2MTVaGA8yMTE4MTEwNTE4MDYxNVowYzELMAkG 6 | A1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFzAVBgNVBAcMDlNpbGljb24g 7 | VmFsbGV5MRIwEAYDVQQKDAlTaW1wbGVUY3AxEjAQBgNVBAMMCXNpbXBsZXRjcDCC 8 | AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMn8DVfN1MVCq/BfOLgTosY7 9 | jSBFU7W2//0ha0BsWrciGL9rQ80Pvpfno1twpUX3OIs0jniooKDAUNKeHJvj2fle 10 | 5lACDOOgaYslXfpiKIOskTcqj4zpOdjhgo4Itpa+KnMaEq0DXj0y45WI2Z4E4Ruj 11 | IZkVMNSsfF3OVa8ZT2R/SurFlNsy5IWqWSeodP9YBV91Lc+7AZ/GbYKy1Qu/5mS6 12 | ++z7ZA8lCr4Nn2wnnoQqdZMU28zeHCTCT/ywlse4T5LTTwDfs4P/YuN4It1lxmSm 13 | qjo4yqpxoOdlXn4CKxRMaDpz29AspU0/g7bRn5keEnPqymYQ/i03FgHbDG9mJizM 14 | 0uw4EnvuWGoilCRz1CkvpKnpZ/Tq8FTy1JIqC7wKIYa4lRLXdhPvs7L4a3jSpWTj 15 | CiXeXKc4hRrAjTNw+OyaviPJW2Bc+lZTDO24SxweGnVIJ6fdeV0SZAPKgynpXKT5 16 | KJvSsxmsCGfh937tasD5wnu6DB9gUQLvIYpDKCc8lZTYv55Exv5JEKZ/Gn8FJU5D 17 | WTlvW1UVQdYdYeppNv5KvAvSOLr08GtzxKOY2Y+k8PI1eUmBYkuo+0r1SORCvj3U 18 | Dcd3njjcFOTv2Agb4Ohbh9FY96yukQ3yxwIQT6ad6LZahr6HeKHKiP5DQ/2xDBJp 19 | I/olGBB59cRVRFgt+7zTAgMBAAGjUzBRMB0GA1UdDgQWBBSpz8QPxENL4/qbHYTV 20 | 5u2uYf8p4TAfBgNVHSMEGDAWgBSpz8QPxENL4/qbHYTV5u2uYf8p4TAPBgNVHRMB 21 | Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCU5B0o9+Vimk7bbgklFSbX2L2/ 22 | ml+oVTIvyi0mQThnXr348ddv0zROVhQnPQUcdnlGdCPiYl+Jpkyr14bnOunvjjhQ 23 | wOer3r+mF4aU/kW618uXhHIsIkiKwCBwXGfOCqd/Phnptknjf6teq/R8OgGBMUY0 24 | twqWT4y7epyA0dFm+4bQzOu1FGHIvgfpv6Hjb0+jWPId8TP2bvgU674w6fm0KKDe 25 | 7ubYVi/Q9i494WbFY5PGY2MBu82gcMSIuVasVLJAaaj6vxsrK31dYkRGWGm6YOjB 26 | 38g4TanmiK0AjVkxhhMuiOJYMFe8R2dOHJBTd6wnnnPKa+IgUk9RJEdPLXV4Pkqh 27 | 2QdI9APAjxlJ5RMZfLa1ADPOP+NBENkIo43W7Cmx+z2BLU4/9OB0doHk7MBiTKFO 28 | V58ZkJOrApY9uFvSdJELWcfQlzn0XDAP12lEnJoLEorQK3mi4peXPWZRs1Iw5dAE 29 | pFz89Qy7Bc3i8MRkMWdu1eD17iiXSAd9G12wHlgaoR4GT2vDG870ce8/aefWInmj 30 | xs2RQTlx9g7DGi4WmMozUJh1ENrgI+ppL7yJiVUCsbDejOHAh1qurvZbgIA7qiHm 31 | SZMu7dm8BPlVhLtdkhR+9pdFWm+TjEhuIsyc6sxP5SqwI4xhGxSsMDQ2DkD+laYP 32 | shLs1ionTO/MVOisLA== 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /src/Test.SslClient/cavemantcp.key: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | 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 53 | n7xqA1MJhdSXXrOzUzs1VQ== 54 | -----END ENCRYPTED PRIVATE KEY----- 55 | -------------------------------------------------------------------------------- /src/Test.SslClient/cavemantcp.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchristn/CavemanTcp/3b36f917dca9422c88e544e85bf249f468efb804/src/Test.SslClient/cavemantcp.pfx -------------------------------------------------------------------------------- /src/Test.SslServer/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using CavemanTcp; 6 | 7 | namespace Test.SslServer 8 | { 9 | class Program 10 | { 11 | static bool _RunForever = true; 12 | static CavemanTcpServer _Server = null; 13 | 14 | static void Main(string[] args) 15 | { 16 | InitializeServer(); 17 | _Server.Start(); 18 | 19 | Console.WriteLine("Listening on ssl://127.0.0.1:8000"); 20 | 21 | while (_RunForever) 22 | { 23 | Console.Write("Command [? for help]: "); 24 | string userInput = Console.ReadLine(); 25 | if (String.IsNullOrEmpty(userInput)) continue; 26 | 27 | if (userInput.Equals("?")) 28 | { 29 | Console.WriteLine("-- Available Commands --"); 30 | Console.WriteLine(""); 31 | Console.WriteLine(" cls Clear the screen"); 32 | Console.WriteLine(" q Quit the program"); 33 | Console.WriteLine(" list List connected clients"); 34 | Console.WriteLine(" send [guid] [data] Send data to a specific client"); 35 | Console.WriteLine(" sendt [ms] [guid] [data] Send data to a specific client with specified timeout"); 36 | Console.WriteLine(" read [guid] [count] Read [count] bytes from a specific client"); 37 | Console.WriteLine(" readt [ms] [guid] [count] Read [count] bytes from a specific client with specified timeout"); 38 | Console.WriteLine(" kick [guid] Disconnect a specific client from the server"); 39 | Console.WriteLine(" dispose Dispose of the server"); 40 | Console.WriteLine(" start Start the server (running: " + (_Server != null ? _Server.IsListening.ToString() : "false") + ")"); 41 | Console.WriteLine(" stats Retrieve statistics"); 42 | Console.WriteLine(""); 43 | continue; 44 | } 45 | 46 | if (userInput.Equals("c") || userInput.Equals("cls")) 47 | { 48 | Console.Clear(); 49 | continue; 50 | } 51 | 52 | if (userInput.Equals("q")) 53 | { 54 | _RunForever = false; 55 | break; 56 | } 57 | 58 | if (userInput.Equals("list")) 59 | { 60 | List clients = _Server.GetClients().ToList(); 61 | if (clients != null) 62 | { 63 | Console.WriteLine("Clients: " + clients.Count); 64 | foreach (ClientMetadata curr in clients) 65 | { 66 | Console.WriteLine(" " + curr.ToString()); 67 | } 68 | } 69 | else 70 | { 71 | Console.WriteLine("(null)"); 72 | } 73 | } 74 | 75 | if (userInput.StartsWith("send ")) 76 | { 77 | string[] parts = userInput.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries); 78 | Guid guid = Guid.Parse(parts[1]); 79 | string data = parts[2]; 80 | 81 | WriteResult wr = _Server.Send(guid, data); 82 | if (wr.Status == WriteResultStatus.Success) 83 | Console.WriteLine("Success"); 84 | else 85 | Console.WriteLine("Non-success status: " + wr.Status); 86 | } 87 | 88 | if (userInput.StartsWith("sendt ")) 89 | { 90 | string[] parts = userInput.Split(new char[] { ' ' }, 4, StringSplitOptions.RemoveEmptyEntries); 91 | int timeoutMs = Convert.ToInt32(parts[1]); 92 | Guid guid = Guid.Parse(parts[2]); 93 | string data = parts[3]; 94 | 95 | WriteResult wr = _Server.SendWithTimeout(timeoutMs, guid, data); 96 | if (wr.Status == WriteResultStatus.Success) 97 | Console.WriteLine("Success"); 98 | else 99 | Console.WriteLine("Non-success status: " + wr.Status); 100 | } 101 | 102 | if (userInput.StartsWith("read ")) 103 | { 104 | string[] parts = userInput.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries); 105 | Guid guid = Guid.Parse(parts[1]); 106 | int count = Convert.ToInt32(parts[2]); 107 | 108 | ReadResult rr = _Server.Read(guid, count); 109 | if (rr.Status == ReadResultStatus.Success) 110 | Console.WriteLine("Retrieved " + rr.BytesRead + " bytes: " + Encoding.UTF8.GetString(rr.Data)); 111 | else 112 | Console.WriteLine("Non-success status: " + rr.Status.ToString()); 113 | } 114 | 115 | if (userInput.StartsWith("readt ")) 116 | { 117 | string[] parts = userInput.Split(new char[] { ' ' }, 4, StringSplitOptions.RemoveEmptyEntries); 118 | int timeoutMs = Convert.ToInt32(parts[1]); 119 | Guid guid = Guid.Parse(parts[2]); 120 | int count = Convert.ToInt32(parts[3]); 121 | 122 | ReadResult rr = _Server.ReadWithTimeout(timeoutMs, guid, count); 123 | if (rr.Status == ReadResultStatus.Success) 124 | Console.WriteLine("Retrieved " + rr.BytesRead + " bytes: " + Encoding.UTF8.GetString(rr.Data)); 125 | else 126 | Console.WriteLine("Non-success status: " + rr.Status.ToString()); 127 | } 128 | 129 | if (userInput.StartsWith("kick ")) 130 | { 131 | string[] parts = userInput.Split(new char[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries); 132 | Guid guid = Guid.Parse(parts[1]); 133 | 134 | _Server.DisconnectClient(guid); 135 | } 136 | 137 | if (userInput.Equals("dispose")) 138 | { 139 | _Server.Dispose(); 140 | } 141 | 142 | if (userInput.Equals("start")) 143 | { 144 | InitializeServer(); 145 | _Server.Start(); 146 | } 147 | 148 | if (userInput.Equals("stats")) 149 | { 150 | Console.WriteLine(_Server.Statistics); 151 | } 152 | } 153 | } 154 | 155 | static void InitializeServer() 156 | { 157 | _Server = new CavemanTcpServer("127.0.0.1", 8000, true, "cavemantcp.pfx", "simpletcp"); 158 | _Server.Logger = Logger; 159 | 160 | _Server.Events.ClientConnected += (s, e) => 161 | { 162 | Console.WriteLine("Client " + e.Client.ToString() + " connected to server"); 163 | }; 164 | 165 | _Server.Events.ClientDisconnected += (s, e) => 166 | { 167 | Console.WriteLine("Client " + e.Client.ToString() + " disconnected from server"); 168 | }; 169 | } 170 | 171 | static void Logger(string msg) 172 | { 173 | Console.WriteLine(msg); 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/Test.SslServer/Test.SslServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net461;net472;net48;net6.0;net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Always 15 | 16 | 17 | Always 18 | 19 | 20 | Always 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Test.SslServer/cavemantcp.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFqTCCA5GgAwIBAgIUALJzgmmtlYlKi/7qIP3KlaC3YN0wDQYJKoZIhvcNAQEL 3 | BQAwYzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFzAVBgNVBAcM 4 | DlNpbGljb24gVmFsbGV5MRIwEAYDVQQKDAlTaW1wbGVUY3AxEjAQBgNVBAMMCXNp 5 | bXBsZXRjcDAgFw0xODExMjkxODA2MTVaGA8yMTE4MTEwNTE4MDYxNVowYzELMAkG 6 | A1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFzAVBgNVBAcMDlNpbGljb24g 7 | VmFsbGV5MRIwEAYDVQQKDAlTaW1wbGVUY3AxEjAQBgNVBAMMCXNpbXBsZXRjcDCC 8 | AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMn8DVfN1MVCq/BfOLgTosY7 9 | jSBFU7W2//0ha0BsWrciGL9rQ80Pvpfno1twpUX3OIs0jniooKDAUNKeHJvj2fle 10 | 5lACDOOgaYslXfpiKIOskTcqj4zpOdjhgo4Itpa+KnMaEq0DXj0y45WI2Z4E4Ruj 11 | IZkVMNSsfF3OVa8ZT2R/SurFlNsy5IWqWSeodP9YBV91Lc+7AZ/GbYKy1Qu/5mS6 12 | ++z7ZA8lCr4Nn2wnnoQqdZMU28zeHCTCT/ywlse4T5LTTwDfs4P/YuN4It1lxmSm 13 | qjo4yqpxoOdlXn4CKxRMaDpz29AspU0/g7bRn5keEnPqymYQ/i03FgHbDG9mJizM 14 | 0uw4EnvuWGoilCRz1CkvpKnpZ/Tq8FTy1JIqC7wKIYa4lRLXdhPvs7L4a3jSpWTj 15 | CiXeXKc4hRrAjTNw+OyaviPJW2Bc+lZTDO24SxweGnVIJ6fdeV0SZAPKgynpXKT5 16 | KJvSsxmsCGfh937tasD5wnu6DB9gUQLvIYpDKCc8lZTYv55Exv5JEKZ/Gn8FJU5D 17 | WTlvW1UVQdYdYeppNv5KvAvSOLr08GtzxKOY2Y+k8PI1eUmBYkuo+0r1SORCvj3U 18 | Dcd3njjcFOTv2Agb4Ohbh9FY96yukQ3yxwIQT6ad6LZahr6HeKHKiP5DQ/2xDBJp 19 | I/olGBB59cRVRFgt+7zTAgMBAAGjUzBRMB0GA1UdDgQWBBSpz8QPxENL4/qbHYTV 20 | 5u2uYf8p4TAfBgNVHSMEGDAWgBSpz8QPxENL4/qbHYTV5u2uYf8p4TAPBgNVHRMB 21 | Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCU5B0o9+Vimk7bbgklFSbX2L2/ 22 | ml+oVTIvyi0mQThnXr348ddv0zROVhQnPQUcdnlGdCPiYl+Jpkyr14bnOunvjjhQ 23 | wOer3r+mF4aU/kW618uXhHIsIkiKwCBwXGfOCqd/Phnptknjf6teq/R8OgGBMUY0 24 | twqWT4y7epyA0dFm+4bQzOu1FGHIvgfpv6Hjb0+jWPId8TP2bvgU674w6fm0KKDe 25 | 7ubYVi/Q9i494WbFY5PGY2MBu82gcMSIuVasVLJAaaj6vxsrK31dYkRGWGm6YOjB 26 | 38g4TanmiK0AjVkxhhMuiOJYMFe8R2dOHJBTd6wnnnPKa+IgUk9RJEdPLXV4Pkqh 27 | 2QdI9APAjxlJ5RMZfLa1ADPOP+NBENkIo43W7Cmx+z2BLU4/9OB0doHk7MBiTKFO 28 | V58ZkJOrApY9uFvSdJELWcfQlzn0XDAP12lEnJoLEorQK3mi4peXPWZRs1Iw5dAE 29 | pFz89Qy7Bc3i8MRkMWdu1eD17iiXSAd9G12wHlgaoR4GT2vDG870ce8/aefWInmj 30 | xs2RQTlx9g7DGi4WmMozUJh1ENrgI+ppL7yJiVUCsbDejOHAh1qurvZbgIA7qiHm 31 | SZMu7dm8BPlVhLtdkhR+9pdFWm+TjEhuIsyc6sxP5SqwI4xhGxSsMDQ2DkD+laYP 32 | shLs1ionTO/MVOisLA== 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /src/Test.SslServer/cavemantcp.key: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | MIIJnDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIzyT8bR+zRwcCAggA 3 | MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECOSHMlux3KUYBIIJSFzHVcFK9B39 4 | 7ctTAIye62oiyvI8crMgRRmu/7FShgXn2X4s4ozM/uMLlAE3m9Fa9EQ7hp8YotUB 5 | +jnugODVmr4lNZ7CiAHmUbixvzBJU+SoWn3b8ivBLrG8D/IT6vLgyrjQNvZExOeN 6 | 2s9Bz1nu3SLORklC1yKB80n/GmPMMtb4XmygFSnQ/wx07wMIep6JKsODMJplVWPl 7 | r07bh9ndlv8oc2RZ54Tt/ApZi5lpqGbCQEgNoXheyHnabiVFiAOlXu58IvHXSOav 8 | qMjp+dK3IwqiamZNCR/8jAq6EltGzv1E/hAWZ8YgTA0s0474ESP6o5uAzVeb1yMJ 9 | PHRm4lOD4HgaYElHgL6hHN4Q4R1DSKtaMIigod6GzMYI9ZRhUiK30x9xJgDlwhjo 10 | IgkoiHZ3LzFR+dUawrUvRInyPyL5tprNx6FhYXe50CS2U99neKw/rDi7lTIoF29P 11 | jdm/JN1PLN78gBZqnjtJNFJEg7FaAtmi0/QF133JHURwN+NfZln9sCMXzmc5+jjD 12 | k7kfKgLsNHHH11V5ZXARTrQQ+r2FM3c8okj7yJXA56gQXi7MsqFI4dqUmSsHllkI 13 | DWCc0HKAo63TOw+2YCU0SRHt2MNt6oHfIVr2HxIPEmsOD2rwDGRIfgzhS+1le+hS 14 | bj8iMCq9hPhpkI2o4yQLu6Mf7FSH8N8ZwHOR32Euwwv50OES/UMa4/I2Ld1qnPa7 15 | hcllwziOAiRJSlqWTL6B75H5Vvx1+gGD3oDnNHpRBM0ld8QzYEvRM4i9BHZcXKEQ 16 | GlLU4H+Fg3WexcoEvmt7jLGTlIbZfibDtQs/IpnfAuYLFO87VGRDMHpEpDX0dzdU 17 | hPa5WGcZ+hF7GUh4KViFwdcPErYpGpNXYOhmhkyJoL/a3WJ4MuFeb2djzdEoNEth 18 | XGGFrKcojAZGynQNvR1ZT8Az7GvUrjSj3fffLMIHRjPSSugLOzDldaBD+sA/Kmaz 19 | CcO/Ufq2pav/BPoVNGgTLzaI8d3moXdRyGKf9CAIANhaxhS1M8JRlrVa2JncfbkF 20 | NB2hh/C28o90ZULBCv3nqdjBeqxWYFgCrH84uhezJNkc+TEJIwNIFur0JB4hYl4d 21 | PiqOzAdDVNhmxKzpdc0grDtxr/wWOa9+lPT4aHoUd8BmgmCTCw8yBp2+VAfQ7qOv 22 | k1x0+mlUxSJNgvU39xCm8oD/c8Sx5RYHjOIJ8FPn/tbVsr2/WuSLkZHpW3w6DXFB 23 | AoEav3A9/LcLhbkraPdwTmUH4RgGjZxS/n+hvR3mIH7IdPQMojybzuWba9EHc6a6 24 | Uf+QmwPGyOZl+DdBNqzlUSSTuRIkbKuri2p2sfT/0EsbC5bQTjR/782NzxgQ/+mJ 25 | 7nh5ThZVWZyvl/wUenq3Kze9gDstGZF+yKk+I1elaZouvds3/ead1LA+6P1dntwo 26 | zcKfYdjix6Pcn/c0HrFzvq1OWn0Gwki5+DWj4NTrUEc15Jf5OqdNYj3lv4a8muQu 27 | zwZW3VubqmATGQ1XjIvqgmteqcKNWg5KaHU7TDccBy2g5WCo5fbeQlXLWum17+OG 28 | Ffi77c0/8t72ywB4ufud+o49KzhHoAs0ylVl6CJw+3TIiZEEre8QMq2VppJdJmTD 29 | UFI4qXT/446ylN6OFUuAc1GHSIe95E/AGkoe9pDNN3QVqOJNQvqlGCWQ2myvIT7M 30 | b0TJtyyOyi1SRoaHkqmciIiALv3NG60xJ7K/CXb5aUCraoodtxxSxfVCDOEXLdTe 31 | W+PXVvVuXGLUiPWv8DuLZdml0S4ID1ZgYUjuf3VznJJcbXQQAbDgyW2Trdg+s94k 32 | Iyhyb213KpeXjEw9GcTdX2VDo/MUWUSGBG1eu3dnxJzJJK9vKNa6V8opI3KSnB9R 33 | 6TAX2p6V8nZsgzOMwB750NTE5c/V2AxZ2pjjZk2QfqNEf0nPwbCv2xeHQN7Wexja 34 | wqALP4rdJvGIs0ONVpvmvux2CTH3JantIs2r+if2V2Vd6qRSf4U4RZZG+LPsZy2a 35 | efafnQV+XsCcSaT6rK7qTvHqNpBEc6I2YL7AkrT9m0Nu0grGSrbuFNkzOqnOS9Le 36 | E24YZxn0fQGQu37Mntp2umDX6ixnPTA2gRExdxiSoyMs3pUAHPthxJiFN6gcyNI9 37 | i3tE21hSHpcEeEzArukdhM8wn8Utph2bmP+AgTWfkotCrFHr5clOiawjIkHcUT/w 38 | yeIDUIT5Ruc0x+4STDAwJTSUQ+DcDeM3yHnmSaint3ih55BnIO3uCwlaY+dsXRkN 39 | 4ZTlFFHHQoZV7Ok/3DP2nS8AeNNWPnqKtf/s0IX5zKZUG671G5lCJUJxLShktCKY 40 | y54m+ZRlJXfuCTthO/Htgio/hWnUXodLefZt4IwiRuBMdYbXjC67gVqpEuoiOpsj 41 | q9I4QXT0PTvFkusE/CxcKQkdF9LHwoV2Dz5xV9adjIDJqoAehX2qI8vOe0+81jkC 42 | Qo1sjSp35C59oT5BAgyebuDgWfcH247wr6RxH4rOuBGCK4IXWv9slYA9lQz/D0IM 43 | LiISwo6g6xpn0PW3jnKXzvk5Avio8/Wg+2eCzQT4KWg56M4lkDKc6b7+njm4d4mA 44 | qNN+wm+Xw7Vq+6ulRnwrU4jy22jHQ10jbWupro1QQ4eHwTvHrj8v+jKPhIUTD9xJ 45 | h3eFizCY31jgUzlV4GNPJ+JophZh0F4PWDFVfRq3rh7LVRbYz+dFcDeIJuuQQv70 46 | WLpdSyQszq2lKvCVUvG985E5fD3l2tHRK/njc7eiQFPLcK0D5RBfI5mzD9cCchFK 47 | WhJOQATzKH8/i6toP0f5yd0w8vjO/0WKpQfG7tbeyt8YQVGolRwGQJmEFpydRiT9 48 | VAYfWS2hUCRbdYmB1hoFrY0tPeRlOhRoRmcIiCXmb4joioFiAZ3q2UnswbnfWKiK 49 | oEBK+KtulXttImREuWFOE4hwvv0B6Dm3TPagMEymn4Ko/ql2Vj9VaXeEj1JAuM4A 50 | NyE5lWW/rfmi9Xbi4Xs/buNWlnG3nn5rVn/XXQK71ip5cjrm8uGI5Rkhmk4r6pXC 51 | cRKM6XjZeRu++6Q2p7UnAU2mjU8ps7HzsFtWMq1UySkJ4JaLsyOOAt338SQw928r 52 | VL2AhrS20lEbrBxZbQGiyz5sTBahWJ127ploEo4C6cJuLwAQmzaIyS3GQ9XHn9Aj 53 | n7xqA1MJhdSXXrOzUzs1VQ== 54 | -----END ENCRYPTED PRIVATE KEY----- 55 | -------------------------------------------------------------------------------- /src/Test.SslServer/cavemantcp.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchristn/CavemanTcp/3b36f917dca9422c88e544e85bf249f468efb804/src/Test.SslServer/cavemantcp.pfx --------------------------------------------------------------------------------