├── .gitignore ├── README.md ├── fast-tcp-benchmark └── main.go └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fast-tcp-server 2 | 3 | For the Nov 29th Golang Phoenix Meetup 4 | 5 | [Fastest TCP Server Competition](https://www.meetup.com/Golang-Phoenix/events/244724036/) 6 | 7 | ## Server 8 | 9 | ```bash 10 | go get -u github.com/tidwall/fast-tcp-server 11 | ``` 12 | 13 | ## Benchmark Tool 14 | 15 | ```bash 16 | go get -u github.com/tidwall/fast-tcp-server/fast-tcp-benchmark 17 | ``` 18 | 19 | 20 | -------------------------------------------------------------------------------- /fast-tcp-benchmark/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "math/rand" 8 | "net" 9 | "sync" 10 | "sync/atomic" 11 | "time" 12 | ) 13 | 14 | func main() { 15 | var addr string 16 | var clients int 17 | var count int 18 | var pipeline int 19 | flag.StringVar(&addr, "a", ":3280", "server address") 20 | flag.IntVar(&clients, "c", 6, "number of clients") 21 | flag.IntVar(&count, "n", 10000000, "number of messages") 22 | flag.IntVar(&pipeline, "P", 1, "pipeline messages") 23 | flag.Parse() 24 | 25 | rand.Seed(time.Now().UnixNano()) 26 | fmt.Printf("generating numbers\n") 27 | var data []byte // includes linebreaks 28 | for i := 0; i < count; i++ { 29 | number := (rand.Int() % (10000000000 - 1000000)) + 1000000 30 | data = append(data, fmt.Sprintf("%010d\n", number)...) 31 | } 32 | 33 | fmt.Printf("connecting to server\n") 34 | var requests int64 35 | var done int64 36 | var wg, wg2 sync.WaitGroup 37 | wg.Add(clients) 38 | for i := 0; i < clients; i++ { 39 | var cdata []byte 40 | if i == clients-1 { 41 | cdata = data 42 | } else { 43 | cdata = data[:count/clients*11] 44 | } 45 | data = data[len(cdata):] 46 | go func(cdata []byte) { 47 | defer wg.Done() 48 | conn, err := net.Dial("tcp", addr) 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | defer conn.Close() 53 | for len(cdata) > 0 { 54 | if len(cdata) <= pipeline*11 { 55 | _, err = conn.Write(cdata) 56 | atomic.AddInt64(&requests, int64(len(cdata)/11)) 57 | cdata = nil 58 | } else { 59 | _, err = conn.Write(cdata[:pipeline*11]) 60 | atomic.AddInt64(&requests, int64(pipeline)) 61 | cdata = cdata[pipeline*11:] 62 | } 63 | if err != nil { 64 | log.Fatal(err) 65 | return 66 | } 67 | } 68 | }(cdata) 69 | } 70 | 71 | wg2.Add(1) 72 | go func() { 73 | start := time.Now() 74 | for atomic.LoadInt64(&done) == 0 { 75 | fmt.Printf("\r%.2f", float64(atomic.LoadInt64(&requests))/time.Since(start).Seconds()) 76 | time.Sleep(time.Second / 10) 77 | } 78 | fmt.Printf(" requests per second\n") 79 | wg2.Done() 80 | }() 81 | wg.Wait() 82 | atomic.StoreInt64(&done, 1) 83 | wg2.Wait() 84 | } 85 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "time" 8 | 9 | "github.com/tidwall/evio" 10 | ) 11 | 12 | func main() { 13 | var pduplicates, puniques int 14 | var ticks int 15 | var logidx int 16 | var requests int 17 | var events evio.Events 18 | streams := make(map[int]*evio.InputStream) 19 | numbers := make(map[int]int) 20 | 21 | for i := 0; ; i++ { 22 | if err := os.Remove(fmt.Sprintf("data.%d.log", i)); err != nil { 23 | if os.IsNotExist(err) { 24 | break 25 | } 26 | log.Fatal(err) 27 | } 28 | } 29 | f, err := os.Create("data.0.log") 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | defer f.Close() 34 | 35 | events.Opened = func(id int, _ evio.Info) (_ []byte, _ evio.Options, action evio.Action) { 36 | if len(streams) == 6 { 37 | action = evio.Close 38 | } else { 39 | streams[id] = new(evio.InputStream) 40 | } 41 | return 42 | } 43 | events.Closed = func(id int, _ error) (_ evio.Action) { 44 | delete(streams, id) 45 | return 46 | } 47 | events.Data = func(id int, in []byte) (out []byte, action evio.Action) { 48 | s := streams[id] 49 | data := s.Begin(in) 50 | nextLine: 51 | if len(data) >= 8 && data[0] == 's' && string(data[:8]) == "shutdown" { 52 | log.Printf("shutdown received") 53 | return nil, evio.Shutdown 54 | } 55 | var number int 56 | for i, j := 0, 0; i < len(data); i++ { 57 | switch { 58 | default: 59 | number, j = number*10+int(data[i]-'0'), j+1 60 | case data[i] == '\r' && i < len(data)-1 && data[i+1] == '\n': 61 | i++ 62 | fallthrough 63 | case data[i] == '\n': 64 | n := len(numbers) 65 | numbers[number]++ 66 | if n == len(numbers) { 67 | pduplicates++ 68 | } else { 69 | puniques++ 70 | if _, err := f.Write(append(append([]byte{}, data[:j]...), '\n')); err != nil { 71 | log.Fatal(err) 72 | } 73 | } 74 | requests++ 75 | data = data[i+1:] 76 | goto nextLine 77 | case data[i] < '0' || data[i] > '9': 78 | log.Printf("malformed data") 79 | return nil, evio.Close 80 | } 81 | } 82 | s.End(data) 83 | return 84 | } 85 | events.Serving = func(_ evio.Server) (_ evio.Action) { 86 | log.Printf("server started on port 3280") 87 | return 88 | } 89 | events.Tick = func() (delay time.Duration, action evio.Action) { 90 | if ticks > 0 { 91 | if ticks%5 == 4 { 92 | log.Printf("period: (unique: %d, duplicates: %d), totals: (uniques: %d, requests: %d)", puniques, pduplicates, len(numbers), requests) 93 | puniques, pduplicates = 0, 0 94 | } 95 | if ticks%10 == 9 { 96 | log.Printf("rotating log: data.%d.log", logidx) 97 | if err := f.Close(); err != nil { 98 | log.Fatal(err) 99 | } 100 | logidx++ 101 | if f, err = os.Create(fmt.Sprintf("data.%d.log", logidx)); err != nil { 102 | log.Fatal(err) 103 | } 104 | } 105 | } 106 | ticks++ 107 | delay = time.Second 108 | return 109 | } 110 | if err := evio.Serve(events, "tcp://:3280"); err != nil { 111 | log.Fatal(err) 112 | } 113 | } 114 | --------------------------------------------------------------------------------