├── LICENSE ├── README.md ├── benchmark ├── .gitignore ├── _results │ └── aws-c5-metal │ │ ├── test01.dat │ │ ├── test01.plt │ │ ├── test01.png │ │ ├── test01_tls.dat │ │ ├── test01_tls.plt │ │ ├── test01_tls.png │ │ ├── test02.dat │ │ ├── test02.plt │ │ ├── test02.png │ │ ├── test02_tls.dat │ │ ├── test02_tls.plt │ │ ├── test02_tls.png │ │ ├── test03.dat │ │ ├── test03.plt │ │ ├── test03.png │ │ ├── test04.dat │ │ ├── test04.plt │ │ ├── test04.png │ │ ├── test05.dat │ │ ├── test05.plt │ │ ├── test05.png │ │ ├── test06.dat │ │ ├── test06.plt │ │ └── test06.png ├── benchmark.sh ├── evio-echo-server │ └── main.go ├── evio-http-server │ └── main.go ├── fasthttp-http-server │ └── main.go ├── gnet-http-server │ └── main.go └── net-http-server │ └── main.go ├── examples ├── echo-server │ └── main.go └── http-server │ └── main.go ├── go.mod ├── go.sum ├── listen_linux.go ├── listen_stub.go ├── listen_windows.go └── tcpserver.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2022 Moritz Fain 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tcpserver 2 | 3 | *tcpserver* is an extremely fast and flexible **IPv4 and IPv6** capable TCP server with **TLS support**, graceful shutdown, **Zero-Copy** (on Linux with splice/sendfile) and supports TCP tuning options like `TCP_FASTOPEN`, `SO_REUSEPORT` and `TCP_DEFER_ACCEPT`. 4 | 5 | This library requires at least Go 1.11 but has no other dependencies, does not contain ugly and incompatible hacks and thus fully integrates into Go's `net/*` universe. 6 | 7 | 8 | ## Architecture 9 | *tcpserver* uses a multi-accept-loop approach combined with an adaptive spawning go routine pool for incoming connections. 10 | 11 | Each connection lives in it's own go routine and it served using a request handler function (`RequestHandlerFunc func(conn tcpserver.Connection)`). 12 | The connection is automatically closed when the request handler function returns. 13 | 14 | Memory allocations in hot paths are reduced to a minimum using `sync.Pool` and the go routine pool from [`maurice2k/ultrapool`](https://github.com/maurice2k/ultrapool) 15 | 16 | As *tcpserver* does not implement a non-blocking/asynchronous event loop itself (like packages such as *evio* or *gnet*) it is fully compatible with everything that is built on top of `net.TCPConn`. 17 | 18 | 19 | ## Example (echo server) 20 | 21 | ```golang 22 | server, err := tcpserver.NewServer("127.0.0.1:5000") 23 | 24 | server.SetRequestHandler(requestHandler) 25 | server.Listen() 26 | server.Serve() 27 | 28 | func requestHandler(conn tcpserver.Connection) { 29 | io.Copy(conn, conn) 30 | } 31 | ``` 32 | 33 | ## Benchmarks 34 | 35 | Benchmarks are always tricky, especially those that depend on network operations. I've tried my best to get fair and realistic results (given that these benchmarks are of course very synthetic). 36 | 37 | The test server, an *AWS c5.metal* dual `Intel(R) Xeon(R) Platinum 8275CL CPU @ 3.00GHz` machine with a total of 96 cores and 192 GB of memory, was installed with latest Amazon Linux 2, Linux Kernel 4.14.214-160.339.amzn2.x86_64 and Golang 1.16. There were no other network daemons running except for SSH. 38 | 39 | 40 | The tests are performed using a super simple HTTP server implementation on top of tcpserver and the other tested libraries. HTTP is well known, there are good benchmarking tools and it's easy to test throughput (using HTTP Keep-Alive) as well as handling thousands of short-lived connections. 41 | 42 | [Bombardier](https://github.com/codesenberg/bombardier) (version [1.2.5](https://github.com/codesenberg/bombardier/releases/tag/v1.2.5) from 2020-10-15) was used as HTTP benchmarking tool. 43 | 44 | The following libraries were benchmarked: 45 | - [net/http](https://golang.org/pkg/net/http/) (Go's own HTTP server implementation) 46 | - [evio](https://github.com/tidwall/evio) 47 | - [gnet](https://github.com/panjf2000/gnet) 48 | - [fasthttp](https://github.com/valyala/fasthttp) (just to see a good HTTP library, also based on `net/*` like tcpserver) 49 | - [tcpserver](https://github.com/maurice2k/tcpserver) 50 | 51 | All tests were performed against localhost. 52 | 53 | **Hint**: If you're looking for a high performant HTTP library, just use [fasthttp](https://github.com/valyala/fasthttp). It is extremly fast and has extraordinary good HTTP support. The second best option is probably to stick to [net/http](https://golang.org/pkg/net/http/). evio, gnet and tcpserver are primarily designed for other use cases like your own protocols, proxy servers and the like. Don't re-invent the wheel ;) 54 | 55 | ## Test #1: Static 1kB content massive connections test 56 | 1000 concurrent clients, 1kB of HTTP payload returned, Keep-Alive turned off, 10 seconds (each HTTP request is a new connection). 57 | ![Test 01](benchmark/_results/aws-c5-metal/test01.png) 58 | 59 | ## Test #1.1 (with TLS): Static 1kB content massive connections test 60 | 1000 concurrent clients, 1kB of HTTP payload returned, Keep-Alive turned off, 10 seconds (each HTTP request is a new connection).\ 61 | Only *net/http*, *fasthttp* and *tcpserver* have been benchmarked, as *evio* and *gnet* do not support TLS. 62 | ![Test 01](benchmark/_results/aws-c5-metal/test01_tls.png) 63 | 64 | ## Test #2: Static 1kB content throughput test 65 | 1000 concurrent clients, 1kB of HTTP payload returned, Keep-Alive turned on, 10 seconds (establishes exactly 1000 TCP connections that are serving HTTP requests). 66 | ![Test 02](benchmark/_results/aws-c5-metal/test02.png) 67 | 68 | ## Test #2.1 (with TLS): Static 1kB content throughput test 69 | 1000 concurrent clients, 1kB of HTTP payload returned, Keep-Alive turned on, 10 seconds (establishes exactly 1000 TCP connections that are serving HTTP requests).\ 70 | Only *net/http*, *fasthttp* and *tcpserver* have been benchmarked, as *evio* and *gnet* do not support TLS. 71 | ![Test 02](benchmark/_results/aws-c5-metal/test02_tls.png) 72 | 73 | ## Test #3: AES-128-CBC crypted 1kB content massive connections test 74 | 1000 concurrent clients, 1kB of AES-128-CBC crypted HTTP payload returned, Keep-Alive turned off, 10 seconds (each HTTP request is a new connection). 75 | ![Test 03](benchmark/_results/aws-c5-metal/test03.png) 76 | 77 | ## Test #4: AES-128-CBC crypted 1kB content throughput test 78 | 1000 concurrent clients, 1kB of AES-128-CBC crypted HTTP payload returned, Keep-Alive turned on, 10 seconds (establishes exactly 1000 TCP connections that are serving HTTP requests). 79 | ![Test 04](benchmark/_results/aws-c5-metal/test04.png) 80 | 81 | ## Test #5: Static 128 byte content throughput test with additional 1ms sleep 82 | 1000 concurrent clients, 128 bytes of HTTP payload returned and 1 ms sleep, Keep-Alive turned on, 10 seconds (establishes exactly 1000 TCP connections that are serving HTTP requests). 83 | ![Test 05](benchmark/_results/aws-c5-metal/test05.png) 84 | 85 | ## Test #6: Static 16kB content massive connections test 86 | 1000 concurrent clients, 16kB of HTTP payload returned, Keep-Alive turned off, 10 seconds (each HTTP request is a new connection). 87 | ![Test 06](benchmark/_results/aws-c5-metal/test06.png) 88 | 89 | ## Why? 90 | I always find it enlightening to know why someone did something. That's why this section is here. 91 | 92 | When I started writing a new [high performance SOCKS5 and HTTP proxy server](https://github.com/maurice2k/moproxy) to replace `danted` in my setup, I realized that I needed some functionality that goes beyond the core listen-accept-handle logic like graceful restart/shutdown and strict error handling. 93 | 94 | I evaluated `evio` and later `gnet` but that reminded me of doing async IO in PHP a decade ago which might have been a necessity there but Go has it's own event loop and I was curious why one would re-implement all this in a client library and relinquish the way you write network code in Go (basically by using a goroutine per connection) – the answer ~~is~~ was *speed*. 95 | 96 | I started benchmarking the mentioned libraries against a naive `go handle(...)` approach and it soon turned out that spawning a *new* goroutine for each connection was simply too expensive. 97 | After creating a new [goroutine worker pool](https://github.com/maurice2k/ultrapool) (modeled after the one found in `fasthttp`) I was able to reach numbers (in reqs/sec) that were on par with these libraries. 98 | 99 | Now, after some rounds of optimization, `tcpserver` is *faster in almost all benchmarks* than any other library I've come across and you get zero-copy and TLS "for free" – simply by using Go's own `net/*` functionality! 100 | 101 | ## License 102 | 103 | *tcpserver* is available under the MIT [license](LICENSE). 104 | -------------------------------------------------------------------------------- /benchmark/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | /test* 3 | -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test01.dat: -------------------------------------------------------------------------------- 1 | GOMAXPROCS net/http evio gnet fasthttp tcpserver 2 | 1 36006.14286715146 39987.480694173675 6834.920037412406 50892.88185095489 55004.431707227035 3 | 2 66644.06304462209 48340.66849649945 14557.514573484117 89499.75163488336 98053.65765527442 4 | 3 94215.26341571617 52356.17555096997 23124.616142149654 126332.91963824276 140029.4219658942 5 | 4 122470.64342121275 87763.95028855168 32564.01479619173 150588.28468639302 171681.52649280682 6 | 6 144042.33299018315 212188.47378162638 53344.93293568409 149820.32957982173 224646.88307474626 7 | 8 143608.73559044683 211058.1228963511 85096.50990976913 145747.10587195546 231365.68140638576 8 | 10 140145.08406255048 211906.15630055868 124799.68317751207 146205.27264674215 229355.92381122184 9 | 12 137656.69872500064 211823.77629035016 143815.596487052 143330.0480324499 228053.3968456726 10 | 14 135916.15402356116 192263.85713637015 139669.83174359516 142417.71173937144 226546.16250758833 11 | 16 134309.78984089708 166473.40251837496 134708.17361614527 142834.90330020836 223896.3243941841 12 | 18 134450.80529812793 146340.33259191728 118926.61562660239 145194.84950343205 225460.82137323898 13 | 24 126397.36498203808 175878.5919778528 131296.8291147398 142705.91510807787 226031.9976331593 14 | 30 118867.09704937357 156788.18869250992 131379.36214184563 143509.93331002138 217136.4052556025 15 | 36 108646.69941018839 117199.09449503751 125494.73068507237 144985.5262526949 218689.3368325038 16 | 42 107075.19120256636 159405.7520303877 122142.98621480672 144503.30946213496 224534.05667709437 17 | 48 108428.9950127669 162091.21407499604 119001.05236387714 145074.12846816154 219900.07420068386 18 | -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test01.plt: -------------------------------------------------------------------------------- 1 | set term png 2 | 3 | set terminal png size 1200,500 4 | set output 'test01.png' 5 | 6 | set grid 7 | set linetype 1 lc rgb '#9400D3' 8 | set linetype 2 lc rgb '#009E73' 9 | set linetype 3 lc rgb '#56B4E9' 10 | set linetype 4 lc rgb '#E69F00' 11 | set linetype 5 lc rgb '#F0E442' 12 | set linetype 6 lc rgb '#0072B2' 13 | 14 | set ylabel "requests/sec" 15 | set format y "%'.0f" 16 | set xlabel "GOMAXPROCS" 17 | set style data histogram 18 | set style histogram cluster gap 1 19 | set style fill solid border -1 20 | set boxwidth 0.8 21 | set xtics rotate by -45 scale 0 22 | set key outside right above 23 | 24 | stats 'test01.dat' matrix rowheaders columnheaders noout 25 | set autoscale ymax 26 | 27 | set ytics 1000 28 | 29 | if (STATS_max > 50000) { 30 | set ytics 5000 31 | } 32 | 33 | if (STATS_max > 100000) { 34 | set ytics 10000 35 | } 36 | 37 | if (STATS_max > 500000) { 38 | set ytics 50000 39 | } 40 | 41 | if (STATS_max > 1500000) { 42 | set ytics 100000 43 | } 44 | 45 | plot 'test01.dat' \ 46 | u 'net/http':xticlabels(1) ti col lt 1, ''\ 47 | u 'evio':xticlabels(1) ti col lt 2, ''\ 48 | u 'gnet':xticlabels(1) ti col lt 3, ''\ 49 | u 'fasthttp':xticlabels(1) ti col lt 4, ''\ 50 | u 'tcpserver':xticlabels(1) ti col lt 5 51 | -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maurice2k/tcpserver/f0f687902d00a79db19fe36429ffc8a0a8ebf2f4/benchmark/_results/aws-c5-metal/test01.png -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test01_tls.dat: -------------------------------------------------------------------------------- 1 | GOMAXPROCS net/http fasthttp tcpserver 2 | 1 0 3482.6378233925275 4089.85715627609 3 | 2 6972.4884368045805 7114.392311156295 8370.517011926093 4 | 3 10210.43199199854 10634.235681129321 12212.194986175264 5 | 4 13488.812150499782 13666.906847702254 16929.82229506799 6 | 6 18972.944892752846 19186.615759285844 24911.98890646137 7 | 8 19479.516059353988 21737.23931873041 33342.20589994229 8 | 10 18171.66563084643 24097.280342851664 40957.24998759799 9 | 12 14535.31041468338 27642.83469708005 48489.95238029749 10 | 14 12564.8315122583 27773.17256045535 55857.03028379014 11 | 16 12165.170551555007 27940.305005801474 63427.290258558685 12 | 18 12187.77722503292 24974.299229631848 70576.61825684595 13 | 24 11029.012125753214 20178.524581143498 86995.67545938569 14 | 30 10083.18009736137 18707.156328175282 92105.79916699765 15 | 36 9452.163177066373 17300.525258132 97040.13573287803 16 | 42 9287.24548117888 16947.74860034855 100572.46556131195 17 | 48 9848.554969011628 18097.147794484066 101505.19154519489 18 | -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test01_tls.plt: -------------------------------------------------------------------------------- 1 | set term png 2 | 3 | set terminal png size 1200,500 4 | set output 'test01_tls.png' 5 | 6 | set grid 7 | set linetype 1 lc rgb '#9400D3' 8 | set linetype 2 lc rgb '#009E73' 9 | set linetype 3 lc rgb '#56B4E9' 10 | set linetype 4 lc rgb '#E69F00' 11 | set linetype 5 lc rgb '#F0E442' 12 | set linetype 6 lc rgb '#0072B2' 13 | 14 | set ylabel "requests/sec" 15 | set format y "%'.0f" 16 | set xlabel "GOMAXPROCS" 17 | set style data histogram 18 | set style histogram cluster gap 1 19 | set style fill solid border -1 20 | set boxwidth 0.8 21 | set xtics rotate by -45 scale 0 22 | set key outside right above 23 | 24 | stats 'test01_tls.dat' matrix rowheaders columnheaders noout 25 | set autoscale ymax 26 | 27 | set ytics 1000 28 | 29 | if (STATS_max > 50000) { 30 | set ytics 5000 31 | } 32 | 33 | if (STATS_max > 100000) { 34 | set ytics 10000 35 | } 36 | 37 | if (STATS_max > 500000) { 38 | set ytics 50000 39 | } 40 | 41 | if (STATS_max > 1500000) { 42 | set ytics 100000 43 | } 44 | 45 | plot 'test01_tls.dat' \ 46 | u 'net/http':xticlabels(1) ti col lt 1, ''\ 47 | \ 48 | \ 49 | u 'fasthttp':xticlabels(1) ti col lt 4, ''\ 50 | u 'tcpserver':xticlabels(1) ti col lt 5 51 | -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test01_tls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maurice2k/tcpserver/f0f687902d00a79db19fe36429ffc8a0a8ebf2f4/benchmark/_results/aws-c5-metal/test01_tls.png -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test02.dat: -------------------------------------------------------------------------------- 1 | GOMAXPROCS net/http evio gnet fasthttp tcpserver 2 | 1 74579.77874015602 120954.98120896511 133779.25157988697 118053.35992450084 140345.2381542752 3 | 2 137524.9014495119 238945.05981380458 255695.40079513227 221981.22128093694 256120.36741455153 4 | 3 201327.12578871223 348143.7800634607 375961.7895339152 326456.78135500714 379149.9546115832 5 | 4 267504.09493370703 460577.7606653743 494638.6788689598 426120.3135794211 499861.700332 6 | 6 395995.5369696746 664636.8502562672 690409.004583394 611453.5553240845 741391.1491909833 7 | 8 517361.09812699136 813052.0484970975 845754.7635315759 777452.4924889852 960905.9190315428 8 | 10 632215.86161327 906399.8860760875 940293.9795668686 883538.5367442205 964739.2603332876 9 | 12 729594.7236902316 1028793.2577489263 1008955.5568981442 914270.7328960998 1113261.2857090652 10 | 14 817959.726269819 1045698.5293394577 970556.2857815367 877764.1842119901 901110.2308295922 11 | 16 876107.6536420753 914774.3456851798 952116.0569772293 848349.6924014131 1042992.8233618963 12 | 18 923041.6238568032 846639.4239180799 828170.3559608271 895263.1451832781 1078374.4035038275 13 | 24 647744.9131374898 830200.6230882701 816576.0342255183 823759.5371921664 1059341.9127113156 14 | 30 697227.865462842 941538.1566392977 774114.0723381906 744478.9323186574 1023178.7255593828 15 | 36 686870.1180062358 866238.26642248 809621.3812975871 718674.772600101 738816.3393284508 16 | 42 624866.7299081737 840049.4959234293 813100.0453635021 711363.2631510184 1009668.099387554 17 | 48 692122.2621128552 898011.7997232154 725324.6670511944 674592.6107627243 1040889.3800324628 18 | -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test02.plt: -------------------------------------------------------------------------------- 1 | set term png 2 | 3 | set terminal png size 1200,500 4 | set output 'test02.png' 5 | 6 | set grid 7 | set linetype 1 lc rgb '#9400D3' 8 | set linetype 2 lc rgb '#009E73' 9 | set linetype 3 lc rgb '#56B4E9' 10 | set linetype 4 lc rgb '#E69F00' 11 | set linetype 5 lc rgb '#F0E442' 12 | set linetype 6 lc rgb '#0072B2' 13 | 14 | set ylabel "requests/sec" 15 | set format y "%'.0f" 16 | set xlabel "GOMAXPROCS" 17 | set style data histogram 18 | set style histogram cluster gap 1 19 | set style fill solid border -1 20 | set boxwidth 0.8 21 | set xtics rotate by -45 scale 0 22 | set key outside right above 23 | 24 | stats 'test02.dat' matrix rowheaders columnheaders noout 25 | set autoscale ymax 26 | 27 | set ytics 1000 28 | 29 | if (STATS_max > 50000) { 30 | set ytics 5000 31 | } 32 | 33 | if (STATS_max > 100000) { 34 | set ytics 10000 35 | } 36 | 37 | if (STATS_max > 500000) { 38 | set ytics 50000 39 | } 40 | 41 | if (STATS_max > 1500000) { 42 | set ytics 100000 43 | } 44 | 45 | plot 'test02.dat' \ 46 | u 'net/http':xticlabels(1) ti col lt 1, ''\ 47 | u 'evio':xticlabels(1) ti col lt 2, ''\ 48 | u 'gnet':xticlabels(1) ti col lt 3, ''\ 49 | u 'fasthttp':xticlabels(1) ti col lt 4, ''\ 50 | u 'tcpserver':xticlabels(1) ti col lt 5 51 | -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maurice2k/tcpserver/f0f687902d00a79db19fe36429ffc8a0a8ebf2f4/benchmark/_results/aws-c5-metal/test02.png -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test02_tls.dat: -------------------------------------------------------------------------------- 1 | GOMAXPROCS net/http fasthttp tcpserver 2 | 1 51044.65917406152 84869.7361597277 101625.32013813872 3 | 2 114984.36054247046 180072.32902853776 204390.8697730125 4 | 3 166864.87118709015 271578.98603187205 319723.70816904906 5 | 4 225059.4226495268 363919.41435512545 417145.50960515987 6 | 6 336428.55047830625 527927.3080129946 624814.0594663164 7 | 8 443959.27360402845 682643.8295365622 819507.7942672651 8 | 10 554099.2576114555 810581.554095508 1014763.1513464343 9 | 12 652603.7238248974 900566.7509028623 970420.6577942036 10 | 14 742191.9043526369 889235.927629677 1081614.1643631437 11 | 16 817835.488729627 891454.0532771213 1068362.628059413 12 | 18 898158.4575889201 861489.4238012214 1091828.2383184724 13 | 24 774055.592634048 736063.4953394033 1113079.1749760506 14 | 30 769375.1294553705 722754.1357525489 872217.4371897998 15 | 36 739847.6994104509 697777.670210067 1021216.1754399949 16 | 42 639635.5703852968 687087.7660308355 691478.5230150688 17 | 48 575309.9627938881 657535.042538528 682807.981862862 18 | -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test02_tls.plt: -------------------------------------------------------------------------------- 1 | set term png 2 | 3 | set terminal png size 1200,500 4 | set output 'test02_tls.png' 5 | 6 | set grid 7 | set linetype 1 lc rgb '#9400D3' 8 | set linetype 2 lc rgb '#009E73' 9 | set linetype 3 lc rgb '#56B4E9' 10 | set linetype 4 lc rgb '#E69F00' 11 | set linetype 5 lc rgb '#F0E442' 12 | set linetype 6 lc rgb '#0072B2' 13 | 14 | set ylabel "requests/sec" 15 | set format y "%'.0f" 16 | set xlabel "GOMAXPROCS" 17 | set style data histogram 18 | set style histogram cluster gap 1 19 | set style fill solid border -1 20 | set boxwidth 0.8 21 | set xtics rotate by -45 scale 0 22 | set key outside right above 23 | 24 | stats 'test02_tls.dat' matrix rowheaders columnheaders noout 25 | set autoscale ymax 26 | 27 | set ytics 1000 28 | 29 | if (STATS_max > 50000) { 30 | set ytics 5000 31 | } 32 | 33 | if (STATS_max > 100000) { 34 | set ytics 10000 35 | } 36 | 37 | if (STATS_max > 500000) { 38 | set ytics 50000 39 | } 40 | 41 | if (STATS_max > 1500000) { 42 | set ytics 100000 43 | } 44 | 45 | plot 'test02_tls.dat' \ 46 | u 'net/http':xticlabels(1) ti col lt 1, ''\ 47 | \ 48 | \ 49 | u 'fasthttp':xticlabels(1) ti col lt 4, ''\ 50 | u 'tcpserver':xticlabels(1) ti col lt 5 51 | -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test02_tls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maurice2k/tcpserver/f0f687902d00a79db19fe36429ffc8a0a8ebf2f4/benchmark/_results/aws-c5-metal/test02_tls.png -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test03.dat: -------------------------------------------------------------------------------- 1 | GOMAXPROCS net/http evio gnet fasthttp tcpserver 2 | 1 31098.763782573886 32353.84323614763 6840.980683941553 38722.09222073587 45034.0745750394 3 | 2 55624.05572291479 40759.86702156838 14085.920634163913 62992.274220476786 82022.02168315857 4 | 3 81661.64648563576 44078.14744567912 21744.867299562073 89664.68603061045 117905.21506738207 5 | 4 107167.52902000565 49555.21596796047 30284.08557053365 129291.96379564264 153104.82820550798 6 | 6 139296.53104276088 215108.61488869775 49895.834900788286 139710.47812647576 201634.57614355753 7 | 8 134727.09876369272 203574.79457170854 74896.5476847117 134817.44816097952 225651.60558170726 8 | 10 131483.19400388564 212871.18160612584 102170.71551807965 134095.2270690342 221631.05209868413 9 | 12 128736.3942670797 185040.1452762662 121012.52484084915 130471.93101502366 222805.11184769246 10 | 14 123381.90782743675 187469.00897190053 132674.92083045878 129185.19825390543 226453.75617854705 11 | 16 119035.400949853 166797.3396705702 133439.36873563958 127182.89238069837 221559.10173197096 12 | 18 115746.61662145109 114600.113148277 131743.44754734635 127537.48219225559 227350.9326251838 13 | 24 98541.10095118568 0 115303.59625803333 128425.70363750086 222411.5378870634 14 | 30 78627.00348324503 191916.05773516308 119423.09086092445 129933.63610402051 222101.91536514205 15 | 36 77140.93735633 153356.2500315656 114730.5557690539 131936.3585024175 222753.8683798265 16 | 42 77575.36197247524 185937.36825915828 113860.67780652775 134107.43228470135 219532.60150057948 17 | 48 79045.87926742127 201487.9594184866 108722.19724800158 132460.80853049757 215547.11409622055 18 | -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test03.plt: -------------------------------------------------------------------------------- 1 | set term png 2 | 3 | set terminal png size 1200,500 4 | set output 'test03.png' 5 | 6 | set grid 7 | set linetype 1 lc rgb '#9400D3' 8 | set linetype 2 lc rgb '#009E73' 9 | set linetype 3 lc rgb '#56B4E9' 10 | set linetype 4 lc rgb '#E69F00' 11 | set linetype 5 lc rgb '#F0E442' 12 | set linetype 6 lc rgb '#0072B2' 13 | 14 | set ylabel "requests/sec" 15 | set format y "%'.0f" 16 | set xlabel "GOMAXPROCS" 17 | set style data histogram 18 | set style histogram cluster gap 1 19 | set style fill solid border -1 20 | set boxwidth 0.8 21 | set xtics rotate by -45 scale 0 22 | set key outside right above 23 | 24 | stats 'test03.dat' matrix rowheaders columnheaders noout 25 | set autoscale ymax 26 | 27 | set ytics 1000 28 | 29 | if (STATS_max > 50000) { 30 | set ytics 5000 31 | } 32 | 33 | if (STATS_max > 100000) { 34 | set ytics 10000 35 | } 36 | 37 | if (STATS_max > 500000) { 38 | set ytics 50000 39 | } 40 | 41 | if (STATS_max > 1500000) { 42 | set ytics 100000 43 | } 44 | 45 | plot 'test03.dat' \ 46 | u 'net/http':xticlabels(1) ti col lt 1, ''\ 47 | u 'evio':xticlabels(1) ti col lt 2, ''\ 48 | u 'gnet':xticlabels(1) ti col lt 3, ''\ 49 | u 'fasthttp':xticlabels(1) ti col lt 4, ''\ 50 | u 'tcpserver':xticlabels(1) ti col lt 5 51 | -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maurice2k/tcpserver/f0f687902d00a79db19fe36429ffc8a0a8ebf2f4/benchmark/_results/aws-c5-metal/test03.png -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test04.dat: -------------------------------------------------------------------------------- 1 | GOMAXPROCS net/http evio gnet fasthttp tcpserver 2 | 1 56331.96614463596 90326.32910194057 95075.93062131942 77483.28004237024 87835.8739253253 3 | 2 104277.13372356437 173272.6961373705 178856.15087516757 141425.67800323415 171569.36056331068 4 | 3 151412.16132188452 251200.56734819373 254021.1846034931 206257.54196768388 258939.3654767893 5 | 4 197213.9101718029 321132.1051261316 322449.2279802644 270077.7278375207 343138.5893274219 6 | 6 282399.7305508579 440946.33202287933 425399.40985447523 384040.69058725116 501958.34062973363 7 | 8 362996.0947994723 524758.7026190042 506375.34646191774 483883.85088458494 653958.3144521544 8 | 10 432599.67536354397 601680.3048495123 569810.36474904 578284.2043568636 807057.0809578934 9 | 12 489172.8203178755 678768.7311776188 615606.4632371308 617198.1631513528 940106.8367069376 10 | 14 531139.9327692784 695142.4931358865 653844.5449060508 649856.7745782534 1057352.8626632649 11 | 16 555269.4256254511 686471.7875279231 662946.3032917815 642249.6647271521 910555.4259747325 12 | 18 571963.2197015028 670470.4551709299 668793.8429171884 638470.0376289749 973728.6908163881 13 | 24 577596.9769908075 707880.1262274232 625153.8771163084 583064.8165006356 766665.1031400425 14 | 30 547915.4220861644 688041.770243315 613797.7077345258 562233.6203906218 939682.7052555186 15 | 36 531737.3128258722 637039.4672937677 630230.5304769754 567125.6567450289 902177.3409604848 16 | 42 515845.15208956023 581903.67484779 614126.1354051678 551650.3504052416 935727.7000036371 17 | 48 490727.4569268235 642227.9134023715 662489.6256425895 536551.1527018475 661698.8932860832 18 | -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test04.plt: -------------------------------------------------------------------------------- 1 | set term png 2 | 3 | set terminal png size 1200,500 4 | set output 'test04.png' 5 | 6 | set grid 7 | set linetype 1 lc rgb '#9400D3' 8 | set linetype 2 lc rgb '#009E73' 9 | set linetype 3 lc rgb '#56B4E9' 10 | set linetype 4 lc rgb '#E69F00' 11 | set linetype 5 lc rgb '#F0E442' 12 | set linetype 6 lc rgb '#0072B2' 13 | 14 | set ylabel "requests/sec" 15 | set format y "%'.0f" 16 | set xlabel "GOMAXPROCS" 17 | set style data histogram 18 | set style histogram cluster gap 1 19 | set style fill solid border -1 20 | set boxwidth 0.8 21 | set xtics rotate by -45 scale 0 22 | set key outside right above 23 | 24 | stats 'test04.dat' matrix rowheaders columnheaders noout 25 | set autoscale ymax 26 | 27 | set ytics 1000 28 | 29 | if (STATS_max > 50000) { 30 | set ytics 5000 31 | } 32 | 33 | if (STATS_max > 100000) { 34 | set ytics 10000 35 | } 36 | 37 | if (STATS_max > 500000) { 38 | set ytics 50000 39 | } 40 | 41 | if (STATS_max > 1500000) { 42 | set ytics 100000 43 | } 44 | 45 | plot 'test04.dat' \ 46 | u 'net/http':xticlabels(1) ti col lt 1, ''\ 47 | u 'evio':xticlabels(1) ti col lt 2, ''\ 48 | u 'gnet':xticlabels(1) ti col lt 3, ''\ 49 | u 'fasthttp':xticlabels(1) ti col lt 4, ''\ 50 | u 'tcpserver':xticlabels(1) ti col lt 5 51 | -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maurice2k/tcpserver/f0f687902d00a79db19fe36429ffc8a0a8ebf2f4/benchmark/_results/aws-c5-metal/test04.png -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test05.dat: -------------------------------------------------------------------------------- 1 | GOMAXPROCS net/http evio gnet fasthttp tcpserver 2 | 1 72969.60075472329 32192.669729049165 28331.684105834505 119708.38555650203 151822.1585911797 3 | 2 138438.96233854082 37216.69903084668 34508.85695377488 218352.58594163018 290836.2564652745 4 | 3 203367.52767663967 39423.40417727109 37063.64294012894 231198.3524983243 432298.43433287716 5 | 4 265617.44358992955 40341.97496511495 38705.446971731035 274630.5866482412 580389.7724144288 6 | 6 379311.5312777015 41791.59289694675 40377.35659649044 544562.1100685509 841456.5180881692 7 | 8 483581.1157014056 42441.40440959345 41248.53834840367 718524.3542112354 976308.1503970431 8 | 10 576484.0509833054 42699.05510991512 41759.157209702134 840681.1570448444 848892.677371722 9 | 12 636398.1121280808 42860.65219796303 41944.25718011232 880635.4363829233 750045.6412825824 10 | 14 662599.2290432337 42916.237081582585 41987.57941060555 875221.3925302589 789099.4712475279 11 | 16 668183.7837842569 42838.53581740147 41747.31832036067 849860.9284147331 794122.8588013104 12 | 18 659705.9684599309 42888.36338016134 41681.19006350851 788987.711110861 799093.1780272766 13 | 24 618525.0215310974 42881.56735025298 41644.65056979182 774585.156654268 718287.3050471091 14 | 30 573110.9003331295 42829.544489261185 41623.81275180921 759192.7336650925 718392.3358352269 15 | 36 577533.530806704 42769.39097887404 41584.75396436638 742613.5256220835 828154.1903657225 16 | 42 552829.1938280431 42730.24583632325 41576.17921334195 721060.8956826278 757421.1965060409 17 | 48 567217.5934831823 42757.789422656104 41557.35380443169 744160.9879248155 814174.8564797044 18 | -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test05.plt: -------------------------------------------------------------------------------- 1 | set term png 2 | 3 | set terminal png size 1200,500 4 | set output 'test05.png' 5 | 6 | set grid 7 | set linetype 1 lc rgb '#9400D3' 8 | set linetype 2 lc rgb '#009E73' 9 | set linetype 3 lc rgb '#56B4E9' 10 | set linetype 4 lc rgb '#E69F00' 11 | set linetype 5 lc rgb '#F0E442' 12 | set linetype 6 lc rgb '#0072B2' 13 | 14 | set ylabel "requests/sec" 15 | set format y "%'.0f" 16 | set xlabel "GOMAXPROCS" 17 | set style data histogram 18 | set style histogram cluster gap 1 19 | set style fill solid border -1 20 | set boxwidth 0.8 21 | set xtics rotate by -45 scale 0 22 | set key outside right above 23 | 24 | stats 'test05.dat' matrix rowheaders columnheaders noout 25 | set autoscale ymax 26 | 27 | set ytics 1000 28 | 29 | if (STATS_max > 50000) { 30 | set ytics 5000 31 | } 32 | 33 | if (STATS_max > 100000) { 34 | set ytics 10000 35 | } 36 | 37 | if (STATS_max > 500000) { 38 | set ytics 50000 39 | } 40 | 41 | if (STATS_max > 1500000) { 42 | set ytics 100000 43 | } 44 | 45 | plot 'test05.dat' \ 46 | u 'net/http':xticlabels(1) ti col lt 1, ''\ 47 | u 'evio':xticlabels(1) ti col lt 2, ''\ 48 | u 'gnet':xticlabels(1) ti col lt 3, ''\ 49 | u 'fasthttp':xticlabels(1) ti col lt 4, ''\ 50 | u 'tcpserver':xticlabels(1) ti col lt 5 51 | -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maurice2k/tcpserver/f0f687902d00a79db19fe36429ffc8a0a8ebf2f4/benchmark/_results/aws-c5-metal/test05.png -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test06.dat: -------------------------------------------------------------------------------- 1 | GOMAXPROCS net/http evio gnet fasthttp tcpserver 2 | 1 22878.640542729794 39235.693798328575 6744.063732282394 34605.87396665513 38480.365904824415 3 | 2 43684.80543225525 48755.92535674675 14002.189870969196 65162.52338029727 71795.64223160034 4 | 3 63587.359478950304 54265.51651259174 21693.15909549001 94696.13996168261 103511.97485648796 5 | 4 82166.40323664279 61459.4845474409 30074.940046443677 123431.964630064 134484.60393727978 6 | 6 118352.95083172427 84658.4107233999 48202.36476558782 147807.8255401087 183534.50173135244 7 | 8 139194.10075422822 83001.25067899369 67771.45978159133 145093.57673173526 212965.6243334261 8 | 10 136849.7145793672 129002.37880120064 87060.77925688843 142199.0779473718 215082.78543085622 9 | 12 137086.06604745353 178984.23903240505 102450.72084711543 140724.86536889258 218119.89412890206 10 | 14 133624.00697765898 170835.08973568043 109928.41438019117 141639.7263771725 215847.79055663236 11 | 16 130715.62451057725 168249.01814345017 109158.09890490182 141802.1991030862 211366.66920351435 12 | 18 128470.18104408929 172395.2133400993 107215.08263220085 141080.16058224198 213449.9952768316 13 | 24 119134.08353566102 154822.64563917374 94080.63155688635 141930.91207290036 213430.0612868225 14 | 30 105442.0622008614 157571.14760438618 92910.36709996393 140315.36885256934 212246.3673208444 15 | 36 97456.08776263366 108713.16390698144 92924.43177965963 140493.09732338393 211709.4259616833 16 | 42 99154.37267024859 67223.44375026255 88754.27845097221 140779.14681838665 209958.46735802342 17 | 48 100157.97016184268 58343.25825008334 87233.84180741367 140345.4324953805 207040.55770288248 18 | -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test06.plt: -------------------------------------------------------------------------------- 1 | set term png 2 | 3 | set terminal png size 1200,500 4 | set output 'test06.png' 5 | 6 | set grid 7 | set linetype 1 lc rgb '#9400D3' 8 | set linetype 2 lc rgb '#009E73' 9 | set linetype 3 lc rgb '#56B4E9' 10 | set linetype 4 lc rgb '#E69F00' 11 | set linetype 5 lc rgb '#F0E442' 12 | set linetype 6 lc rgb '#0072B2' 13 | 14 | set ylabel "requests/sec" 15 | set format y "%'.0f" 16 | set xlabel "GOMAXPROCS" 17 | set style data histogram 18 | set style histogram cluster gap 1 19 | set style fill solid border -1 20 | set boxwidth 0.8 21 | set xtics rotate by -45 scale 0 22 | set key outside right above 23 | 24 | stats 'test06.dat' matrix rowheaders columnheaders noout 25 | set autoscale ymax 26 | 27 | set ytics 1000 28 | 29 | if (STATS_max > 50000) { 30 | set ytics 5000 31 | } 32 | 33 | if (STATS_max > 100000) { 34 | set ytics 10000 35 | } 36 | 37 | if (STATS_max > 500000) { 38 | set ytics 50000 39 | } 40 | 41 | if (STATS_max > 1500000) { 42 | set ytics 100000 43 | } 44 | 45 | plot 'test06.dat' \ 46 | u 'net/http':xticlabels(1) ti col lt 1, ''\ 47 | u 'evio':xticlabels(1) ti col lt 2, ''\ 48 | u 'gnet':xticlabels(1) ti col lt 3, ''\ 49 | u 'fasthttp':xticlabels(1) ti col lt 4, ''\ 50 | u 'tcpserver':xticlabels(1) ti col lt 5 51 | -------------------------------------------------------------------------------- /benchmark/_results/aws-c5-metal/test06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maurice2k/tcpserver/f0f687902d00a79db19fe36429ffc8a0a8ebf2f4/benchmark/_results/aws-c5-metal/test06.png -------------------------------------------------------------------------------- /benchmark/benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | go='/opt/go/current/bin/go' 4 | bombardier='/opt/bombardier-linux-amd64' 5 | jq='/usr/bin/jq' 6 | 7 | numactl_http_server='numactl -N0' 8 | numactl_bombardier='numactl -N1' 9 | 10 | cpus=`numactl -N0 -s |grep -P -o 'physcpubind: \K.*' |awk '{print NF}'` 11 | conns=1000 12 | duration=10 # duration of test in seconds 13 | 14 | if [[ -n $CPUS && $CPUS -gt 0 ]] 15 | then 16 | cpus=$CPUS 17 | fi 18 | 19 | if [[ -n $CONNS && $CONNS -gt 0 ]] 20 | then 21 | conns=$CONNS 22 | fi 23 | 24 | if [[ -n $DURATION && $DURATION -gt 0 ]] 25 | then 26 | duration=$DURATION 27 | fi 28 | 29 | if [[ -n $DURATION && $DURATION -gt 0 ]] 30 | then 31 | duration=$DURATION 32 | fi 33 | 34 | 35 | ## kill every process that matches "test_http_server" 36 | ps a |grep "[t]est_http_serv" |awk '{print $1}' |xargs -I{} kill -9 {} 37 | 38 | run_server() { 39 | echo "Starting server: $2" 40 | eval "GOMAXPROCS=$1 $numactl_http_server $2 &" 41 | pid=$! 42 | } 43 | 44 | kill_server() { 45 | disown $pid 2>/dev/null ; kill -9 $pid 2>/dev/null 46 | } 47 | 48 | declare -a results 49 | 50 | test_http_server() { 51 | results=() 52 | used_cpus=() 53 | rm test_http_server 2>/dev/null 54 | killall -9 test_http_server 2>/dev/null 55 | 56 | echo "Building $1" 57 | $go build -mod vendor -o test_http_server $1 58 | 59 | start_cpu=1 60 | exact=0 61 | if [[ -n $START_CPU && $START_CPU -gt 0 ]] 62 | then 63 | start_cpu=$START_CPU 64 | fi 65 | 66 | if [[ -n $ONLYGIVENCPUS && $ONLYGIVENCPUS -eq 1 ]] 67 | then 68 | echo "Testing with exactly $cpus CPU(s)" 69 | start_cpu=$cpus 70 | exact=1 71 | else 72 | echo "Testing with $start_cpu..$cpus CPUs" 73 | fi 74 | 75 | for ((i=$start_cpu; i<=$cpus; i++)) 76 | do 77 | if [[ $exact -eq 0 && $i -ne $cpus ]]; then 78 | if [[ $cpus -gt 12 && $i -gt 4 && $(($i%2)) -ne 0 ]]; then 79 | continue 80 | fi 81 | if [[ $i -gt 16 && $(($i%3)) -ne 0 ]]; then 82 | continue 83 | fi 84 | fi 85 | port=$((8000+$i)) 86 | params=${2/"{port}"/$port} 87 | for ((j=0; j<3; j++)) ## max. 3 retries 88 | do 89 | run_server $i "./test_http_server $params" # 1>/dev/null 2>/dev/null' 90 | sleep 1 91 | server_running=`ps aux |grep "[t]est_http_server" |wc -l` 92 | if [ $server_running -ne 1 ] 93 | then 94 | if [ $j -eq 2 ] 95 | then 96 | echo "Server not running! Something weird happend; exiting" 97 | exit 98 | fi 99 | sleep 2 100 | continue 101 | fi 102 | break 103 | done 104 | 105 | url=${3/"{port}"/$port} 106 | used_cpus+=($i) 107 | #result=`GOGC=400 $numactl_bombardier $bombardier -c $conns -d ${duration}s -pr -oj --fasthttp -k $url |$jq . |tee /dev/fd/2 |$jq '.|select(.result.others==0) .result.rps.mean//0'` 108 | result=`GOGC=off $numactl_bombardier $bombardier -c $conns -d ${duration}s -pr -oj --fasthttp -k $url |$jq '.|select(.result.others==0) .result.rps.mean//0'` 109 | echo "--> $result reqs/sec @GOMAXPROCS=$i" >/dev/fd/2 110 | results+=($result) 111 | 112 | kill_server 113 | sleep 2 114 | done 115 | 116 | rm test_http_server 2>/dev/null 117 | } 118 | 119 | plot_results() { 120 | echo -e "GOMAXPROCS\t${results_net:+net/http\t}${results_evio:+evio\t}${results_gnet:+gnet\t}${results_fasthttp:+fasthttp\t}${results_tcpserver:+tcpserver}" >$1.dat 121 | 122 | for ((i=0; i<${#used_cpus[@]}; i++)) 123 | do 124 | echo -e "${used_cpus[$i]}\t${results_net:+${results_net[$i]:-0}\t}${results_evio:+${results_evio[$i]:-0}\t}${results_gnet:+${results_gnet[$i]:-0}\t}${results_fasthttp:+${results_fasthttp[$i]:-0}\t}${results_tcpserver:+${results_tcpserver[$i]:-0}}" >>$1.dat 125 | done 126 | 127 | cat <$1.plt 128 | set term png 129 | 130 | set terminal png size 1200,500 131 | set output '$1.png' 132 | 133 | set grid 134 | set linetype 1 lc rgb '#9400D3' 135 | set linetype 2 lc rgb '#009E73' 136 | set linetype 3 lc rgb '#56B4E9' 137 | set linetype 4 lc rgb '#E69F00' 138 | set linetype 5 lc rgb '#F0E442' 139 | set linetype 6 lc rgb '#0072B2' 140 | 141 | set ylabel "requests/sec" 142 | set format y "%'.0f" 143 | set xlabel "GOMAXPROCS" 144 | set style data histogram 145 | set style histogram cluster gap 1 146 | set style fill solid border -1 147 | set boxwidth 0.8 148 | set xtics rotate by -45 scale 0 149 | set key outside right above 150 | 151 | stats '$1.dat' matrix rowheaders columnheaders noout 152 | set autoscale ymax 153 | 154 | set ytics 1000 155 | 156 | if (STATS_max > 50000) { 157 | set ytics 5000 158 | } 159 | 160 | if (STATS_max > 100000) { 161 | set ytics 10000 162 | } 163 | 164 | if (STATS_max > 500000) { 165 | set ytics 50000 166 | } 167 | 168 | if (STATS_max > 1500000) { 169 | set ytics 100000 170 | } 171 | 172 | plot '$1.dat' \\ 173 | ${results_net:+u 'net/http':xticlabels(1) ti col lt 1, ''}\\ 174 | ${results_evio:+u 'evio':xticlabels(1) ti col lt 2, ''}\\ 175 | ${results_gnet:+u 'gnet':xticlabels(1) ti col lt 3, ''}\\ 176 | ${results_fasthttp:+u 'fasthttp':xticlabels(1) ti col lt 4, ''}\\ 177 | u 'tcpserver':xticlabels(1) ti col lt 5 178 | EOT 179 | 180 | gnuplot $1.plt 181 | } 182 | 183 | 184 | run_test1() { 185 | echo "====[ Running test #1: HTTP returning 1024 byte, ${conns} concurrent connections, keepalive off ]====" 186 | 187 | test_http_server 'net-http-server/main.go' '-keepalive=0 -listen=127.0.0.10:{port} -aaaa=1024 -sleep=0' 'http://127.0.0.10:{port}/' 188 | results_net=("${results[@]}") 189 | echo "" 190 | 191 | test_http_server 'evio-http-server/main.go' '-keepalive=0 -listen=127.0.0.11:{port} -aaaa=1024 -sleep=0 -loops=-1' 'http://127.0.0.11:{port}/' 192 | results_evio=("${results[@]}") 193 | echo "" 194 | 195 | test_http_server 'gnet-http-server/main.go' '-keepalive=0 -listen=127.0.0.12:{port} -aaaa=1024 -sleep=0 -loops=-1' 'http://127.0.0.12:{port}/' 196 | results_gnet=("${results[@]}") 197 | echo "" 198 | 199 | test_http_server 'fasthttp-http-server/main.go' '-keepalive=0 -listen=127.0.0.13:{port} -aaaa=1024 -sleep=0' 'http://127.0.0.13:{port}/' 200 | results_fasthttp=("${results[@]}") 201 | echo "" 202 | 203 | test_http_server '../examples/http-server/main.go' '-keepalive=0 -listen=127.0.0.14:{port} -aaaa=1024 -sleep=0' 'http://127.0.0.14:{port}/' 204 | results_tcpserver=("${results[@]}") 205 | echo "" 206 | 207 | plot_results "test01" 208 | echo "FINISHED." 209 | echo "" 210 | } 211 | 212 | 213 | run_test1_tls() { 214 | echo "====[ Running test #1 (with TLS): HTTP returning 1024 byte, ${conns} concurrent connections, keepalive off ]====" 215 | 216 | test_http_server 'net-http-server/main.go' '-keepalive=0 -listen=127.0.1.10:{port} -aaaa=1024 -sleep=0 -useTls' 'https://127.0.1.10:{port}/' 217 | results_net=("${results[@]}") 218 | echo "" 219 | 220 | test_http_server 'fasthttp-http-server/main.go' '-keepalive=0 -listen=127.0.1.13:{port} -aaaa=1024 -sleep=0 -useTls' 'https://127.0.1.13:{port}/' 221 | results_fasthttp=("${results[@]}") 222 | echo "" 223 | 224 | test_http_server '../examples/http-server/main.go' '-keepalive=0 -listen=127.0.1.14:{port} -aaaa=1024 -sleep=0 -useTls' 'https://127.0.1.14:{port}/' 225 | results_tcpserver=("${results[@]}") 226 | echo "" 227 | 228 | results_evio= 229 | results_gnet= 230 | 231 | plot_results "test01_tls" 232 | echo "FINISHED." 233 | echo "" 234 | } 235 | 236 | 237 | run_test2() { 238 | echo "====[ Running test #2: HTTP returning 1024 byte, ${conns} concurrent connections, keepalive on ]====" 239 | 240 | test_http_server 'net-http-server/main.go' '-keepalive=1 -listen=127.0.0.20:{port} -aaaa=1024 -sleep=0' 'http://127.0.0.20:{port}/' 241 | results_net=("${results[@]}") 242 | echo "" 243 | 244 | test_http_server 'evio-http-server/main.go' '-keepalive=1 -listen=127.0.0.21:{port} -aaaa=1024 -sleep=0 -loops=-1' 'http://127.0.0.21:{port}/' 245 | results_evio=("${results[@]}") 246 | echo "" 247 | 248 | test_http_server 'gnet-http-server/main.go' '-keepalive=1 -listen=127.0.0.22:{port} -aaaa=1024 -sleep=0 -loops=-1' 'http://127.0.0.22:{port}/' 249 | results_gnet=("${results[@]}") 250 | echo "" 251 | 252 | test_http_server 'fasthttp-http-server/main.go' '-keepalive=1 -listen=127.0.0.23:{port} -aaaa=1024 -sleep=0' 'http://127.0.0.23:{port}/' 253 | results_fasthttp=("${results[@]}") 254 | echo "" 255 | 256 | test_http_server '../examples/http-server/main.go' '-keepalive=1 -listen=127.0.0.24:{port} -aaaa=1024 -sleep=0' 'http://127.0.0.24:{port}/' 257 | results_tcpserver=("${results[@]}") 258 | echo "" 259 | 260 | plot_results "test02" 261 | echo "FINISHED." 262 | echo "" 263 | } 264 | 265 | 266 | run_test2_tls() { 267 | echo "====[ Running test #2 (with TLS): HTTP returning 1024 byte, ${conns} concurrent connections, keepalive on ]====" 268 | 269 | test_http_server 'net-http-server/main.go' '-keepalive=1 -listen=127.0.1.20:{port} -aaaa=1024 -sleep=0 -useTls' 'https://127.0.1.20:{port}/' 270 | results_net=("${results[@]}") 271 | echo "" 272 | 273 | test_http_server 'fasthttp-http-server/main.go' '-keepalive=1 -listen=127.0.1.23:{port} -aaaa=1024 -sleep=0 -useTls' 'https://127.0.1.23:{port}/' 274 | results_fasthttp=("${results[@]}") 275 | echo "" 276 | 277 | test_http_server '../examples/http-server/main.go' '-keepalive=1 -listen=127.0.1.24:{port} -aaaa=1024 -sleep=0 -useTls' 'https://127.0.1.24:{port}/' 278 | results_tcpserver=("${results[@]}") 279 | echo "" 280 | 281 | results_evio= 282 | results_gnet= 283 | 284 | plot_results "test02_tls" 285 | echo "FINISHED." 286 | echo "" 287 | } 288 | 289 | 290 | run_test3() { 291 | echo "====[ Running test #3: HTTP returning AES128(1024 byte), ${conns} concurrent connections, keepalive off ]====" 292 | 293 | test_http_server 'net-http-server/main.go' '-keepalive=0 -listen=127.0.0.30:{port} -aaaa=1024 -aes128 -sleep=0' 'http://127.0.0.30:{port}/' 294 | results_net=("${results[@]}") 295 | echo "" 296 | 297 | test_http_server 'evio-http-server/main.go' '-keepalive=0 -listen=127.0.0.31:{port} -aaaa=1024 -aes128 -sleep=0 -loops=-1' 'http://127.0.0.31:{port}/' 298 | results_evio=("${results[@]}") 299 | echo "" 300 | 301 | test_http_server 'gnet-http-server/main.go' '-keepalive=0 -listen=127.0.0.32:{port} -aaaa=1024 -aes128 -sleep=0 -loops=-1' 'http://127.0.0.32:{port}/' 302 | results_gnet=("${results[@]}") 303 | echo "" 304 | 305 | test_http_server 'fasthttp-http-server/main.go' '-keepalive=0 -listen=127.0.0.33:{port} -aaaa=1024 -aes128 -sleep=0' 'http://127.0.0.33:{port}/' 306 | results_fasthttp=("${results[@]}") 307 | echo "" 308 | 309 | test_http_server '../examples/http-server/main.go' '-keepalive=0 -listen=127.0.0.34:{port} -aaaa=1024 -aes128 -sleep=0' 'http://127.0.0.34:{port}/' 310 | results_tcpserver=("${results[@]}") 311 | echo "" 312 | 313 | plot_results "test03" 314 | echo "FINISHED." 315 | echo "" 316 | } 317 | 318 | 319 | run_test4() { 320 | echo "====[ Running test #4: HTTP returning AES128(1024 byte), ${conns} concurrent connections, keepalive on ]====" 321 | 322 | test_http_server 'net-http-server/main.go' '-keepalive=1 -listen=127.0.0.40:{port} -aaaa=1024 -aes128 -sleep=0' 'http://127.0.0.40:{port}/' 323 | results_net=("${results[@]}") 324 | echo "" 325 | 326 | test_http_server 'evio-http-server/main.go' '-keepalive=1 -listen=127.0.0.41:{port} -aaaa=1024 -aes128 -sleep=0 -loops=-1' 'http://127.0.0.41:{port}/' 327 | results_evio=("${results[@]}") 328 | echo "" 329 | 330 | test_http_server 'gnet-http-server/main.go' '-keepalive=1 -listen=127.0.0.42:{port} -aaaa=1024 -aes128 -sleep=0 -loops=-1' 'http://127.0.0.42:{port}/' 331 | results_gnet=("${results[@]}") 332 | echo "" 333 | 334 | test_http_server 'fasthttp-http-server/main.go' '-keepalive=1 -listen=127.0.0.43:{port} -aaaa=1024 -aes128 -sleep=0' 'http://127.0.0.43:{port}/' 335 | results_fasthttp=("${results[@]}") 336 | echo "" 337 | 338 | test_http_server '../examples/http-server/main.go' '-keepalive=1 -listen=127.0.0.44:{port} -aaaa=1024 -aes128 -sleep=0' 'http://127.0.0.44:{port}/' 339 | results_tcpserver=("${results[@]}") 340 | echo "" 341 | 342 | plot_results "test04" 343 | echo "FINISHED." 344 | echo "" 345 | } 346 | 347 | 348 | run_test5() { 349 | echo "====[ Running test #5: HTTP returning 128 byte, ${conns} concurrent connections, keepalive on, sleep 1 ms ]====" 350 | 351 | test_http_server 'net-http-server/main.go' '-keepalive=1 -listen=127.0.0.50:{port} -aaaa=128 -sleep=1' 'http://127.0.0.50:{port}/' 352 | results_net=("${results[@]}") 353 | echo "" 354 | 355 | test_http_server 'evio-http-server/main.go' '-keepalive=1 -listen=127.0.0.51:{port} -aaaa=128 -sleep=1 -loops=-1' 'http://127.0.0.51:{port}/' 356 | results_evio=("${results[@]}") 357 | echo "" 358 | 359 | test_http_server 'gnet-http-server/main.go' '-keepalive=1 -listen=127.0.0.52:{port} -aaaa=128 -sleep=1 -loops=-1' 'http://127.0.0.52:{port}/' 360 | results_gnet=("${results[@]}") 361 | echo "" 362 | 363 | test_http_server 'fasthttp-http-server/main.go' '-keepalive=1 -listen=127.0.0.53:{port} -aaaa=128 -sleep=1' 'http://127.0.0.53:{port}/' 364 | results_fasthttp=("${results[@]}") 365 | echo "" 366 | 367 | test_http_server '../examples/http-server/main.go' '-keepalive=1 -listen=127.0.0.54:{port} -aaaa=128 -sleep=1' 'http://127.0.0.54:{port}/' 368 | results_tcpserver=("${results[@]}") 369 | echo "" 370 | 371 | plot_results "test05" 372 | echo "FINISHED." 373 | echo "" 374 | } 375 | 376 | 377 | run_test6() { 378 | echo "====[ Running test #6: HTTP returning 16384 byte, ${conns} concurrent connections, keepalive off ]====" 379 | 380 | test_http_server 'net-http-server/main.go' '-keepalive=0 -listen=127.0.0.60:{port} -aaaa=16384 -sleep=0' 'http://127.0.0.60:{port}/' 381 | results_net=("${results[@]}") 382 | echo "" 383 | 384 | test_http_server 'evio-http-server/main.go' '-keepalive=0 -listen=127.0.0.61:{port} -aaaa=16384 -sleep=0 -loops=-1' 'http://127.0.0.61:{port}/' 385 | results_evio=("${results[@]}") 386 | echo "" 387 | 388 | test_http_server 'gnet-http-server/main.go' '-keepalive=0 -listen=127.0.0.62:{port} -aaaa=16384 -sleep=0 -loops=-1' 'http://127.0.0.62:{port}/' 389 | results_gnet=("${results[@]}") 390 | echo "" 391 | 392 | test_http_server 'fasthttp-http-server/main.go' '-keepalive=0 -listen=127.0.0.63:{port} -aaaa=16384 -sleep=0' 'http://127.0.0.63:{port}/' 393 | results_fasthttp=("${results[@]}") 394 | echo "" 395 | 396 | test_http_server '../examples/http-server/main.go' '-keepalive=0 -listen=127.0.0.64:{port} -aaaa=16384 -sleep=0' 'http://127.0.0.64:{port}/' 397 | results_tcpserver=("${results[@]}") 398 | echo "" 399 | 400 | plot_results "test06" 401 | echo "FINISHED." 402 | echo "" 403 | } 404 | 405 | run_install() { 406 | pwd=`pwd` 407 | cd /opt 408 | 409 | ## install required packages 410 | sudo apt install gnuplot git jq mc screen pv numactl -y 411 | 412 | ## setup golang 413 | go_version=1.19.2 414 | go_file=go${go_version}.linux-amd64.tar.gz 415 | go_installdir=/opt/go/${go_version} 416 | sudo mkdir -p ${go_installdir} 417 | sudo wget -O ${go_file} https://dl.google.com/go/${go_file} ; sudo tar xzf ${go_file} --strip-components=1 -C ${go_installdir} 418 | sudo rm -rf /opt/go/current 419 | sudo ln -fs /opt/go/${go_version} /opt/go/current 420 | sudo echo "export PATH=/opt/go/current/bin:$PATH" > /etc/profile.d/go.sh 421 | sudo echo "export GOROOT=/opt/go/current" >> /etc/profile.d/go.sh 422 | source /etc/profile.d/go.sh 423 | 424 | ## setup bombardier 425 | bombardier_version=1.2.5 426 | bombardier_file=bombardier-linux-amd64 427 | sudo wget -O ${bombardier_file} https://github.com/codesenberg/bombardier/releases/download/v${bombardier_version}/${bombardier_file} 428 | sudo chmod +x /opt/${bombardier_file} 429 | 430 | cd $pwd 431 | } 432 | 433 | run_all_tests() { 434 | run_test1 435 | run_test1_tls 436 | run_test2 437 | run_test2_tls 438 | run_test3 439 | run_test4 440 | run_test5 441 | run_test6 442 | } 443 | 444 | case "$1" in 445 | test1) run_test1 446 | ;; 447 | test1_tls) run_test1_tls 448 | ;; 449 | test2) run_test2 450 | ;; 451 | test2_tls) run_test2_tls 452 | ;; 453 | test3) run_test3 454 | ;; 455 | test4) run_test4 456 | ;; 457 | test5) run_test5 458 | ;; 459 | test6) run_test6 460 | ;; 461 | install) run_install 462 | ;; 463 | *) run_all_tests 464 | ;; 465 | esac 466 | exit 0 467 | -------------------------------------------------------------------------------- /benchmark/evio-echo-server/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Joshua J Baker. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "log" 11 | "runtime" 12 | "strings" 13 | 14 | "github.com/tidwall/evio" 15 | ) 16 | 17 | func main() { 18 | var listenAddr string 19 | var loops int 20 | var udp bool 21 | var trace bool 22 | var reuseport bool 23 | var stdlib bool 24 | 25 | flag.StringVar(&listenAddr, "listen", "127.0.0.1:8000", "server listen addr") 26 | flag.BoolVar(&udp, "udp", false, "listen on udp") 27 | flag.BoolVar(&reuseport, "reuseport", false, "reuseport (SO_REUSEPORT)") 28 | flag.BoolVar(&trace, "trace", false, "print packets to console") 29 | flag.IntVar(&loops, "loops", 0, "num loops") 30 | flag.BoolVar(&stdlib, "stdlib", false, "use stdlib") 31 | flag.Parse() 32 | 33 | var events evio.Events 34 | events.NumLoops = loops 35 | events.Serving = func(srv evio.Server) (action evio.Action) { 36 | log.Printf("echo server started on %s with GOMAXPROCS=%d (loops: %d)", listenAddr, runtime.GOMAXPROCS(0), srv.NumLoops) 37 | if reuseport { 38 | log.Printf("reuseport") 39 | } 40 | if stdlib { 41 | log.Printf("stdlib") 42 | } 43 | return 44 | } 45 | events.Data = func(c evio.Conn, in []byte) (out []byte, action evio.Action) { 46 | if trace { 47 | log.Printf("%s", strings.TrimSpace(string(in))) 48 | } 49 | out = in 50 | return 51 | } 52 | scheme := "tcp" 53 | if udp { 54 | scheme = "udp" 55 | } 56 | if stdlib { 57 | scheme += "-net" 58 | } 59 | log.Fatal(evio.Serve(events, fmt.Sprintf("%s://%s?reuseport=%t", scheme, listenAddr, reuseport))) 60 | } 61 | -------------------------------------------------------------------------------- /benchmark/evio-http-server/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Joshua J Baker. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "bytes" 9 | "crypto/aes" 10 | "crypto/cipher" 11 | "crypto/rand" 12 | "crypto/sha256" 13 | "encoding/hex" 14 | "flag" 15 | "fmt" 16 | "io" 17 | "log" 18 | "runtime" 19 | "strconv" 20 | "strings" 21 | "time" 22 | "unsafe" 23 | 24 | "github.com/tidwall/evio" 25 | ) 26 | 27 | var res string 28 | var resbytes []byte 29 | 30 | type request struct { 31 | proto, method string 32 | path, query string 33 | head, body string 34 | remoteAddr string 35 | } 36 | 37 | var listenAddr string 38 | var keepAlive bool 39 | var sleep int 40 | var aes128 bool 41 | var sha bool 42 | 43 | var status200Ok = []byte("200 OK") 44 | var status500Error = []byte("500 Error") 45 | 46 | var aesKey = []byte("0123456789ABCDEF") 47 | 48 | func main() { 49 | /*go func() { 50 | defer os.Exit(0) 51 | cpuProfile, err := os.Create("evio-cpu.prof") 52 | if err != nil { 53 | log.Fatal(err) 54 | } 55 | defer pprof.StopCPUProfile() 56 | pprof.StartCPUProfile(cpuProfile) 57 | 58 | time.Sleep(time.Second * 10) 59 | fmt.Println("Writing cpu & mem profile...") 60 | 61 | // Memory Profile 62 | memProfile, err := os.Create("evio-mem.prof") 63 | if err != nil { 64 | log.Fatal(err) 65 | } 66 | defer memProfile.Close() 67 | runtime.GC() 68 | if err := pprof.WriteHeapProfile(memProfile); err != nil { 69 | log.Fatal(err) 70 | } 71 | }()/**/ 72 | 73 | var loops int 74 | var aaaa int 75 | var unixsocket string 76 | var stdlib bool 77 | 78 | flag.StringVar(&unixsocket, "unixsocket", "", "unix socket") 79 | flag.StringVar(&listenAddr, "listen", "127.0.0.1:8000", "server listen addr") 80 | flag.IntVar(&aaaa, "aaaa", 0, "aaaaa.... (default output is 'Hello World')") 81 | flag.BoolVar(&stdlib, "stdlib", false, "use stdlib") 82 | flag.IntVar(&loops, "loops", 0, "num loops") 83 | flag.BoolVar(&keepAlive, "keepalive", true, "use HTTP Keep-Alive") 84 | flag.BoolVar(&aes128, "aes128", false, "encrypt response with aes-128-cbc") 85 | flag.BoolVar(&sha, "sha", false, "output sha256 instead of plain response") 86 | flag.IntVar(&sleep, "sleep", 0, "sleep number of milliseconds per request") 87 | flag.Parse() 88 | 89 | if aaaa > 0 { 90 | res = strings.Repeat("a", aaaa) 91 | } else { 92 | res = "Hello World!\r\n" 93 | } 94 | 95 | resbytes = []byte(res) 96 | 97 | var events evio.Events 98 | events.NumLoops = loops 99 | events.Serving = func(srv evio.Server) (action evio.Action) { 100 | log.Printf("http server using evio started on %s with GOMAXPROCS=%d (loops: %d)", listenAddr, runtime.GOMAXPROCS(0), srv.NumLoops) 101 | if unixsocket != "" { 102 | log.Printf("http server started at %s", unixsocket) 103 | } 104 | if stdlib { 105 | log.Printf("stdlib") 106 | } 107 | return 108 | } 109 | 110 | events.Opened = func(c evio.Conn) (out []byte, opts evio.Options, action evio.Action) { 111 | c.SetContext(&evio.InputStream{}) 112 | //log.Printf("opened: laddr: %v: raddr: %v", c.LocalAddr(), c.RemoteAddr()) 113 | return 114 | } 115 | 116 | events.Closed = func(c evio.Conn, err error) (action evio.Action) { 117 | //log.Printf("closed: %s: %s", c.LocalAddr().String(), c.RemoteAddr().String()) 118 | return 119 | } 120 | 121 | events.Data = func(c evio.Conn, in []byte) (out []byte, action evio.Action) { 122 | if in == nil { 123 | return 124 | } 125 | is := c.Context().(*evio.InputStream) 126 | data := is.Begin(in) 127 | // process the pipeline 128 | var req request 129 | leftover, err := parsereq(data, &req) 130 | if err != nil { 131 | // bad thing happened 132 | out = appendresp(out, status500Error, nil, []byte(err.Error()+"\n")) 133 | action = evio.Close 134 | return 135 | } else if len(leftover) == len(data) { 136 | // request not ready, yet 137 | return 138 | } 139 | // handle the request 140 | if aes128 { 141 | cryptedResbytes, _ := encryptCBC(resbytes, aesKey) 142 | out = appendresp(out, status200Ok, nil, cryptedResbytes) 143 | } else if sha { 144 | sha256sum := sha256.Sum256(resbytes) 145 | out = appendresp(out, status200Ok, nil, []byte(hex.EncodeToString(sha256sum[:]))) 146 | } else { 147 | out = appendresp(out, status200Ok, nil, resbytes) 148 | } 149 | 150 | data = leftover 151 | 152 | if sleep > 0 { 153 | time.Sleep(time.Millisecond * time.Duration(sleep)) 154 | } 155 | 156 | if !keepAlive { 157 | action = evio.Close 158 | } 159 | is.End(data) 160 | return 161 | } 162 | var ssuf string 163 | if stdlib { 164 | ssuf = "-net" 165 | } 166 | // We at least want the single http address. 167 | addrs := []string{fmt.Sprintf("tcp"+ssuf+"://%s", listenAddr)} 168 | if unixsocket != "" { 169 | addrs = append(addrs, fmt.Sprintf("unix"+ssuf+"://%s", unixsocket)) 170 | } 171 | // Start serving! 172 | log.Fatal(evio.Serve(events, addrs...)) 173 | } 174 | 175 | var headerHTTP11 = []byte("HTTP/1.1") 176 | var headerDate = []byte("Date: ") 177 | var headerConnectionClose = []byte("Connection: close") 178 | var headerServerIdentity = []byte("Server: evio") 179 | var headerContentLength = []byte("Content-Length: ") 180 | var headerContentType = []byte("Content-Type: ") 181 | var headerContentTypeTextPlain = []byte("text/plain") 182 | var newLine = []byte("\r\n") 183 | 184 | // appendresp will append a valid http response to the provide bytes. 185 | // The status param should be the code plus text such as "200 OK". 186 | // The head parameter should be a series of lines ending with "\r\n" or empty. 187 | func appendresp(b []byte, status, head, body []byte) []byte { 188 | b = append(b, headerHTTP11...) 189 | b = append(b, ' ') 190 | b = append(b, status...) 191 | b = append(b, newLine...) 192 | b = append(b, headerServerIdentity...) 193 | b = append(b, newLine...) 194 | if !keepAlive { 195 | b = append(b, headerConnectionClose...) 196 | b = append(b, newLine...) 197 | } 198 | b = append(b, headerDate...) 199 | b = time.Now().AppendFormat(b, "Mon, 02 Jan 2006 15:04:05 GMT") 200 | b = append(b, newLine...) 201 | if len(body) > 0 { 202 | b = append(b, headerContentType...) 203 | b = append(b, headerContentTypeTextPlain...) 204 | b = append(b, newLine...) 205 | b = append(b, headerContentLength...) 206 | b = strconv.AppendInt(b, int64(len(body)), 10) 207 | b = append(b, newLine...) 208 | } 209 | b = append(b, head...) 210 | b = append(b, newLine...) 211 | if len(body) > 0 { 212 | b = append(b, body...) 213 | } 214 | return b 215 | } 216 | 217 | // parsereq is a very simple http request parser. This operation 218 | // waits for the entire payload to be buffered before returning a 219 | // valid request. 220 | func parsereq(data []byte, req *request) (leftover []byte, err error) { 221 | sdata := data 222 | var i, s int 223 | var top string 224 | var clen int 225 | var q = -1 226 | // method, path, proto line 227 | for ; i < len(sdata); i++ { 228 | if sdata[i] == ' ' { 229 | req.method = b2s(sdata[s:i]) 230 | for i, s = i+1, i+1; i < len(sdata); i++ { 231 | if sdata[i] == '?' && q == -1 { 232 | q = i - s 233 | } else if sdata[i] == ' ' { 234 | if q != -1 { 235 | req.path = b2s(sdata[s:q]) 236 | req.query = req.path[q+1 : i] 237 | } else { 238 | req.path = b2s(sdata[s:i]) 239 | } 240 | for i, s = i+1, i+1; i < len(sdata); i++ { 241 | if sdata[i] == '\n' && sdata[i-1] == '\r' { 242 | req.proto = b2s(sdata[s:i]) 243 | i, s = i+1, i+1 244 | break 245 | } 246 | } 247 | break 248 | } 249 | } 250 | break 251 | } 252 | } 253 | if req.proto == "" { 254 | return data, fmt.Errorf("malformed request") 255 | } 256 | top = b2s(sdata[:s]) 257 | for ; i < len(sdata); i++ { 258 | if i > 1 && sdata[i] == '\n' && sdata[i-1] == '\r' { 259 | line := b2s(sdata[s : i-1]) 260 | s = i + 1 261 | if line == "" { 262 | req.head = b2s(sdata[len(top)+2 : i+1]) 263 | i++ 264 | if clen > 0 { 265 | if len(sdata[i:]) < clen { 266 | break 267 | } 268 | req.body = b2s(sdata[i : i+clen]) 269 | i += clen 270 | } 271 | return data[i:], nil 272 | } 273 | if strings.HasPrefix(line, "Content-Length:") { 274 | n, err := strconv.ParseInt(strings.TrimSpace(line[len("Content-Length:"):]), 10, 64) 275 | if err == nil { 276 | clen = int(n) 277 | } 278 | } 279 | } 280 | } 281 | // not enough data 282 | return data, nil 283 | } 284 | 285 | // b2s converts byte slice to a string without memory allocation. 286 | // See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ . 287 | // 288 | // Note it may break if string and/or slice header will change 289 | // in the future go versions. 290 | func b2s(b []byte) string { 291 | return *(*string)(unsafe.Pointer(&b)) 292 | } 293 | 294 | // Encrypts given cipher text (prepended with the IV) with AES-128 or AES-256 295 | // (depending on the length of the key) 296 | func encryptCBC(plainText, key []byte) (cipherText []byte, err error) { 297 | block, err := aes.NewCipher(key) 298 | if err != nil { 299 | return nil, err 300 | } 301 | 302 | plainText = pad(aes.BlockSize, plainText) 303 | 304 | cipherText = make([]byte, aes.BlockSize+len(plainText)) 305 | iv := cipherText[:aes.BlockSize] 306 | _, err = io.ReadFull(rand.Reader, iv) 307 | if err != nil { 308 | return nil, err 309 | } 310 | 311 | mode := cipher.NewCBCEncrypter(block, iv) 312 | mode.CryptBlocks(cipherText[aes.BlockSize:], plainText) 313 | 314 | return cipherText, nil 315 | } 316 | 317 | // Adds PKCS#7 padding (variable block length <= 255 bytes) 318 | func pad(blockSize int, buf []byte) []byte { 319 | padLen := blockSize - (len(buf) % blockSize) 320 | padding := bytes.Repeat([]byte{byte(padLen)}, padLen) 321 | return append(buf, padding...) 322 | } 323 | -------------------------------------------------------------------------------- /benchmark/fasthttp-http-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/rand" 8 | "crypto/sha256" 9 | "encoding/hex" 10 | "flag" 11 | "io" 12 | "log" 13 | "runtime" 14 | "strings" 15 | "time" 16 | 17 | "github.com/valyala/fasthttp" 18 | ) 19 | 20 | func main() { 21 | /* go func() { 22 | sigIntChan := make(chan os.Signal) 23 | signal.Notify(sigIntChan, os.Interrupt) 24 | 25 | traceFile, err := os.Create("fasthttp.trace") 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | trace.Start(traceFile) 31 | <-sigIntChan 32 | 33 | trace.Stop() 34 | traceFile.Close() 35 | fmt.Println("Closed trace file") 36 | os.Exit(1) 37 | }() 38 | /* go func() { 39 | defer os.Exit(0) 40 | cpuProfile, err := os.Create("fasthttp-cpu.prof") 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | defer pprof.StopCPUProfile() 45 | pprof.StartCPUProfile(cpuProfile) 46 | 47 | time.Sleep(time.Second * 10) 48 | fmt.Println("Writing cpu & mem profile...") 49 | 50 | // Memory Profile 51 | memProfile, err := os.Create("fasthttp-mem.prof") 52 | if err != nil { 53 | log.Fatal(err) 54 | } 55 | defer memProfile.Close() 56 | runtime.GC() 57 | if err := pprof.WriteHeapProfile(memProfile); err != nil { 58 | log.Fatal(err) 59 | } 60 | }()*/ 61 | var res string 62 | var listenAddr string 63 | var aaaa int 64 | var keepAlive bool 65 | var aes128 bool 66 | var sha bool 67 | var sleep int 68 | var useTls bool 69 | 70 | var aesKey = []byte("0123456789ABCDEF") 71 | 72 | flag.StringVar(&listenAddr, "listen", "127.0.0.1:8000", "server listen addr") 73 | flag.IntVar(&aaaa, "aaaa", 0, "aaaaa.... (default output is 'Hello World')") 74 | flag.BoolVar(&keepAlive, "keepalive", true, "use HTTP Keep-Alive") 75 | flag.BoolVar(&aes128, "aes128", false, "encrypt response with aes-128-cbc") 76 | flag.BoolVar(&sha, "sha", false, "output sha256 instead of plain response") 77 | flag.IntVar(&sleep, "sleep", 0, "sleep number of milliseconds per request") 78 | flag.BoolVar(&useTls, "useTls", false, "use HTTPS") 79 | flag.Parse() 80 | 81 | if aaaa > 0 { 82 | res = strings.Repeat("a", aaaa) 83 | } else { 84 | res = "Hello World!\r\n" 85 | } 86 | 87 | resbytes := []byte(res) 88 | 89 | log.Printf("http server using valyala/fasthttp starting on %s with GOMAXPROCS=%d", listenAddr, runtime.GOMAXPROCS(0)) 90 | s := &fasthttp.Server{ 91 | Handler: func(c *fasthttp.RequestCtx) { 92 | 93 | if aes128 { 94 | cryptedResbytes, _ := encryptCBC(resbytes, aesKey) 95 | c.Write(cryptedResbytes) 96 | } else if sha { 97 | sha256sum := sha256.Sum256(resbytes) 98 | c.WriteString(hex.EncodeToString(sha256sum[:])) 99 | } else { 100 | c.Write(resbytes) 101 | } 102 | 103 | if sleep > 0 { 104 | time.Sleep(time.Millisecond * time.Duration(sleep)) 105 | } 106 | }, 107 | DisableKeepalive: !keepAlive, 108 | } 109 | if useTls { 110 | s.ListenAndServeTLSEmbed(listenAddr, []byte(certPem), []byte(keyPem)) 111 | } else { 112 | s.ListenAndServe(listenAddr) 113 | } 114 | } 115 | 116 | // Encrypts given cipher text (prepended with the IV) with AES-128 or AES-256 117 | // (depending on the length of the key) 118 | func encryptCBC(plainText, key []byte) (cipherText []byte, err error) { 119 | block, err := aes.NewCipher(key) 120 | if err != nil { 121 | return nil, err 122 | } 123 | 124 | plainText = pad(aes.BlockSize, plainText) 125 | 126 | cipherText = make([]byte, aes.BlockSize+len(plainText)) 127 | iv := cipherText[:aes.BlockSize] 128 | _, err = io.ReadFull(rand.Reader, iv) 129 | if err != nil { 130 | return nil, err 131 | } 132 | 133 | mode := cipher.NewCBCEncrypter(block, iv) 134 | mode.CryptBlocks(cipherText[aes.BlockSize:], plainText) 135 | 136 | return cipherText, nil 137 | } 138 | 139 | // Adds PKCS#7 padding (variable block length <= 255 bytes) 140 | func pad(blockSize int, buf []byte) []byte { 141 | padLen := blockSize - (len(buf) % blockSize) 142 | padding := bytes.Repeat([]byte{byte(padLen)}, padLen) 143 | return append(buf, padding...) 144 | } 145 | 146 | // Testing cert 147 | var certPem = ` 148 | -----BEGIN CERTIFICATE----- 149 | MIIFgzCCA2ugAwIBAgIUOAG3o6IsqyYwaSecWpft29luvD0wDQYJKoZIhvcNAQEL 150 | BQAwUTELMAkGA1UEBhMCREUxEzARBgNVBAgMClNvbWUtU3RhdGUxDzANBgNVBAcM 151 | Bk11bmljaDEcMBoGA1UECgwTbWF1cmljZTJrL3RjcHNlcnZlcjAeFw0yMTAyMDgw 152 | OTUyMTBaFw0zMTAyMDYwOTUyMTBaMFExCzAJBgNVBAYTAkRFMRMwEQYDVQQIDApT 153 | b21lLVN0YXRlMQ8wDQYDVQQHDAZNdW5pY2gxHDAaBgNVBAoME21hdXJpY2Uyay90 154 | Y3BzZXJ2ZXIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO043SaQoE 155 | QNSEALBnG/1qPLvwSwe+JDC11ebRBhaWvYLzycwzDK3IewM8Oa2ygqCLi1MhV8TX 156 | qfuu5R0+OFwyp4tBGyTmtcyg4k7HK7lrtq8jVlLzyVmg5k3g9RR4ab+aiAc7R54T 157 | DcR2kLm7Xl8Jn2XJhKlyneK2HMufxmUh5EF2S5jMsHh0b8yrbmfio1Dxi3QZGDrs 158 | QHULPZ47TbcC1B790Z8bVnfzOmFYJUF92H8l2utAb0q0ARHImPRJOjwW7TOYIWbi 159 | QYI4aE4Te2zq4V26qjEcP/IWFVxNFg7+1uSrb4RlyjTKoKvSGlYj/hDitQOheOIg 160 | XDqKyEs3yxfQOATsUE8/J26SGTnwauBRblrZBYi8jrHDm+FJcmc65/dsAZe42wCd 161 | oTs7k9gV0CvjXvvXRITr/YkRA7epYfEErVHl112wZ30p6T+YznPiBh8xNbijWlcH 162 | T/mER0TaGX8vzyTj/Dy1fY0oQhaP79LwAVbUgTtMBv7bwtrH4xX+kvBm5j5NLrUS 163 | diXmeFYB6H1ZUFzEnlIsICs5rb1fCvJlSbQxwq6fqNkZxyZU3e9JxMzQ8pgDmrKg 164 | KPmxDsm/7sX3tCKX7o9Fd6PH4rlEsWQxMM7/1mINgR0SkdRLZCogybvFELrWFLdb 165 | bmlZc52FqSIvMnj8fTfG6rxNVJ8A6pLd3QIDAQABo1MwUTAdBgNVHQ4EFgQUo1A/ 166 | GiyZkQEnTbtyvVJl9D1qFHowHwYDVR0jBBgwFoAUo1A/GiyZkQEnTbtyvVJl9D1q 167 | FHowDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAjQGOL7fxT4bT 168 | eAjbIZLzbSX7euvJsQAQJHikI5ZY9ROnFlx1N26bGK28OaqbaW6bqkPcoRm8qWSV 169 | +xqYiEx0ebKnGRf5OppRjgg9DmOL3n9PiYpC/dJBkPg2V4F7iFGL4YQJnHsNRl4v 170 | Ke6YO2qCI430WwOLY/69imkOc+ob+G3GYt0Oim58z+SRFU4eUwiYxQqCZaNVAEV5 171 | IQg5QUWOgT5kSI0e3HK7QgutlMP3AhawMACXfPWM+iN3v7DJk8mDAbh0cCWRi8PG 172 | Q7Ms+hR8Vx+CiekPO/S2TgvWiBYvsF8QJ2Cyg6x7rXTwFBSMaDvBYQ5CIPYxQMng 173 | B6L4z1SC9o9pFomwU8W/BrloUEUeTe66YeL4v+Yy9brXVM7nK0U0qxhPpIH39oFS 174 | 5v/k9JZ071nZbpdr6P1E55xCEFbB18f6ljYQ55xNpjFMzuvYWy89bTjA+M7q47t5 175 | 2PXFal8i++Z8jqfhUvOxidf/EqQ93GFCzchS2Zf3ut9nqmQYz1zWqFY7GHO0UIuH 176 | DDXnt9CZduCL5Jpc8J6kITuO+2MWrgLd2OoCNZLOhD/yWPQcSjA6C+bDc0MN/TtT 177 | Y6UOlBoByevGWAejLP2XjNJELr1VkTgv/sYXoFazDggNIovSWNTmcoSJJ6Zmy4Zo 178 | V3Dl12p/TQ3/eu5v7x3D7zBcwluxrvI= 179 | -----END CERTIFICATE----- 180 | ` 181 | var keyPem = ` 182 | -----BEGIN PRIVATE KEY----- 183 | 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 232 | 3uj4+nXPk8tk7guNm0WV0n8KBKwR 233 | -----END PRIVATE KEY----- 234 | ` 235 | -------------------------------------------------------------------------------- /benchmark/gnet-http-server/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Andy Pan. All rights reserved. 2 | // Copyright 2017 Joshua J Baker. All rights reserved. 3 | // Use of this source code is governed by an MIT-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "bytes" 10 | "crypto/aes" 11 | "crypto/cipher" 12 | "crypto/rand" 13 | "crypto/sha256" 14 | "encoding/hex" 15 | "flag" 16 | "fmt" 17 | "io" 18 | "log" 19 | "runtime" 20 | "strconv" 21 | "strings" 22 | "time" 23 | "unsafe" 24 | 25 | "github.com/panjf2000/gnet" 26 | ) 27 | 28 | var res string 29 | var resbytes []byte 30 | 31 | type request struct { 32 | proto, method string 33 | path, query string 34 | head, body string 35 | remoteAddr string 36 | } 37 | 38 | var listenAddr string 39 | var keepAlive bool 40 | var sleep int 41 | var aes128 bool 42 | var sha bool 43 | 44 | var status200Ok = []byte("200 OK") 45 | var status500Error = []byte("500 Error") 46 | 47 | var aesKey = []byte("0123456789ABCDEF") 48 | 49 | type httpServer struct { 50 | *gnet.EventServer 51 | } 52 | 53 | type httpCodec struct { 54 | req request 55 | } 56 | 57 | var errMsg = "Internal Server Error" 58 | var errMsgBytes = []byte(errMsg) 59 | 60 | func (hc *httpCodec) Encode(c gnet.Conn, buf []byte) (out []byte, err error) { 61 | if c.Context() == nil { 62 | return appendresp(out, status200Ok, nil, buf), nil 63 | } 64 | return appendresp(out, status500Error, nil, []byte(errMsg+"\n")), nil 65 | } 66 | 67 | func (hc *httpCodec) Decode(c gnet.Conn) ([]byte, error) { 68 | buf := c.Read() 69 | // process the pipeline 70 | leftover, err := parsereq(buf, &hc.req) 71 | // bad thing happened 72 | if err != nil { 73 | c.SetContext(err) 74 | return nil, err 75 | } else if len(leftover) == len(buf) { 76 | // request not ready, yet 77 | return nil, nil 78 | } 79 | c.ResetBuffer() 80 | return buf, nil 81 | } 82 | 83 | func (hs *httpServer) OnInitComplete(srv gnet.Server) (action gnet.Action) { 84 | log.Printf("http server using gnet started on %s with GOMAXPROCS=%d, loops: %d; built with %s", listenAddr, runtime.GOMAXPROCS(0), srv.NumEventLoop, runtime.Version()) 85 | return 86 | } 87 | 88 | func (hs *httpServer) React(frame []byte, c gnet.Conn) (out []byte, action gnet.Action) { 89 | // process the pipeline 90 | if c.Context() != nil { 91 | // bad thing happened 92 | out = errMsgBytes 93 | action = gnet.Close 94 | return 95 | } 96 | 97 | if sleep > 0 { 98 | time.Sleep(time.Millisecond * time.Duration(sleep)) 99 | } 100 | 101 | // handle the request 102 | if aes128 { 103 | cryptedResbytes, _ := encryptCBC(resbytes, aesKey) 104 | out = cryptedResbytes 105 | } else if sha { 106 | sha256sum := sha256.Sum256(resbytes) 107 | out = []byte(hex.EncodeToString(sha256sum[:])) 108 | } else { 109 | out = resbytes 110 | } 111 | 112 | if !keepAlive { 113 | action = gnet.Close 114 | } 115 | return 116 | } 117 | 118 | func main() { 119 | /* go func() { 120 | sigIntChan := make(chan os.Signal) 121 | signal.Notify(sigIntChan, os.Interrupt) 122 | 123 | traceFile, err := os.Create("gnet.trace") 124 | if err != nil { 125 | panic(err) 126 | } 127 | 128 | trace.Start(traceFile) 129 | <-sigIntChan 130 | 131 | trace.Stop() 132 | traceFile.Close() 133 | fmt.Println("Closed trace file") 134 | os.Exit(1) 135 | }()/**/ 136 | 137 | var loops int 138 | var aaaa int 139 | var unixsocket string 140 | var stdlib bool 141 | 142 | flag.StringVar(&unixsocket, "unixsocket", "", "unix socket") 143 | flag.StringVar(&listenAddr, "listen", "127.0.0.1:8000", "server listen addr") 144 | flag.IntVar(&aaaa, "aaaa", 0, "aaaaa.... (default output is 'Hello World')") 145 | flag.BoolVar(&stdlib, "stdlib", false, "use stdlib") 146 | flag.IntVar(&loops, "loops", 0, "num loops") 147 | flag.BoolVar(&keepAlive, "keepalive", true, "use HTTP Keep-Alive") 148 | flag.BoolVar(&aes128, "aes128", false, "encrypt response with aes-128-cbc") 149 | flag.BoolVar(&sha, "sha", false, "output sha256 instead of plain response") 150 | flag.IntVar(&sleep, "sleep", 0, "sleep number of milliseconds per request") 151 | flag.Parse() 152 | 153 | if aaaa > 0 { 154 | res = strings.Repeat("a", aaaa) 155 | } else { 156 | res = "Hello World!\r\n" 157 | } 158 | 159 | resbytes = []byte(res) 160 | 161 | http := new(httpServer) 162 | hc := new(httpCodec) 163 | 164 | // Start serving! 165 | log.Fatal(gnet.Serve(http, "tcp://"+listenAddr, gnet.WithMulticore(true), gnet.WithCodec(hc), gnet.WithLockOSThread(true))) 166 | 167 | } 168 | 169 | var headerHTTP11 = []byte("HTTP/1.1") 170 | var headerDate = []byte("Date: ") 171 | var headerConnectionClose = []byte("Connection: close") 172 | var headerServerIdentity = []byte("Server: tsrv") 173 | var headerContentLength = []byte("Content-Length: ") 174 | var headerContentType = []byte("Content-Type: ") 175 | var headerContentTypeTextPlain = []byte("text/plain") 176 | var newLine = []byte("\r\n") 177 | 178 | // appendresp will append a valid http response to the provide bytes. 179 | // The status param should be the code plus text such as "200 OK". 180 | // The head parameter should be a series of lines ending with "\r\n" or empty. 181 | func appendresp(b []byte, status, head, body []byte) []byte { 182 | b = append(b, headerHTTP11...) 183 | b = append(b, ' ') 184 | b = append(b, status...) 185 | b = append(b, newLine...) 186 | b = append(b, headerServerIdentity...) 187 | b = append(b, newLine...) 188 | if !keepAlive { 189 | b = append(b, headerConnectionClose...) 190 | b = append(b, newLine...) 191 | } 192 | b = append(b, headerDate...) 193 | b = time.Now().AppendFormat(b, "Mon, 02 Jan 2006 15:04:05 GMT") 194 | b = append(b, newLine...) 195 | if len(body) > 0 { 196 | b = append(b, headerContentType...) 197 | b = append(b, headerContentTypeTextPlain...) 198 | b = append(b, newLine...) 199 | b = append(b, headerContentLength...) 200 | b = strconv.AppendInt(b, int64(len(body)), 10) 201 | b = append(b, newLine...) 202 | } 203 | b = append(b, head...) 204 | b = append(b, newLine...) 205 | if len(body) > 0 { 206 | b = append(b, body...) 207 | } 208 | return b 209 | } 210 | 211 | // parsereq is a very simple http request parser. This operation 212 | // waits for the entire payload to be buffered before returning a 213 | // valid request. 214 | func parsereq(data []byte, req *request) (leftover []byte, err error) { 215 | sdata := data 216 | var i, s int 217 | var top string 218 | var clen int 219 | var q = -1 220 | // method, path, proto line 221 | for ; i < len(sdata); i++ { 222 | if sdata[i] == ' ' { 223 | req.method = b2s(sdata[s:i]) 224 | for i, s = i+1, i+1; i < len(sdata); i++ { 225 | if sdata[i] == '?' && q == -1 { 226 | q = i - s 227 | } else if sdata[i] == ' ' { 228 | if q != -1 { 229 | req.path = b2s(sdata[s:q]) 230 | req.query = req.path[q+1 : i] 231 | } else { 232 | req.path = b2s(sdata[s:i]) 233 | } 234 | for i, s = i+1, i+1; i < len(sdata); i++ { 235 | if sdata[i] == '\n' && sdata[i-1] == '\r' { 236 | req.proto = b2s(sdata[s:i]) 237 | i, s = i+1, i+1 238 | break 239 | } 240 | } 241 | break 242 | } 243 | } 244 | break 245 | } 246 | } 247 | if req.proto == "" { 248 | return data, fmt.Errorf("malformed request") 249 | } 250 | top = b2s(sdata[:s]) 251 | for ; i < len(sdata); i++ { 252 | if i > 1 && sdata[i] == '\n' && sdata[i-1] == '\r' { 253 | line := b2s(sdata[s : i-1]) 254 | s = i + 1 255 | if line == "" { 256 | req.head = b2s(sdata[len(top)+2 : i+1]) 257 | i++ 258 | if clen > 0 { 259 | if len(sdata[i:]) < clen { 260 | break 261 | } 262 | req.body = b2s(sdata[i : i+clen]) 263 | i += clen 264 | } 265 | return data[i:], nil 266 | } 267 | if strings.HasPrefix(line, "Content-Length:") { 268 | n, err := strconv.ParseInt(strings.TrimSpace(line[len("Content-Length:"):]), 10, 64) 269 | if err == nil { 270 | clen = int(n) 271 | } 272 | } 273 | } 274 | } 275 | // not enough data 276 | return data, nil 277 | } 278 | 279 | // b2s converts byte slice to a string without memory allocation. 280 | // See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ . 281 | // 282 | // Note it may break if string and/or slice header will change 283 | // in the future go versions. 284 | func b2s(b []byte) string { 285 | return *(*string)(unsafe.Pointer(&b)) 286 | } 287 | 288 | // Encrypts given cipher text (prepended with the IV) with AES-128 or AES-256 289 | // (depending on the length of the key) 290 | func encryptCBC(plainText, key []byte) (cipherText []byte, err error) { 291 | block, err := aes.NewCipher(key) 292 | if err != nil { 293 | return nil, err 294 | } 295 | 296 | plainText = pad(aes.BlockSize, plainText) 297 | 298 | cipherText = make([]byte, aes.BlockSize+len(plainText)) 299 | iv := cipherText[:aes.BlockSize] 300 | _, err = io.ReadFull(rand.Reader, iv) 301 | if err != nil { 302 | return nil, err 303 | } 304 | 305 | mode := cipher.NewCBCEncrypter(block, iv) 306 | mode.CryptBlocks(cipherText[aes.BlockSize:], plainText) 307 | 308 | return cipherText, nil 309 | } 310 | 311 | // Adds PKCS#7 padding (variable block length <= 255 bytes) 312 | func pad(blockSize int, buf []byte) []byte { 313 | padLen := blockSize - (len(buf) % blockSize) 314 | padding := bytes.Repeat([]byte{byte(padLen)}, padLen) 315 | return append(buf, padding...) 316 | } 317 | -------------------------------------------------------------------------------- /benchmark/net-http-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/rand" 8 | "crypto/sha256" 9 | "crypto/tls" 10 | "encoding/hex" 11 | "flag" 12 | "io" 13 | "log" 14 | "net/http" 15 | "runtime" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | func main() { 21 | var res string 22 | var listenAddr string 23 | var aaaa int 24 | var keepAlive bool 25 | var aes128 bool 26 | var sha bool 27 | var sleep int 28 | var useTls bool 29 | 30 | var aesKey = []byte("0123456789ABCDEF") 31 | 32 | flag.StringVar(&listenAddr, "listen", "127.0.0.1:8000", "server listen addr") 33 | flag.IntVar(&aaaa, "aaaa", 0, "aaaaa.... (default output is 'Hello World')") 34 | flag.BoolVar(&keepAlive, "keepalive", true, "use HTTP Keep-Alive") 35 | flag.BoolVar(&aes128, "aes128", false, "encrypt response with aes-128-cbc") 36 | flag.BoolVar(&sha, "sha", false, "output sha256 instead of plain response") 37 | flag.IntVar(&sleep, "sleep", 0, "sleep number of milliseconds per request") 38 | flag.BoolVar(&useTls, "useTls", false, "use HTTPS") 39 | flag.Parse() 40 | 41 | if aaaa > 0 { 42 | res = strings.Repeat("a", aaaa) 43 | } else { 44 | res = "Hello World!\r\n" 45 | } 46 | 47 | resbytes := []byte(res) 48 | 49 | log.Printf("http server using plain golang net/* starting on %s with GOMAXPROCS=%d", listenAddr, runtime.GOMAXPROCS(0)) 50 | s := &http.Server{ 51 | Addr: listenAddr, 52 | Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 53 | // handle the request 54 | if aes128 { 55 | cryptedResbytes, _ := encryptCBC(resbytes, aesKey) 56 | w.Write(cryptedResbytes) 57 | } else if sha { 58 | sha256sum := sha256.Sum256(resbytes) 59 | w.Write([]byte(hex.EncodeToString(sha256sum[:]))) 60 | } else { 61 | w.Write(resbytes) 62 | } 63 | 64 | if sleep > 0 { 65 | time.Sleep(time.Millisecond * time.Duration(sleep)) 66 | } 67 | }), 68 | } 69 | 70 | s.SetKeepAlivesEnabled(keepAlive) 71 | 72 | var err error 73 | if useTls { 74 | s.TLSConfig = &tls.Config{Certificates: []tls.Certificate{getCert()}} 75 | err = s.ListenAndServeTLS("", "") 76 | } else { 77 | err = s.ListenAndServe() 78 | } 79 | if err != nil { 80 | log.Fatal(err) 81 | } 82 | } 83 | 84 | // Encrypts given cipher text (prepended with the IV) with AES-128 or AES-256 85 | // (depending on the length of the key) 86 | func encryptCBC(plainText, key []byte) (cipherText []byte, err error) { 87 | block, err := aes.NewCipher(key) 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | plainText = pad(aes.BlockSize, plainText) 93 | 94 | cipherText = make([]byte, aes.BlockSize+len(plainText)) 95 | iv := cipherText[:aes.BlockSize] 96 | _, err = io.ReadFull(rand.Reader, iv) 97 | if err != nil { 98 | return nil, err 99 | } 100 | 101 | mode := cipher.NewCBCEncrypter(block, iv) 102 | mode.CryptBlocks(cipherText[aes.BlockSize:], plainText) 103 | 104 | return cipherText, nil 105 | } 106 | 107 | // Adds PKCS#7 padding (variable block length <= 255 bytes) 108 | func pad(blockSize int, buf []byte) []byte { 109 | padLen := blockSize - (len(buf) % blockSize) 110 | padding := bytes.Repeat([]byte{byte(padLen)}, padLen) 111 | return append(buf, padding...) 112 | } 113 | 114 | 115 | // Returns testing cert 116 | func getCert() (cert tls.Certificate) { 117 | certPem := ` 118 | -----BEGIN CERTIFICATE----- 119 | MIIFgzCCA2ugAwIBAgIUOAG3o6IsqyYwaSecWpft29luvD0wDQYJKoZIhvcNAQEL 120 | BQAwUTELMAkGA1UEBhMCREUxEzARBgNVBAgMClNvbWUtU3RhdGUxDzANBgNVBAcM 121 | Bk11bmljaDEcMBoGA1UECgwTbWF1cmljZTJrL3RjcHNlcnZlcjAeFw0yMTAyMDgw 122 | OTUyMTBaFw0zMTAyMDYwOTUyMTBaMFExCzAJBgNVBAYTAkRFMRMwEQYDVQQIDApT 123 | b21lLVN0YXRlMQ8wDQYDVQQHDAZNdW5pY2gxHDAaBgNVBAoME21hdXJpY2Uyay90 124 | Y3BzZXJ2ZXIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO043SaQoE 125 | QNSEALBnG/1qPLvwSwe+JDC11ebRBhaWvYLzycwzDK3IewM8Oa2ygqCLi1MhV8TX 126 | qfuu5R0+OFwyp4tBGyTmtcyg4k7HK7lrtq8jVlLzyVmg5k3g9RR4ab+aiAc7R54T 127 | DcR2kLm7Xl8Jn2XJhKlyneK2HMufxmUh5EF2S5jMsHh0b8yrbmfio1Dxi3QZGDrs 128 | QHULPZ47TbcC1B790Z8bVnfzOmFYJUF92H8l2utAb0q0ARHImPRJOjwW7TOYIWbi 129 | QYI4aE4Te2zq4V26qjEcP/IWFVxNFg7+1uSrb4RlyjTKoKvSGlYj/hDitQOheOIg 130 | XDqKyEs3yxfQOATsUE8/J26SGTnwauBRblrZBYi8jrHDm+FJcmc65/dsAZe42wCd 131 | oTs7k9gV0CvjXvvXRITr/YkRA7epYfEErVHl112wZ30p6T+YznPiBh8xNbijWlcH 132 | T/mER0TaGX8vzyTj/Dy1fY0oQhaP79LwAVbUgTtMBv7bwtrH4xX+kvBm5j5NLrUS 133 | diXmeFYB6H1ZUFzEnlIsICs5rb1fCvJlSbQxwq6fqNkZxyZU3e9JxMzQ8pgDmrKg 134 | KPmxDsm/7sX3tCKX7o9Fd6PH4rlEsWQxMM7/1mINgR0SkdRLZCogybvFELrWFLdb 135 | bmlZc52FqSIvMnj8fTfG6rxNVJ8A6pLd3QIDAQABo1MwUTAdBgNVHQ4EFgQUo1A/ 136 | GiyZkQEnTbtyvVJl9D1qFHowHwYDVR0jBBgwFoAUo1A/GiyZkQEnTbtyvVJl9D1q 137 | FHowDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAjQGOL7fxT4bT 138 | eAjbIZLzbSX7euvJsQAQJHikI5ZY9ROnFlx1N26bGK28OaqbaW6bqkPcoRm8qWSV 139 | +xqYiEx0ebKnGRf5OppRjgg9DmOL3n9PiYpC/dJBkPg2V4F7iFGL4YQJnHsNRl4v 140 | Ke6YO2qCI430WwOLY/69imkOc+ob+G3GYt0Oim58z+SRFU4eUwiYxQqCZaNVAEV5 141 | IQg5QUWOgT5kSI0e3HK7QgutlMP3AhawMACXfPWM+iN3v7DJk8mDAbh0cCWRi8PG 142 | Q7Ms+hR8Vx+CiekPO/S2TgvWiBYvsF8QJ2Cyg6x7rXTwFBSMaDvBYQ5CIPYxQMng 143 | B6L4z1SC9o9pFomwU8W/BrloUEUeTe66YeL4v+Yy9brXVM7nK0U0qxhPpIH39oFS 144 | 5v/k9JZ071nZbpdr6P1E55xCEFbB18f6ljYQ55xNpjFMzuvYWy89bTjA+M7q47t5 145 | 2PXFal8i++Z8jqfhUvOxidf/EqQ93GFCzchS2Zf3ut9nqmQYz1zWqFY7GHO0UIuH 146 | DDXnt9CZduCL5Jpc8J6kITuO+2MWrgLd2OoCNZLOhD/yWPQcSjA6C+bDc0MN/TtT 147 | Y6UOlBoByevGWAejLP2XjNJELr1VkTgv/sYXoFazDggNIovSWNTmcoSJJ6Zmy4Zo 148 | V3Dl12p/TQ3/eu5v7x3D7zBcwluxrvI= 149 | -----END CERTIFICATE----- 150 | ` 151 | keyPem := ` 152 | -----BEGIN PRIVATE KEY----- 153 | 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 202 | 3uj4+nXPk8tk7guNm0WV0n8KBKwR 203 | -----END PRIVATE KEY----- 204 | ` 205 | cert, _ = tls.X509KeyPair([]byte(certPem), []byte(keyPem)) 206 | return 207 | } 208 | -------------------------------------------------------------------------------- /examples/echo-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "flag" 6 | "fmt" 7 | "io" 8 | 9 | "github.com/maurice2k/tcpserver" 10 | ) 11 | 12 | var listenAddr string 13 | var port int 14 | var zeroCopy bool 15 | var loops int 16 | var wpShards int 17 | var useTls bool 18 | 19 | func main() { 20 | tfMap := make(map[bool]string) 21 | tfMap[true] = "on" 22 | tfMap[false] = "off" 23 | 24 | flag.StringVar(&listenAddr, "listen", "127.0.0.1:5000", "server listen addr") 25 | flag.BoolVar(&zeroCopy, "zerocopy", true, "use splice/sendfile zero copy") 26 | flag.IntVar(&loops, "loops", -1, "number of accept loops (defaults to 4 which is more than enough for most use cases)") 27 | flag.IntVar(&wpShards, "wpshards", -1, "number of workerpool shards") 28 | flag.BoolVar(&useTls, "useTls", false, "use HTTPS") 29 | flag.Parse() 30 | 31 | fmt.Printf("Running echo server on %s\n", listenAddr) 32 | if useTls { 33 | fmt.Printf(" - using TLS\n") 34 | } 35 | if zeroCopy { 36 | if !useTls { 37 | fmt.Printf(" - using zerocopy\n") 38 | } else { 39 | zeroCopy = false 40 | fmt.Printf(" - NOT POSSIBLE to use zerocopy with TLS\n") 41 | } 42 | } 43 | 44 | server, _ := tcpserver.NewServer(listenAddr) 45 | server.SetListenConfig(&tcpserver.ListenConfig{ 46 | SocketReusePort: true, 47 | SocketFastOpen: false, 48 | SocketDeferAccept: false, 49 | }) 50 | server.SetRequestHandler(requestHandler) 51 | server.SetLoops(loops) 52 | server.SetWorkerpoolShards(2) 53 | server.SetAllowThreadLocking(true) 54 | 55 | var err error 56 | if useTls { 57 | server.SetTLSConfig(&tls.Config{Certificates: []tls.Certificate{getCert()}}) 58 | err = server.ListenTLS() 59 | } else { 60 | err = server.Listen() 61 | } 62 | 63 | if err != nil { 64 | panic("Error listening on interface: " + err.Error()) 65 | } 66 | 67 | err = server.Serve() 68 | if err != nil { 69 | panic("Error serving: " + err.Error()) 70 | } 71 | } 72 | 73 | func requestHandler(conn tcpserver.Connection) { 74 | if zeroCopy { 75 | // io.Copy will use splice/sendfile (zerocopy) only if src/dst are of type *net.TCPConn 76 | _, _ = io.Copy(conn.GetNetConn(), conn.GetNetConn()) 77 | } else { 78 | _, _ = io.Copy(conn, conn) 79 | } 80 | } 81 | 82 | // Returns testing cert 83 | func getCert() (cert tls.Certificate) { 84 | certPem := ` 85 | -----BEGIN CERTIFICATE----- 86 | MIIFgzCCA2ugAwIBAgIUOAG3o6IsqyYwaSecWpft29luvD0wDQYJKoZIhvcNAQEL 87 | BQAwUTELMAkGA1UEBhMCREUxEzARBgNVBAgMClNvbWUtU3RhdGUxDzANBgNVBAcM 88 | Bk11bmljaDEcMBoGA1UECgwTbWF1cmljZTJrL3RjcHNlcnZlcjAeFw0yMTAyMDgw 89 | OTUyMTBaFw0zMTAyMDYwOTUyMTBaMFExCzAJBgNVBAYTAkRFMRMwEQYDVQQIDApT 90 | b21lLVN0YXRlMQ8wDQYDVQQHDAZNdW5pY2gxHDAaBgNVBAoME21hdXJpY2Uyay90 91 | Y3BzZXJ2ZXIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO043SaQoE 92 | QNSEALBnG/1qPLvwSwe+JDC11ebRBhaWvYLzycwzDK3IewM8Oa2ygqCLi1MhV8TX 93 | qfuu5R0+OFwyp4tBGyTmtcyg4k7HK7lrtq8jVlLzyVmg5k3g9RR4ab+aiAc7R54T 94 | DcR2kLm7Xl8Jn2XJhKlyneK2HMufxmUh5EF2S5jMsHh0b8yrbmfio1Dxi3QZGDrs 95 | QHULPZ47TbcC1B790Z8bVnfzOmFYJUF92H8l2utAb0q0ARHImPRJOjwW7TOYIWbi 96 | QYI4aE4Te2zq4V26qjEcP/IWFVxNFg7+1uSrb4RlyjTKoKvSGlYj/hDitQOheOIg 97 | XDqKyEs3yxfQOATsUE8/J26SGTnwauBRblrZBYi8jrHDm+FJcmc65/dsAZe42wCd 98 | oTs7k9gV0CvjXvvXRITr/YkRA7epYfEErVHl112wZ30p6T+YznPiBh8xNbijWlcH 99 | T/mER0TaGX8vzyTj/Dy1fY0oQhaP79LwAVbUgTtMBv7bwtrH4xX+kvBm5j5NLrUS 100 | diXmeFYB6H1ZUFzEnlIsICs5rb1fCvJlSbQxwq6fqNkZxyZU3e9JxMzQ8pgDmrKg 101 | KPmxDsm/7sX3tCKX7o9Fd6PH4rlEsWQxMM7/1mINgR0SkdRLZCogybvFELrWFLdb 102 | bmlZc52FqSIvMnj8fTfG6rxNVJ8A6pLd3QIDAQABo1MwUTAdBgNVHQ4EFgQUo1A/ 103 | GiyZkQEnTbtyvVJl9D1qFHowHwYDVR0jBBgwFoAUo1A/GiyZkQEnTbtyvVJl9D1q 104 | FHowDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAjQGOL7fxT4bT 105 | eAjbIZLzbSX7euvJsQAQJHikI5ZY9ROnFlx1N26bGK28OaqbaW6bqkPcoRm8qWSV 106 | +xqYiEx0ebKnGRf5OppRjgg9DmOL3n9PiYpC/dJBkPg2V4F7iFGL4YQJnHsNRl4v 107 | Ke6YO2qCI430WwOLY/69imkOc+ob+G3GYt0Oim58z+SRFU4eUwiYxQqCZaNVAEV5 108 | IQg5QUWOgT5kSI0e3HK7QgutlMP3AhawMACXfPWM+iN3v7DJk8mDAbh0cCWRi8PG 109 | Q7Ms+hR8Vx+CiekPO/S2TgvWiBYvsF8QJ2Cyg6x7rXTwFBSMaDvBYQ5CIPYxQMng 110 | B6L4z1SC9o9pFomwU8W/BrloUEUeTe66YeL4v+Yy9brXVM7nK0U0qxhPpIH39oFS 111 | 5v/k9JZ071nZbpdr6P1E55xCEFbB18f6ljYQ55xNpjFMzuvYWy89bTjA+M7q47t5 112 | 2PXFal8i++Z8jqfhUvOxidf/EqQ93GFCzchS2Zf3ut9nqmQYz1zWqFY7GHO0UIuH 113 | DDXnt9CZduCL5Jpc8J6kITuO+2MWrgLd2OoCNZLOhD/yWPQcSjA6C+bDc0MN/TtT 114 | Y6UOlBoByevGWAejLP2XjNJELr1VkTgv/sYXoFazDggNIovSWNTmcoSJJ6Zmy4Zo 115 | V3Dl12p/TQ3/eu5v7x3D7zBcwluxrvI= 116 | -----END CERTIFICATE----- 117 | ` 118 | keyPem := ` 119 | -----BEGIN PRIVATE KEY----- 120 | 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 169 | 3uj4+nXPk8tk7guNm0WV0n8KBKwR 170 | -----END PRIVATE KEY----- 171 | ` 172 | cert, _ = tls.X509KeyPair([]byte(certPem), []byte(keyPem)) 173 | return 174 | } 175 | -------------------------------------------------------------------------------- /examples/http-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "crypto/aes" 7 | "crypto/cipher" 8 | "crypto/rand" 9 | "crypto/sha256" 10 | "crypto/tls" 11 | "encoding/hex" 12 | "flag" 13 | "fmt" 14 | "io" 15 | "reflect" 16 | "runtime" 17 | "strconv" 18 | "strings" 19 | "sync" 20 | "sync/atomic" 21 | "time" 22 | "unsafe" 23 | 24 | "github.com/maurice2k/tcpserver" 25 | ) 26 | 27 | type request struct { 28 | proto, method string 29 | path, query string 30 | head, body string 31 | remoteAddr string 32 | } 33 | 34 | type reqVars struct { 35 | data [2048]byte 36 | out []byte 37 | req request 38 | } 39 | 40 | var reqVarsPool *sync.Pool = &sync.Pool{ 41 | New: func() interface{} { 42 | return &reqVars{ 43 | out: make([]byte, 0, 2048), 44 | } 45 | }, 46 | } 47 | 48 | var serverDate atomic.Value 49 | var bwPool *sync.Pool = &sync.Pool{} 50 | var brPool *sync.Pool = &sync.Pool{} 51 | 52 | var listenAddr string 53 | var sleep int 54 | var keepAlive bool 55 | var aaaa int 56 | var aes128 bool 57 | var sha bool 58 | var res string 59 | var resbytes []byte 60 | var loops int 61 | var useTls bool 62 | 63 | var status200Ok = []byte("200 OK") 64 | var status500Error = []byte("500 Error") 65 | 66 | var aesKey = []byte("0123456789ABCDEF") 67 | 68 | func main() { 69 | /*go func() { 70 | sigIntChan := make(chan os.Signal) 71 | signal.Notify(sigIntChan, os.Interrupt) 72 | 73 | traceFile, err := os.Create("tcpserver.trace") 74 | if err != nil { 75 | panic(err) 76 | } 77 | 78 | trace.Start(traceFile) 79 | <-sigIntChan 80 | 81 | trace.Stop() 82 | traceFile.Close() 83 | fmt.Println("Closed trace file") 84 | os.Exit(1) 85 | }() 86 | 87 | /*go func() { 88 | defer os.Exit(0) 89 | cpuProfile, err := os.Create("tcpserver-cpu.prof") 90 | if err != nil { 91 | log.Fatal(err) 92 | } 93 | defer pprof.StopCPUProfile() 94 | pprof.StartCPUProfile(cpuProfile) 95 | 96 | time.Sleep(time.Second * 10) 97 | fmt.Println("Writing cpu & mem profile...") 98 | 99 | // Memory Profile 100 | memProfile, err := os.Create("tcpserver-mem.prof") 101 | if err != nil { 102 | log.Fatal(err) 103 | } 104 | defer memProfile.Close() 105 | runtime.GC() 106 | if err := pprof.WriteHeapProfile(memProfile); err != nil { 107 | log.Fatal(err) 108 | } 109 | }()/**/ 110 | 111 | go func() { 112 | for { 113 | buf := appendTime(nil, time.Now()) 114 | serverDate.Store(buf) 115 | time.Sleep(time.Second) 116 | } 117 | }() 118 | 119 | tfMap := make(map[bool]string) 120 | tfMap[true] = "on" 121 | tfMap[false] = "off" 122 | 123 | flag.StringVar(&listenAddr, "listen", "127.0.0.1:8000", "server listen addr") 124 | flag.IntVar(&aaaa, "aaaa", 0, "aaaaa.... (default output is 'Hello World')") 125 | flag.BoolVar(&keepAlive, "keepalive", true, "use HTTP Keep-Alive") 126 | flag.BoolVar(&aes128, "aes128", false, "encrypt response with aes-128-cbc") 127 | flag.BoolVar(&sha, "sha", false, "output sha256 instead of plain response") 128 | flag.IntVar(&sleep, "sleep", 0, "sleep number of milliseconds per request") 129 | flag.IntVar(&loops, "loops", -1, "number of accept loops (defaults to GOMAXPROCS)") 130 | flag.BoolVar(&useTls, "useTls", false, "use HTTPS") 131 | flag.Parse() 132 | 133 | if aaaa > 0 { 134 | res = strings.Repeat("a", aaaa) 135 | } else { 136 | res = "Hello World!\r\n" 137 | } 138 | 139 | resbytes = []byte(res) 140 | 141 | fmt.Printf("Running http server on %s with GOMAXPROCS=%d, loops=%d; built with %s\n", listenAddr, runtime.GOMAXPROCS(0), loops, runtime.Version()) 142 | fmt.Printf(" - keepalive: %s\n", tfMap[keepAlive]) 143 | if sleep > 0 { 144 | fmt.Printf(" - sleep ms per request: %d ms\n", sleep) 145 | } 146 | if aes128 { 147 | fmt.Printf(" - encrypt response with aes-128-cbc\n") 148 | } 149 | if !aes128 && sha { 150 | fmt.Printf(" - output sha256 of reponse\n") 151 | } 152 | if useTls { 153 | fmt.Printf(" - using TLS\n") 154 | } 155 | 156 | for i := 0; i < 0; i++ { 157 | go func() { 158 | server, _ := tcpserver.NewServer(listenAddr) 159 | server.SetListenConfig(&tcpserver.ListenConfig{ 160 | SocketReusePort: true, 161 | SocketFastOpen: false, 162 | SocketDeferAccept: false, 163 | }) 164 | server.SetRequestHandler(requestHandlerSimple) 165 | server.SetLoops(loops) 166 | server.SetAllowThreadLocking(true) 167 | var err error 168 | if useTls { 169 | server.SetTLSConfig(&tls.Config{Certificates: []tls.Certificate{getCert()}}) 170 | err = server.ListenTLS() 171 | } else { 172 | err = server.Listen() 173 | } 174 | if err != nil { 175 | panic("Error listening on interface: " + err.Error()) 176 | } 177 | err = server.Serve() 178 | if err != nil { 179 | panic("Error serving: " + err.Error()) 180 | } 181 | 182 | }() 183 | } 184 | 185 | server, _ := tcpserver.NewServer(listenAddr) 186 | 187 | server.SetListenConfig(&tcpserver.ListenConfig{ 188 | SocketReusePort: true, 189 | SocketFastOpen: false, 190 | SocketDeferAccept: false, 191 | }) 192 | server.SetRequestHandler(requestHandlerSimple) 193 | server.SetLoops(loops) 194 | server.SetAllowThreadLocking(true) 195 | server.SetBallast(100) 196 | 197 | var err error 198 | if useTls { 199 | server.SetTLSConfig(&tls.Config{Certificates: []tls.Certificate{getCert()}}) 200 | err = server.ListenTLS() 201 | } else { 202 | err = server.Listen() 203 | } 204 | if err != nil { 205 | panic("Error listening on interface: " + err.Error()) 206 | } 207 | err = server.Serve() 208 | if err != nil { 209 | panic("Error serving: " + err.Error()) 210 | } 211 | } 212 | 213 | func acquireReader(conn *tcpserver.TCPConn) *bufio.Reader { 214 | v := brPool.Get() 215 | if v == nil { 216 | return bufio.NewReader(conn) 217 | } 218 | br := v.(*bufio.Reader) 219 | br.Reset(conn) 220 | return br 221 | } 222 | 223 | func releaseReader(br *bufio.Reader) { 224 | brPool.Put(br) 225 | } 226 | 227 | func acquireWriter(conn tcpserver.Connection) *bufio.Writer { 228 | v := bwPool.Get() 229 | if v == nil { 230 | return bufio.NewWriter(conn) 231 | } 232 | bw := v.(*bufio.Writer) 233 | bw.Reset(conn) 234 | return bw 235 | } 236 | 237 | func releaseWriter(bw *bufio.Writer) { 238 | bwPool.Put(bw) 239 | } 240 | 241 | func requestHandler(conn tcpserver.Connection) { 242 | var leftover []byte 243 | var bw *bufio.Writer 244 | 245 | rv := reqVarsPool.Get().(*reqVars) 246 | 247 | bufSize := 0 248 | var buf [50]byte 249 | for { 250 | n, err := conn.Read(rv.data[bufSize:2048]) 251 | if err != nil && n == 0 || n == 0 { 252 | break 253 | } 254 | bufSize += n 255 | 256 | leftover, err = parsereq(rv.data[0:bufSize], &rv.req) 257 | if err != nil { 258 | // bad thing happened 259 | if bw == nil { 260 | bw = acquireWriter(conn) 261 | } 262 | appendrespbw(bw, buf[:0], status500Error, []byte(err.Error()+"\n")) 263 | bw.Flush() 264 | break 265 | } 266 | 267 | if len(leftover) == len(rv.data) { 268 | // request not ready, yet 269 | continue 270 | } 271 | // handle the request 272 | if bw == nil { 273 | bw = acquireWriter(conn) 274 | } 275 | if aes128 { 276 | cryptedResbytes, _ := encryptCBC(resbytes, aesKey) 277 | appendrespbw(bw, buf[:0], status200Ok, cryptedResbytes) 278 | } else if sha { 279 | sha256sum := sha256.Sum256(resbytes) 280 | appendrespbw(bw, buf[:0], status200Ok, []byte(hex.EncodeToString(sha256sum[:]))) 281 | } else { 282 | appendrespbw(bw, buf[:0], status200Ok, resbytes) 283 | } 284 | 285 | if sleep > 0 { 286 | time.Sleep(time.Millisecond * time.Duration(sleep)) 287 | } 288 | 289 | bw.Flush() 290 | 291 | if !keepAlive { 292 | break 293 | } 294 | 295 | bufSize = 0 296 | } 297 | 298 | if bw != nil { 299 | releaseWriter(bw) 300 | } 301 | 302 | reqVarsPool.Put(rv) 303 | return 304 | } 305 | 306 | func requestHandlerSimple(conn tcpserver.Connection) { 307 | var leftover []byte 308 | 309 | rv := reqVarsPool.Get().(*reqVars) 310 | 311 | bufSize := 0 312 | var buf [50]byte 313 | for { 314 | n, err := conn.Read(rv.data[bufSize:2048]) 315 | if err != nil && n == 0 || n == 0 { 316 | break 317 | } 318 | bufSize += n 319 | 320 | leftover, err = parsereq(rv.data[0:bufSize], &rv.req) 321 | if err != nil { 322 | // bad thing happened 323 | writeResponse(conn, rv.out[:0], buf[:0], status500Error, []byte(err.Error()+"\n")) 324 | break 325 | } 326 | 327 | if len(leftover) == len(rv.data) { 328 | // request not ready, yet 329 | continue 330 | } 331 | // handle the request 332 | if aes128 { 333 | cryptedResbytes, _ := encryptCBC(resbytes, aesKey) 334 | writeResponse(conn, rv.out[:0], buf[:0], status200Ok, cryptedResbytes) 335 | } else if sha { 336 | sha256sum := sha256.Sum256(resbytes) 337 | writeResponse(conn, rv.out[:0], buf[:0], status200Ok, []byte(hex.EncodeToString(sha256sum[:]))) 338 | } else { 339 | writeResponse(conn, rv.out[:0], buf[:0], status200Ok, resbytes) 340 | } 341 | 342 | if sleep > 0 { 343 | time.Sleep(time.Millisecond * time.Duration(sleep)) 344 | } 345 | 346 | if !keepAlive { 347 | break 348 | } 349 | 350 | bufSize = 0 351 | } 352 | 353 | reqVarsPool.Put(rv) 354 | return 355 | } 356 | 357 | var headerHTTP11 = []byte("HTTP/1.1 ") 358 | var headerDate = []byte("Date: ") 359 | var headerConnectionClose = []byte("Connection: close") 360 | var headerConnectionKeepAlive = []byte("Connection: keep-alive") 361 | var headerServerIdentity = []byte("Server: tsrv") 362 | var headerContentLength = []byte("Content-Length: ") 363 | var headerContentType = []byte("Content-Type: ") 364 | var headerContentTypeTextPlain = []byte("text/plain") 365 | var newLine = []byte("\r\n") 366 | 367 | // writeResponse will append a valid http response to the provide bytes. 368 | // The status param should be the code plus text such as "200 OK". 369 | // The head parameter should be a series of lines ending with "\r\n" or empty. 370 | func writeResponse(w io.Writer, b, buf, status, body []byte) { 371 | b = append(b, headerHTTP11...) 372 | b = append(b, status...) 373 | b = append(b, newLine...) 374 | b = append(b, headerServerIdentity...) 375 | b = append(b, newLine...) 376 | if !keepAlive { 377 | b = append(b, headerConnectionClose...) 378 | b = append(b, newLine...) 379 | } else { 380 | b = append(b, headerConnectionKeepAlive...) 381 | b = append(b, newLine...) 382 | } 383 | b = append(b, headerDate...) 384 | b = append(b, serverDate.Load().([]byte)...) 385 | b = append(b, newLine...) 386 | if len(body) > 0 { 387 | b = append(b, headerContentType...) 388 | b = append(b, headerContentTypeTextPlain...) 389 | b = append(b, newLine...) 390 | b = append(b, headerContentLength...) 391 | b = append(b, AppendUint(buf[:0], len(body))...) 392 | //b = strconv.AppendInt(b, int64(len(body)), 10) 393 | b = append(b, newLine...) 394 | } 395 | b = append(b, newLine...) 396 | if len(body) > cap(b)-len(b) { 397 | w.Write(b) 398 | w.Write(body) 399 | return 400 | } 401 | b = append(b, body...) 402 | w.Write(b) 403 | } 404 | 405 | func appendrespbw(bw *bufio.Writer, buf []byte, status, body []byte) { 406 | bw.Write(headerHTTP11) 407 | bw.Write(status) 408 | bw.Write(newLine) 409 | bw.Write(headerServerIdentity) 410 | bw.Write(newLine) 411 | if !keepAlive { 412 | bw.Write(headerConnectionClose) 413 | bw.Write(newLine) 414 | } else { 415 | bw.Write(headerConnectionKeepAlive) 416 | bw.Write(newLine) 417 | } 418 | bw.Write(headerDate) 419 | bw.Write(serverDate.Load().([]byte)) 420 | bw.Write(newLine) 421 | 422 | if len(body) > 0 { 423 | bw.Write(headerContentType) 424 | bw.Write(headerContentTypeTextPlain) 425 | bw.Write(newLine) 426 | bw.Write(headerContentLength) 427 | bw.Write(AppendUint(buf[:0], len(body))) 428 | bw.Write(newLine) 429 | } 430 | bw.Write(newLine) 431 | if len(body) > 0 { 432 | bw.Write(body) 433 | } 434 | } 435 | 436 | // AppendUint appends n to dst and returns the extended dst. 437 | func AppendUint(dst []byte, n int) []byte { 438 | if n < 0 { 439 | panic("BUG: int must be positive") 440 | } 441 | 442 | var b [20]byte 443 | buf := b[:] 444 | i := len(buf) 445 | var q int 446 | for n >= 10 { 447 | i-- 448 | q = n / 10 449 | buf[i] = '0' + byte(n-q*10) 450 | n = q 451 | } 452 | i-- 453 | buf[i] = '0' + byte(n) 454 | 455 | dst = append(dst, buf[i:]...) 456 | return dst 457 | } 458 | 459 | func appendTime(b []byte, t time.Time) []byte { 460 | const days = "SunMonTueWedThuFriSat" 461 | const months = "JanFebMarAprMayJunJulAugSepOctNovDec" 462 | 463 | t = t.UTC() 464 | yy, mm, dd := t.Date() 465 | hh, mn, ss := t.Clock() 466 | day := days[3*t.Weekday():] 467 | mon := months[3*(mm-1):] 468 | 469 | return append(b, 470 | day[0], day[1], day[2], ',', ' ', 471 | byte('0'+dd/10), byte('0'+dd%10), ' ', 472 | mon[0], mon[1], mon[2], ' ', 473 | byte('0'+yy/1000), byte('0'+(yy/100)%10), byte('0'+(yy/10)%10), byte('0'+yy%10), ' ', 474 | byte('0'+hh/10), byte('0'+hh%10), ':', 475 | byte('0'+mn/10), byte('0'+mn%10), ':', 476 | byte('0'+ss/10), byte('0'+ss%10), ' ', 477 | 'G', 'M', 'T') 478 | } 479 | 480 | // parsereq is a very simple http request parser. This operation 481 | // waits for the entire payload to be buffered before returning a 482 | // valid request. 483 | func parsereq(data []byte, req *request) (leftover []byte, err error) { 484 | sdata := data 485 | var i, s int 486 | var top string 487 | var clen int 488 | var q = -1 489 | // method, path, proto line 490 | for ; i < len(sdata); i++ { 491 | if sdata[i] == ' ' { 492 | req.method = b2s(sdata[s:i]) 493 | for i, s = i+1, i+1; i < len(sdata); i++ { 494 | if sdata[i] == '?' && q == -1 { 495 | q = i - s 496 | } else if sdata[i] == ' ' { 497 | if q != -1 { 498 | req.path = b2s(sdata[s:q]) 499 | req.query = req.path[q+1 : i] 500 | } else { 501 | req.path = b2s(sdata[s:i]) 502 | } 503 | for i, s = i+1, i+1; i < len(sdata); i++ { 504 | if sdata[i] == '\n' && sdata[i-1] == '\r' { 505 | req.proto = b2s(sdata[s:i]) 506 | i, s = i+1, i+1 507 | break 508 | } 509 | } 510 | break 511 | } 512 | } 513 | break 514 | } 515 | } 516 | if req.proto == "" { 517 | return data, fmt.Errorf("malformed request") 518 | } 519 | top = b2s(sdata[:s]) 520 | for ; i < len(sdata); i++ { 521 | if i > 1 && sdata[i] == '\n' && sdata[i-1] == '\r' { 522 | line := b2s(sdata[s : i-1]) 523 | s = i + 1 524 | if line == "" { 525 | req.head = b2s(sdata[len(top)+2 : i+1]) 526 | i++ 527 | if clen > 0 { 528 | if len(sdata[i:]) < clen { 529 | break 530 | } 531 | req.body = b2s(sdata[i : i+clen]) 532 | i += clen 533 | } 534 | return data[i:], nil 535 | } 536 | if strings.HasPrefix(line, "Content-Length:") { 537 | n, err := strconv.ParseInt(strings.TrimSpace(line[len("Content-Length:"):]), 10, 64) 538 | if err == nil { 539 | clen = int(n) 540 | } 541 | } 542 | } 543 | } 544 | // not enough data 545 | return data, nil 546 | } 547 | 548 | // b2s converts byte slice to a string without memory allocation. 549 | // See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ . 550 | // 551 | // Note it may break if string and/or slice header will change 552 | // in the future go versions. 553 | func b2s(b []byte) string { 554 | return *(*string)(unsafe.Pointer(&b)) 555 | } 556 | 557 | // s2b converts string to a byte slice without memory allocation. 558 | // 559 | // Note it may break if string and/or slice header will change 560 | // in the future go versions. 561 | func s2b(s string) []byte { 562 | sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) 563 | bh := reflect.SliceHeader{ 564 | Data: sh.Data, 565 | Len: sh.Len, 566 | Cap: sh.Len, 567 | } 568 | return *(*[]byte)(unsafe.Pointer(&bh)) 569 | } 570 | 571 | // Encrypts given cipher text (prepended with the IV) with AES-128 or AES-256 572 | // (depending on the length of the key) 573 | func encryptCBC(plainText, key []byte) (cipherText []byte, err error) { 574 | block, err := aes.NewCipher(key) 575 | if err != nil { 576 | return nil, err 577 | } 578 | 579 | plainText = pad(aes.BlockSize, plainText) 580 | 581 | cipherText = make([]byte, aes.BlockSize+len(plainText)) 582 | iv := cipherText[:aes.BlockSize] 583 | _, err = io.ReadFull(rand.Reader, iv) 584 | if err != nil { 585 | return nil, err 586 | } 587 | 588 | mode := cipher.NewCBCEncrypter(block, iv) 589 | mode.CryptBlocks(cipherText[aes.BlockSize:], plainText) 590 | 591 | return cipherText, nil 592 | } 593 | 594 | // Adds PKCS#7 padding (variable block length <= 255 bytes) 595 | func pad(blockSize int, buf []byte) []byte { 596 | padLen := blockSize - (len(buf) % blockSize) 597 | padding := bytes.Repeat([]byte{byte(padLen)}, padLen) 598 | return append(buf, padding...) 599 | } 600 | 601 | // Returns testing cert 602 | func getCert() (cert tls.Certificate) { 603 | certPem := ` 604 | -----BEGIN CERTIFICATE----- 605 | MIIFgzCCA2ugAwIBAgIUOAG3o6IsqyYwaSecWpft29luvD0wDQYJKoZIhvcNAQEL 606 | BQAwUTELMAkGA1UEBhMCREUxEzARBgNVBAgMClNvbWUtU3RhdGUxDzANBgNVBAcM 607 | Bk11bmljaDEcMBoGA1UECgwTbWF1cmljZTJrL3RjcHNlcnZlcjAeFw0yMTAyMDgw 608 | OTUyMTBaFw0zMTAyMDYwOTUyMTBaMFExCzAJBgNVBAYTAkRFMRMwEQYDVQQIDApT 609 | b21lLVN0YXRlMQ8wDQYDVQQHDAZNdW5pY2gxHDAaBgNVBAoME21hdXJpY2Uyay90 610 | Y3BzZXJ2ZXIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO043SaQoE 611 | QNSEALBnG/1qPLvwSwe+JDC11ebRBhaWvYLzycwzDK3IewM8Oa2ygqCLi1MhV8TX 612 | qfuu5R0+OFwyp4tBGyTmtcyg4k7HK7lrtq8jVlLzyVmg5k3g9RR4ab+aiAc7R54T 613 | DcR2kLm7Xl8Jn2XJhKlyneK2HMufxmUh5EF2S5jMsHh0b8yrbmfio1Dxi3QZGDrs 614 | QHULPZ47TbcC1B790Z8bVnfzOmFYJUF92H8l2utAb0q0ARHImPRJOjwW7TOYIWbi 615 | QYI4aE4Te2zq4V26qjEcP/IWFVxNFg7+1uSrb4RlyjTKoKvSGlYj/hDitQOheOIg 616 | XDqKyEs3yxfQOATsUE8/J26SGTnwauBRblrZBYi8jrHDm+FJcmc65/dsAZe42wCd 617 | oTs7k9gV0CvjXvvXRITr/YkRA7epYfEErVHl112wZ30p6T+YznPiBh8xNbijWlcH 618 | T/mER0TaGX8vzyTj/Dy1fY0oQhaP79LwAVbUgTtMBv7bwtrH4xX+kvBm5j5NLrUS 619 | diXmeFYB6H1ZUFzEnlIsICs5rb1fCvJlSbQxwq6fqNkZxyZU3e9JxMzQ8pgDmrKg 620 | KPmxDsm/7sX3tCKX7o9Fd6PH4rlEsWQxMM7/1mINgR0SkdRLZCogybvFELrWFLdb 621 | bmlZc52FqSIvMnj8fTfG6rxNVJ8A6pLd3QIDAQABo1MwUTAdBgNVHQ4EFgQUo1A/ 622 | GiyZkQEnTbtyvVJl9D1qFHowHwYDVR0jBBgwFoAUo1A/GiyZkQEnTbtyvVJl9D1q 623 | FHowDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAjQGOL7fxT4bT 624 | eAjbIZLzbSX7euvJsQAQJHikI5ZY9ROnFlx1N26bGK28OaqbaW6bqkPcoRm8qWSV 625 | +xqYiEx0ebKnGRf5OppRjgg9DmOL3n9PiYpC/dJBkPg2V4F7iFGL4YQJnHsNRl4v 626 | Ke6YO2qCI430WwOLY/69imkOc+ob+G3GYt0Oim58z+SRFU4eUwiYxQqCZaNVAEV5 627 | IQg5QUWOgT5kSI0e3HK7QgutlMP3AhawMACXfPWM+iN3v7DJk8mDAbh0cCWRi8PG 628 | Q7Ms+hR8Vx+CiekPO/S2TgvWiBYvsF8QJ2Cyg6x7rXTwFBSMaDvBYQ5CIPYxQMng 629 | B6L4z1SC9o9pFomwU8W/BrloUEUeTe66YeL4v+Yy9brXVM7nK0U0qxhPpIH39oFS 630 | 5v/k9JZ071nZbpdr6P1E55xCEFbB18f6ljYQ55xNpjFMzuvYWy89bTjA+M7q47t5 631 | 2PXFal8i++Z8jqfhUvOxidf/EqQ93GFCzchS2Zf3ut9nqmQYz1zWqFY7GHO0UIuH 632 | DDXnt9CZduCL5Jpc8J6kITuO+2MWrgLd2OoCNZLOhD/yWPQcSjA6C+bDc0MN/TtT 633 | Y6UOlBoByevGWAejLP2XjNJELr1VkTgv/sYXoFazDggNIovSWNTmcoSJJ6Zmy4Zo 634 | V3Dl12p/TQ3/eu5v7x3D7zBcwluxrvI= 635 | -----END CERTIFICATE----- 636 | ` 637 | keyPem := ` 638 | -----BEGIN PRIVATE KEY----- 639 | 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 688 | 3uj4+nXPk8tk7guNm0WV0n8KBKwR 689 | -----END PRIVATE KEY----- 690 | ` 691 | cert, _ = tls.X509KeyPair([]byte(certPem), []byte(keyPem)) 692 | return 693 | } 694 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/maurice2k/tcpserver 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/maurice2k/ultrapool v1.2.0 7 | github.com/panjf2000/gnet v1.6.6 8 | github.com/tidwall/evio v1.0.8 9 | github.com/valyala/fasthttp v1.40.0 10 | ) 11 | 12 | require ( 13 | github.com/andybalholm/brotli v1.0.4 // indirect 14 | github.com/kavu/go_reuseport v1.5.0 // indirect 15 | github.com/klauspost/compress v1.15.11 // indirect 16 | github.com/pkg/errors v0.9.1 // indirect 17 | github.com/valyala/bytebufferpool v1.0.0 // indirect 18 | go.uber.org/atomic v1.10.0 // indirect 19 | go.uber.org/multierr v1.8.0 // indirect 20 | go.uber.org/zap v1.23.0 // indirect 21 | golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 // indirect 22 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= 4 | github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 5 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= 6 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/kavu/go_reuseport v1.5.0 h1:UNuiY2OblcqAtVDE8Gsg1kZz8zbBWg907sP1ceBV+bk= 11 | github.com/kavu/go_reuseport v1.5.0/go.mod h1:CG8Ee7ceMFSMnx/xr25Vm0qXaj2Z4i5PWoUx+JZ5/CU= 12 | github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 13 | github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= 14 | github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= 15 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 16 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 17 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 18 | github.com/maurice2k/ultrapool v1.2.0 h1:P9eFuK8OPdOF3yQ7jR4aLLDJ70XywQFn/tlA8OGw5WY= 19 | github.com/maurice2k/ultrapool v1.2.0/go.mod h1:ZHJoFbaeP8qXjCsMZDJlaNuSWYz1GCuk4RjpZevd5aM= 20 | github.com/panjf2000/ants/v2 v2.4.7/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= 21 | github.com/panjf2000/ants/v2 v2.5.0 h1:1rWGWSnxCsQBga+nQbA4/iY6VMeNoOIAM0ZWh9u3q2Q= 22 | github.com/panjf2000/gnet v1.6.6 h1:P6bApc54hnVcJVgH+SMe41mn47ECCajB6E/dKq27Y0c= 23 | github.com/panjf2000/gnet v1.6.6/go.mod h1:KcOU7QsCaCBjeD5kyshBIamG3d9kAQtlob4Y0v0E+sc= 24 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 25 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 26 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 27 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 28 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 29 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 30 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 31 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 32 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 33 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 34 | github.com/tidwall/evio v1.0.8 h1:+M7lh83rL4KwEObDGtXP3J1wE5utH80LeaAhrKCGVfE= 35 | github.com/tidwall/evio v1.0.8/go.mod h1:MJhRp4iVVqx/n/5mJk77oKmSABVhC7yYykcJiKaFYYw= 36 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 37 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 38 | github.com/valyala/fasthttp v1.40.0 h1:CRq/00MfruPGFLTQKY8b+8SfdK60TxNztjRMnH0t1Yc= 39 | github.com/valyala/fasthttp v1.40.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= 40 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 41 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 42 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 43 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 44 | go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= 45 | go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 46 | go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 47 | go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= 48 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 49 | go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= 50 | go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= 51 | go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= 52 | go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= 53 | go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= 54 | go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= 55 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 56 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 57 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 58 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 59 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 60 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 61 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 62 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 63 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 64 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 65 | golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 66 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 67 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 68 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 69 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 70 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 71 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 72 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 73 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 74 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 75 | golang.org/x/sys v0.0.0-20211204120058-94396e421777/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 76 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 77 | golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 78 | golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM= 79 | golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 80 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 81 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 82 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 83 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 84 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 85 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 86 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 87 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 88 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 89 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 90 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 91 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 92 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 93 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 94 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 95 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= 96 | gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= 97 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 98 | gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 99 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 100 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 101 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 102 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 103 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 104 | -------------------------------------------------------------------------------- /listen_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2022 Moritz Fain 2 | // Moritz Fain 3 | 4 | // +build linux 5 | 6 | package tcpserver 7 | 8 | import ( 9 | "fmt" 10 | "syscall" 11 | ) 12 | 13 | const ( 14 | soReusePort = 0x0F 15 | tcpFastOpen = 0x17 16 | ) 17 | 18 | type controlFunc func(network, address string, c syscall.RawConn) error 19 | 20 | func applyListenSocketOptions(lc *ListenConfig) controlFunc { 21 | return func(network, address string, c syscall.RawConn) error { 22 | var err error 23 | c.Control(func(fd uintptr) { 24 | if lc.SocketReusePort { 25 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, soReusePort, 1) 26 | if err != nil { 27 | err = fmt.Errorf("unable to set SO_REUSEPORT option: %s", err) 28 | } 29 | } 30 | if lc.SocketFastOpen { 31 | qlen := lc.SocketFastOpenQueueLen 32 | if qlen <= 0 { 33 | qlen = 256 34 | } 35 | err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, tcpFastOpen, qlen) 36 | if err != nil { 37 | err = fmt.Errorf("unable to set TCP_FASTOPEN option: %s", err) 38 | } 39 | } 40 | if lc.SocketDeferAccept { 41 | err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_DEFER_ACCEPT, 1) 42 | if err != nil { 43 | err = fmt.Errorf("unable to set TCP_DEFER_ACCEPT option: %s", err) 44 | } 45 | } 46 | }) 47 | return err 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /listen_stub.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2022 Moritz Fain 2 | // Moritz Fain 3 | 4 | //go:build !linux && !windows 5 | // +build !linux,!windows 6 | 7 | package tcpserver 8 | 9 | import "syscall" 10 | 11 | type controlFunc func(network, address string, c syscall.RawConn) error 12 | 13 | func applyListenSocketOptions(lc *ListenConfig) controlFunc { 14 | return nil 15 | } 16 | -------------------------------------------------------------------------------- /listen_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2022 Moritz Fain 2 | // Moritz Fain 3 | 4 | //go:build windows 5 | // +build windows 6 | 7 | package tcpserver 8 | 9 | import ( 10 | "fmt" 11 | "syscall" 12 | ) 13 | 14 | const ( 15 | tcpFastOpen = 0x17 16 | ) 17 | 18 | type controlFunc func(network, address string, c syscall.RawConn) error 19 | 20 | func applyListenSocketOptions(lc *ListenConfig) controlFunc { 21 | return func(network, address string, c syscall.RawConn) error { 22 | var err error 23 | c.Control(func(fd uintptr) { 24 | if lc.SocketFastOpen { 25 | qlen := lc.SocketFastOpenQueueLen 26 | if qlen <= 0 { 27 | qlen = 256 28 | } 29 | err = syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_TCP, tcpFastOpen, qlen) 30 | if err != nil { 31 | err = fmt.Errorf("unable to set TCP_FASTOPEN option: %s", err) 32 | } 33 | } 34 | }) 35 | return err 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tcpserver.go: -------------------------------------------------------------------------------- 1 | // Package tcpserver implements an extremely fast and flexible IPv4 and 2 | // IPv6 capable TCP server with TLS support, graceful shutdown and some 3 | // TCP tuning options like TCP_FASTOPEN, SO_RESUSEPORT and TCP_DEFER_ACCEPT. 4 | // 5 | // Copyright 2019-2022 Moritz Fain 6 | // Moritz Fain 7 | // 8 | // Source available at github.com/maurice2k/tcpserver, 9 | // licensed under the MIT license (see LICENSE file). 10 | 11 | package tcpserver 12 | 13 | import ( 14 | "context" 15 | "crypto/tls" 16 | "fmt" 17 | "net" 18 | "runtime" 19 | "sync" 20 | "sync/atomic" 21 | "time" 22 | 23 | "github.com/maurice2k/ultrapool" 24 | ) 25 | 26 | // Server struct 27 | type Server struct { 28 | listenAddr *net.TCPAddr 29 | listener *net.TCPListener 30 | shutdown bool 31 | shutdownDeadline time.Time 32 | requestHandler RequestHandlerFunc 33 | connectionCreator ConnectionCreatorFunc 34 | ctx *context.Context 35 | activeConnections int32 36 | maxAcceptConnections int32 37 | acceptedConnections int32 38 | tlsConfig *tls.Config 39 | tlsEnabled bool 40 | listenConfig *ListenConfig 41 | connWaitGroup sync.WaitGroup 42 | connStructPool sync.Pool 43 | loops int 44 | wp *ultrapool.WorkerPool 45 | allowThreadLocking bool 46 | ballast []byte 47 | } 48 | 49 | // Connection interface 50 | type Connection interface { 51 | net.Conn 52 | GetNetConn() net.Conn 53 | GetServer() *Server 54 | GetClientAddr() *net.TCPAddr 55 | GetServerAddr() *net.TCPAddr 56 | GetStartTime() time.Time 57 | SetContext(ctx *context.Context) 58 | GetContext() *context.Context 59 | 60 | // used internally 61 | Start() 62 | Reset(netConn net.Conn) 63 | SetServer(server *Server) 64 | } 65 | 66 | // TCPConn struct implementing Connection and embedding net.Conn 67 | type TCPConn struct { 68 | net.Conn 69 | server *Server 70 | ctx *context.Context 71 | ts int64 72 | _cacheLinePadding [24]byte 73 | } 74 | 75 | // Listener config struct 76 | type ListenConfig struct { 77 | lc net.ListenConfig 78 | // Enable/disable SO_REUSEPORT (requires Linux >=2.4) 79 | SocketReusePort bool 80 | // Enable/disable TCP_FASTOPEN (requires Linux >=3.7 or Windows 10, version 1607) 81 | // For Linux: 82 | // - see https://lwn.net/Articles/508865/ 83 | // - enable with "echo 3 >/proc/sys/net/ipv4/tcp_fastopen" for client and server 84 | // For Windows: 85 | // - enable with "netsh int tcp set global fastopen=enabled" 86 | SocketFastOpen bool 87 | // Queue length for TCP_FASTOPEN (default 256) 88 | SocketFastOpenQueueLen int 89 | // Enable/disable TCP_DEFER_ACCEPT (requires Linux >=2.4) 90 | SocketDeferAccept bool 91 | } 92 | 93 | // Request handler function type 94 | type RequestHandlerFunc func(conn Connection) 95 | 96 | // Connection creator function 97 | type ConnectionCreatorFunc func() Connection 98 | 99 | var defaultListenConfig *ListenConfig = &ListenConfig{ 100 | SocketReusePort: true, 101 | } 102 | 103 | // Creates a new server instance 104 | func NewServer(listenAddr string) (*Server, error) { 105 | la, err := net.ResolveTCPAddr("tcp", listenAddr) 106 | if err != nil { 107 | return nil, fmt.Errorf("error resolving address '%s': %s", listenAddr, err) 108 | } 109 | var s *Server 110 | 111 | s = &Server{ 112 | listenAddr: la, 113 | listenConfig: defaultListenConfig, 114 | connStructPool: sync.Pool{ 115 | New: func() interface{} { 116 | conn := s.connectionCreator() 117 | conn.SetServer(s) 118 | return conn 119 | }, 120 | }, 121 | } 122 | 123 | s.connectionCreator = func() Connection { 124 | return &TCPConn{} 125 | } 126 | 127 | s.SetBallast(20) 128 | 129 | return s, nil 130 | } 131 | 132 | // Sets TLS config but does not enable TLS yet. TLS can be either enabled 133 | // by using server.ListenTLS() or later by using connection.StartTLS() 134 | func (s *Server) SetTLSConfig(config *tls.Config) { 135 | s.tlsConfig = config 136 | } 137 | 138 | // Returns previously set TLS config 139 | func (s *Server) GetTLSConfig() *tls.Config { 140 | return s.tlsConfig 141 | } 142 | 143 | // Enable TLS (use server.SetTLSConfig() first) 144 | func (s *Server) EnableTLS() error { 145 | if s.GetTLSConfig() == nil { 146 | return fmt.Errorf("no TLS config set") 147 | } 148 | s.tlsEnabled = true 149 | return nil 150 | } 151 | 152 | // Sets listen config 153 | func (s *Server) SetListenConfig(config *ListenConfig) { 154 | s.listenConfig = config 155 | } 156 | 157 | // Returns listen config 158 | func (s *Server) GetListenConfig() *ListenConfig { 159 | return s.listenConfig 160 | } 161 | 162 | // Starts listening 163 | func (s *Server) Listen() (err error) { 164 | network := "tcp4" 165 | if IsIPv6Addr(s.listenAddr) { 166 | network = "tcp6" 167 | } 168 | 169 | s.listenConfig.lc.Control = applyListenSocketOptions(s.listenConfig) 170 | l, err := s.listenConfig.lc.Listen(*s.GetContext(), network, s.listenAddr.String()) 171 | if err != nil { 172 | return err 173 | } 174 | if tcpl, ok := l.(*net.TCPListener); ok { 175 | s.listener = tcpl 176 | } else { 177 | return fmt.Errorf("listener must be of type net.TCPListener") 178 | } 179 | 180 | return nil 181 | } 182 | 183 | // Starts listening using TLS 184 | func (s *Server) ListenTLS() (err error) { 185 | err = s.EnableTLS() 186 | if err != nil { 187 | return err 188 | } 189 | return s.Listen() 190 | } 191 | 192 | // Sets maximum number of connections that are being accepted before the 193 | // server automatically shutdowns 194 | func (s *Server) SetMaxAcceptConnections(limit int32) { 195 | atomic.StoreInt32(&s.maxAcceptConnections, limit) 196 | } 197 | 198 | // Returns number of currently active connections 199 | func (s *Server) GetActiveConnections() int32 { 200 | return s.activeConnections 201 | } 202 | 203 | // Returns number of accepted connections 204 | func (s *Server) GetAcceptedConnections() int32 { 205 | return s.acceptedConnections 206 | } 207 | 208 | // Returns listening address 209 | func (s *Server) GetListenAddr() *net.TCPAddr { 210 | if s.listener == nil { 211 | return nil 212 | } 213 | return s.listener.Addr().(*net.TCPAddr) 214 | } 215 | 216 | // Gracefully shutdown server but wait no longer than d for active connections. 217 | // Use d = 0 to wait indefinitely for active connections. 218 | func (s *Server) Shutdown(d time.Duration) (err error) { 219 | s.shutdownDeadline = time.Time{} 220 | if d > 0 { 221 | s.shutdownDeadline = time.Now().Add(d) 222 | } 223 | s.shutdown = true 224 | err = s.listener.Close() 225 | if err != nil { 226 | return err 227 | } 228 | return nil 229 | } 230 | 231 | // Shutdown server immediately, do not wait for any connections 232 | func (s *Server) Halt() (err error) { 233 | return s.Shutdown(-1 * time.Second) 234 | } 235 | 236 | // Serves requests (accept / handle loop) 237 | func (s *Server) Serve() error { 238 | if s.listener == nil { 239 | return fmt.Errorf("no valid listener found; call Listen() or ListenTLS() first") 240 | } 241 | 242 | maxProcs := runtime.GOMAXPROCS(0) 243 | loops := s.GetLoops() 244 | 245 | s.wp = ultrapool.NewWorkerPool(s.serveConn) 246 | s.wp.SetNumShards(maxProcs * 2) 247 | s.wp.SetIdleWorkerLifetime(5 * time.Second) 248 | s.wp.Start() 249 | defer s.wp.Stop() 250 | 251 | errChan := make(chan error, loops) 252 | 253 | for i := 0; i < loops; i++ { 254 | go func(id int) { 255 | if s.allowThreadLocking && maxProcs >= 2 && id < loops/2 { 256 | runtime.LockOSThread() 257 | defer runtime.UnlockOSThread() 258 | } 259 | 260 | errChan <- s.acceptLoop(id) 261 | }(i) 262 | } 263 | 264 | for i := 0; i < loops; i++ { 265 | err := <-errChan 266 | if err != nil { 267 | return err 268 | } 269 | } 270 | 271 | if s.activeConnections == 0 { 272 | return nil 273 | } 274 | 275 | if s.shutdownDeadline.IsZero() { 276 | // just wait for all connections to be closed 277 | s.connWaitGroup.Wait() 278 | 279 | } else { 280 | diff := s.shutdownDeadline.Sub(time.Now()) 281 | if diff > 0 { 282 | // wait specified time for still active connections to be closed 283 | time.Sleep(diff) 284 | } 285 | } 286 | 287 | return nil 288 | } 289 | 290 | // Sets a connection creator function 291 | // This can be used to created custom Connection implementations 292 | func (s *Server) SetConnectionCreator(f ConnectionCreatorFunc) { 293 | s.connectionCreator = f 294 | } 295 | 296 | // Sets request handler function 297 | func (s *Server) SetRequestHandler(f RequestHandlerFunc) { 298 | s.requestHandler = f 299 | } 300 | 301 | // Sets context to the server that is later passed to the handleRequest method 302 | func (s *Server) SetContext(ctx *context.Context) { 303 | s.ctx = ctx 304 | } 305 | 306 | // Returns server's context or creates a new one if none is present 307 | func (s *Server) GetContext() *context.Context { 308 | if s.ctx == nil { 309 | ctx := context.Background() 310 | s.ctx = &ctx 311 | } 312 | return s.ctx 313 | } 314 | 315 | // Sets number of accept loops 316 | func (s *Server) SetLoops(loops int) { 317 | s.loops = loops 318 | } 319 | 320 | // Returns number of accept loops (defaults to 8 which is more than enough for most use cases) 321 | func (s *Server) GetLoops() int { 322 | if s.loops < 1 { 323 | s.loops = 8 324 | } 325 | return s.loops 326 | } 327 | 328 | // Whether or not allow thread locking in accept loops 329 | func (s *Server) SetAllowThreadLocking(allow bool) { 330 | s.allowThreadLocking = allow 331 | } 332 | 333 | // This is kind of a hack to reduce GC cycles. Normally Go's GC kicks in 334 | // whenever used memory doubles (see runtime.SetGCPercent() or GOGC env var: 335 | // https://golang.org/pkg/runtime/debug/#SetGCPercent). 336 | // With a very low memory footprint this might dramatically impact your 337 | // performance, especially with lots of connections coming in waves. 338 | func (s *Server) SetBallast(sizeInMiB int) { 339 | s.ballast = make([]byte, sizeInMiB*1024*1024) 340 | } 341 | 342 | // Main accept loop 343 | func (s *Server) acceptLoop(id int) error { 344 | var ( 345 | tempDelay time.Duration 346 | tcpConn net.Conn 347 | err error 348 | ) 349 | 350 | for { 351 | if s.maxAcceptConnections > 0 && s.acceptedConnections >= s.maxAcceptConnections { 352 | s.Shutdown(0) 353 | } 354 | 355 | if s.shutdown { 356 | _ = s.listener.Close() 357 | break 358 | } 359 | 360 | tcpConn, err = s.listener.AcceptTCP() 361 | if err != nil { 362 | if opErr, ok := err.(*net.OpError); ok { 363 | 364 | if opErr.Timeout() { 365 | tempDelay = 0 366 | continue 367 | } 368 | 369 | if !(opErr.Temporary() && opErr.Timeout()) && s.shutdown { 370 | break 371 | } 372 | 373 | if opErr.Temporary() { 374 | if tempDelay == 0 { 375 | tempDelay = 10 * time.Millisecond 376 | } else { 377 | tempDelay *= 2 378 | } 379 | 380 | if max := time.Second; tempDelay > max { 381 | tempDelay = max 382 | } 383 | 384 | time.Sleep(tempDelay) 385 | continue 386 | } 387 | 388 | } 389 | 390 | s.listener.Close() 391 | return err 392 | } 393 | 394 | tempDelay = 0 395 | 396 | newAcceptedConns := atomic.AddInt32(&s.acceptedConnections, 1) 397 | if s.maxAcceptConnections > 0 && newAcceptedConns > s.maxAcceptConnections { 398 | // We have accepted too much connections which might happen due to 399 | // the fact that we use multiple accept loops without locking. 400 | // In this case we just close the connection (we shouldn't have accepted 401 | // in the first place) and continue for shutting down the server. 402 | tcpConn.Close() 403 | continue 404 | } 405 | 406 | s.wp.AddTask(tcpConn) 407 | //go s.serveConn(tcpConn) 408 | tcpConn = nil 409 | } 410 | return nil 411 | } 412 | 413 | // Serve a single connection (called from ultrapool) 414 | func (s *Server) serveConn(task ultrapool.Task) { 415 | conn := s.connStructPool.Get().(*TCPConn) 416 | netConn := task.(net.Conn) 417 | 418 | atomic.AddInt32(&s.activeConnections, 1) 419 | 420 | if s.tlsEnabled { 421 | netConn = tls.Server(netConn, s.GetTLSConfig()) 422 | } 423 | 424 | conn.Reset(netConn) 425 | conn.Start() 426 | s.requestHandler(conn) 427 | conn.Close() 428 | atomic.AddInt32(&s.activeConnections, -1) 429 | 430 | s.connStructPool.Put(conn) 431 | } 432 | 433 | // Returns client IP and port 434 | func (conn *TCPConn) GetClientAddr() *net.TCPAddr { 435 | return conn.RemoteAddr().(*net.TCPAddr) 436 | } 437 | 438 | // Returns server IP and port (the addr the connection was accepted at) 439 | func (conn *TCPConn) GetServerAddr() *net.TCPAddr { 440 | return conn.LocalAddr().(*net.TCPAddr) 441 | } 442 | 443 | // Returns start timestamp 444 | func (conn *TCPConn) GetStartTime() time.Time { 445 | return time.Unix(conn.ts/1e9, conn.ts%1e9) 446 | } 447 | 448 | // Sets context to the connection 449 | func (conn *TCPConn) SetContext(ctx *context.Context) { 450 | conn.ctx = ctx 451 | } 452 | 453 | // Returns connection's context or creates a new one if none is present 454 | func (conn *TCPConn) GetContext() *context.Context { 455 | if conn.ctx == nil { 456 | ctx := context.Background() 457 | conn.ctx = &ctx 458 | } 459 | return conn.ctx 460 | } 461 | 462 | // Returns underlying net.Conn connection (most likely either *net.TCPConn or *tls.Conn) 463 | func (conn *TCPConn) GetNetConn() net.Conn { 464 | return conn.Conn 465 | } 466 | 467 | // Returns underlying net.Conn connection as *net.TCPConn or nil if net.Conn is something else 468 | func (conn *TCPConn) GetNetTCPConn() (c *net.TCPConn) { 469 | c, _ = conn.Conn.(*net.TCPConn) 470 | return 471 | } 472 | 473 | // Sets server handler 474 | func (conn *TCPConn) SetServer(s *Server) { 475 | conn.server = s 476 | } 477 | 478 | // Returns server 479 | func (conn *TCPConn) GetServer() *Server { 480 | return conn.server 481 | } 482 | 483 | // Resets the TCPConn for re-use 484 | func (conn *TCPConn) Reset(netConn net.Conn) { 485 | conn.Conn = netConn 486 | conn.ctx = nil 487 | } 488 | 489 | // Sets start timer to "now" 490 | func (conn *TCPConn) Start() { 491 | conn.ts = time.Now().UnixNano() 492 | } 493 | 494 | // Starts TLS inline 495 | func (conn *TCPConn) StartTLS(config *tls.Config) error { 496 | if config == nil { 497 | config = conn.GetServer().GetTLSConfig() 498 | } 499 | if config == nil { 500 | return fmt.Errorf("no valid TLS config given") 501 | } 502 | conn.Conn = tls.Server(conn.Conn, config) 503 | return nil 504 | } 505 | 506 | // Checks whether given net.TCPAddr is a IPv6 address 507 | func IsIPv6Addr(addr *net.TCPAddr) bool { 508 | return addr.IP.To4() == nil && len(addr.IP) == net.IPv6len 509 | } 510 | --------------------------------------------------------------------------------