├── CONTRIBUTORS ├── LICENSE ├── README.md ├── client.go ├── doc.go ├── handlers.go ├── tcp.go ├── tcp_config.go ├── tcp_handlers_test.go └── tcp_test.go /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # This is the official list of people who can contribute 2 | # (and typically have contributed) code to the gotraining repository. 3 | # 4 | # Names should be added to this file only after verifying that 5 | # the individual or the individual's organization has agreed to 6 | # the appropriate Contributor License Agreement, found here: 7 | # 8 | # http://code.google.com/legal/individual-cla-v1.0.html 9 | # http://code.google.com/legal/corporate-cla-v1.0.html 10 | # 11 | # The agreement for individuals can be filled out on the web. 12 | 13 | # Names should be added to this file like so: 14 | # Name 15 | # 16 | # An entry with two email addresses specifies that the 17 | # first address should be used in the submit logs and 18 | # that the second address should be recognized as the 19 | # same person when interacting with Rietveld. 20 | 21 | # Please keep the list sorted. 22 | 23 | William Kennedy -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # tcp 4 | `import "github.com/ardanlabs/tcp"` 5 | 6 | * [Overview](#pkg-overview) 7 | * [Index](#pkg-index) 8 | 9 | ## Overview 10 | Package tcp provides the boilerpale code for working with TCP based data. The package 11 | allows you to establish a TCP listener that can accept client connections on a specified IP address 12 | and port. It also provides a function to send data back to the client. 13 | 14 | There are three interfaces that need to be implemented to use the package. These 15 | interfaces provide the API for processing data. 16 | 17 | ConnHandler 18 | 19 | 20 | type ConnHandler interface { 21 | Bind(conn net.Conn) (io.Reader, io.Writer) 22 | } 23 | 24 | The ConnHandler interface is implemented by the user to bind the client connection 25 | to a reader and writer for processing. 26 | 27 | ReqHandler 28 | 29 | 30 | type ReqHandler interface { 31 | Read(ipAddress string, reader io.Reader) ([]byte, int, error) 32 | Process(r *Request) 33 | } 34 | 35 | type Request struct { 36 | TCP *TCP 37 | TCPAddr *net.TCPAddr 38 | Data []byte 39 | Length int 40 | } 41 | 42 | The ReqHandler interface is implemented by the user to implement the processing 43 | of request messages from the client. Read is provided an ipaddress and the user-defined 44 | reader and must return the data read off the wire and the length. Returning io.EOF or a non 45 | temporary error will show down the listener. 46 | 47 | RespHandler 48 | 49 | 50 | type RespHandler interface { 51 | Write(r *Response, writer io.Writer) error 52 | } 53 | 54 | type Response struct { 55 | TCPAddr *net.TCPAddr 56 | Data []byte 57 | Length int 58 | } 59 | 60 | The RespHandler interface is implemented by the user to implement the processing 61 | of the response messages to the client. Write is provided the user-defined 62 | writer and the data to write. 63 | 64 | ### Sample Application 65 | After implementing the interfaces, the following code is all that is needed to 66 | start processing messages. 67 | 68 | 69 | func main() { 70 | log.Println("Starting Test App") 71 | 72 | cfg := tcp.Config{ 73 | NetType: "tcp4", 74 | Addr: ":9000", 75 | WorkRoutines: 2, 76 | WorkStats: time.Minute, 77 | ConnHandler: tcpConnHandler{}, 78 | ReqHandler: udpReqHandler{}, 79 | RespHandler: udpRespHandler{}, 80 | } 81 | 82 | t, err := tcp.New(&cfg) 83 | if err != nil { 84 | log.Println(err) 85 | return 86 | } 87 | 88 | if err := t.Start(); err != nil { 89 | log.Println(err) 90 | return 91 | } 92 | 93 | // Wait for a signal to shutdown. 94 | sigChan := make(chan os.Signal, 1) 95 | signal.Notify(sigChan, os.Interrupt) 96 | <-sigChan 97 | 98 | t.Stop() 99 | log.Println("down") 100 | } 101 | 102 | 103 | 104 | 105 | ## Index 106 | * [Constants](#pkg-constants) 107 | * [Variables](#pkg-variables) 108 | * [type CltError](#CltError) 109 | * [func (ce CltError) Error() string](#CltError.Error) 110 | * [type Config](#Config) 111 | * [func (cfg *Config) Event(evt, typ int, ipAddress string, format string, a ...interface{})](#Config.Event) 112 | * [func (cfg *Config) Validate() error](#Config.Validate) 113 | * [type ConnHandler](#ConnHandler) 114 | * [type OptEvent](#OptEvent) 115 | * [type OptRateLimit](#OptRateLimit) 116 | * [type ReqHandler](#ReqHandler) 117 | * [type Request](#Request) 118 | * [type RespHandler](#RespHandler) 119 | * [type Response](#Response) 120 | * [type Stat](#Stat) 121 | * [type TCP](#TCP) 122 | * [func New(name string, cfg Config) (*TCP, error)](#New) 123 | * [func (t *TCP) Addr() net.Addr](#TCP.Addr) 124 | * [func (t *TCP) ClientStats() []Stat](#TCP.ClientStats) 125 | * [func (t *TCP) Clients() int](#TCP.Clients) 126 | * [func (t *TCP) Connections() int](#TCP.Connections) 127 | * [func (t *TCP) Drop(tcpAddr *net.TCPAddr) error](#TCP.Drop) 128 | * [func (t *TCP) DropConnections(drop bool)](#TCP.DropConnections) 129 | * [func (t *TCP) Groom(d time.Duration)](#TCP.Groom) 130 | * [func (t *TCP) Send(ctx context.Context, r *Response) error](#TCP.Send) 131 | * [func (t *TCP) SendAll(ctx context.Context, r *Response) error](#TCP.SendAll) 132 | * [func (t *TCP) Start() error](#TCP.Start) 133 | * [func (t *TCP) Stop() error](#TCP.Stop) 134 | 135 | 136 | #### Package files 137 | [client.go](/src/github.com/ardanlabs/tcp/client.go) [doc.go](/src/github.com/ardanlabs/tcp/doc.go) [handlers.go](/src/github.com/ardanlabs/tcp/handlers.go) [tcp.go](/src/github.com/ardanlabs/tcp/tcp.go) [tcp_config.go](/src/github.com/ardanlabs/tcp/tcp_config.go) 138 | 139 | 140 | ## Constants 141 | ``` go 142 | const ( 143 | EvtAccept = iota + 1 144 | EvtJoin 145 | EvtRead 146 | EvtRemove 147 | EvtDrop 148 | EvtGroom 149 | ) 150 | ``` 151 | Set of event types. 152 | 153 | ``` go 154 | const ( 155 | TypError = iota + 1 156 | TypInfo 157 | TypTrigger 158 | ) 159 | ``` 160 | Set of event sub types. 161 | 162 | 163 | ## Variables 164 | ``` go 165 | var ( 166 | ErrInvalidConfiguration = errors.New("invalid configuration") 167 | ErrInvalidNetType = errors.New("invalid net type configuration") 168 | ErrInvalidConnHandler = errors.New("invalid connection handler configuration") 169 | ErrInvalidReqHandler = errors.New("invalid request handler configuration") 170 | ErrInvalidRespHandler = errors.New("invalid response handler configuration") 171 | ) 172 | ``` 173 | Set of error variables for start up. 174 | 175 | 176 | 177 | 178 | ## type [CltError](/src/target/tcp.go?s=795:816#L32) 179 | ``` go 180 | type CltError []error 181 | ``` 182 | CltError provides support for multi client operations that might error. 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | ### func (CltError) [Error](/src/target/tcp.go?s=871:904#L35) 194 | ``` go 195 | func (ce CltError) Error() string 196 | ``` 197 | Error implments the error interface for CltError. 198 | 199 | 200 | 201 | 202 | ## type [Config](/src/target/tcp_config.go?s=470:1102#L7) 203 | ``` go 204 | type Config struct { 205 | NetType string // "tcp", tcp4" or "tcp6" 206 | Addr string // "host:port" or "[ipv6-host%zone]:port" 207 | 208 | ConnHandler ConnHandler // Support for binding new connections to a reader and writer. 209 | ReqHandler ReqHandler // Support for handling the specific request workflow. 210 | RespHandler RespHandler // Support for handling the specific response workflow. 211 | 212 | OptRateLimit 213 | OptEvent 214 | } 215 | ``` 216 | Config provides a data structure of required configuration parameters. 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | ### func (\*Config) [Event](/src/target/tcp_config.go?s=1626:1715#L49) 228 | ``` go 229 | func (cfg *Config) Event(evt, typ int, ipAddress string, format string, a ...interface{}) 230 | ``` 231 | Event fires events back to the user for important events. 232 | 233 | 234 | 235 | 236 | ### func (\*Config) [Validate](/src/target/tcp_config.go?s=1160:1195#L24) 237 | ``` go 238 | func (cfg *Config) Validate() error 239 | ``` 240 | Validate checks the configuration to required items. 241 | 242 | 243 | 244 | 245 | ## type [ConnHandler](/src/target/handlers.go?s=485:609#L20) 246 | ``` go 247 | type ConnHandler interface { 248 | 249 | // Bind is called to set the reader and writer. 250 | Bind(conn net.Conn) (io.Reader, io.Writer) 251 | } 252 | ``` 253 | ConnHandler is implemented by the user to bind the connection 254 | to a reader and writer for processing. 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | ## type [OptEvent](/src/target/tcp_config.go?s=293:394#L2) 266 | ``` go 267 | type OptEvent struct { 268 | Event func(evt, typ int, ipAddress string, format string, a ...interface{}) 269 | } 270 | ``` 271 | OptEvent defines an handler used to provide events. 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | ## type [OptRateLimit](/src/target/tcp_config.go?s=128:236#L1) 283 | ``` go 284 | type OptRateLimit struct { 285 | RateLimit func() time.Duration // Connection rate limit per single connection. 286 | } 287 | ``` 288 | OptRateLimit declares fields for the user to provide configuration 289 | for connection rate limit. 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | ## type [ReqHandler](/src/target/handlers.go?s=720:1356#L28) 301 | ``` go 302 | type ReqHandler interface { 303 | 304 | // Read is provided an ipaddress and the user-defined reader and must return 305 | // the data read off the wire and the length. Returning io.EOF or a non 306 | // temporary error will show down the listener. 307 | Read(ipAddress string, reader io.Reader) ([]byte, int, error) 308 | 309 | // Process is used to handle the processing of the request. 310 | Process(r *Request) 311 | } 312 | ``` 313 | ReqHandler is implemented by the user to implement the processing 314 | of request messages from the client. 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | ## type [Request](/src/target/handlers.go?s=107:253#L1) 326 | ``` go 327 | type Request struct { 328 | TCP *TCP 329 | TCPAddr *net.TCPAddr 330 | IsIPv6 bool 331 | ReadAt time.Time 332 | Context context.Context 333 | Data []byte 334 | Length int 335 | } 336 | ``` 337 | Request is the message received by the client. 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | ## type [RespHandler](/src/target/handlers.go?s=1471:1619#L46) 349 | ``` go 350 | type RespHandler interface { 351 | 352 | // Write is provided the response to write and the user-defined writer. 353 | Write(r *Response, writer io.Writer) error 354 | } 355 | ``` 356 | RespHandler is implemented by the user to implement the processing 357 | of the response messages to the client. 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | ## type [Response](/src/target/handlers.go?s=301:376#L12) 369 | ``` go 370 | type Response struct { 371 | TCPAddr *net.TCPAddr 372 | Data []byte 373 | Length int 374 | } 375 | ``` 376 | Response is message to send to the client. 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | ## type [Stat](/src/target/tcp.go?s=8518:8623#L376) 388 | ``` go 389 | type Stat struct { 390 | IP string 391 | Reads int 392 | Writes int 393 | TimeConn time.Time 394 | LastAct time.Time 395 | } 396 | ``` 397 | Stat represents a client statistic. 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | ## type [TCP](/src/target/tcp.go?s=1084:1384#L45) 409 | ``` go 410 | type TCP struct { 411 | Config 412 | Name string 413 | // contains filtered or unexported fields 414 | } 415 | ``` 416 | TCP contains a set of networked client connections. 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | ### func [New](/src/target/tcp.go?s=1435:1482#L68) 425 | ``` go 426 | func New(name string, cfg Config) (*TCP, error) 427 | ``` 428 | New creates a new manager to service clients. 429 | 430 | 431 | 432 | 433 | 434 | ### func (\*TCP) [Addr](/src/target/tcp.go?s=8106:8135#L352) 435 | ``` go 436 | func (t *TCP) Addr() net.Addr 437 | ``` 438 | Addr returns the listener's network address. This may be different than the values 439 | provided in the configuration, for example if configuration port value is 0. 440 | 441 | 442 | 443 | 444 | ### func (\*TCP) [ClientStats](/src/target/tcp.go?s=8679:8713#L385) 445 | ``` go 446 | func (t *TCP) ClientStats() []Stat 447 | ``` 448 | ClientStats return details for all active clients. 449 | 450 | 451 | 452 | 453 | ### func (\*TCP) [Clients](/src/target/tcp.go?s=9132:9159#L410) 454 | ``` go 455 | func (t *TCP) Clients() int 456 | ``` 457 | Clients returns the number of active clients connected. 458 | 459 | 460 | 461 | 462 | ### func (\*TCP) [Connections](/src/target/tcp.go?s=8350:8381#L363) 463 | ``` go 464 | func (t *TCP) Connections() int 465 | ``` 466 | Connections returns the number of client connections. 467 | 468 | 469 | 470 | 471 | ### func (\*TCP) [Drop](/src/target/tcp.go?s=6106:6152#L269) 472 | ``` go 473 | func (t *TCP) Drop(tcpAddr *net.TCPAddr) error 474 | ``` 475 | Drop will close the socket connection. 476 | 477 | 478 | 479 | 480 | ### func (\*TCP) [DropConnections](/src/target/tcp.go?s=7797:7837#L341) 481 | ``` go 482 | func (t *TCP) DropConnections(drop bool) 483 | ``` 484 | DropConnections sets a flag to tell the accept routine to immediately 485 | drop connections that come in. 486 | 487 | 488 | 489 | 490 | ### func (\*TCP) [Groom](/src/target/tcp.go?s=9343:9379#L422) 491 | ``` go 492 | func (t *TCP) Groom(d time.Duration) 493 | ``` 494 | Groom drops connections that are not active for the specified duration. 495 | 496 | 497 | 498 | 499 | ### func (\*TCP) [Send](/src/target/tcp.go?s=6659:6717#L291) 500 | ``` go 501 | func (t *TCP) Send(ctx context.Context, r *Response) error 502 | ``` 503 | Send will deliver the response back to the client. 504 | 505 | 506 | 507 | 508 | ### func (\*TCP) [SendAll](/src/target/tcp.go?s=7237:7298#L314) 509 | ``` go 510 | func (t *TCP) SendAll(ctx context.Context, r *Response) error 511 | ``` 512 | SendAll will deliver the response back to all connected clients. 513 | 514 | 515 | 516 | 517 | ### func (\*TCP) [Start](/src/target/tcp.go?s=2178:2205#L102) 518 | ``` go 519 | func (t *TCP) Start() error 520 | ``` 521 | Start creates the accept routine and begins to accept connections. 522 | 523 | 524 | 525 | 526 | ### func (\*TCP) [Stop](/src/target/tcp.go?s=5082:5108#L221) 527 | ``` go 528 | func (t *TCP) Stop() error 529 | ``` 530 | Stop shuts down the manager and closes all connections. 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | - - - 540 | Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md) 541 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "io" 7 | "net" 8 | "strconv" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | // client represents a single networked connection. 14 | type client struct { 15 | t *TCP 16 | conn net.Conn 17 | ipAddress string 18 | isIPv6 bool 19 | reader io.Reader 20 | writer io.Writer 21 | wg sync.WaitGroup 22 | 23 | timeConn time.Time 24 | lastAct time.Time 25 | nReads int 26 | nWrites int 27 | } 28 | 29 | // newClient creates a new client for an incoming connection. 30 | func newClient(t *TCP, conn net.Conn) *client { 31 | now := time.Now().UTC() 32 | ipAddress := conn.RemoteAddr().String() 33 | 34 | // Ask the user to bind the reader and writer they want to 35 | // use for this connection. 36 | r, w := t.ConnHandler.Bind(conn) 37 | 38 | c := client{ 39 | t: t, 40 | conn: conn, 41 | ipAddress: ipAddress, 42 | reader: r, 43 | writer: w, 44 | timeConn: now, 45 | lastAct: now, 46 | } 47 | 48 | // Check to see if this connection is ipv6. 49 | if raddr := conn.RemoteAddr().(*net.TCPAddr); raddr.IP.To4() == nil { 50 | c.isIPv6 = true 51 | } 52 | 53 | // Launch a goroutine for this connection. 54 | c.wg.Add(1) 55 | go c.read() 56 | 57 | return &c 58 | } 59 | 60 | // drop closes the client connection and read operation. 61 | func (c *client) drop() { 62 | 63 | // Close the connection. 64 | c.conn.Close() 65 | c.wg.Wait() 66 | 67 | c.t.Event(EvtDrop, TypInfo, c.ipAddress, "connect dropped") 68 | } 69 | 70 | // read waits for a message and sends it to the user for procesing. 71 | func (c *client) read() { 72 | c.t.Event(EvtRead, TypTrigger, c.ipAddress, "ready") 73 | 74 | close: 75 | for { 76 | 77 | // Wait for a message to arrive. 78 | data, length, err := c.t.ReqHandler.Read(c.ipAddress, c.reader) 79 | c.lastAct = time.Now().UTC() 80 | c.nReads++ 81 | 82 | if err != nil { 83 | 84 | // temporary is declared to test for the existence of 85 | // the method coming from the net package. 86 | type temporary interface { 87 | Temporary() bool 88 | } 89 | 90 | if e, ok := err.(temporary); ok { 91 | if !e.Temporary() { 92 | break close 93 | } 94 | } 95 | 96 | if err == io.EOF { 97 | break close 98 | } 99 | 100 | continue 101 | } 102 | 103 | // Convert the IP:socket for populating TCPAddr value. 104 | parts := bytes.Split([]byte(c.ipAddress), []byte(":")) 105 | ipAddress := string(parts[0]) 106 | port, _ := strconv.Atoi(string(parts[1])) 107 | 108 | // Create the request. 109 | r := Request{ 110 | TCP: c.t, 111 | TCPAddr: &net.TCPAddr{ 112 | IP: net.ParseIP(ipAddress), 113 | Port: port, 114 | Zone: c.t.tcpAddr.Zone, 115 | }, 116 | IsIPv6: c.isIPv6, 117 | ReadAt: c.lastAct, 118 | Context: context.Background(), 119 | Data: data, 120 | Length: length, 121 | } 122 | 123 | // Process the request on this goroutine that is 124 | // handling the socket connection. 125 | c.t.ReqHandler.Process(&r) 126 | } 127 | 128 | // Remove from the list of connections and report we are done. 129 | c.t.remove(c.conn) 130 | c.wg.Done() 131 | c.t.Event(EvtDrop, TypTrigger, c.ipAddress, "dropped connection") 132 | } 133 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package tcp provides the boilerpale code for working with TCP based data. The package 2 | // allows you to establish a TCP listener that can accept client connections on a specified IP address 3 | // and port. It also provides a function to send data back to the client. 4 | // 5 | // There are three interfaces that need to be implemented to use the package. These 6 | // interfaces provide the API for processing data. 7 | // 8 | // ConnHandler 9 | // 10 | // type ConnHandler interface { 11 | // Bind(conn net.Conn) (io.Reader, io.Writer) 12 | // } 13 | // 14 | // The ConnHandler interface is implemented by the user to bind the client connection 15 | // to a reader and writer for processing. 16 | // 17 | // ReqHandler 18 | // 19 | // type ReqHandler interface { 20 | // Read(ipAddress string, reader io.Reader) ([]byte, int, error) 21 | // Process(r *Request) 22 | // } 23 | // 24 | // type Request struct { 25 | // TCP *TCP 26 | // TCPAddr *net.TCPAddr 27 | // Data []byte 28 | // Length int 29 | // } 30 | // 31 | // The ReqHandler interface is implemented by the user to implement the processing 32 | // of request messages from the client. Read is provided an ipaddress and the user-defined 33 | // reader and must return the data read off the wire and the length. Returning io.EOF or a non 34 | // temporary error will show down the listener. 35 | // 36 | // RespHandler 37 | // 38 | // type RespHandler interface { 39 | // Write(r *Response, writer io.Writer) error 40 | // } 41 | // 42 | // type Response struct { 43 | // TCPAddr *net.TCPAddr 44 | // Data []byte 45 | // Length int 46 | // } 47 | // 48 | // The RespHandler interface is implemented by the user to implement the processing 49 | // of the response messages to the client. Write is provided the user-defined 50 | // writer and the data to write. 51 | // 52 | // Sample Application 53 | // 54 | // After implementing the interfaces, the following code is all that is needed to 55 | // start processing messages. 56 | // 57 | // func main() { 58 | // log.Println("Starting Test App") 59 | // 60 | // cfg := tcp.Config{ 61 | // NetType: "tcp4", 62 | // Addr: ":9000", 63 | // WorkRoutines: 2, 64 | // WorkStats: time.Minute, 65 | // ConnHandler: tcpConnHandler{}, 66 | // ReqHandler: udpReqHandler{}, 67 | // RespHandler: udpRespHandler{}, 68 | // } 69 | // 70 | // t, err := tcp.New(&cfg) 71 | // if err != nil { 72 | // log.Println(err) 73 | // return 74 | // } 75 | // 76 | // if err := t.Start(); err != nil { 77 | // log.Println(err) 78 | // return 79 | // } 80 | // 81 | // // Wait for a signal to shutdown. 82 | // sigChan := make(chan os.Signal, 1) 83 | // signal.Notify(sigChan, os.Interrupt) 84 | // <-sigChan 85 | // 86 | // t.Stop() 87 | // log.Println("down") 88 | // } 89 | package tcp 90 | -------------------------------------------------------------------------------- /handlers.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net" 7 | "time" 8 | ) 9 | 10 | // Request is the message received by the client. 11 | type Request struct { 12 | TCP *TCP 13 | TCPAddr *net.TCPAddr 14 | IsIPv6 bool 15 | ReadAt time.Time 16 | Context context.Context 17 | Data []byte 18 | Length int 19 | } 20 | 21 | // Response is message to send to the client. 22 | type Response struct { 23 | TCPAddr *net.TCPAddr 24 | Data []byte 25 | Length int 26 | } 27 | 28 | // ConnHandler is implemented by the user to bind the connection 29 | // to a reader and writer for processing. 30 | type ConnHandler interface { 31 | 32 | // Bind is called to set the reader and writer. 33 | Bind(conn net.Conn) (io.Reader, io.Writer) 34 | } 35 | 36 | // ReqHandler is implemented by the user to implement the processing 37 | // of request messages from the client. 38 | type ReqHandler interface { 39 | 40 | // Read is provided a request and a user-defined reader for each client 41 | // connection on its own routine. Read must read a full request and return 42 | // the populated request value. 43 | // Returning io.EOF or a non temporary error will show down the connection. 44 | 45 | // Read is provided an ipaddress and the user-defined reader and must return 46 | // the data read off the wire and the length. Returning io.EOF or a non 47 | // temporary error will show down the listener. 48 | Read(ipAddress string, reader io.Reader) ([]byte, int, error) 49 | 50 | // Process is used to handle the processing of the request. 51 | Process(r *Request) 52 | } 53 | 54 | // RespHandler is implemented by the user to implement the processing 55 | // of the response messages to the client. 56 | type RespHandler interface { 57 | 58 | // Write is provided the response to write and the user-defined writer. 59 | Write(r *Response, writer io.Writer) error 60 | } 61 | -------------------------------------------------------------------------------- /tcp.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "fmt" 8 | "net" 9 | "strconv" 10 | "sync" 11 | "sync/atomic" 12 | "time" 13 | ) 14 | 15 | // Set of error variables for start up. 16 | var ( 17 | ErrInvalidConfiguration = errors.New("invalid configuration") 18 | ErrInvalidNetType = errors.New("invalid net type configuration") 19 | ErrInvalidConnHandler = errors.New("invalid connection handler configuration") 20 | ErrInvalidReqHandler = errors.New("invalid request handler configuration") 21 | ErrInvalidRespHandler = errors.New("invalid response handler configuration") 22 | ) 23 | 24 | // Set of event types. 25 | const ( 26 | EvtAccept = iota + 1 27 | EvtJoin 28 | EvtRead 29 | EvtRemove 30 | EvtDrop 31 | EvtGroom 32 | ) 33 | 34 | // Set of event sub types. 35 | const ( 36 | TypError = iota + 1 37 | TypInfo 38 | TypTrigger 39 | ) 40 | 41 | // CltError provides support for multi client operations that might error. 42 | type CltError []error 43 | 44 | // Error implments the error interface for CltError. 45 | func (ce CltError) Error() string { 46 | var b bytes.Buffer 47 | for _, err := range ce { 48 | b.WriteString(err.Error()) 49 | b.WriteString("\n") 50 | } 51 | return b.String() 52 | } 53 | 54 | // TCP contains a set of networked client connections. 55 | type TCP struct { 56 | Config 57 | Name string 58 | 59 | ipAddress string 60 | port int 61 | tcpAddr *net.TCPAddr 62 | 63 | listener *net.TCPListener 64 | listenerMu sync.Mutex 65 | 66 | clients map[string]*client 67 | clientsMu sync.Mutex 68 | 69 | wg sync.WaitGroup 70 | 71 | dropConns int32 72 | shuttingDown int32 73 | 74 | lastAcceptedConnection time.Time 75 | } 76 | 77 | // New creates a new manager to service clients. 78 | func New(name string, cfg Config) (*TCP, error) { 79 | 80 | // Validate the configuration. 81 | if err := cfg.Validate(); err != nil { 82 | return nil, err 83 | } 84 | 85 | // Resolve the addr that is provided. 86 | tcpAddr, err := net.ResolveTCPAddr(cfg.NetType, cfg.Addr) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | // Create a TCP for this ipaddress and port. 92 | t := TCP{ 93 | Config: cfg, 94 | Name: name, 95 | 96 | ipAddress: tcpAddr.IP.String(), 97 | port: tcpAddr.Port, 98 | tcpAddr: tcpAddr, 99 | 100 | clients: make(map[string]*client), 101 | } 102 | 103 | return &t, nil 104 | } 105 | 106 | // join takes an IP and port values and creates a cleaner string. 107 | func join(ip string, port int) string { 108 | return net.JoinHostPort(ip, strconv.Itoa(port)) 109 | } 110 | 111 | // Start creates the accept routine and begins to accept connections. 112 | func (t *TCP) Start() error { 113 | t.listenerMu.Lock() 114 | { 115 | // If the listener has been started already, return an error. 116 | if t.listener != nil { 117 | t.listenerMu.Unlock() 118 | return errors.New("this TCP has already been started") 119 | } 120 | } 121 | t.listenerMu.Unlock() 122 | 123 | // We need to wait for the goroutine we are about to 124 | // create to initialize itself. 125 | var waitStart sync.WaitGroup 126 | waitStart.Add(1) 127 | 128 | // Start the connection accept routine. 129 | t.wg.Add(1) 130 | go func() { 131 | var listener *net.TCPListener 132 | 133 | for { 134 | t.listenerMu.Lock() 135 | { 136 | // Start a listener for the specified addr and port is one 137 | // does not exist. 138 | if t.listener == nil { 139 | var err error 140 | listener, err = net.ListenTCP(t.NetType, t.tcpAddr) 141 | if err != nil { 142 | panic(err) 143 | } 144 | 145 | t.listener = listener 146 | waitStart.Done() 147 | 148 | t.Event(EvtAccept, TypInfo, join(t.ipAddress, t.port), "waiting") 149 | } 150 | } 151 | t.listenerMu.Unlock() 152 | 153 | // Listen for new connections. 154 | conn, err := listener.Accept() 155 | if err != nil { 156 | shutdown := atomic.LoadInt32(&t.shuttingDown) 157 | 158 | if shutdown == 0 { 159 | t.Event(EvtAccept, TypError, conn.RemoteAddr().String(), err.Error()) 160 | } else { 161 | t.listenerMu.Lock() 162 | { 163 | t.listener = nil 164 | } 165 | t.listenerMu.Unlock() 166 | break 167 | } 168 | 169 | // temporary is declared to test for the existence of 170 | // the method coming from the net package. 171 | type temporary interface { 172 | Temporary() bool 173 | } 174 | 175 | if e, ok := err.(temporary); ok && !e.Temporary() { 176 | t.listenerMu.Lock() 177 | { 178 | t.listener.Close() 179 | t.listener = nil 180 | } 181 | t.listenerMu.Unlock() 182 | 183 | // Don't want to add a flag. So setting this back to 184 | // 1 so when the listener is re-established, the call 185 | // to Done does not fail. 186 | waitStart.Add(1) 187 | } 188 | 189 | continue 190 | } 191 | 192 | // Check if we are being asked to drop all new connections. 193 | if drop := atomic.LoadInt32(&t.dropConns); drop == 1 { 194 | t.Event(EvtAccept, TypInfo, "", "dropping new connection") 195 | conn.Close() 196 | continue 197 | } 198 | 199 | // Check if rate limit is enabled. 200 | if t.RateLimit != nil { 201 | now := time.Now().UTC() 202 | 203 | // We will only accept 1 connection per duration. Anything 204 | // connection above that must be dropped. 205 | if t.lastAcceptedConnection.Add(t.RateLimit()).After(now) { 206 | t.Event(EvtAccept, TypError, conn.RemoteAddr().String(), "rate limit drop : Local[ %v ] Limit[ %v ]", conn.LocalAddr(), t.RateLimit()) 207 | conn.Close() 208 | continue 209 | } 210 | 211 | // Since we accepted connection, mark the time. 212 | t.lastAcceptedConnection = now 213 | } 214 | 215 | // Add this new connection to the manager map. 216 | t.join(conn) 217 | } 218 | 219 | // Shutting down the routine. 220 | t.wg.Done() 221 | t.Event(EvtAccept, TypError, join(t.ipAddress, t.port), "shutdown") 222 | }() 223 | 224 | // Wait for the goroutine to initialize itself. 225 | waitStart.Wait() 226 | 227 | return nil 228 | } 229 | 230 | // Stop shuts down the manager and closes all connections. 231 | func (t *TCP) Stop() error { 232 | t.listenerMu.Lock() 233 | { 234 | // If the listener has been stopped already, return an error. 235 | if t.listener == nil { 236 | t.listenerMu.Unlock() 237 | return errors.New("this TCP has already been stopped") 238 | } 239 | } 240 | t.listenerMu.Unlock() 241 | 242 | // Mark that we are shutting down. 243 | atomic.StoreInt32(&t.shuttingDown, 1) 244 | 245 | // Don't accept anymore client connections. 246 | t.listenerMu.Lock() 247 | { 248 | t.listener.Close() 249 | } 250 | t.listenerMu.Unlock() 251 | 252 | // Make a copy of all the connections. We need to do this 253 | // since we have to lock the map to read it. Dropping a 254 | // connection requires locks as well. 255 | var clients map[string]*client 256 | t.clientsMu.Lock() 257 | { 258 | clients = make(map[string]*client) 259 | for k, v := range t.clients { 260 | clients[k] = v 261 | } 262 | } 263 | t.clientsMu.Unlock() 264 | 265 | // Drop all the existing connections. 266 | for _, c := range clients { 267 | 268 | // This waits for each routine to terminate. 269 | c.drop() 270 | } 271 | 272 | // Wait for the accept routine to terminate. 273 | t.wg.Wait() 274 | 275 | return nil 276 | } 277 | 278 | // Drop will close the socket connection. 279 | func (t *TCP) Drop(tcpAddr *net.TCPAddr) error { 280 | 281 | // Find the client connection for this IPAddress. 282 | var c *client 283 | t.clientsMu.Lock() 284 | { 285 | // Validate this ipaddress and socket exists first. 286 | var ok bool 287 | if c, ok = t.clients[tcpAddr.String()]; !ok { 288 | t.clientsMu.Unlock() 289 | return fmt.Errorf("IP[ %s ] : disconnected", tcpAddr.String()) 290 | } 291 | } 292 | t.clientsMu.Unlock() 293 | 294 | // Drop the connection using a goroutine since we are on the 295 | // socket goroutine most likely. 296 | go c.drop() 297 | return nil 298 | } 299 | 300 | // Send will deliver the response back to the client. 301 | func (t *TCP) Send(ctx context.Context, r *Response) error { 302 | 303 | // Find the client connection for this IPAddress. 304 | var c *client 305 | t.clientsMu.Lock() 306 | { 307 | // Validate this ipaddress and socket exists first. 308 | var ok bool 309 | if c, ok = t.clients[r.TCPAddr.String()]; !ok { 310 | t.clientsMu.Unlock() 311 | return fmt.Errorf("IP[ %s ] : disconnected", r.TCPAddr.String()) 312 | } 313 | 314 | // Increment the number of writes. 315 | c.nWrites++ 316 | } 317 | t.clientsMu.Unlock() 318 | 319 | // Send the response. 320 | return t.RespHandler.Write(r, c.writer) 321 | } 322 | 323 | // SendAll will deliver the response back to all connected clients. 324 | func (t *TCP) SendAll(ctx context.Context, r *Response) error { 325 | var clts []*client 326 | t.clientsMu.Lock() 327 | { 328 | for _, c := range t.clients { 329 | clts = append(clts, c) 330 | c.nWrites++ 331 | } 332 | } 333 | t.clientsMu.Unlock() 334 | 335 | // TODO: Consider doing this in parallel. 336 | var errors CltError 337 | for _, c := range clts { 338 | if err := t.RespHandler.Write(r, c.writer); err != nil { 339 | errors = append(errors, err) 340 | } 341 | } 342 | 343 | if errors != nil { 344 | return errors 345 | } 346 | return nil 347 | } 348 | 349 | // DropConnections sets a flag to tell the accept routine to immediately 350 | // drop connections that come in. 351 | func (t *TCP) DropConnections(drop bool) { 352 | if drop { 353 | atomic.StoreInt32(&t.dropConns, 1) 354 | return 355 | } 356 | 357 | atomic.StoreInt32(&t.dropConns, 0) 358 | } 359 | 360 | // Addr returns the listener's network address. This may be different than the values 361 | // provided in the configuration, for example if configuration port value is 0. 362 | func (t *TCP) Addr() net.Addr { 363 | 364 | // We are aware this read is not safe with the 365 | // goroutine accepting connections. 366 | if t.listener == nil { 367 | return nil 368 | } 369 | return t.listener.Addr() 370 | } 371 | 372 | // Connections returns the number of client connections. 373 | func (t *TCP) Connections() int { 374 | var l int 375 | 376 | t.clientsMu.Lock() 377 | { 378 | l = len(t.clients) 379 | } 380 | t.clientsMu.Unlock() 381 | 382 | return l 383 | } 384 | 385 | // Stat represents a client statistic. 386 | type Stat struct { 387 | IP string 388 | Reads int 389 | Writes int 390 | TimeConn time.Time 391 | LastAct time.Time 392 | } 393 | 394 | // ClientStats return details for all active clients. 395 | func (t *TCP) ClientStats() []Stat { 396 | var clts []*client 397 | t.clientsMu.Lock() 398 | { 399 | for _, v := range t.clients { 400 | clts = append(clts, v) 401 | } 402 | } 403 | t.clientsMu.Unlock() 404 | 405 | stats := make([]Stat, len(clts)) 406 | for i, c := range clts { 407 | stats[i] = Stat{ 408 | IP: c.ipAddress, 409 | Reads: c.nReads, 410 | Writes: c.nWrites, 411 | TimeConn: c.timeConn, 412 | LastAct: c.lastAct, 413 | } 414 | } 415 | 416 | return stats 417 | } 418 | 419 | // Clients returns the number of active clients connected. 420 | func (t *TCP) Clients() int { 421 | var count int 422 | t.clientsMu.Lock() 423 | { 424 | count = len(t.clients) 425 | } 426 | t.clientsMu.Unlock() 427 | 428 | return count 429 | } 430 | 431 | // Groom drops connections that are not active for the specified duration. 432 | func (t *TCP) Groom(d time.Duration) { 433 | var clts []*client 434 | t.clientsMu.Lock() 435 | { 436 | for _, v := range t.clients { 437 | clts = append(clts, v) 438 | } 439 | } 440 | t.clientsMu.Unlock() 441 | 442 | now := time.Now().UTC() 443 | for _, c := range clts { 444 | sub := now.Sub(c.lastAct) 445 | if sub >= d { 446 | 447 | // TODO 448 | // This is a blocking call that waits for the socket goroutine 449 | // to report its done. This parallel call should work well since 450 | // there is no error handling needed. 451 | t.Event(EvtGroom, TypInfo, c.ipAddress, "Last[ %v ] Dur[ %v ]", c.lastAct.Format(time.RFC3339), sub) 452 | go c.drop() 453 | } 454 | } 455 | } 456 | 457 | // join takes a new connection and adds it to the manager. 458 | func (t *TCP) join(conn net.Conn) { 459 | ipAddress := conn.RemoteAddr().String() 460 | t.Event(EvtJoin, TypTrigger, ipAddress, "new connection") 461 | 462 | t.clientsMu.Lock() 463 | { 464 | // Validate this has not been joined already. 465 | if _, ok := t.clients[ipAddress]; ok { 466 | t.Event(EvtJoin, TypError, ipAddress, "already connected") 467 | conn.Close() 468 | 469 | t.clientsMu.Unlock() 470 | return 471 | } 472 | 473 | // Add the client connection to the map. 474 | t.clients[ipAddress] = newClient(t, conn) 475 | } 476 | t.clientsMu.Unlock() 477 | } 478 | 479 | // remove deletes a connection from the manager. 480 | func (t *TCP) remove(conn net.Conn) { 481 | ipAddress := conn.RemoteAddr().String() 482 | 483 | t.clientsMu.Lock() 484 | { 485 | // Validate this has not been removed already. 486 | if _, ok := t.clients[ipAddress]; !ok { 487 | t.Event(EvtRemove, TypError, ipAddress, "already removed") 488 | t.clientsMu.Unlock() 489 | return 490 | } 491 | 492 | // Remove the client connection from the map. 493 | delete(t.clients, ipAddress) 494 | } 495 | t.clientsMu.Unlock() 496 | 497 | // Close the connection for safe keeping. 498 | conn.Close() 499 | } 500 | -------------------------------------------------------------------------------- /tcp_config.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import "time" 4 | 5 | // OptRateLimit declares fields for the user to provide configuration 6 | // for connection rate limit. 7 | type OptRateLimit struct { 8 | RateLimit func() time.Duration // Connection rate limit per single connection. 9 | } 10 | 11 | // OptEvent defines an handler used to provide events. 12 | type OptEvent struct { 13 | Event func(evt, typ int, ipAddress string, format string, a ...interface{}) 14 | } 15 | 16 | // Config provides a data structure of required configuration parameters. 17 | type Config struct { 18 | NetType string // "tcp", tcp4" or "tcp6" 19 | Addr string // "host:port" or "[ipv6-host%zone]:port" 20 | 21 | ConnHandler ConnHandler // Support for binding new connections to a reader and writer. 22 | ReqHandler ReqHandler // Support for handling the specific request workflow. 23 | RespHandler RespHandler // Support for handling the specific response workflow. 24 | 25 | // ************************************************************************* 26 | // ** Not Required, optional ** 27 | // ************************************************************************* 28 | 29 | OptRateLimit 30 | OptEvent 31 | } 32 | 33 | // Validate checks the configuration to required items. 34 | func (cfg *Config) Validate() error { 35 | if cfg == nil { 36 | return ErrInvalidConfiguration 37 | } 38 | 39 | if cfg.NetType != "tcp" && cfg.NetType != "tcp4" && cfg.NetType != "tcp6" { 40 | return ErrInvalidNetType 41 | } 42 | 43 | if cfg.ConnHandler == nil { 44 | return ErrInvalidConnHandler 45 | } 46 | 47 | if cfg.ReqHandler == nil { 48 | return ErrInvalidReqHandler 49 | } 50 | 51 | if cfg.RespHandler == nil { 52 | return ErrInvalidRespHandler 53 | } 54 | 55 | return nil 56 | } 57 | 58 | // Event fires events back to the user for important events. 59 | func (cfg *Config) Event(evt, typ int, ipAddress string, format string, a ...interface{}) { 60 | if cfg.OptEvent.Event != nil { 61 | cfg.OptEvent.Event(evt, typ, ipAddress, format, a...) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tcp_handlers_test.go: -------------------------------------------------------------------------------- 1 | package tcp_test 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "net" 7 | "sync/atomic" 8 | "time" 9 | 10 | "github.com/ardanlabs/tcp" 11 | ) 12 | 13 | // tcpConnHandler is required to process data. 14 | type tcpConnHandler struct{} 15 | 16 | // Bind is called to init to reader and writer. 17 | func (tch tcpConnHandler) Bind(conn net.Conn) (io.Reader, io.Writer) { 18 | return bufio.NewReader(conn), bufio.NewWriter(conn) 19 | } 20 | 21 | // tcpReqHandler is required to process client messages. 22 | type tcpReqHandler struct{} 23 | 24 | // Read implements the udp.ReqHandler interface. It is provided a request 25 | // value to popular and a io.Reader that was created in the Bind above. 26 | func (tcpReqHandler) Read(ipAddress string, reader io.Reader) ([]byte, int, error) { 27 | bufReader := reader.(*bufio.Reader) 28 | 29 | // Read a small string to keep the code simple. 30 | line, err := bufReader.ReadString('\n') 31 | if err != nil { 32 | return nil, 0, err 33 | } 34 | 35 | return []byte(line), len(line), nil 36 | } 37 | 38 | var dur int64 39 | 40 | // Process is used to handle the processing of the message. 41 | func (tcpReqHandler) Process(r *tcp.Request) { 42 | resp := tcp.Response{ 43 | TCPAddr: r.TCPAddr, 44 | Data: []byte("GOT IT\n"), 45 | Length: 7, 46 | } 47 | 48 | r.TCP.Send(r.Context, &resp) 49 | 50 | d := int64(time.Since(r.ReadAt)) 51 | atomic.StoreInt64(&dur, d) 52 | } 53 | 54 | type tcpRespHandler struct{} 55 | 56 | // Write is provided the user-defined writer and the data to write. 57 | func (tcpRespHandler) Write(r *tcp.Response, writer io.Writer) error { 58 | bufWriter := writer.(*bufio.Writer) 59 | if _, err := bufWriter.WriteString(string(r.Data)); err != nil { 60 | return err 61 | } 62 | 63 | return bufWriter.Flush() 64 | } 65 | -------------------------------------------------------------------------------- /tcp_test.go: -------------------------------------------------------------------------------- 1 | package tcp_test 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "net" 7 | "os" 8 | "sync/atomic" 9 | "testing" 10 | "time" 11 | 12 | "github.com/ardanlabs/tcp" 13 | ) 14 | 15 | // TestTCP provide a test of listening for a connection and 16 | // echoing the data back. 17 | func TestTCP(t *testing.T) { 18 | resetLog() 19 | defer displayLog() 20 | 21 | t.Log("Given the need to listen and process TCP data.") 22 | { 23 | // Create a configuration. 24 | cfg := tcp.Config{ 25 | NetType: "tcp4", 26 | Addr: ":0", 27 | 28 | ConnHandler: tcpConnHandler{}, 29 | ReqHandler: tcpReqHandler{}, 30 | RespHandler: tcpRespHandler{}, 31 | } 32 | 33 | // Create a new TCP value. 34 | u, err := tcp.New("TEST", cfg) 35 | if err != nil { 36 | t.Fatal("\tShould be able to create a new TCP listener.", failed, err) 37 | } 38 | t.Log("\tShould be able to create a new TCP listener.", success) 39 | 40 | // Start accepting client data. 41 | if err := u.Start(); err != nil { 42 | t.Fatal("\tShould be able to start the TCP listener.", failed, err) 43 | } 44 | t.Log("\tShould be able to start the TCP listener.", success) 45 | 46 | defer u.Stop() 47 | 48 | // Let's connect back and send a TCP package 49 | conn, err := net.Dial("tcp4", u.Addr().String()) 50 | if err != nil { 51 | t.Fatal("\tShould be able to dial a new TCP connection.", failed, err) 52 | } 53 | t.Log("\tShould be able to dial a new TCP connection.", success) 54 | 55 | // Setup a bufio reader to extract the response. 56 | bufReader := bufio.NewReader(conn) 57 | bufWriter := bufio.NewWriter(conn) 58 | 59 | // Send some know data to the tcp listener. 60 | if _, err := bufWriter.WriteString("Hello\n"); err != nil { 61 | t.Fatal("\tShould be able to send data to the connection.", failed, err) 62 | } 63 | t.Log("\tShould be able to send data to the connection.", success) 64 | 65 | bufWriter.Flush() 66 | 67 | // Let's read the response. 68 | response, err := bufReader.ReadString('\n') 69 | if err != nil { 70 | t.Fatal("\tShould be able to read the response from the connection.", failed, err) 71 | } 72 | t.Log("\tShould be able to read the response from the connection.", success) 73 | 74 | if response == "GOT IT\n" { 75 | t.Log("\tShould receive the string \"GOT IT\".", success) 76 | } else { 77 | t.Error("\tShould receive the string \"GOT IT\".", failed, response) 78 | } 79 | 80 | d := atomic.LoadInt64(&dur) 81 | duration := time.Duration(d) 82 | 83 | if duration <= 2*time.Second { 84 | t.Log("\tShould be less that 2 seconds.", success) 85 | } else { 86 | t.Error("\tShould be less that 2 seconds.", failed, duration) 87 | } 88 | } 89 | } 90 | 91 | // Test tcp.Addr works correctly. 92 | func TestTCPAddr(t *testing.T) { 93 | resetLog() 94 | defer displayLog() 95 | 96 | t.Log("Given the need to listen on any open port and know that bound address.") 97 | { 98 | // Create a configuration. 99 | cfg := tcp.Config{ 100 | NetType: "tcp4", 101 | Addr: ":0", // Defer port assignment to OS. 102 | 103 | ConnHandler: tcpConnHandler{}, 104 | ReqHandler: tcpReqHandler{}, 105 | RespHandler: tcpRespHandler{}, 106 | } 107 | 108 | // Create a new TCP value. 109 | u, err := tcp.New("TEST", cfg) 110 | if err != nil { 111 | t.Fatal("\tShould be able to create a new TCP listener.", failed, err) 112 | } 113 | t.Log("\tShould be able to create a new TCP listener.", success) 114 | 115 | // Addr should be nil before Start. 116 | if addr := u.Addr(); addr != nil { 117 | t.Fatalf("\tAddr() should be nil before Start; Addr() = %q. %s", addr, failed) 118 | } 119 | t.Log("\tAddr() should be nil before Start.", success) 120 | 121 | // Start accepting client data. 122 | if err := u.Start(); err != nil { 123 | t.Fatal("\tShould be able to start the TCP listener.", failed, err) 124 | } 125 | defer u.Stop() 126 | 127 | // Addr should be non-nil after Start. 128 | addr := u.Addr() 129 | if addr == nil { 130 | t.Fatal("\tAddr() should be not be nil after Start.", failed) 131 | } 132 | t.Log("\tAddr() should be not be nil after Start.", success) 133 | 134 | // The OS should assign a random open port, which shouldn't be 0. 135 | _, port, err := net.SplitHostPort(addr.String()) 136 | if err != nil { 137 | t.Fatalf("\tSplitHostPort should not fail. failed %v. %s", err, failed) 138 | } 139 | if port == "0" { 140 | t.Fatalf("\tAddr port should not be %q. %s", port, failed) 141 | } 142 | t.Logf("\tAddr() should be not be 0 after Start (port = %q). %s", port, success) 143 | } 144 | } 145 | 146 | // TestDropConnections tests we can drop connections when configured. 147 | func TestDropConnections(t *testing.T) { 148 | resetLog() 149 | defer displayLog() 150 | 151 | t.Log("Given the need to drop TCP connections.") 152 | { 153 | // Create a configuration. 154 | cfg := tcp.Config{ 155 | NetType: "tcp4", 156 | Addr: ":0", 157 | ConnHandler: tcpConnHandler{}, 158 | ReqHandler: tcpReqHandler{}, 159 | RespHandler: tcpRespHandler{}, 160 | } 161 | 162 | // Create a new TCP value. 163 | u, err := tcp.New("TEST", cfg) 164 | if err != nil { 165 | t.Fatal("\tShould be able to create a new TCP listener.", failed, err) 166 | } 167 | t.Log("\tShould be able to create a new TCP listener.", success) 168 | 169 | // Set the drop connection flag to true. 170 | t.Log("\tSet the drop connections flag to TRUE.", success) 171 | u.DropConnections(true) 172 | 173 | // Start accepting client data. 174 | if err := u.Start(); err != nil { 175 | t.Fatal("\tShould be able to start the TCP listener.", failed, err) 176 | } 177 | t.Log("\tShould be able to start the TCP listener.", success) 178 | 179 | defer u.Stop() 180 | 181 | // Let's connect to the host:port. 182 | conn, err := net.Dial("tcp4", u.Addr().String()) 183 | if err != nil { 184 | t.Fatal("\tShould be able to dial a new TCP connection.", failed, err) 185 | } 186 | t.Log("\tShould be able to dial a new TCP connection.", success) 187 | 188 | // An attempt to read should result in an EOF. 189 | b := make([]byte, 1) 190 | if _, err = conn.Read(b); err == nil { 191 | t.Fatal("\tShould not be able to read the response from the connection.", failed, err) 192 | } 193 | t.Log("\tShould not be able to read the response from the connection.", success) 194 | } 195 | } 196 | 197 | // TestRateLimit tests we can drop connections when they come in too fast. 198 | func TestRateLimit(t *testing.T) { 199 | resetLog() 200 | defer displayLog() 201 | 202 | const ratelimit = 1 * time.Second 203 | 204 | t.Log("Given the need to drop TCP connections.") 205 | { 206 | // Create a configuration. 207 | cfg := tcp.Config{ 208 | NetType: "tcp4", 209 | Addr: ":0", 210 | ConnHandler: tcpConnHandler{}, 211 | ReqHandler: tcpReqHandler{}, 212 | RespHandler: tcpRespHandler{}, 213 | 214 | OptRateLimit: tcp.OptRateLimit{ 215 | RateLimit: func() time.Duration { return ratelimit }, 216 | }, 217 | } 218 | 219 | // Create a new TCP value. 220 | u, err := tcp.New("TEST", cfg) 221 | if err != nil { 222 | t.Fatal("\tShould be able to create a new TCP listener.", failed, err) 223 | } 224 | t.Log("\tShould be able to create a new TCP listener.", success) 225 | 226 | // Start accepting client data. 227 | if err := u.Start(); err != nil { 228 | t.Fatal("\tShould be able to start the TCP listener.", failed, err) 229 | } 230 | t.Log("\tShould be able to start the TCP listener.", success) 231 | 232 | defer u.Stop() 233 | 234 | newconn := func() (*bufio.Writer, *bufio.Reader, net.Conn, error) { 235 | // Let's connect to the host:port. 236 | conn, err := net.Dial("tcp4", u.Addr().String()) 237 | if err != nil { 238 | return nil, nil, nil, err 239 | } 240 | return bufio.NewWriter(conn), bufio.NewReader(conn), conn, nil 241 | } 242 | 243 | // Make a successful connection 244 | successfulTest := func(Context interface{}) { 245 | w, r, c, err := newconn() 246 | if err != nil { 247 | t.Fatal("\tShould be able to dial a new TCP connection.", Context, failed, err) 248 | } 249 | t.Log("\tShould be able to dial a new TCP connection.", Context, success) 250 | 251 | defer c.Close() 252 | 253 | if _, err := w.WriteString("Hello\n"); err != nil { 254 | t.Fatal("\tShould be able to send data to the connection.", Context, failed, err) 255 | } 256 | t.Log("\tShould be able to send data to the connection.", Context, success) 257 | 258 | if err := w.Flush(); err != nil { 259 | t.Fatal("\tShould be able to flush the writer.", Context, failed, err) 260 | } 261 | t.Log("\tShould be able to flush the writer.", Context, success) 262 | 263 | // Let's read the response. 264 | response, err := r.ReadString('\n') 265 | if err != nil { 266 | t.Fatal("\tShould be able to read the response from the connection.", Context, failed, err) 267 | } 268 | t.Log("\tShould be able to read the response from the connection.", Context, success) 269 | 270 | t.Log(response) 271 | } 272 | 273 | successfulTest("PRE-LIMIT") 274 | 275 | // The next 100 connections should fail (assuming it's all under rateLimit amount of time). 276 | for i := 0; i < 100; i++ { 277 | // Apparently, even though the connection should not exist, we are still allowed 278 | // to connect to the remote socket and write to it. The error is exhibited 279 | // only when it's time to perform a read on that connection. 280 | w, r, c, err := newconn() 281 | if err != nil { 282 | t.Fatal("\tShould be able to dial a non-first TCP connection.", failed, err) 283 | } 284 | t.Log("\tShould be able to dial a non-first TCP connection", c.LocalAddr(), success) 285 | 286 | defer c.Close() 287 | 288 | if _, err := w.WriteString("Hello\n"); err != nil { 289 | t.Fatal("\tShould be able to send data to the connection.", failed, err) 290 | } 291 | t.Log("\tShould be able to send data to the connection.", success) 292 | 293 | if err := w.Flush(); err != nil { 294 | t.Fatal("\tShould be able to flush the writer.", failed, err) 295 | } 296 | t.Log("\tShould be able to flush the writer.", success) 297 | 298 | // Let's read the response. 299 | _, err = r.ReadString('\n') 300 | if err == nil { 301 | t.Fatal("\tShould have failed to read from the connection.", failed) 302 | } 303 | t.Log("\tShould have failed to read from the connection", success, err) 304 | } 305 | 306 | // Sleep for rateLimit to perform another successful test. 307 | time.Sleep(ratelimit) 308 | successfulTest("POST-LIMIT") 309 | 310 | // NOTE If you call another 'successfulTest' here, we will fail because we expect 311 | // the test to fail due to the limit. 312 | } 313 | } 314 | 315 | // ============================================================================= 316 | 317 | // Success and failure markers. 318 | var ( 319 | success = "\u2713" 320 | failed = "\u2717" 321 | ) 322 | 323 | // logdash is the central buffer where all logs are stored. 324 | var logdash bytes.Buffer 325 | 326 | // resetLog resets the contents of Logdash. 327 | func resetLog() { 328 | logdash.Reset() 329 | } 330 | 331 | // displayLog writes the Logdash data to standand out, if testing in verbose mode 332 | // was turned on. 333 | func displayLog() { 334 | if !testing.Verbose() { 335 | return 336 | } 337 | 338 | logdash.WriteTo(os.Stdout) 339 | } 340 | --------------------------------------------------------------------------------