├── benchmarks ├── echo-client │ └── main.go ├── evio-server │ └── main.go ├── gev-server │ └── main.go ├── goreactor-server │ └── main.go └── netpoll-server │ └── main.go ├── callbacks.go ├── eventloop_goroutine.go ├── eventloop_goroutine_poll.go ├── examples ├── chinese_chess │ ├── backend │ │ ├── common_packets │ │ │ ├── chess.go │ │ │ ├── packet.go │ │ │ └── parse.go │ │ ├── common_settings │ │ │ └── settings.go │ │ ├── game_handler │ │ │ └── handler.go │ │ ├── main.go │ │ └── tools │ │ │ ├── chess.go │ │ │ └── packet.go │ └── frontend │ │ ├── comm_packets │ │ ├── chess.go │ │ ├── packet.go │ │ └── parse.go │ │ ├── comm_settings │ │ └── settings.go │ │ ├── main.go │ │ └── tools │ │ ├── draw.go │ │ └── tools.go ├── echo │ └── main.go ├── timer │ └── main.go └── wait_forever │ └── main.go ├── go.mod ├── go.sum ├── load_balance.go ├── pkg ├── async_log │ ├── log.go │ └── log_buffer.go ├── buffer │ └── buffer.go ├── cond │ └── cond.go ├── context │ └── context.go └── event_loop │ ├── channel.go │ ├── evloop.go │ ├── gid.go │ ├── id_generator.go │ ├── poller.go │ └── timer_queue.go ├── tcp_acceptor.go ├── tcp_conn.go └── tcp_server.go /benchmarks/echo-client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "flag" 6 | "fmt" 7 | "net" 8 | "time" 9 | ) 10 | 11 | var addr = flag.String("a", "localhost:8000", "address") 12 | var num = flag.Int("c", 1, "connection number") 13 | var timeOut = flag.Int("t", 2, "timeout second") 14 | var msgLen = flag.Int("m", 1024, "message length") 15 | 16 | var msg []byte 17 | 18 | func main() { 19 | flag.Parse() 20 | 21 | fmt.Printf("*** %d connections, %d seconds, %d byte packets ***\n", *num, *timeOut, *msgLen) 22 | 23 | msg = make([]byte, *msgLen) 24 | rand.Read(msg) 25 | 26 | startC := make(chan interface{}) 27 | closeC := make(chan interface{}) 28 | result := make(chan int64, *num) 29 | req := make(chan int64, *num) 30 | 31 | for i := 0; i < *num; i++ { 32 | conn, err := net.Dial("tcp", *addr) 33 | if err != nil { 34 | panic(err) 35 | } 36 | go handler(conn, startC, closeC, result, req) 37 | } 38 | 39 | // start 40 | close(startC) 41 | 42 | time.Sleep(time.Duration(*timeOut) * time.Second) 43 | // stop 44 | close(closeC) 45 | 46 | var totalMessagesRead, reqCount int64 47 | for i := 0; i < *num; i++ { 48 | totalMessagesRead += <-result 49 | reqCount += <-req 50 | } 51 | 52 | fmt.Println(totalMessagesRead/int64(*timeOut*1024*1024), " MiB/s throughput") 53 | fmt.Println(reqCount/int64(*timeOut), " qps") 54 | } 55 | 56 | func handler(conn net.Conn, startC chan interface{}, closeC chan interface{}, result, req chan int64) { 57 | var count, reqCount int64 58 | buf := make([]byte, 2*(*msgLen)) 59 | <-startC 60 | 61 | _, e := conn.Write(msg) 62 | if e != nil { 63 | fmt.Println("Error to send message because of ", e.Error()) 64 | } 65 | 66 | for { 67 | select { 68 | case <-closeC: 69 | result <- count 70 | req <- reqCount 71 | conn.Close() 72 | return 73 | default: 74 | n, err := conn.Read(buf) 75 | if n > 0 { 76 | count += int64(n) 77 | reqCount++ 78 | } 79 | if err != nil { 80 | fmt.Print("Error to read message because of ", err) 81 | result <- count 82 | conn.Close() 83 | return 84 | } 85 | 86 | _, err = conn.Write(buf[:n]) 87 | if err != nil { 88 | fmt.Println("Error to send message because of ", e.Error()) 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /benchmarks/evio-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "strings" 8 | 9 | "github.com/tidwall/evio" 10 | ) 11 | 12 | func main() { 13 | var port int 14 | var loops int 15 | var udp bool 16 | var trace bool 17 | var reuseport bool 18 | var stdlib bool 19 | 20 | flag.IntVar(&port, "port", 8000, "server port") 21 | flag.BoolVar(&udp, "udp", false, "listen on udp") 22 | flag.BoolVar(&reuseport, "reuseport", false, "reuseport (SO_REUSEPORT)") 23 | flag.BoolVar(&trace, "trace", false, "print packets to console") 24 | flag.IntVar(&loops, "loops", -1, "num loops") 25 | flag.BoolVar(&stdlib, "stdlib", false, "use stdlib") 26 | flag.Parse() 27 | 28 | var events evio.Events 29 | events.NumLoops = loops 30 | events.Serving = func(srv evio.Server) (action evio.Action) { 31 | if reuseport { 32 | log.Printf("reuseport") 33 | } 34 | if stdlib { 35 | log.Printf("stdlib") 36 | } 37 | return 38 | } 39 | events.Data = func(c evio.Conn, in []byte) (out []byte, action evio.Action) { 40 | if trace { 41 | log.Printf("%s", strings.TrimSpace(string(in))) 42 | } 43 | out = in 44 | return 45 | } 46 | scheme := "tcp" 47 | if udp { 48 | scheme = "udp" 49 | } 50 | if stdlib { 51 | scheme += "-net" 52 | } 53 | log.Fatal(evio.Serve(events, fmt.Sprintf("%s://:%d?reuseport=%t", scheme, port, reuseport))) 54 | } 55 | -------------------------------------------------------------------------------- /benchmarks/gev-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "net/http" 6 | _ "net/http/pprof" 7 | "strconv" 8 | 9 | "github.com/Allenxuxu/gev" 10 | ) 11 | 12 | type example struct { 13 | } 14 | 15 | func (s *example) OnConnect(c *gev.Connection) {} 16 | func (s *example) OnMessage(c *gev.Connection, ctx interface{}, data []byte) (out interface{}) { 17 | 18 | out = data 19 | 20 | //msg := append([]byte{}, data...) 21 | //go func() { 22 | // if err := c.Send(msg); err != nil { 23 | // //log.Errorf("send error :%v", err) 24 | // } 25 | //}() 26 | return 27 | } 28 | 29 | func (s *example) OnClose(c *gev.Connection) { 30 | //log.Error("onclose ") 31 | } 32 | 33 | func main() { 34 | go func() { 35 | if err := http.ListenAndServe(":6089", nil); err != nil { 36 | panic(err) 37 | } 38 | }() 39 | 40 | handler := new(example) 41 | var port int 42 | var loops int 43 | 44 | flag.IntVar(&port, "port", 8000, "server port") 45 | flag.IntVar(&loops, "loops", -1, "num loops") 46 | flag.Parse() 47 | 48 | s, err := gev.NewServer(handler, 49 | gev.Network("tcp"), 50 | gev.Address(":"+strconv.Itoa(port)), 51 | gev.NumLoops(loops), 52 | ) 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | s.Start() 58 | } 59 | -------------------------------------------------------------------------------- /benchmarks/goreactor-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "runtime" 5 | 6 | eventloop "github.com/markity/go-reactor/pkg/event_loop" 7 | 8 | "github.com/markity/go-reactor/pkg/buffer" 9 | 10 | goreactor "github.com/markity/go-reactor" 11 | ) 12 | 13 | func main() { 14 | evloop := eventloop.NewEventLoop() 15 | server := goreactor.NewTCPServer(evloop, "127.0.0.1:8000", runtime.NumCPU(), goreactor.RoundRobin()) 16 | server.SetMessageCallback(func(t goreactor.TCPConnection, b buffer.Buffer) { 17 | t.Send(b.Peek()) 18 | b.RetrieveAll() 19 | }) 20 | server.Start() 21 | evloop.Loop() 22 | } 23 | -------------------------------------------------------------------------------- /benchmarks/netpoll-server/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "time" 22 | 23 | "github.com/cloudwego/netpoll" 24 | ) 25 | 26 | func main() { 27 | network, address := "tcp", ":8000" 28 | listener, _ := netpoll.CreateListener(network, address) 29 | 30 | eventLoop, _ := netpoll.NewEventLoop( 31 | handle, 32 | netpoll.WithOnPrepare(prepare), 33 | netpoll.WithOnConnect(connect), 34 | netpoll.WithReadTimeout(time.Second), 35 | ) 36 | 37 | // start listen loop ... 38 | eventLoop.Serve(listener) 39 | } 40 | 41 | var _ netpoll.OnPrepare = prepare 42 | var _ netpoll.OnConnect = connect 43 | var _ netpoll.OnRequest = handle 44 | var _ netpoll.CloseCallback = close 45 | 46 | func prepare(connection netpoll.Connection) context.Context { 47 | return context.Background() 48 | } 49 | 50 | func close(connection netpoll.Connection) error { 51 | return nil 52 | } 53 | 54 | func connect(ctx context.Context, connection netpoll.Connection) context.Context { 55 | connection.AddCloseCallback(close) 56 | return ctx 57 | } 58 | 59 | func handle(ctx context.Context, connection netpoll.Connection) error { 60 | reader, writer := connection.Reader(), connection.Writer() 61 | defer reader.Release() 62 | 63 | msg, _ := reader.ReadString(reader.Len()) 64 | 65 | writer.WriteString(msg) 66 | writer.Flush() 67 | 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /callbacks.go: -------------------------------------------------------------------------------- 1 | package goreactor 2 | 3 | import "github.com/markity/go-reactor/pkg/buffer" 4 | 5 | type ConnectedCallbackFunc func(TCPConnection) 6 | type DisConnectedCallbackFunc func(TCPConnection) 7 | type MessageCallbackFunc func(TCPConnection, buffer.Buffer) 8 | type HighWaterCallbackFunc func(TCPConnection, int) 9 | type WriteCompleteCallbackFunc func(TCPConnection) 10 | 11 | func defaultHighWaterMarkCallback(tc TCPConnection, sz int) { 12 | // just do nothing 13 | } 14 | 15 | func defaultWriteCompleteCallback(tc TCPConnection) { 16 | // just do nothing 17 | } 18 | 19 | func defaultDisConnectedCallback(tc TCPConnection) { 20 | // just do nothing 21 | } 22 | 23 | func defaultConnectedCallback(tc TCPConnection) { 24 | // just do nothing 25 | } 26 | 27 | func defaultMessageCallback(tc TCPConnection, buf buffer.Buffer) { 28 | buf.RetrieveAsString() 29 | } 30 | -------------------------------------------------------------------------------- /eventloop_goroutine.go: -------------------------------------------------------------------------------- 1 | package goreactor 2 | 3 | import ( 4 | "sync/atomic" 5 | 6 | eventloop "github.com/markity/go-reactor/pkg/event_loop" 7 | ) 8 | 9 | type eventloopGoroutine struct { 10 | started int64 11 | loop eventloop.EventLoop 12 | } 13 | 14 | func (routine *eventloopGoroutine) startLoop() { 15 | if !atomic.CompareAndSwapInt64(&routine.started, 0, 1) { 16 | panic("already started") 17 | } 18 | 19 | go func() { 20 | routine.loop.Loop() 21 | }() 22 | } 23 | 24 | func newEventLoopGoroutine() *eventloopGoroutine { 25 | loop := eventloop.NewEventLoop() 26 | return &eventloopGoroutine{ 27 | started: 0, 28 | loop: loop, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /eventloop_goroutine_poll.go: -------------------------------------------------------------------------------- 1 | package goreactor 2 | 3 | import ( 4 | eventloop "github.com/markity/go-reactor/pkg/event_loop" 5 | ) 6 | 7 | type eventloopGoroutinePoll struct { 8 | numOfGoroutine int 9 | started int64 10 | loopGoroutines []*eventloopGoroutine 11 | loops []eventloop.EventLoop 12 | baseLoop eventloop.EventLoop 13 | strategy LoadBalanceStrategy 14 | } 15 | 16 | func newEventloopGoroutinePoll(baseLoop eventloop.EventLoop, 17 | numOfGoroutinePoll int, strategy LoadBalanceStrategy) *eventloopGoroutinePoll { 18 | if numOfGoroutinePoll < 0 { 19 | panic(numOfGoroutinePoll) 20 | } 21 | 22 | loopGoroutines := make([]*eventloopGoroutine, 0, numOfGoroutinePoll) 23 | loops := make([]eventloop.EventLoop, 0, numOfGoroutinePoll) 24 | for i := 1; i <= numOfGoroutinePoll; i++ { 25 | g := newEventLoopGoroutine() 26 | loopGoroutines = append(loopGoroutines, g) 27 | loops = append(loops, g.loop) 28 | } 29 | 30 | return &eventloopGoroutinePoll{ 31 | numOfGoroutine: numOfGoroutinePoll, 32 | started: 0, 33 | baseLoop: baseLoop, 34 | strategy: strategy, 35 | loopGoroutines: loopGoroutines, 36 | loops: loops, 37 | } 38 | } 39 | 40 | func (poll *eventloopGoroutinePoll) start() { 41 | if poll.started == 1 { 42 | panic(poll.started) 43 | } 44 | 45 | for _, loop := range poll.loopGoroutines { 46 | loop.startLoop() 47 | } 48 | 49 | poll.started = 1 50 | } 51 | 52 | func (poll *eventloopGoroutinePoll) getNext() eventloop.EventLoop { 53 | if poll.started == 0 { 54 | panic("not started yet") 55 | } 56 | 57 | loop := poll.baseLoop 58 | if poll.numOfGoroutine != 0 { 59 | loop = poll.strategy(poll.loops) 60 | } 61 | 62 | return loop 63 | } 64 | -------------------------------------------------------------------------------- /examples/chinese_chess/backend/common_packets/chess.go: -------------------------------------------------------------------------------- 1 | package commpackets 2 | 3 | // 游戏布局是上黑下红 4 | // 红方先手, 棋盘的大小是一半9*5, 共90个点 5 | // 记左下角为0,0, 右上角为8,9 6 | // 一个棋盘由9*10的二维数组组成, 这里给成90大小的一维数组 7 | /* 8 | y 9 | 上 10 | | 11 | | 12 | |--------右 x 13 | */ 14 | 15 | // 棋盘是一个100大小的数组 16 | 17 | type PieceType int 18 | 19 | const ( 20 | // 车 21 | PieceTypeChe PieceType = iota 22 | // 马 23 | PieceTypeMa 24 | // 象 25 | PieceTypeXiang 26 | // 士 27 | PieceTypeShi 28 | // 帅 29 | PieceTypeShuai 30 | // 炮 31 | PieceTypePao 32 | // 兵 33 | PieceTypeBing 34 | ) 35 | 36 | type GameSide int 37 | 38 | const ( 39 | GameSideRed GameSide = iota 40 | GameSideBlack 41 | ) 42 | 43 | type ChessPoint struct { 44 | X int 45 | Y int 46 | Type PieceType `json:"chess_piece_type"` 47 | Side GameSide `json:"side"` 48 | } 49 | 50 | // 棋盘对象, 如果为null, 那么就是没有棋子 51 | type ChessTable [90]*ChessPoint 52 | 53 | func (ct *ChessTable) ClearPoint(x int, y int) { 54 | ct[y*9+x] = nil 55 | } 56 | 57 | // 返回原来的棋子 58 | func (ct *ChessTable) SetPoint(x int, y int, side GameSide, piece PieceType) *ChessPoint { 59 | former := ct[y*9+x] 60 | ct[y*9+x] = &ChessPoint{X: x, Y: y, Side: side, Type: piece} 61 | 62 | return former 63 | } 64 | 65 | // 修改拿到的点不会修改棋盘布局, 可以把拿到的对象占为己有 66 | func (ct *ChessTable) GetPoint(x int, y int) *ChessPoint { 67 | p := ct[y*9+x] 68 | if p == nil { 69 | return nil 70 | } 71 | return &ChessPoint{X: x, Y: y, Type: p.Type, Side: p.Side} 72 | } 73 | 74 | // 返回默认布局的棋盘, 就是开始游戏的棋盘布局 75 | func NewDefaultChessTable() *ChessTable { 76 | emptyTable := ChessTable{} 77 | 78 | // 红方 79 | emptyTable.SetPoint(0, 0, GameSideRed, PieceTypeChe) 80 | emptyTable.SetPoint(1, 0, GameSideRed, PieceTypeMa) 81 | emptyTable.SetPoint(2, 0, GameSideRed, PieceTypeXiang) 82 | emptyTable.SetPoint(3, 0, GameSideRed, PieceTypeShi) 83 | emptyTable.SetPoint(4, 0, GameSideRed, PieceTypeShuai) 84 | emptyTable.SetPoint(5, 0, GameSideRed, PieceTypeShi) 85 | emptyTable.SetPoint(6, 0, GameSideRed, PieceTypeXiang) 86 | emptyTable.SetPoint(7, 0, GameSideRed, PieceTypeMa) 87 | emptyTable.SetPoint(8, 0, GameSideRed, PieceTypeChe) 88 | emptyTable.SetPoint(1, 2, GameSideRed, PieceTypePao) 89 | emptyTable.SetPoint(7, 2, GameSideRed, PieceTypePao) 90 | emptyTable.SetPoint(0, 3, GameSideRed, PieceTypeBing) 91 | emptyTable.SetPoint(2, 3, GameSideRed, PieceTypeBing) 92 | emptyTable.SetPoint(4, 3, GameSideRed, PieceTypeBing) 93 | emptyTable.SetPoint(6, 3, GameSideRed, PieceTypeBing) 94 | emptyTable.SetPoint(8, 3, GameSideRed, PieceTypeBing) 95 | 96 | // 黑方 97 | emptyTable.SetPoint(0, 9, GameSideBlack, PieceTypeChe) 98 | emptyTable.SetPoint(1, 9, GameSideBlack, PieceTypeMa) 99 | emptyTable.SetPoint(2, 9, GameSideBlack, PieceTypeXiang) 100 | emptyTable.SetPoint(3, 9, GameSideBlack, PieceTypeShi) 101 | emptyTable.SetPoint(4, 9, GameSideBlack, PieceTypeShuai) 102 | emptyTable.SetPoint(5, 9, GameSideBlack, PieceTypeShi) 103 | emptyTable.SetPoint(6, 9, GameSideBlack, PieceTypeXiang) 104 | emptyTable.SetPoint(7, 9, GameSideBlack, PieceTypeMa) 105 | emptyTable.SetPoint(8, 9, GameSideBlack, PieceTypeChe) 106 | emptyTable.SetPoint(1, 7, GameSideBlack, PieceTypePao) 107 | emptyTable.SetPoint(7, 7, GameSideBlack, PieceTypePao) 108 | emptyTable.SetPoint(0, 6, GameSideBlack, PieceTypeBing) 109 | emptyTable.SetPoint(2, 6, GameSideBlack, PieceTypeBing) 110 | emptyTable.SetPoint(4, 6, GameSideBlack, PieceTypeBing) 111 | emptyTable.SetPoint(6, 6, GameSideBlack, PieceTypeBing) 112 | emptyTable.SetPoint(8, 6, GameSideBlack, PieceTypeBing) 113 | 114 | return &emptyTable 115 | } 116 | -------------------------------------------------------------------------------- /examples/chinese_chess/backend/common_packets/packet.go: -------------------------------------------------------------------------------- 1 | package commpackets 2 | 3 | import "encoding/json" 4 | 5 | type PacketType int 6 | 7 | const ( 8 | // 心跳包 9 | PacketTypeHeartbeat PacketType = iota 10 | // 客户端要求开始匹配 11 | PacketTypeClientStartMatch 12 | // 服务端表示已经开始匹配 13 | PacketTypeServerMatching 14 | // 匹配完毕, 即将开始游戏 15 | PacketTypeServerMatchedOK 16 | // 客户端发送下棋的消息 17 | PacketTypeClientMove 18 | // 服务端告知用户下棋结果 19 | PacketTypeServerMoveResp 20 | // 通知游戏结束 21 | PacketTypeServerGameOver 22 | // 通知对方掉线 23 | PacketTypeServerRemoteLoseConnection 24 | // 对方下棋下好了 25 | PacketTypeServerNotifyRemoteMove 26 | ) 27 | 28 | type PacketHeader struct { 29 | Type *PacketType `json:"type"` 30 | } 31 | 32 | type PacketHeartbeat struct { 33 | PacketHeader 34 | } 35 | 36 | func (p *PacketHeartbeat) MustMarshalToBytes() []byte { 37 | i := PacketTypeHeartbeat 38 | p.Type = &i 39 | bs, err := json.Marshal(p) 40 | if err != nil { 41 | panic(err) 42 | } 43 | 44 | return bs 45 | } 46 | 47 | type PacketClientStartMatch struct { 48 | PacketHeader 49 | } 50 | 51 | func (p *PacketClientStartMatch) MustMarshalToBytes() []byte { 52 | i := PacketTypeClientStartMatch 53 | p.Type = &i 54 | bs, err := json.Marshal(p) 55 | if err != nil { 56 | panic(err) 57 | } 58 | 59 | return bs 60 | } 61 | 62 | type PacketServerMatching struct { 63 | PacketHeader 64 | } 65 | 66 | func (p *PacketServerMatching) MustMarshalToBytes() []byte { 67 | i := PacketTypeServerMatching 68 | p.Type = &i 69 | bs, err := json.Marshal(p) 70 | if err != nil { 71 | panic(err) 72 | } 73 | 74 | return bs 75 | } 76 | 77 | type PacketServerMatchedOK struct { 78 | PacketHeader 79 | Side GameSide `json:"game_side"` 80 | Table *ChessTable `json:"game_table"` 81 | } 82 | 83 | func (p *PacketServerMatchedOK) MustMarshalToBytes() []byte { 84 | i := PacketTypeServerMatchedOK 85 | p.Type = &i 86 | bs, err := json.Marshal(p) 87 | if err != nil { 88 | panic(err) 89 | } 90 | 91 | return bs 92 | } 93 | 94 | type PacketClientMove struct { 95 | PacketHeader 96 | FromX int `json:"from_x"` 97 | FromY int `json:"from_y"` 98 | ToX int `json:"to_x"` 99 | ToY int `json:"to_y"` 100 | } 101 | 102 | func (p *PacketClientMove) MustMarshalToBytes() []byte { 103 | i := PacketTypeClientMove 104 | p.Type = &i 105 | bs, err := json.Marshal(p) 106 | if err != nil { 107 | panic(err) 108 | } 109 | 110 | return bs 111 | } 112 | 113 | type PacketServerMoveResp struct { 114 | PacketHeader 115 | OK bool `json:"ok"` 116 | // 下面的字段只有在OK == true的时候出现 117 | TableOnOK *ChessTable `json:"table,omitempty"` 118 | 119 | // 下面的字段只有在OK == false的时候出现 120 | ErrMsgOnFailed *string `json:"errmsg,omitempty"` 121 | } 122 | 123 | func (p *PacketServerMoveResp) MustMarshalToBytes() []byte { 124 | i := PacketTypeServerMoveResp 125 | p.Type = &i 126 | bs, err := json.Marshal(p) 127 | if err != nil { 128 | panic(err) 129 | } 130 | 131 | return bs 132 | } 133 | 134 | type PacketServerGameOver struct { 135 | PacketHeader 136 | Table *ChessTable `json:"final_table"` 137 | WinnerSide GameSide `json:"winner_side"` 138 | } 139 | 140 | func (p *PacketServerGameOver) MustMarshalToBytes() []byte { 141 | i := PacketTypeServerGameOver 142 | p.Type = &i 143 | bs, err := json.Marshal(p) 144 | if err != nil { 145 | panic(err) 146 | } 147 | 148 | return bs 149 | } 150 | 151 | type PacketServerRemoteLoseConnection struct { 152 | PacketHeader 153 | } 154 | 155 | func (p *PacketServerRemoteLoseConnection) MustMarshalToBytes() []byte { 156 | i := PacketTypeServerRemoteLoseConnection 157 | p.Type = &i 158 | bs, err := json.Marshal(p) 159 | if err != nil { 160 | panic(err) 161 | } 162 | 163 | return bs 164 | } 165 | 166 | type PacketServerNotifyRemoteMove struct { 167 | PacketHeader 168 | Table *ChessTable 169 | } 170 | 171 | func (p *PacketServerNotifyRemoteMove) MustMarshalToBytes() []byte { 172 | i := PacketTypeServerNotifyRemoteMove 173 | p.Type = &i 174 | bs, err := json.Marshal(p) 175 | if err != nil { 176 | panic(err) 177 | } 178 | 179 | return bs 180 | } 181 | -------------------------------------------------------------------------------- /examples/chinese_chess/backend/common_packets/parse.go: -------------------------------------------------------------------------------- 1 | package commpackets 2 | 3 | import "encoding/json" 4 | 5 | // 提供给客户端的解析函数, 如果返回值为nil那么读的包为错误的包 6 | // 此时服务端应当断开连接 7 | func ClientParse(bs []byte) interface{} { 8 | header := PacketHeader{} 9 | err := json.Unmarshal(bs, &header) 10 | if err != nil { 11 | return nil 12 | } 13 | 14 | // 有可能发来的是四个字节表示0, 然后包本体一个字节也没有, 那么 15 | // 此时不对应任何一个包, 返回nil 16 | if header.Type == nil { 17 | return nil 18 | } 19 | 20 | switch *header.Type { 21 | case PacketTypeHeartbeat: 22 | return &PacketHeartbeat{} 23 | case PacketTypeServerGameOver: 24 | p := PacketServerGameOver{} 25 | json.Unmarshal(bs, &p) 26 | return &p 27 | case PacketTypeServerMatchedOK: 28 | p := PacketServerMatchedOK{} 29 | json.Unmarshal(bs, &p) 30 | return &p 31 | case PacketTypeServerMatching: 32 | p := PacketServerMatching{} 33 | json.Unmarshal(bs, &p) 34 | return &p 35 | case PacketTypeServerMoveResp: 36 | p := PacketServerMoveResp{} 37 | json.Unmarshal(bs, &p) 38 | return &p 39 | case PacketTypeServerRemoteLoseConnection: 40 | p := PacketServerRemoteLoseConnection{} 41 | json.Unmarshal(bs, &p) 42 | return &p 43 | case PacketTypeServerNotifyRemoteMove: 44 | p := PacketServerNotifyRemoteMove{} 45 | json.Unmarshal(bs, &p) 46 | return &p 47 | default: 48 | return nil 49 | } 50 | } 51 | 52 | // 提供给服务端的解析函数 53 | func ServerParse(bs []byte) interface{} { 54 | header := PacketHeader{} 55 | err := json.Unmarshal(bs, &header) 56 | if err != nil { 57 | return nil 58 | } 59 | 60 | if header.Type == nil { 61 | return nil 62 | } 63 | 64 | switch *header.Type { 65 | case PacketTypeHeartbeat: 66 | return &PacketHeartbeat{} 67 | case PacketTypeClientMove: 68 | p := PacketClientMove{} 69 | json.Unmarshal(bs, &p) 70 | return &p 71 | case PacketTypeClientStartMatch: 72 | p := PacketClientStartMatch{} 73 | json.Unmarshal(bs, &p) 74 | return &p 75 | default: 76 | return nil 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /examples/chinese_chess/backend/common_settings/settings.go: -------------------------------------------------------------------------------- 1 | package commsettings 2 | 3 | // 下面是心跳配置, 最多耗时1s就能检测到对方是否断线 4 | // 客户端和服务端丢需要检测, 双方都进行判定 5 | 6 | // 单位秒, 发送心跳的频率ms 7 | const HeartbeatInterval = 200 8 | 9 | // 最大丢丢失心跳包的个数 10 | const MaxLoseHeartbeat = 5 11 | 12 | // 服务端的配置 13 | const ServerListenIP = "0.0.0.0" 14 | const ServerListenPort = 8080 15 | -------------------------------------------------------------------------------- /examples/chinese_chess/backend/game_handler/handler.go: -------------------------------------------------------------------------------- 1 | package gamehandler 2 | 3 | import ( 4 | "fmt" 5 | "sync/atomic" 6 | 7 | goreactor "github.com/markity/go-reactor" 8 | commsettings "github.com/markity/go-reactor/examples/chinese_chess/backend/common_settings" 9 | "github.com/markity/go-reactor/examples/chinese_chess/backend/tools" 10 | "github.com/markity/go-reactor/pkg/buffer" 11 | 12 | commpackets "github.com/markity/go-reactor/examples/chinese_chess/backend/common_packets" 13 | ) 14 | 15 | type ConnState int 16 | 17 | const ( 18 | ConnStateNone ConnState = iota 19 | ConnStateMatching 20 | ConnStateGaming 21 | ) 22 | 23 | type ConnContext struct { 24 | ID int 25 | LoseHertbeatCount int 26 | Conn goreactor.TCPConnection 27 | ConnState ConnState 28 | 29 | // 下面的字段只有在ConnState为Gaming时有意义 30 | Gcontext *GameContext 31 | } 32 | 33 | type GameContext struct { 34 | RedConnID int 35 | BlackConnID int 36 | Table *commpackets.ChessTable 37 | TurnNow commpackets.GameSide 38 | } 39 | 40 | // 包含所有连接的上下文, 用锁保护 41 | var ConnMap map[int]*ConnContext 42 | 43 | // 用来做自增连接id的计数器 44 | var AtomicIDIncrease atomic.Int32 45 | 46 | func init() { 47 | ConnMap = make(map[int]*ConnContext) 48 | } 49 | 50 | // 用来剔除过时的连接, 并发送心跳包 51 | func OnTimeout(timerID int) { 52 | fmt.Println("on timeout") 53 | var packet = commpackets.PacketHeartbeat{} 54 | heartPacketBytesWithHeader := tools.DoPackWith4BytesHeader(packet.MustMarshalToBytes()) 55 | 56 | for k := range ConnMap { 57 | ConnMap[k].Conn.Send(heartPacketBytesWithHeader) 58 | ConnMap[k].LoseHertbeatCount++ 59 | if ConnMap[k].LoseHertbeatCount >= commsettings.MaxLoseHeartbeat { 60 | ConnMap[k].Conn.ForceClose() 61 | } 62 | } 63 | } 64 | 65 | func OnConnect(c goreactor.TCPConnection) { 66 | fmt.Println("on connect") 67 | connID := int(AtomicIDIncrease.Add(1)) 68 | connCtx := &ConnContext{ID: int(connID), LoseHertbeatCount: 0, Conn: c, ConnState: ConnStateNone, Gcontext: nil} 69 | 70 | ConnMap[connID] = connCtx 71 | 72 | c.SetContext("conn_id", connID) 73 | c.SetDisConnectedCallback(OnClose) 74 | } 75 | 76 | func OnClose(c goreactor.TCPConnection) { 77 | fmt.Println("on close") 78 | connID_, _ := c.GetContext("conn_id") 79 | connID := connID_.(int) 80 | if ConnMap[connID].ConnState == ConnStateGaming { 81 | var remoteID int 82 | if ConnMap[connID].Gcontext.BlackConnID == connID { 83 | remoteID = ConnMap[connID].Gcontext.RedConnID 84 | } else { 85 | remoteID = ConnMap[connID].Gcontext.BlackConnID 86 | } 87 | ConnMap[remoteID].Gcontext = nil 88 | ConnMap[remoteID].ConnState = ConnStateNone 89 | packet := commpackets.PacketServerRemoteLoseConnection{} 90 | packetBytesWithHeader := tools.DoPackWith4BytesHeader(packet.MustMarshalToBytes()) 91 | ConnMap[remoteID].Conn.Send(packetBytesWithHeader) 92 | } 93 | delete(ConnMap, connID) 94 | } 95 | 96 | func OnMessage(c goreactor.TCPConnection, buf buffer.Buffer) { 97 | fmt.Println("on message") 98 | connID_, _ := c.GetContext("conn_id") 99 | connID := connID_.(int) 100 | packetBytes, ok := tools.IsBytesCompleteWith4BytesHeader(buf.Peek()) 101 | if !ok { 102 | fmt.Println("not ok") 103 | return 104 | } 105 | buf.Retrieve(len(packetBytes) + 4) 106 | 107 | packIface := commpackets.ServerParse(packetBytes) 108 | switch packet := packIface.(type) { 109 | // heartbeat包, 清空心跳包 110 | case *commpackets.PacketHeartbeat: 111 | ConnMap[connID].LoseHertbeatCount = 0 112 | return 113 | case *commpackets.PacketClientStartMatch: 114 | if ConnMap[connID].ConnState != ConnStateNone { 115 | // 协议错误 116 | fmt.Println("state none") 117 | c.ForceClose() 118 | } 119 | for _, v := range ConnMap { 120 | // 找到一个正在matching的连接, 做黑方 121 | if v.ID != connID && v.ConnState == ConnStateMatching { 122 | table := commpackets.NewDefaultChessTable() 123 | gameContext := GameContext{ 124 | RedConnID: connID, 125 | BlackConnID: v.ID, 126 | Table: table, 127 | TurnNow: commpackets.GameSideRed, 128 | } 129 | 130 | matchingPacket := commpackets.PacketServerMatching{} 131 | matchingPacketWithHeader := tools.DoPackWith4BytesHeader(matchingPacket.MustMarshalToBytes()) 132 | 133 | packetForBlack := commpackets.PacketServerMatchedOK{Side: commpackets.GameSideBlack, Table: table} 134 | packetForBlackBytesWithHeader := tools.DoPackWith4BytesHeader(packetForBlack.MustMarshalToBytes()) 135 | v.ConnState = ConnStateGaming 136 | v.Gcontext = &gameContext 137 | 138 | v.Conn.Send(matchingPacketWithHeader) 139 | v.Conn.Send(packetForBlackBytesWithHeader) 140 | 141 | packetForRed := commpackets.PacketServerMatchedOK{Side: commpackets.GameSideRed, Table: table} 142 | packetForRedBytesWithHeader := tools.DoPackWith4BytesHeader(packetForRed.MustMarshalToBytes()) 143 | ConnMap[connID].ConnState = ConnStateGaming 144 | ConnMap[connID].Gcontext = &gameContext 145 | 146 | ConnMap[connID].Conn.Send(matchingPacketWithHeader) 147 | ConnMap[connID].Conn.Send(packetForRedBytesWithHeader) 148 | return 149 | } 150 | } 151 | 152 | // 找不到一个匹配的, 那么标记为正在匹配 153 | ConnMap[connID].ConnState = ConnStateMatching 154 | retPacket := commpackets.PacketServerMatching{} 155 | retPacketBytesWithHeader := tools.DoPackWith4BytesHeader(retPacket.MustMarshalToBytes()) 156 | c.Send(retPacketBytesWithHeader) 157 | return 158 | case *commpackets.PacketClientMove: 159 | connCtx := ConnMap[connID] 160 | var connSide commpackets.GameSide 161 | var remoteSide commpackets.GameSide 162 | var remoteConnCtx *ConnContext 163 | if connCtx.Gcontext.BlackConnID == connID { 164 | connSide = commpackets.GameSideBlack 165 | remoteSide = commpackets.GameSideRed 166 | remoteConnCtx = ConnMap[connCtx.Gcontext.RedConnID] 167 | } else { 168 | connSide = commpackets.GameSideRed 169 | remoteSide = commpackets.GameSideBlack 170 | remoteConnCtx = ConnMap[connCtx.Gcontext.BlackConnID] 171 | } 172 | 173 | // 协议错误, 强行关闭连接 174 | if connCtx.ConnState != ConnStateGaming { 175 | c.ForceClose() 176 | return 177 | } 178 | 179 | if connCtx.Gcontext.TurnNow != connSide { 180 | retPacket := commpackets.PacketServerMoveResp{OK: false} 181 | msg := "it is not your turn" 182 | retPacket.ErrMsgOnFailed = &msg 183 | retPacketBytesWithHeader := tools.DoPackWith4BytesHeader(retPacket.MustMarshalToBytes()) 184 | c.Send(retPacketBytesWithHeader) 185 | return 186 | } 187 | 188 | gameover, ok := tools.DoMove(connSide, connCtx.Gcontext.Table, packet.FromX, packet.FromY, packet.ToX, packet.ToY) 189 | if !ok { 190 | retPacket := commpackets.PacketServerMoveResp{OK: false} 191 | msg := "you cannot move like this" 192 | retPacket.ErrMsgOnFailed = &msg 193 | retPacketBytesWithHeader := tools.DoPackWith4BytesHeader(retPacket.MustMarshalToBytes()) 194 | c.Send(retPacketBytesWithHeader) 195 | return 196 | } 197 | 198 | if !gameover { 199 | notifyPacket := commpackets.PacketServerNotifyRemoteMove{Table: connCtx.Gcontext.Table} 200 | notifyPacketBytesWithHeader := tools.DoPackWith4BytesHeader(notifyPacket.MustMarshalToBytes()) 201 | remoteConnCtx.Conn.Send(notifyPacketBytesWithHeader) 202 | 203 | retPacket := commpackets.PacketServerMoveResp{OK: true, TableOnOK: connCtx.Gcontext.Table} 204 | retPacketBytesWithHeader := tools.DoPackWith4BytesHeader(retPacket.MustMarshalToBytes()) 205 | connCtx.Gcontext.TurnNow = remoteSide 206 | c.Send(retPacketBytesWithHeader) 207 | return 208 | } 209 | 210 | // 游戏结束 211 | retPacket := commpackets.PacketServerGameOver{Table: connCtx.Gcontext.Table, WinnerSide: connSide} 212 | retPacketBytesWithHeader := tools.DoPackWith4BytesHeader(retPacket.MustMarshalToBytes()) 213 | 214 | // 清空资源 215 | connCtx.ConnState = ConnStateNone 216 | remoteConnCtx.ConnState = ConnStateNone 217 | connCtx.Gcontext = nil 218 | remoteConnCtx.Gcontext = nil 219 | 220 | remoteConnCtx.Conn.Send(retPacketBytesWithHeader) 221 | return 222 | case nil: 223 | // 协议错误, 直接关闭 224 | c.ForceClose() 225 | return 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /examples/chinese_chess/backend/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | goreactor "github.com/markity/go-reactor" 8 | commsettings "github.com/markity/go-reactor/examples/chinese_chess/backend/common_settings" 9 | gamehandler "github.com/markity/go-reactor/examples/chinese_chess/backend/game_handler" 10 | eventloop "github.com/markity/go-reactor/pkg/event_loop" 11 | ) 12 | 13 | func main() { 14 | loop := eventloop.NewEventLoop() 15 | loop.RunAt(time.Now(), time.Millisecond*commsettings.HeartbeatInterval, gamehandler.OnTimeout) 16 | 17 | listenIPPort := fmt.Sprintf("%v:%v", commsettings.ServerListenIP, commsettings.ServerListenPort) 18 | fmt.Println(listenIPPort) 19 | server := goreactor.NewTCPServer(loop, listenIPPort, 0, goreactor.RoundRobin()) 20 | server.SetConnectionCallback(gamehandler.OnConnect) 21 | server.SetMessageCallback(gamehandler.OnMessage) 22 | 23 | err := server.Start() 24 | if err != nil { 25 | panic(err) 26 | } 27 | loop.Loop() 28 | } 29 | -------------------------------------------------------------------------------- /examples/chinese_chess/backend/tools/chess.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "math" 5 | 6 | commpackets "github.com/markity/go-reactor/examples/chinese_chess/backend/common_packets" 7 | ) 8 | 9 | func isCrossedRiver(side commpackets.GameSide, x, y int) bool { 10 | if side == commpackets.GameSideRed { 11 | return y >= 5 12 | } else { 13 | return y <= 4 14 | } 15 | } 16 | 17 | func pointMatch(x1 int, y1 int, x2 int, y2 int) bool { 18 | return x1 == x2 && y1 == y2 19 | } 20 | 21 | func isPointCrossRange(x, y int) bool { 22 | return x > 8 || y > 9 || x < 0 || y < 0 23 | } 24 | 25 | func isSameLine(x1, y1, x2, y2 int) bool { 26 | return x1 == x2 || y1 == y2 27 | } 28 | 29 | // 检查两个点之间有没有点, 假设已经在一条直线上, 两点不能是同一点 30 | // 不包含两个点, 如果要判断端点, 自行加判断 31 | func hasChessBetweenTwoPoints(table *commpackets.ChessTable, x1 int, y1 int, x2 int, y2 int) bool { 32 | if x1 == x2 { 33 | var yMin int 34 | var yMax int 35 | if y1 < y2 { 36 | yMin = y1 37 | yMax = y2 38 | } else { 39 | yMin = y2 40 | yMax = y1 41 | } 42 | 43 | for y0 := yMin + 1; y0 < yMax; y0++ { 44 | if table.GetPoint(x1, y0) != nil { 45 | return true 46 | } 47 | } 48 | } else { 49 | var xMin int 50 | var xMax int 51 | if x1 < x2 { 52 | xMin = x1 53 | xMax = x2 54 | } else { 55 | xMin = x2 56 | xMax = x1 57 | } 58 | 59 | for x0 := xMin + 1; x0 < xMax; x0++ { 60 | if table.GetPoint(x0, y1) != nil { 61 | return true 62 | } 63 | } 64 | } 65 | return false 66 | 67 | } 68 | 69 | func DoMove(side commpackets.GameSide, table *commpackets.ChessTable, fromX int, fromY int, toX int, toY int) (gameover bool, moveok bool) { 70 | // 不能有非法的坐标 71 | if isPointCrossRange(fromX, fromY) || isPointCrossRange(toX, toY) { 72 | return false, false 73 | } 74 | 75 | // from点不能是空的 76 | from := table.GetPoint(fromX, fromY) 77 | if from == nil { 78 | return false, false 79 | } 80 | 81 | // 不能移动非己方的棋子 82 | if from.Side != side { 83 | return false, false 84 | } 85 | 86 | // 不能不移动 87 | if from.X == toX && from.Y == toY { 88 | return false, false 89 | } 90 | 91 | switch from.Type { 92 | case commpackets.PieceTypeBing: 93 | if isCrossedRiver(side, from.X, from.Y) { 94 | // 过河了, 移动方向只能是上下左右 95 | if !pointMatch(toX, toY, from.X+1, from.Y) && 96 | !pointMatch(toX, toY, from.X, from.Y+1) && 97 | !pointMatch(toX, toY, from.X-1, from.Y) && 98 | !pointMatch(toX, toY, from.X, from.Y-1) { 99 | return false, false 100 | } 101 | 102 | // 红方的y不能减小 103 | if side == commpackets.GameSideRed { 104 | if from.Y > toY { 105 | return false, false 106 | } 107 | } else { 108 | // 黑方的y不能增大 109 | if from.Y < toY { 110 | return false, false 111 | } 112 | } 113 | } else { 114 | // 没有过河, 只能前后移动 115 | if !pointMatch(toX, toY, from.X, from.Y+1) && 116 | !pointMatch(toX, toY, from.X, from.Y-1) { 117 | return false, false 118 | } 119 | 120 | // 红方的y不能减小 121 | if side == commpackets.GameSideRed { 122 | if from.Y > toY { 123 | return false, false 124 | } 125 | } else { 126 | // 黑方的y不能增大 127 | if from.Y < toY { 128 | return false, false 129 | } 130 | } 131 | } 132 | table.ClearPoint(fromX, fromY) 133 | pre := table.SetPoint(toX, toY, side, commpackets.PieceTypeBing) 134 | return pre != nil && pre.Type == commpackets.PieceTypeShuai, true 135 | case commpackets.PieceTypePao: 136 | // 炮至少要走直线 137 | if !isSameLine(from.X, from.Y, toX, toY) { 138 | return false, false 139 | } 140 | 141 | // 如果两个点中间没有子 142 | if !hasChessBetweenTwoPoints(table, from.X, from.Y, toX, toY) { 143 | // 如果终点有子, 失败 144 | if table.GetPoint(toX, toY) != nil { 145 | return false, false 146 | } 147 | } else { 148 | // 如果中间有子, 判断对面是否是敌方的子 149 | if table.GetPoint(toX, toY).Side == side { 150 | return false, false 151 | } 152 | } 153 | table.ClearPoint(from.X, from.Y) 154 | pre := table.SetPoint(toX, toY, side, commpackets.PieceTypePao) 155 | return pre != nil && pre.Type == commpackets.PieceTypeShuai, true 156 | case commpackets.PieceTypeChe: 157 | // 必须是直线 158 | if !isSameLine(from.X, from.Y, toX, toY) { 159 | return false, false 160 | } 161 | 162 | // 中间如果有子 163 | if hasChessBetweenTwoPoints(table, from.X, from.Y, toX, toY) { 164 | return false, false 165 | } 166 | 167 | // 直线, 中间无子, 对端有子是己方阵营 168 | if table.GetPoint(toX, toY) != nil { 169 | if table.GetPoint(toX, toY).Side == side { 170 | return false, false 171 | } 172 | } 173 | 174 | table.ClearPoint(from.X, from.Y) 175 | pre := table.SetPoint(toX, toY, side, commpackets.PieceTypeChe) 176 | return pre != nil && pre.Type == commpackets.PieceTypeShuai, true 177 | case commpackets.PieceTypeMa: 178 | if !pointMatch(toX, toY, from.X+1, from.Y+2) && 179 | !pointMatch(toX, toY, from.X+1, from.Y-2) && 180 | !pointMatch(toX, toY, from.X+2, from.Y+1) && 181 | !pointMatch(toX, toY, from.X+2, from.Y-1) && 182 | !pointMatch(toX, toY, from.X-2, from.Y+1) && 183 | !pointMatch(toX, toY, from.X-2, from.Y-1) && 184 | !pointMatch(toX, toY, from.X-1, from.Y+2) && 185 | !pointMatch(toX, toY, from.X-1, from.Y-2) { 186 | return false, false 187 | } 188 | 189 | var pX, pY int 190 | 191 | if from.X-toX == -2 { 192 | pX = from.X + 1 193 | pY = from.Y 194 | } 195 | if from.X-toX == 2 { 196 | pX = from.X - 1 197 | pY = from.Y 198 | } 199 | if from.Y-toY == -2 { 200 | pX = from.X 201 | pY = from.Y + 1 202 | } 203 | if from.Y-toY == 2 { 204 | pX = from.X 205 | pY = from.Y - 1 206 | } 207 | 208 | if table.GetPoint(pX, pY) != nil { 209 | return false, false 210 | } 211 | 212 | table.ClearPoint(from.X, from.Y) 213 | pre := table.SetPoint(toX, toY, side, commpackets.PieceTypeMa) 214 | return pre != nil && pre.Type == commpackets.PieceTypeShuai, true 215 | case commpackets.PieceTypeXiang: 216 | if !pointMatch(toX, toY, from.X+2, from.Y+2) && 217 | !pointMatch(toX, toY, from.X+2, from.Y-2) && 218 | !pointMatch(toX, toY, from.X-2, from.Y+2) && 219 | !pointMatch(toX, toY, from.X-2, from.Y-2) { 220 | return false, false 221 | } 222 | 223 | // 象不能过河 224 | if isCrossedRiver(side, toX, toY) { 225 | return false, false 226 | } 227 | 228 | diffX := from.X - toX 229 | diffY := from.Y - toY 230 | pX := from.X 231 | pY := from.Y 232 | 233 | if diffX == -2 { 234 | pX++ 235 | } 236 | if diffX == 2 { 237 | pX-- 238 | } 239 | if diffY == -2 { 240 | pY++ 241 | } 242 | if diffY == 2 { 243 | pY-- 244 | } 245 | 246 | // 不能堵象眼 247 | if table.GetPoint(pX, pY) != nil { 248 | return false, false 249 | } 250 | 251 | table.ClearPoint(from.X, from.Y) 252 | pre := table.SetPoint(toX, toY, side, commpackets.PieceTypeXiang) 253 | return pre != nil && pre.Type == commpackets.PieceTypeShuai, true 254 | case commpackets.PieceTypeShi: 255 | // 必须出现在特定的位置 256 | if side == commpackets.GameSideRed { 257 | if !pointMatch(toX, toY, 3, 0) && 258 | !pointMatch(toX, toY, 5, 0) && 259 | !pointMatch(toX, toY, 4, 1) && 260 | !pointMatch(toX, toY, 3, 2) && 261 | !pointMatch(toX, toY, 5, 2) { 262 | return false, false 263 | } 264 | } else { 265 | if !pointMatch(toX, toY, 3, 9) && 266 | !pointMatch(toX, toY, 5, 9) && 267 | !pointMatch(toX, toY, 4, 8) && 268 | !pointMatch(toX, toY, 3, 7) && 269 | !pointMatch(toX, toY, 5, 7) { 270 | return false, false 271 | } 272 | } 273 | 274 | // 不能限制移动的位置 275 | diffX := from.X - toX 276 | diffY := from.Y - toY 277 | if diffX > 1 || diffX < -1 { 278 | return false, false 279 | } 280 | if diffY > 1 || diffY < -1 { 281 | return false, false 282 | } 283 | 284 | table.ClearPoint(from.X, from.Y) 285 | pre := table.SetPoint(toX, toY, side, commpackets.PieceTypeShi) 286 | return pre != nil && pre.Type == commpackets.PieceTypeShuai, true 287 | case commpackets.PieceTypeShuai: 288 | // 特殊规则, 如果帅与将之间没有间隔的东西 289 | if table.GetPoint(toX, toY) != nil && table.GetPoint(toX, toY).Type == commpackets.PieceTypeShuai && 290 | isSameLine(from.X, from.Y, toX, toY) && 291 | !hasChessBetweenTwoPoints(table, from.X, from.Y, toX, toY) { 292 | table.ClearPoint(from.X, from.Y) 293 | table.SetPoint(toX, toY, side, commpackets.PieceTypeShuai) 294 | return true, true 295 | } 296 | 297 | // 一般规则 298 | 299 | // 要求to在九宫格之内 300 | if side == commpackets.GameSideRed { 301 | if toX < 3 || toX > 5 || toY > 2 { 302 | return false, false 303 | } 304 | } else { 305 | if toX < 3 || toX > 5 || toY < 7 { 306 | return false, false 307 | } 308 | } 309 | 310 | // 要求只能走一格 311 | diffX := int(math.Abs(float64(from.X) - float64(toX))) 312 | diffY := int(math.Abs(float64(from.Y) - float64(toY))) 313 | if diffX+diffY != 1 { 314 | return false, false 315 | } 316 | 317 | // 要求to的地方没有自己的子 318 | if table.GetPoint(toX, toY) != nil && table.GetPoint(toX, toY).Side == side { 319 | return false, false 320 | } 321 | 322 | table.ClearPoint(fromX, fromY) 323 | table.SetPoint(toX, toY, side, commpackets.PieceTypeShuai) 324 | return false, true 325 | } 326 | 327 | // unreachable 328 | return false, false 329 | } 330 | -------------------------------------------------------------------------------- /examples/chinese_chess/backend/tools/packet.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "net" 7 | ) 8 | 9 | // 从conn读取封包, 有四个字节指定封包的长度 10 | func ReadPacketBytesWith4BytesHeader(conn net.Conn) ([]byte, error) { 11 | lengthBytes := make([]byte, 4) 12 | _, err := io.ReadFull(conn, lengthBytes) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | packetBytes := make([]byte, binary.BigEndian.Uint32(lengthBytes)) 18 | _, err = io.ReadFull(conn, packetBytes) 19 | return packetBytes, err 20 | } 21 | 22 | // 做封包操作 23 | func DoPackWith4BytesHeader(bs []byte) []byte { 24 | buf := make([]byte, 4, 4+len(bs)) 25 | binary.BigEndian.PutUint32(buf, uint32(len(bs))) 26 | buf = append(buf, bs...) 27 | return buf 28 | } 29 | 30 | // 检查字节是否能够组成一个包, 如果能够组成, 拿出包的本体 31 | // 如果bs的前四个字节为0, 此时返回包为空的包->([], true) 32 | func IsBytesCompleteWith4BytesHeader(bs []byte) ([]byte, bool) { 33 | // 不够4个字节, 不能组成包 34 | lenBS := len(bs) 35 | if lenBS < 4 { 36 | return nil, false 37 | } 38 | 39 | bytesLength := binary.BigEndian.Uint32(bs[:4]) 40 | if uint32(lenBS) < 4+bytesLength { 41 | return nil, false 42 | } 43 | 44 | newBS := make([]byte, bytesLength) 45 | copy(newBS, bs[4:4+bytesLength]) 46 | 47 | return newBS, true 48 | } 49 | -------------------------------------------------------------------------------- /examples/chinese_chess/frontend/comm_packets/chess.go: -------------------------------------------------------------------------------- 1 | package commpackets 2 | 3 | // 游戏布局是上黑下红 4 | // 红方先手, 棋盘的大小是一半9*5, 共90个点 5 | // 记左下角为0,0, 右上角为8,9 6 | // 一个棋盘由9*10的二维数组组成, 这里给成90大小的一维数组 7 | /* 8 | y 9 | 上 10 | | 11 | | 12 | |--------右 x 13 | */ 14 | 15 | // 棋盘是一个100大小的数组 16 | 17 | type PieceType int 18 | 19 | const ( 20 | // 车 21 | PieceTypeChe PieceType = iota 22 | // 马 23 | PieceTypeMa 24 | // 象 25 | PieceTypeXiang 26 | // 士 27 | PieceTypeShi 28 | // 帅 29 | PieceTypeShuai 30 | // 炮 31 | PieceTypePao 32 | // 兵 33 | PieceTypeBing 34 | ) 35 | 36 | type GameSide int 37 | 38 | const ( 39 | GameSideRed GameSide = iota 40 | GameSideBlack 41 | ) 42 | 43 | type ChessPoint struct { 44 | X int 45 | Y int 46 | Type PieceType `json:"chess_piece_type"` 47 | Side GameSide `json:"side"` 48 | } 49 | 50 | // 棋盘对象, 如果为null, 那么就是没有棋子 51 | type ChessTable [90]*ChessPoint 52 | 53 | func (ct ChessTable) ClearPoint(x int, y int) { 54 | ct[y*9+x] = nil 55 | } 56 | 57 | // 返回原来的棋子 58 | func (ct *ChessTable) SetPoint(x int, y int, side GameSide, piece PieceType) *ChessPoint { 59 | former := ct[y*9+x] 60 | ct[y*9+x] = &ChessPoint{X: x, Y: y, Side: side, Type: piece} 61 | 62 | return former 63 | } 64 | 65 | // 修改拿到的点不会修改棋盘布局, 可以把拿到的对象占为己有 66 | func (ct *ChessTable) GetPoint(x int, y int) *ChessPoint { 67 | p := ct[y*9+x] 68 | if p == nil { 69 | return nil 70 | } 71 | return &ChessPoint{X: x, Y: y, Type: p.Type, Side: p.Side} 72 | } 73 | 74 | // 返回默认布局的棋盘, 就是开始游戏的棋盘布局 75 | func NewDefaultChessTable() *ChessTable { 76 | emptyTable := ChessTable{} 77 | 78 | // 红方 79 | emptyTable.SetPoint(0, 0, GameSideRed, PieceTypeChe) 80 | emptyTable.SetPoint(1, 0, GameSideRed, PieceTypeMa) 81 | emptyTable.SetPoint(2, 0, GameSideRed, PieceTypeXiang) 82 | emptyTable.SetPoint(3, 0, GameSideRed, PieceTypeShi) 83 | emptyTable.SetPoint(4, 0, GameSideRed, PieceTypeShuai) 84 | emptyTable.SetPoint(5, 0, GameSideRed, PieceTypeShi) 85 | emptyTable.SetPoint(6, 0, GameSideRed, PieceTypeXiang) 86 | emptyTable.SetPoint(7, 0, GameSideRed, PieceTypeMa) 87 | emptyTable.SetPoint(8, 0, GameSideRed, PieceTypeChe) 88 | emptyTable.SetPoint(1, 2, GameSideRed, PieceTypePao) 89 | emptyTable.SetPoint(7, 2, GameSideRed, PieceTypePao) 90 | emptyTable.SetPoint(0, 3, GameSideRed, PieceTypeBing) 91 | emptyTable.SetPoint(2, 3, GameSideRed, PieceTypeBing) 92 | emptyTable.SetPoint(4, 3, GameSideRed, PieceTypeBing) 93 | emptyTable.SetPoint(6, 3, GameSideRed, PieceTypeBing) 94 | emptyTable.SetPoint(8, 3, GameSideRed, PieceTypeBing) 95 | 96 | // 黑方 97 | emptyTable.SetPoint(0, 9, GameSideBlack, PieceTypeChe) 98 | emptyTable.SetPoint(1, 9, GameSideBlack, PieceTypeMa) 99 | emptyTable.SetPoint(2, 9, GameSideBlack, PieceTypeXiang) 100 | emptyTable.SetPoint(3, 9, GameSideBlack, PieceTypeShi) 101 | emptyTable.SetPoint(4, 9, GameSideBlack, PieceTypeShuai) 102 | emptyTable.SetPoint(5, 9, GameSideBlack, PieceTypeShi) 103 | emptyTable.SetPoint(6, 9, GameSideBlack, PieceTypeXiang) 104 | emptyTable.SetPoint(7, 9, GameSideBlack, PieceTypeMa) 105 | emptyTable.SetPoint(8, 9, GameSideBlack, PieceTypeChe) 106 | emptyTable.SetPoint(1, 7, GameSideBlack, PieceTypePao) 107 | emptyTable.SetPoint(7, 7, GameSideBlack, PieceTypePao) 108 | emptyTable.SetPoint(0, 6, GameSideBlack, PieceTypeBing) 109 | emptyTable.SetPoint(2, 6, GameSideBlack, PieceTypeBing) 110 | emptyTable.SetPoint(4, 6, GameSideBlack, PieceTypeBing) 111 | emptyTable.SetPoint(6, 6, GameSideBlack, PieceTypeBing) 112 | emptyTable.SetPoint(8, 6, GameSideBlack, PieceTypeBing) 113 | 114 | return &emptyTable 115 | } 116 | -------------------------------------------------------------------------------- /examples/chinese_chess/frontend/comm_packets/packet.go: -------------------------------------------------------------------------------- 1 | package commpackets 2 | 3 | import "encoding/json" 4 | 5 | type PacketType int 6 | 7 | const ( 8 | // 心跳包 9 | PacketTypeHeartbeat PacketType = iota 10 | // 客户端要求开始匹配 11 | PacketTypeClientStartMatch 12 | // 服务端表示已经开始匹配 13 | PacketTypeServerMatching 14 | // 匹配完毕, 即将开始游戏 15 | PacketTypeServerMatchedOK 16 | // 客户端发送下棋的消息 17 | PacketTypeClientMove 18 | // 服务端告知用户下棋结果 19 | PacketTypeServerMoveResp 20 | // 通知游戏结束 21 | PacketTypeServerGameOver 22 | // 通知对方掉线 23 | PacketTypeServerRemoteLoseConnection 24 | // 对方下棋下好了 25 | PacketTypeServerNotifyRemoteMove 26 | ) 27 | 28 | type PacketHeader struct { 29 | Type *PacketType `json:"type"` 30 | } 31 | 32 | type PacketHeartbeat struct { 33 | PacketHeader 34 | } 35 | 36 | func (p *PacketHeartbeat) MustMarshalToBytes() []byte { 37 | i := PacketTypeHeartbeat 38 | p.Type = &i 39 | bs, err := json.Marshal(p) 40 | if err != nil { 41 | panic(err) 42 | } 43 | 44 | return bs 45 | } 46 | 47 | type PacketClientStartMatch struct { 48 | PacketHeader 49 | } 50 | 51 | func (p *PacketClientStartMatch) MustMarshalToBytes() []byte { 52 | i := PacketTypeClientStartMatch 53 | p.Type = &i 54 | bs, err := json.Marshal(p) 55 | if err != nil { 56 | panic(err) 57 | } 58 | 59 | return bs 60 | } 61 | 62 | type PacketServerMatching struct { 63 | PacketHeader 64 | } 65 | 66 | func (p *PacketServerMatching) MustMarshalToBytes() []byte { 67 | i := PacketTypeServerMatching 68 | p.Type = &i 69 | bs, err := json.Marshal(p) 70 | if err != nil { 71 | panic(err) 72 | } 73 | 74 | return bs 75 | } 76 | 77 | type PacketServerMatchedOK struct { 78 | PacketHeader 79 | Side GameSide `json:"game_side"` 80 | Table *ChessTable `json:"game_table"` 81 | } 82 | 83 | func (p *PacketServerMatchedOK) MustMarshalToBytes() []byte { 84 | i := PacketTypeServerMatchedOK 85 | p.Type = &i 86 | bs, err := json.Marshal(p) 87 | if err != nil { 88 | panic(err) 89 | } 90 | 91 | return bs 92 | } 93 | 94 | type PacketClientMove struct { 95 | PacketHeader 96 | FromX int `json:"from_x"` 97 | FromY int `json:"from_y"` 98 | ToX int `json:"to_x"` 99 | ToY int `json:"to_y"` 100 | } 101 | 102 | func (p *PacketClientMove) MustMarshalToBytes() []byte { 103 | i := PacketTypeClientMove 104 | p.Type = &i 105 | bs, err := json.Marshal(p) 106 | if err != nil { 107 | panic(err) 108 | } 109 | 110 | return bs 111 | } 112 | 113 | type PacketServerMoveResp struct { 114 | PacketHeader 115 | OK bool `json:"ok"` 116 | // 下面的字段只有在OK == true的时候出现 117 | TableOnOK *ChessTable `json:"table,omitempty"` 118 | 119 | // 下面的字段只有在OK == false的时候出现 120 | ErrMsgOnFailed *string `json:"errmsg,omitempty"` 121 | } 122 | 123 | func (p *PacketServerMoveResp) MustMarshalToBytes() []byte { 124 | i := PacketTypeServerMoveResp 125 | p.Type = &i 126 | bs, err := json.Marshal(p) 127 | if err != nil { 128 | panic(err) 129 | } 130 | 131 | return bs 132 | } 133 | 134 | type PacketServerGameOver struct { 135 | PacketHeader 136 | Table *ChessTable `json:"final_table"` 137 | WinnerSide GameSide `json:"winner_side"` 138 | } 139 | 140 | func (p *PacketServerGameOver) MustMarshalToBytes() []byte { 141 | i := PacketTypeServerGameOver 142 | p.Type = &i 143 | bs, err := json.Marshal(p) 144 | if err != nil { 145 | panic(err) 146 | } 147 | 148 | return bs 149 | } 150 | 151 | type PacketServerRemoteLoseConnection struct { 152 | PacketHeader 153 | } 154 | 155 | func (p *PacketServerRemoteLoseConnection) MustMarshalToBytes() []byte { 156 | i := PacketTypeServerRemoteLoseConnection 157 | p.Type = &i 158 | bs, err := json.Marshal(p) 159 | if err != nil { 160 | panic(err) 161 | } 162 | 163 | return bs 164 | } 165 | 166 | type PacketServerNotifyRemoteMove struct { 167 | PacketHeader 168 | Table *ChessTable 169 | } 170 | 171 | func (p *PacketServerNotifyRemoteMove) MustMarshalToBytes() []byte { 172 | i := PacketTypeServerNotifyRemoteMove 173 | p.Type = &i 174 | bs, err := json.Marshal(p) 175 | if err != nil { 176 | panic(err) 177 | } 178 | 179 | return bs 180 | } 181 | -------------------------------------------------------------------------------- /examples/chinese_chess/frontend/comm_packets/parse.go: -------------------------------------------------------------------------------- 1 | package commpackets 2 | 3 | import "encoding/json" 4 | 5 | // 提供给客户端的解析函数, 如果返回值为nil那么读的包为错误的包 6 | // 此时服务端应当断开连接 7 | func ClientParse(bs []byte) interface{} { 8 | header := PacketHeader{} 9 | err := json.Unmarshal(bs, &header) 10 | if err != nil { 11 | return nil 12 | } 13 | 14 | // 有可能发来的是四个字节表示0, 然后包本体一个字节也没有, 那么 15 | // 此时不对应任何一个包, 返回nil 16 | if header.Type == nil { 17 | return nil 18 | } 19 | 20 | switch *header.Type { 21 | case PacketTypeHeartbeat: 22 | return &PacketHeartbeat{} 23 | case PacketTypeServerGameOver: 24 | p := PacketServerGameOver{} 25 | json.Unmarshal(bs, &p) 26 | return &p 27 | case PacketTypeServerMatchedOK: 28 | p := PacketServerMatchedOK{} 29 | json.Unmarshal(bs, &p) 30 | return &p 31 | case PacketTypeServerMatching: 32 | p := PacketServerMatching{} 33 | json.Unmarshal(bs, &p) 34 | return &p 35 | case PacketTypeServerMoveResp: 36 | p := PacketServerMoveResp{} 37 | json.Unmarshal(bs, &p) 38 | return &p 39 | case PacketTypeServerRemoteLoseConnection: 40 | p := PacketServerRemoteLoseConnection{} 41 | json.Unmarshal(bs, &p) 42 | return &p 43 | case PacketTypeServerNotifyRemoteMove: 44 | p := PacketServerNotifyRemoteMove{} 45 | json.Unmarshal(bs, &p) 46 | return &p 47 | default: 48 | return nil 49 | } 50 | } 51 | 52 | // 提供给服务端的解析函数 53 | func ServerParse(bs []byte) interface{} { 54 | header := PacketHeader{} 55 | err := json.Unmarshal(bs, &header) 56 | if err != nil { 57 | return nil 58 | } 59 | 60 | if header.Type == nil { 61 | return nil 62 | } 63 | 64 | switch *header.Type { 65 | case PacketTypeHeartbeat: 66 | return &PacketHeartbeat{} 67 | case PacketTypeClientMove: 68 | p := PacketClientMove{} 69 | json.Unmarshal(bs, &p) 70 | return &p 71 | case PacketTypeClientStartMatch: 72 | p := PacketClientStartMatch{} 73 | json.Unmarshal(bs, &p) 74 | return &p 75 | default: 76 | return nil 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /examples/chinese_chess/frontend/comm_settings/settings.go: -------------------------------------------------------------------------------- 1 | package commsettings 2 | 3 | // 下面是心跳配置, 最多耗时1s就能检测到对方是否断线 4 | // 客户端和服务端丢需要检测, 双方都进行判定 5 | 6 | // 单位秒, 发送心跳的频率ms 7 | const HeartbeatInterval = 200 8 | 9 | // 最大丢丢失心跳包的个数 10 | const MaxLoseHeartbeat = 5 11 | 12 | // 服务端的配置 13 | const ServerListenIP = "127.0.0.1" 14 | const ServerListenPort = 8080 15 | -------------------------------------------------------------------------------- /examples/chinese_chess/frontend/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strings" 7 | "time" 8 | 9 | commpackets "github.com/markity/go-reactor/examples/chinese_chess/frontend/comm_packets" 10 | commsettings "github.com/markity/go-reactor/examples/chinese_chess/frontend/comm_settings" 11 | "github.com/markity/go-reactor/examples/chinese_chess/frontend/tools" 12 | 13 | interactive "github.com/markity/Interactive-Console" 14 | ) 15 | 16 | const ( 17 | StateNone = 0 18 | StateMatching = 1 19 | StateGaming = 2 20 | StateOver = 3 21 | ) 22 | 23 | func main() { 24 | // 连上服务端 25 | conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", commsettings.ServerListenIP, commsettings.ServerListenPort)) 26 | if err != nil { 27 | fmt.Printf("failed to dial to server: %v\n", err) 28 | return 29 | } 30 | 31 | heartbeatLoseCount := 0 32 | state := StateNone 33 | myTurn := false 34 | 35 | errChan := make(chan error, 1) 36 | readFromConnChan := make(chan interface{}) 37 | heartbeatChan := time.NewTicker(commsettings.HeartbeatInterval * time.Millisecond) 38 | 39 | // conn reader 40 | go func() { 41 | for { 42 | packetBytes, err := tools.ReadPacketBytesWith4BytesHeader(conn) 43 | if err != nil { 44 | errChan <- err 45 | return 46 | } 47 | 48 | readFromConnChan <- commpackets.ClientParse(packetBytes) 49 | } 50 | }() 51 | 52 | // 写入start matching的包, 开始匹配 53 | startMatchPacket := commpackets.PacketClientStartMatch{} 54 | startMatchPacketBytesWithHeader := tools.DoPackWith4BytesHeader(startMatchPacket.MustMarshalToBytes()) 55 | _, err = conn.Write(startMatchPacketBytesWithHeader) 56 | if err != nil { 57 | fmt.Printf("network error: %v\n", err) 58 | return 59 | } 60 | 61 | var win *interactive.Win = interactive.Run(interactive.GetDefaultConfig()) 62 | win.SendLineBack("matching...") 63 | 64 | for { 65 | select { 66 | case cmd := <-win.GetCmdChan(): 67 | if strings.TrimSpace(cmd) == "quit" { 68 | win.Stop() 69 | return 70 | } 71 | if state == StateGaming { 72 | var fromX, fromY, toX, toY int 73 | _, err := fmt.Sscanf(cmd, "%d %d %d %d", &fromX, &fromY, &toX, &toY) 74 | if err != nil { 75 | win.PopBackLine() 76 | win.SendLineBack("invalid input") 77 | continue 78 | } 79 | 80 | movePacket := commpackets.PacketClientMove{FromX: fromX, FromY: fromY, ToX: toX, ToY: toY} 81 | movePacketBytesWithHeader := tools.DoPackWith4BytesHeader(movePacket.MustMarshalToBytes()) 82 | _, err = conn.Write(movePacketBytesWithHeader) 83 | if err != nil { 84 | win.Stop() 85 | fmt.Printf("network error: %v\n", err) 86 | return 87 | } 88 | } 89 | case err := <-errChan: 90 | win.Stop() 91 | fmt.Printf("error happened: %v\n", err) 92 | return 93 | case packIface := <-readFromConnChan: 94 | switch packet := packIface.(type) { 95 | case *commpackets.PacketServerMatchedOK: 96 | if state != StateMatching { 97 | panic("protocol error") 98 | } 99 | state = StateGaming 100 | msg := "" 101 | if packet.Side == commpackets.GameSideRed { 102 | msg = "it is your turn, red one" 103 | myTurn = true 104 | } else { 105 | msg = "it is not your turn, you are purple one" 106 | myTurn = false 107 | } 108 | tools.DrawTable(*packet.Table, win, msg) 109 | case *commpackets.PacketServerMatching: 110 | state = StateMatching 111 | case *commpackets.PacketServerMoveResp: 112 | if !packet.OK { 113 | win.PopBackLine() 114 | win.SendLineBack(*packet.ErrMsgOnFailed) 115 | continue 116 | } 117 | tools.DrawTable(*packet.TableOnOK, win, "it is not your turn") 118 | myTurn = false 119 | case *commpackets.PacketServerGameOver: 120 | winner := "" 121 | if myTurn { 122 | winner = "you" 123 | } else { 124 | winner = "him" 125 | } 126 | state = StateOver 127 | tools.DrawTable(*packet.Table, win, "game over, winner is "+winner) 128 | time.Sleep(time.Second * 10) 129 | win.Stop() 130 | return 131 | case *commpackets.PacketHeartbeat: 132 | heartbeatLoseCount = 0 133 | case *commpackets.PacketServerRemoteLoseConnection: 134 | win.Stop() 135 | fmt.Println("remote player closed connection") 136 | return 137 | case *commpackets.PacketServerNotifyRemoteMove: 138 | myTurn = true 139 | tools.DrawTable(*packet.Table, win, "it is your turn") 140 | } 141 | case <-heartbeatChan.C: 142 | heartbeatLoseCount++ 143 | if heartbeatLoseCount >= commsettings.MaxLoseHeartbeat { 144 | win.Stop() 145 | fmt.Println("network error: connection lost") 146 | return 147 | } 148 | heartbeatPacket := commpackets.PacketHeartbeat{} 149 | heartbeatPacketBytesWithHeader := tools.DoPackWith4BytesHeader(heartbeatPacket.MustMarshalToBytes()) 150 | _, err := conn.Write(heartbeatPacketBytesWithHeader) 151 | if err != nil { 152 | win.Stop() 153 | fmt.Printf("network error: %v\n", err) 154 | return 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /examples/chinese_chess/frontend/tools/draw.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "fmt" 5 | 6 | commpackets "github.com/markity/go-reactor/examples/chinese_chess/frontend/comm_packets" 7 | 8 | interactive "github.com/markity/Interactive-Console" 9 | ) 10 | 11 | func getPieceTypeName(point *commpackets.ChessPoint) string { 12 | if point == nil { 13 | return " " 14 | } 15 | switch point.Type { 16 | case commpackets.PieceTypeBing: 17 | return "兵" 18 | case commpackets.PieceTypeChe: 19 | return "车" 20 | case commpackets.PieceTypeMa: 21 | return "马" 22 | case commpackets.PieceTypePao: 23 | return "炮" 24 | case commpackets.PieceTypeShi: 25 | return "士" 26 | case commpackets.PieceTypeShuai: 27 | return "帅" 28 | case commpackets.PieceTypeXiang: 29 | return "象" 30 | default: 31 | panic("check here") 32 | } 33 | } 34 | 35 | func DrawTable(table commpackets.ChessTable, win *interactive.Win, msg string) { 36 | win.Clear() 37 | win.SendLineBackWithColor(" 0 1 2 3 4 5 6 7 8") 38 | for y := 9; y >= 0; y-- { 39 | sendbuf := make([]interface{}, 0) 40 | sendbuf = append(sendbuf, fmt.Sprintf("%d ", y)) 41 | for x := 0; x <= 8; x++ { 42 | name := getPieceTypeName(table.GetPoint(x, y)) 43 | if table.GetPoint(x, y) != nil { 44 | if table.GetPoint(x, y).Side == commpackets.GameSideRed { 45 | color := interactive.GetDefaultSytleAttr() 46 | color.Foreground = interactive.ColorRed 47 | sendbuf = append(sendbuf, color) 48 | } else { 49 | color := interactive.GetDefaultSytleAttr() 50 | color.Foreground = interactive.ColorBlue 51 | sendbuf = append(sendbuf, color) 52 | } 53 | } 54 | sendbuf = append(sendbuf, name) 55 | } 56 | sendbuf = append(sendbuf, interactive.GetDefaultSytleAttr(), fmt.Sprint(y)) 57 | win.SendLineBackWithColor(sendbuf...) 58 | if y == 5 { 59 | riverColor := interactive.GetDefaultSytleAttr() 60 | riverColor.Foreground = interactive.ColorDarkTurquoise 61 | win.SendLineBackWithColor(riverColor, "========楚河=========") 62 | } 63 | } 64 | win.SendLineBackWithColor(" 0 1 2 3 4 5 6 7 8") 65 | win.SendLineBack(msg) 66 | } 67 | -------------------------------------------------------------------------------- /examples/chinese_chess/frontend/tools/tools.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "net" 7 | ) 8 | 9 | func ReadPacketBytesWith4BytesHeader(conn net.Conn) ([]byte, error) { 10 | lengthBytes := make([]byte, 4) 11 | _, err := io.ReadFull(conn, lengthBytes) 12 | if err != nil { 13 | return nil, err 14 | } 15 | packetBytes := make([]byte, binary.BigEndian.Uint32(lengthBytes)) 16 | 17 | _, err = io.ReadFull(conn, packetBytes) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | return packetBytes, nil 23 | } 24 | 25 | // 做封包操作 26 | func DoPackWith4BytesHeader(bs []byte) []byte { 27 | buf := make([]byte, 4, 4+len(bs)) 28 | binary.BigEndian.PutUint32(buf, uint32(len(bs))) 29 | buf = append(buf, bs...) 30 | return buf 31 | } 32 | -------------------------------------------------------------------------------- /examples/echo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | eventloop "github.com/markity/go-reactor/pkg/event_loop" 7 | 8 | "github.com/markity/go-reactor/pkg/buffer" 9 | 10 | goreactor "github.com/markity/go-reactor" 11 | ) 12 | 13 | func main() { 14 | loop := eventloop.NewEventLoop() 15 | 16 | server := goreactor.NewTCPServer(loop, "127.0.0.1:8000", 0, goreactor.RoundRobin()) 17 | server.SetConnectionCallback(func(t goreactor.TCPConnection) { 18 | fmt.Println("a new connection join, loop is", t.GetEventLoop().GetID()) 19 | t.SetDisConnectedCallback(func(t goreactor.TCPConnection) { 20 | fmt.Println("a connection disconnected") 21 | }) 22 | }) 23 | server.SetMessageCallback(func(t goreactor.TCPConnection, b buffer.Buffer) { 24 | t.Send([]byte(b.RetrieveAsString())) 25 | }) 26 | server.Start() 27 | loop.Loop() 28 | } 29 | -------------------------------------------------------------------------------- /examples/timer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | eventloop "github.com/markity/go-reactor/pkg/event_loop" 8 | ) 9 | 10 | func onTimeout(timerId int) { 11 | fmt.Println("timeout", timerId) 12 | } 13 | 14 | func main() { 15 | loop := eventloop.NewEventLoop() 16 | loop.RunAt(time.Now(), time.Second*3, onTimeout) 17 | loop.Loop() 18 | } 19 | -------------------------------------------------------------------------------- /examples/wait_forever/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | eventloop "github.com/markity/go-reactor/pkg/event_loop" 5 | ) 6 | 7 | func main() { 8 | loop := eventloop.NewEventLoop() 9 | loop.Loop() 10 | } 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/markity/go-reactor 2 | 3 | go 1.21.1 4 | 5 | require ( 6 | github.com/Allenxuxu/gev v0.5.0 7 | github.com/cloudwego/netpoll v0.6.0 8 | github.com/markity/Interactive-Console v0.0.0-20230622112502-6658419229ed 9 | github.com/tidwall/evio v1.0.8 10 | ) 11 | 12 | require ( 13 | github.com/Allenxuxu/ringbuffer v0.0.11 // indirect 14 | github.com/Allenxuxu/toolkit v0.0.1 // indirect 15 | github.com/RussellLuo/timingwheel v0.0.0-20201029015908-64de9d088c74 // indirect 16 | github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7 // indirect 17 | github.com/gdamore/encoding v1.0.0 // indirect 18 | github.com/gdamore/tcell v1.4.0 // indirect 19 | github.com/kavu/go_reuseport v1.5.0 // indirect 20 | github.com/libp2p/go-reuseport v0.0.1 // indirect 21 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 22 | github.com/mattn/go-runewidth v0.0.14 // indirect 23 | github.com/petermattis/goid v0.0.0-20240503122002-4b96552b8156 // indirect 24 | github.com/pkg/errors v0.9.1 // indirect 25 | github.com/rivo/uniseg v0.4.4 // indirect 26 | golang.org/x/sys v0.19.0 // indirect 27 | golang.org/x/text v0.14.0 // indirect 28 | ) 29 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Allenxuxu/eviop v0.0.0-20190901123806-035c218f739a/go.mod h1:I5+IvzRy5ddv0t9uiuUf4LsfJOXczEb9rxSuPWFfE0w= 2 | github.com/Allenxuxu/gev v0.5.0 h1:N5mMIqUyEDwZBt0eEQUCFGCDMEf6ZF/+oLOTxfRWVoo= 3 | github.com/Allenxuxu/gev v0.5.0/go.mod h1:eM6UgX9+UttS77jtXxxtuoype6utFqDbiC+URLcRbnQ= 4 | github.com/Allenxuxu/ringbuffer v0.0.0-20190803184500-fa400f2fe92b/go.mod h1:9Rg4D7ixiHGlU50BJWJEg6vwDFcGiOYKQFcHK6Vx9m4= 5 | github.com/Allenxuxu/ringbuffer v0.0.11 h1:51J/QakUlldfRBeKFAy81PD0IunxOQehvoBG/EvWT7k= 6 | github.com/Allenxuxu/ringbuffer v0.0.11/go.mod h1:F2Ela+/miJmKYwnXr3X0+spOmSEwL/iFAEzeUJ4SFMI= 7 | github.com/Allenxuxu/toolkit v0.0.1 h1:xY4AK/nmjxQC1sVbolUUqVeH27+TalfCPLd85y2VfS0= 8 | github.com/Allenxuxu/toolkit v0.0.1/go.mod h1:kamv5tj0iNT29zmKIYaxoIcYgDnzerxnOZiHBKbVp/o= 9 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 10 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 11 | github.com/RussellLuo/timingwheel v0.0.0-20201029015908-64de9d088c74 h1:kAsSVLB5MpjNyLoQ96YBqPaTHc870iNa99HQvLUQb/A= 12 | github.com/RussellLuo/timingwheel v0.0.0-20201029015908-64de9d088c74/go.mod h1:3VIJp8oOAlnDUnPy3kwyBGqsMiJJujqTP6ic9Jv6NbM= 13 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 14 | github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7 h1:PtwsQyQJGxf8iaPptPNaduEIu9BnrNms+pcRdHAxZaM= 15 | github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q= 16 | github.com/cloudwego/netpoll v0.6.0 h1:JRMkrA1o8k/4quxzg6Q1XM+zIhwZsyoWlq6ef+ht31U= 17 | github.com/cloudwego/netpoll v0.6.0/go.mod h1:xVefXptcyheopwNDZjDPcfU6kIjZXZ4nY550k1yH9eQ= 18 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 19 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 20 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 21 | github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 22 | github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= 23 | github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= 24 | github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU= 25 | github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0= 26 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= 27 | github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= 28 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 29 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 30 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 31 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 32 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 33 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 34 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 35 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 36 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 37 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 38 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 39 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 40 | github.com/kavu/go_reuseport v1.4.0/go.mod h1:CG8Ee7ceMFSMnx/xr25Vm0qXaj2Z4i5PWoUx+JZ5/CU= 41 | github.com/kavu/go_reuseport v1.5.0 h1:UNuiY2OblcqAtVDE8Gsg1kZz8zbBWg907sP1ceBV+bk= 42 | github.com/kavu/go_reuseport v1.5.0/go.mod h1:CG8Ee7ceMFSMnx/xr25Vm0qXaj2Z4i5PWoUx+JZ5/CU= 43 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 44 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 45 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 46 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 47 | github.com/libp2p/go-reuseport v0.0.1 h1:7PhkfH73VXfPJYKQ6JwS5I/eVcoyYi9IMNGc6FWpFLw= 48 | github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= 49 | github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 50 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 51 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 52 | github.com/markity/Interactive-Console v0.0.0-20230622112502-6658419229ed h1:ihfUbzbW7u2gsVTrmBi1kSLjN55T0biJC2GmgjIPOSY= 53 | github.com/markity/Interactive-Console v0.0.0-20230622112502-6658419229ed/go.mod h1:a8hH83DuDxbh2oVyBGHG9IwxOk0j+YkBYnRd1UemXwQ= 54 | github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 55 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= 56 | github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 57 | github.com/panjf2000/ants/v2 v2.4.3/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= 58 | github.com/panjf2000/gnet v1.4.0/go.mod h1:Wpb/yLODhgxE26mOXwnhkO7XnnjNY5lg+KhPPX/THw4= 59 | github.com/petermattis/goid v0.0.0-20240503122002-4b96552b8156 h1:UOk0WKXxKXmHSlIkwQNhT5AWlMtkijU5pfj8bCOI9vQ= 60 | github.com/petermattis/goid v0.0.0-20240503122002-4b96552b8156/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= 61 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 62 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 63 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 64 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 65 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 66 | github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= 67 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 68 | github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= 69 | github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 70 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 71 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 72 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 73 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 74 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 75 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 76 | github.com/tidwall/evio v1.0.2/go.mod h1:cYtY49LddNrlpsOmW7qJnqM8B2gOjrFrzT8+Fnb/GKs= 77 | github.com/tidwall/evio v1.0.8 h1:+M7lh83rL4KwEObDGtXP3J1wE5utH80LeaAhrKCGVfE= 78 | github.com/tidwall/evio v1.0.8/go.mod h1:MJhRp4iVVqx/n/5mJk77oKmSABVhC7yYykcJiKaFYYw= 79 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 80 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 81 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 82 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 83 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 84 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 85 | go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= 86 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 87 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 88 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 89 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 90 | golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 91 | golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 92 | golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 93 | golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 94 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 95 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 96 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 97 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 98 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 99 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 100 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 101 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 102 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 103 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 104 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 105 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 106 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 107 | golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 108 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 109 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 110 | golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 111 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 112 | golang.org/x/sys v0.0.0-20201116194326-cc9327a14d48/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 113 | golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 114 | golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 115 | golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= 116 | golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 117 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 118 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 119 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 120 | golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 121 | golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 122 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 123 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 124 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 125 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 126 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 127 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 128 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 129 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 130 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 131 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= 132 | gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= 133 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 134 | gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= 135 | gonum.org/v1/plot v0.0.0-20190615073203-9aa86143727f/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= 136 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 137 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 138 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 139 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 140 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 141 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 142 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 143 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 144 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 145 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 146 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 147 | gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 148 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 149 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 150 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 151 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 152 | modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= 153 | modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= 154 | modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= 155 | modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= 156 | modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= 157 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 158 | -------------------------------------------------------------------------------- /load_balance.go: -------------------------------------------------------------------------------- 1 | package goreactor 2 | 3 | import ( 4 | eventloop "github.com/markity/go-reactor/pkg/event_loop" 5 | ) 6 | 7 | type LoadBalanceStrategy func([]eventloop.EventLoop) eventloop.EventLoop 8 | 9 | func RoundRobin() LoadBalanceStrategy { 10 | var nextLoopIndex int 11 | return func(loops []eventloop.EventLoop) eventloop.EventLoop { 12 | l := loops[nextLoopIndex] 13 | nextLoopIndex = (nextLoopIndex + 1) % len(loops) 14 | return l 15 | } 16 | } 17 | 18 | func LeastConnection() LoadBalanceStrategy { 19 | return func(loops []eventloop.EventLoop) eventloop.EventLoop { 20 | l := loops[0] 21 | 22 | for i := 1; i < len(loops); i++ { 23 | if loops[i].GetChannelCount() < l.GetChannelCount() { 24 | l = loops[i] 25 | } 26 | } 27 | 28 | return l 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pkg/async_log/log.go: -------------------------------------------------------------------------------- 1 | package async_log 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "sync" 7 | "syscall" 8 | "time" 9 | 10 | "github.com/markity/go-reactor/pkg/cond" 11 | ) 12 | 13 | type LoggerLevel int 14 | 15 | const ( 16 | DEBUG LoggerLevel = iota 17 | INFO 18 | WARN 19 | ERROR 20 | FATAL 21 | ) 22 | 23 | func (le LoggerLevel) String() string { 24 | switch le { 25 | case DEBUG: 26 | return "DEBUG" 27 | case INFO: 28 | return "INFO" 29 | case WARN: 30 | return "WARN" 31 | case ERROR: 32 | return "ERROR" 33 | case FATAL: 34 | return "FATAL" 35 | } 36 | return "UNKNOWN" 37 | } 38 | 39 | type logger struct { 40 | mu sync.Mutex 41 | cond cond.Cond 42 | 43 | currentBuffer *buffer 44 | backupBuffers []*buffer 45 | fullBuffers []*buffer 46 | 47 | // readonly variables, can share without lock 48 | file *os.File 49 | bufferSize int64 50 | level LoggerLevel 51 | } 52 | 53 | type Logger interface { 54 | Logf(level LoggerLevel, f string, args ...interface{}) 55 | Metrics() (backup int, full int) 56 | } 57 | 58 | func NewLogger(level LoggerLevel, path string, backupBufferNums int, bufferSize int64) Logger { 59 | if backupBufferNums <= 0 || bufferSize <= 0 { 60 | panic("check your params") 61 | } 62 | 63 | backup := make([]*buffer, 0) 64 | for i := 0; i < backupBufferNums; i++ { 65 | backup = append(backup, newLogBuffer(bufferSize)) 66 | } 67 | lo := &logger{ 68 | currentBuffer: newLogBuffer(bufferSize), 69 | backupBuffers: backup, 70 | fullBuffers: make([]*buffer, 0), 71 | bufferSize: bufferSize, 72 | } 73 | lo.cond = *cond.NewCond(&lo.mu) 74 | 75 | f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600) 76 | if err != nil { 77 | panic(err) 78 | } 79 | lo.file = f 80 | 81 | // 开启背景协程异步刷入到磁盘 82 | go func() { 83 | for { 84 | lo.mu.Lock() 85 | if lo.currentBuffer.Empty() { 86 | lo.cond.WaitWithTimeout(time.Second * 3) 87 | } 88 | if !lo.currentBuffer.Empty() { 89 | lo.fullBuffers = append(lo.fullBuffers, lo.currentBuffer) 90 | if len(lo.backupBuffers) != 0 { 91 | lo.currentBuffer = lo.backupBuffers[len(lo.backupBuffers)-1] 92 | lo.backupBuffers = lo.backupBuffers[:len(lo.backupBuffers)-1] 93 | } else { 94 | lo.currentBuffer = newLogBuffer(lo.bufferSize) 95 | } 96 | } 97 | tobeWritten := lo.fullBuffers 98 | lo.fullBuffers = make([]*buffer, 0) 99 | lo.mu.Unlock() 100 | 101 | for _, v := range tobeWritten { 102 | f.Write(v.data) 103 | v.Reset() 104 | } 105 | lo.mu.Lock() 106 | lo.backupBuffers = append(lo.backupBuffers, tobeWritten...) 107 | lo.mu.Unlock() 108 | } 109 | }() 110 | return lo 111 | } 112 | 113 | func (lo *logger) Logf(level LoggerLevel, f string, args ...interface{}) { 114 | if lo.level > level { 115 | return 116 | } 117 | 118 | lo.mu.Lock() 119 | defer lo.mu.Unlock() 120 | 121 | tv := syscall.Timeval{} 122 | err := syscall.Gettimeofday(&tv) 123 | if err != nil { 124 | panic(err) 125 | } 126 | 127 | s := time.Unix(tv.Sec, 0).Format("2006-01-02 15:04:05") + " " + level.String() + " " + fmt.Sprintf(f, args...) + "\n" 128 | if len(s) > int(lo.bufferSize) { 129 | panic("log is too large") 130 | } 131 | 132 | ok := lo.currentBuffer.Append([]byte(s)) 133 | if !ok { 134 | lo.fullBuffers = append(lo.fullBuffers, lo.currentBuffer) 135 | if len(lo.backupBuffers) != 0 { 136 | lo.currentBuffer = lo.backupBuffers[len(lo.backupBuffers)-1] 137 | lo.backupBuffers = lo.backupBuffers[:len(lo.backupBuffers)-1] 138 | } else { 139 | lo.currentBuffer = newLogBuffer(lo.bufferSize) 140 | } 141 | lo.currentBuffer.Append([]byte(s)) 142 | lo.cond.Broadcast() 143 | } 144 | } 145 | 146 | func (lo *logger) Metrics() (backup int, full int) { 147 | lo.mu.Lock() 148 | backup = len(lo.backupBuffers) 149 | full = len(lo.fullBuffers) 150 | lo.mu.Unlock() 151 | return 152 | } 153 | -------------------------------------------------------------------------------- /pkg/async_log/log_buffer.go: -------------------------------------------------------------------------------- 1 | package async_log 2 | 3 | type buffer struct { 4 | data []byte 5 | } 6 | 7 | func (buf *buffer) Empty() bool { 8 | return len(buf.data) == 0 9 | } 10 | 11 | func (buf *buffer) Reset() { 12 | buf.data = buf.data[0:0] 13 | } 14 | 15 | func (buf *buffer) Append(bs []byte) bool { 16 | if cap(buf.data) < len(buf.data)+len(bs) { 17 | return false 18 | } 19 | 20 | buf.data = append(buf.data, bs...) 21 | return true 22 | } 23 | 24 | func newLogBuffer(size int64) *buffer { 25 | return &buffer{ 26 | data: make([]byte, 0, size), 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pkg/buffer/buffer.go: -------------------------------------------------------------------------------- 1 | package buffer 2 | 3 | import ( 4 | "syscall" 5 | "unsafe" 6 | ) 7 | 8 | type buffer struct { 9 | data []byte 10 | readIndex int 11 | writeIndex int 12 | } 13 | 14 | func (buf *buffer) ReadableBytes() int { 15 | return buf.writeIndex - buf.readIndex 16 | } 17 | 18 | func (buf *buffer) Peek() []byte { 19 | return buf.data[buf.readIndex:buf.writeIndex] 20 | } 21 | 22 | func (buf *buffer) Retrieve(i int) { 23 | if buf.ReadableBytes() < i { 24 | panic("retrieve too many bytes") 25 | } 26 | 27 | buf.readIndex += i 28 | } 29 | 30 | func (buf *buffer) RetrieveAll() { 31 | buf.readIndex = 0 32 | buf.writeIndex = 0 33 | } 34 | 35 | func (buf *buffer) RetrieveAsString() string { 36 | s := string(buf.data[buf.readIndex:buf.writeIndex]) 37 | buf.readIndex = 0 38 | buf.writeIndex = 0 39 | return s 40 | } 41 | 42 | func (buf *buffer) Append(bs []byte) { 43 | if len(buf.data)-buf.writeIndex >= len(bs) { 44 | copy(buf.data[buf.writeIndex:], bs) 45 | buf.writeIndex += len(bs) 46 | } else { 47 | if len(buf.data) >= buf.writeIndex-buf.readIndex+len(bs) { 48 | sz := buf.writeIndex - buf.readIndex + len(bs) 49 | copy(buf.data, buf.data[buf.readIndex:buf.readIndex+buf.writeIndex-buf.readIndex]) 50 | copy(buf.data[buf.writeIndex-buf.readIndex:], bs) 51 | buf.readIndex = 0 52 | buf.writeIndex = sz 53 | } else { 54 | newBytes := make([]byte, buf.writeIndex-buf.readIndex+len(bs)+8192) 55 | sz := buf.writeIndex - buf.readIndex + len(bs) 56 | copy(newBytes, buf.data[buf.readIndex:buf.writeIndex]) 57 | copy(newBytes[buf.writeIndex:], bs) 58 | buf.data = newBytes 59 | buf.readIndex = 0 60 | buf.writeIndex = sz 61 | } 62 | } 63 | } 64 | 65 | func (buf *buffer) ReadFD(fd int, extrabuf []byte) int { 66 | writable := len(buf.data) - buf.writeIndex 67 | if writable == 0 { 68 | return 0 69 | } 70 | ptr := uintptr(unsafe.Pointer(&buf.data[buf.writeIndex])) 71 | base := (*byte)(unsafe.Pointer(ptr)) 72 | iovec := [2]syscall.Iovec{ 73 | { 74 | Base: base, 75 | Len: uint64(writable), 76 | }, 77 | { 78 | Base: (*byte)(unsafe.Pointer(&extrabuf[0])), 79 | Len: uint64(len(extrabuf)), 80 | }, 81 | } 82 | 83 | sz, _, _ := syscall.Syscall(syscall.SYS_READV, uintptr(fd), uintptr(unsafe.Pointer(&iovec)), 2) 84 | 85 | size := int(sz) 86 | if size < 0 { 87 | size = 0 88 | } 89 | 90 | if size == 0 { 91 | return 0 92 | } 93 | 94 | if size <= writable { 95 | buf.writeIndex += size 96 | } else { 97 | buf.writeIndex = len(buf.data) 98 | buf.Append(extrabuf[:size-writable]) 99 | } 100 | 101 | return size 102 | } 103 | 104 | func NewBuffer() Buffer { 105 | return &buffer{ 106 | data: make([]byte, 8192), 107 | readIndex: 0, 108 | writeIndex: 0, 109 | } 110 | } 111 | 112 | type Buffer interface { 113 | ReadableBytes() int 114 | Peek() []byte 115 | Retrieve(int) 116 | RetrieveAll() 117 | RetrieveAsString() string 118 | Append([]byte) 119 | ReadFD(int, []byte) int 120 | } 121 | -------------------------------------------------------------------------------- /pkg/cond/cond.go: -------------------------------------------------------------------------------- 1 | package cond 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "time" 7 | "unsafe" 8 | ) 9 | 10 | // cond variable with timeout 11 | 12 | type Cond struct { 13 | L sync.Locker 14 | n unsafe.Pointer 15 | } 16 | 17 | func NewCond(l sync.Locker) *Cond { 18 | c := &Cond{L: l} 19 | n := make(chan struct{}) 20 | c.n = unsafe.Pointer(&n) 21 | return c 22 | } 23 | 24 | // Waits for Broadcast calls. Similar to regular sync.Cond, this unlocks the underlying 25 | // locker first, waits on changes and re-locks it before returning. 26 | func (c *Cond) Wait() { 27 | n := c.NotifyChan() 28 | c.L.Unlock() 29 | <-n 30 | c.L.Lock() 31 | } 32 | 33 | // Same as Wait() call, but will only wait up to a given timeout. 34 | func (c *Cond) WaitWithTimeout(t time.Duration) { 35 | n := c.NotifyChan() 36 | c.L.Unlock() 37 | select { 38 | case <-n: 39 | case <-time.After(t): 40 | } 41 | c.L.Lock() 42 | } 43 | 44 | // Returns a channel that can be used to wait for next Broadcast() call. 45 | func (c *Cond) NotifyChan() <-chan struct{} { 46 | ptr := atomic.LoadPointer(&c.n) 47 | return *((*chan struct{})(ptr)) 48 | } 49 | 50 | // Broadcast call notifies everyone that something has changed. 51 | func (c *Cond) Broadcast() { 52 | n := make(chan struct{}) 53 | ptrOld := atomic.SwapPointer(&c.n, unsafe.Pointer(&n)) 54 | close(*(*chan struct{})(ptrOld)) 55 | } 56 | -------------------------------------------------------------------------------- /pkg/context/context.go: -------------------------------------------------------------------------------- 1 | package kvcontext 2 | 3 | import "sync" 4 | 5 | type keyValueContext struct { 6 | mu sync.RWMutex 7 | 8 | kv map[string]interface{} 9 | } 10 | 11 | type KVContext interface { 12 | Set(key string, value interface{}) 13 | Delete(key string) 14 | Get(key string) (value interface{}, exists bool) 15 | } 16 | 17 | func (c *keyValueContext) Set(key string, value interface{}) { 18 | c.mu.Lock() 19 | if c.kv == nil { 20 | c.kv = make(map[string]interface{}) 21 | } 22 | 23 | c.kv[key] = value 24 | c.mu.Unlock() 25 | } 26 | 27 | func (c *keyValueContext) Delete(key string) { 28 | c.mu.Lock() 29 | delete(c.kv, key) 30 | c.mu.Unlock() 31 | } 32 | 33 | func (c *keyValueContext) Get(key string) (value interface{}, exists bool) { 34 | c.mu.RLock() 35 | value, exists = c.kv[key] 36 | c.mu.RUnlock() 37 | return 38 | } 39 | 40 | func (c *keyValueContext) reset() { 41 | c.mu.Lock() 42 | c.kv = nil 43 | c.mu.Unlock() 44 | } 45 | 46 | func NewContext() KVContext { 47 | return &keyValueContext{ 48 | kv: make(map[string]interface{}), 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pkg/event_loop/channel.go: -------------------------------------------------------------------------------- 1 | package eventloop 2 | 3 | import ( 4 | "syscall" 5 | ) 6 | 7 | type ReactorEvent int 8 | 9 | const ( 10 | // do not care about anything 11 | NoneEvent ReactorEvent = 0 12 | ReadableEvent ReactorEvent = syscall.EPOLLIN 13 | WritableEvent ReactorEvent = syscall.EPOLLOUT 14 | AllEvent ReactorEvent = ReadableEvent | WritableEvent 15 | ) 16 | 17 | type channel struct { 18 | // file descripor, each channel is used only for handle one fd 19 | fd int 20 | 21 | // events that we are interested, if we want to do something when the fd 22 | // is readable, we need to set events to ReadableEvent and SetReadCallback 23 | events ReactorEvent 24 | 25 | // revents tells what events are active, for example, after the fd is readable 26 | // poller.Poll() returns the channel with (revents & ReadableEvent == 1) 27 | revents ReactorEvent 28 | 29 | // used by poller, if index is zero, the poller knows it is a new channel, 30 | // and poller will set a index for the channel 31 | index int 32 | 33 | // callbacks 34 | readCallback func() 35 | writeCallback func() 36 | } 37 | 38 | // some setters and getters 39 | 40 | func (c *channel) GetEvent() ReactorEvent { 41 | return c.events 42 | } 43 | 44 | func (c *channel) SetEvent(e ReactorEvent) { 45 | c.events = e 46 | } 47 | 48 | func (c *channel) GetRevent() ReactorEvent { 49 | return c.revents 50 | } 51 | 52 | func (c *channel) SetRevent(e ReactorEvent) { 53 | c.revents = e 54 | } 55 | 56 | func (c *channel) GetIndex() int { 57 | return c.index 58 | } 59 | 60 | func (c *channel) SetIndex(i int) { 61 | c.index = i 62 | } 63 | 64 | func (c *channel) GetFD() int { 65 | return c.fd 66 | } 67 | 68 | func (c *channel) SetReadCallback(f func()) { 69 | c.readCallback = f 70 | } 71 | 72 | func (c *channel) SetWriteCallback(f func()) { 73 | c.writeCallback = f 74 | } 75 | 76 | func (c *channel) IsWriting() bool { 77 | return c.events&WritableEvent != 0 78 | } 79 | 80 | func (c *channel) IsReading() bool { 81 | return c.events&ReadableEvent != 0 82 | } 83 | 84 | // make events with WritableEvent set, if WritableEvent is already set before the call 85 | // returns false, it is used for better performance, when we call EnableWrite() with 86 | // false returns, we do not need to call eventloop.UpdateChannelInLoopGoroutine, this 87 | // save the cost of the epoll_ctl system call 88 | func (c *channel) EnableWrite() bool { 89 | if c.events&WritableEvent != 0 { 90 | return false 91 | } 92 | 93 | c.events |= WritableEvent 94 | return true 95 | } 96 | 97 | // if WritableEvent is not set before, returns false, the return value is be used to 98 | // save the cost of the epoll_ctl, see EnableWrite comments 99 | func (c *channel) DisableWrite() bool { 100 | if c.events&WritableEvent == 0 { 101 | return false 102 | } 103 | 104 | c.events &= ^WritableEvent 105 | return true 106 | } 107 | 108 | // enable read 109 | func (c *channel) EnableRead() bool { 110 | if c.events&ReadableEvent != 0 { 111 | return false 112 | } 113 | 114 | c.events |= ReadableEvent 115 | return true 116 | } 117 | 118 | // disable read 119 | func (c *channel) DisableRead() bool { 120 | if c.events&ReadableEvent == 0 { 121 | return false 122 | } 123 | 124 | c.events &= ^ReadableEvent 125 | return true 126 | } 127 | 128 | // handle all events for the channel 129 | func (c *channel) HandleEvent() { 130 | // TODO: EPOLLHUP 131 | 132 | if c.revents&ReadableEvent != 0 { 133 | if c.readCallback != nil { 134 | c.readCallback() 135 | } 136 | } 137 | 138 | if c.revents&WritableEvent != 0 { 139 | if c.writeCallback != nil { 140 | c.writeCallback() 141 | } 142 | } 143 | } 144 | 145 | // create a new channel 146 | func NewChannel(fd int) Channel { 147 | return &channel{ 148 | index: -1, 149 | fd: fd, 150 | } 151 | } 152 | 153 | // Channel is used to manage a fd events, and handle callbacks 154 | type Channel interface { 155 | GetEvent() ReactorEvent 156 | SetEvent(ReactorEvent) 157 | 158 | GetRevent() ReactorEvent 159 | SetRevent(ReactorEvent) 160 | 161 | GetIndex() int 162 | SetIndex(int) 163 | 164 | GetFD() int 165 | 166 | SetReadCallback(func()) 167 | SetWriteCallback(func()) 168 | 169 | HandleEvent() 170 | 171 | IsWriting() bool 172 | IsReading() bool 173 | EnableWrite() bool 174 | DisableWrite() bool 175 | EnableRead() bool 176 | DisableRead() bool 177 | } 178 | -------------------------------------------------------------------------------- /pkg/event_loop/evloop.go: -------------------------------------------------------------------------------- 1 | package eventloop 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | "sync/atomic" 7 | "syscall" 8 | "time" 9 | 10 | kvcontext "github.com/markity/go-reactor/pkg/context" 11 | ) 12 | 13 | type eventloop struct { 14 | // Poller is epoll poller 15 | poller Poller 16 | 17 | // eventfd, be used to wake up epoll_wait syscall 18 | wakeupEventChannel Channel 19 | 20 | // be used to manage timers, timerQueue contains a timerfd 21 | timerQueue *timerQueue 22 | 23 | // mu is used to protect functors 24 | mu sync.Mutex 25 | functors []func() 26 | 27 | // be used to stop eventloop, make eventloop.Loop returns 28 | running int64 29 | 30 | // gid is goroutine id, set when event loop calls Loop 31 | gid int64 32 | 33 | // echo eventloop has a id 34 | id int 35 | 36 | // eventloop level kv 37 | ctx kvcontext.KVContext 38 | 39 | // functions execute when loop startup 40 | doOnLoop func(EventLoop) 41 | 42 | // functions execute when loop stops 43 | doOnStop func(EventLoop) 44 | 45 | // eacho loop has a big space for fd readv(iovec) 46 | extraForReadFD []byte 47 | } 48 | 49 | // create an EventLoop, it's Loop function can be only triggered 50 | // at the goroutine which creates the eventloop 51 | func NewEventLoop() EventLoop { 52 | // create an eventfd, the eventfd is used to wake up epoll_wait 53 | 54 | // for example, when a loop.RunInLoop(f) is called, but now the 55 | // loop goroutine is waiting on the epoll_wait() syscall, we need to 56 | // notify that there is a functor to call. after putting a functor into 57 | // loop.functors, we need to write to eventfd to notify evloop to handle 58 | // the functors 59 | r1, _, errno := syscall.Syscall(syscall.SYS_EVENTFD, 0, 0, 0) 60 | if errno != 0 { 61 | panic(errno) 62 | } 63 | 64 | // to make sure syscall.Read(eventfd) will not block, it is not necessary actually, 65 | // but sometimes the user may misread our eventfd by mistake, after witch reading 66 | // eventfd will make the loop goroutine block 67 | err := syscall.SetNonblock(int(r1), true) 68 | if err != nil { 69 | panic(err) 70 | } 71 | 72 | // create a channel for eventfd 73 | c := NewChannel(int(r1)) 74 | c.SetEvent(ReadableEvent) 75 | c.SetReadCallback(func() { 76 | _, err := syscall.Read(int(r1), make([]byte, 8)) 77 | if err != nil { 78 | panic(err) 79 | } 80 | }) 81 | 82 | ev := &eventloop{ 83 | poller: NewPoller(), 84 | wakeupEventChannel: c, 85 | functors: make([]func(), 0), 86 | running: 0, 87 | id: int(idGen.Add(1)), 88 | ctx: kvcontext.NewContext(), 89 | extraForReadFD: make([]byte, 65536), 90 | } 91 | 92 | // register the channel into epoll 93 | ev.UpdateChannelInLoopGoroutine(ev.wakeupEventChannel) 94 | 95 | // create a timer queue 96 | ev.timerQueue = newTimerQueue(ev) 97 | 98 | return ev 99 | } 100 | 101 | // EventLoop interface describe the functions designed for the users 102 | type EventLoop interface { 103 | // Loop() will never return unless Stop() is called 104 | Loop() 105 | 106 | // queue a functor into eventloop, the function will be called latter in loop goroutine 107 | RunInLoop(func()) 108 | 109 | // stop eventloop and make Loop() return 110 | Stop() 111 | 112 | // create a timer, it will be triggered at specified timepoint 113 | RunAt(triggerAt time.Time, interval time.Duration, f func(timerID int)) int 114 | 115 | // cancel a timer, if it is removed successfully, returns true 116 | // if the timer is already executed or the id is invalid, returns false 117 | CancelTimer(id int) bool 118 | 119 | // for tcp server, each eventloop has its id, it may be used by users 120 | GetID() int 121 | 122 | // get current channel count in this loop, it may be used for load balance 123 | GetChannelCount() int 124 | 125 | // for go-reactor developers, this is be used to register channel into epollfd 126 | // go-reacotr users can ignore functions below 127 | 128 | // when a channel is change, it is necessary to notify epollfd 129 | UpdateChannelInLoopGoroutine(Channel) 130 | 131 | // remove a channel from eventloop, the fd will also be remove from epollfd 132 | RemoveChannelInLoopGoroutine(Channel) 133 | 134 | // about kv context 135 | GetContext(key string) (interface{}, bool) 136 | MustGetContext(key string) interface{} 137 | SetContext(key string, val interface{}) 138 | DeleteContext(key string) 139 | 140 | // be called when start loop 141 | DoOnLoop(func(EventLoop)) 142 | 143 | // be called when stops loop 144 | DoOnStop(func(EventLoop)) 145 | 146 | // get extra data for readfd 147 | GetExtraData() []byte 148 | } 149 | 150 | func (ev *eventloop) GetExtraData() []byte { 151 | return ev.extraForReadFD 152 | } 153 | 154 | func (ev *eventloop) GetContext(key string) (interface{}, bool) { 155 | return ev.ctx.Get(key) 156 | } 157 | 158 | func (ev *eventloop) MustGetContext(key string) interface{} { 159 | v, ok := ev.ctx.Get(key) 160 | if !ok { 161 | panic("must get context not exists") 162 | } 163 | return v 164 | } 165 | 166 | func (ev *eventloop) SetContext(key string, val interface{}) { 167 | ev.ctx.Set(key, val) 168 | } 169 | 170 | func (ev *eventloop) DeleteContext(key string) { 171 | ev.ctx.Delete(key) 172 | } 173 | 174 | // the function can be only triggered at eventloop goroutine 175 | func (ev *eventloop) UpdateChannelInLoopGoroutine(c Channel) { 176 | ev.poller.UpdateChannel(c) 177 | } 178 | 179 | // the function can be only triggered at eventloop goroutine 180 | func (ev *eventloop) RemoveChannelInLoopGoroutine(c Channel) { 181 | ev.poller.RemoveChannel(c) 182 | } 183 | 184 | func (ev *eventloop) DoOnLoop(f func(EventLoop)) { 185 | ev.doOnLoop = f 186 | } 187 | 188 | func (ev *eventloop) DoOnStop(f func(EventLoop)) { 189 | ev.doOnStop = f 190 | } 191 | 192 | // start event loop, if Stop() is not called, Loop() will never return 193 | func (ev *eventloop) Loop() { 194 | // atomic operation, make running switch 0 to 1 195 | if !atomic.CompareAndSwapInt64(&ev.running, 0, 1) { 196 | panic("it is already running? don't run it again") 197 | } 198 | 199 | ev.gid = getGid() 200 | 201 | if ev.doOnLoop != nil { 202 | ev.doOnLoop(ev) 203 | } 204 | 205 | // check running, if running is 0, Loop should returns 206 | for atomic.LoadInt64(&ev.running) == 1 { 207 | // wait epoll_wait returns, and get the active event channels 208 | channels := ev.poller.Poll() 209 | 210 | // execute functions for each channel 211 | for _, v := range channels { 212 | v.HandleEvent() 213 | } 214 | 215 | // get all functors 216 | ev.mu.Lock() 217 | f := ev.functors 218 | ev.functors = make([]func(), 0) 219 | ev.mu.Unlock() 220 | 221 | // execute all functors 222 | for _, v := range f { 223 | v() 224 | } 225 | } 226 | 227 | ev.doOnStop(ev) 228 | } 229 | 230 | // queue a functor into a loop, func will be called in the loop goroutine later 231 | func (ev *eventloop) RunInLoop(f func()) { 232 | // if is running and it is in eventloop goroutine, just execute it right now 233 | if running := atomic.LoadInt64(&ev.running) == 1; running && ev.gid == getGid() { 234 | f() 235 | } else { 236 | // or queue the functor into ev.functors, the lock protects functors 237 | ev.mu.Lock() 238 | ev.functors = append(ev.functors, f) 239 | ev.mu.Unlock() 240 | 241 | // make sure epoll_wait returns 242 | ev.wakeup() 243 | } 244 | } 245 | 246 | // stop a eventloop 247 | func (ev *eventloop) Stop() { 248 | ev.RunInLoop(func() { 249 | // atomic operation is better than lock 250 | atomic.StoreInt64(&ev.running, 0) 251 | }) 252 | } 253 | 254 | func (ev *eventloop) GetID() int { 255 | return ev.id 256 | } 257 | 258 | func (ev *eventloop) GetChannelCount() int { 259 | if getGid() == ev.gid { 260 | return ev.poller.GetChannelCount() 261 | } 262 | 263 | c := make(chan int, 1) 264 | ev.RunInLoop(func() { 265 | c <- ev.poller.GetChannelCount() 266 | }) 267 | return <-c 268 | } 269 | 270 | // setup a timer, returns its id, it can be cancelled, see CancelTimer(id int) 271 | func (ev *eventloop) RunAt(triggerAt time.Time, interval time.Duration, f func(timerID int)) int { 272 | // ev.timerQueue can noly be operated in loop goroutine, we need to use RunInLoop 273 | // and get its return value by golang channel 274 | if getGid() == ev.gid { 275 | return ev.timerQueue.AddTimer(triggerAt, interval, f) 276 | } 277 | 278 | c := make(chan int, 1) 279 | ev.RunInLoop(func() { 280 | c <- ev.timerQueue.AddTimer(triggerAt, interval, f) 281 | }) 282 | return <-c 283 | } 284 | 285 | // cancel a timer 286 | func (ev *eventloop) CancelTimer(id int) bool { 287 | if getGid() == ev.gid { 288 | return ev.timerQueue.CancelTimer(id) 289 | } 290 | 291 | c := make(chan bool, 1) 292 | ev.RunInLoop(func() { 293 | c <- ev.timerQueue.CancelTimer(id) 294 | }) 295 | return <-c 296 | } 297 | 298 | // wakeup writes something into evnetfd, so that epoll_wait can return 299 | func (ev *eventloop) wakeup() { 300 | _, err := syscall.Write(ev.wakeupEventChannel.GetFD(), []byte{0, 0, 0, 0, 0, 0, 0, 1}) 301 | if err != nil { 302 | // notice here, if the content of eventfd is not comsumed, write will returns EAGAIN 303 | // for example, double writes to an eventfd will trigger EAGAIN 304 | if !errors.Is(err, syscall.EAGAIN) { 305 | panic(err) 306 | } 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /pkg/event_loop/gid.go: -------------------------------------------------------------------------------- 1 | package eventloop 2 | 3 | import ( 4 | "github.com/petermattis/goid" 5 | ) 6 | 7 | // get goroutine id 8 | func getGid() (gid int64) { 9 | return goid.Get() 10 | } 11 | -------------------------------------------------------------------------------- /pkg/event_loop/id_generator.go: -------------------------------------------------------------------------------- 1 | package eventloop 2 | 3 | import "sync/atomic" 4 | 5 | var idGen atomic.Int64 6 | -------------------------------------------------------------------------------- /pkg/event_loop/poller.go: -------------------------------------------------------------------------------- 1 | package eventloop 2 | 3 | import ( 4 | "errors" 5 | "syscall" 6 | ) 7 | 8 | type poller struct { 9 | // epoll file descriptor 10 | epollFD int 11 | 12 | // key is fd, value is Channel 13 | channelMap map[int]Channel 14 | 15 | // params of epoll_wait, it can expand if one epoll_wait syscall fills up this array 16 | eventList []syscall.EpollEvent 17 | 18 | // for each channel, assign a index, the first index is 1 19 | idxCounter int 20 | } 21 | 22 | // create a new poller, poller contains a epollfd, 23 | func NewPoller() Poller { 24 | // size is ignored since linux 2.6.8, see man epoll_create(2) 25 | fd, err := syscall.EpollCreate(1) 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | return &poller{ 31 | epollFD: fd, 32 | channelMap: make(map[int]Channel), 33 | eventList: make([]syscall.EpollEvent, 128), 34 | idxCounter: 0, 35 | } 36 | } 37 | 38 | type Poller interface { 39 | // wait on epoll_wait and returns the active Channels 40 | Poll() []Channel 41 | 42 | // UpdateChannel calls epoll_ctl 43 | UpdateChannel(Channel) 44 | 45 | // RemoveChannel removes a fd from epollfd 46 | RemoveChannel(Channel) 47 | 48 | // GetChannelCount get current epoll wait fd nums, may be used to implement load balance 49 | GetChannelCount() int 50 | } 51 | 52 | // wait on epoll_wait and returns the active Channels 53 | func (p *poller) Poll() []Channel { 54 | var n int 55 | 56 | for { 57 | var err error 58 | n, err = syscall.EpollWait(p.epollFD, p.eventList, -1) 59 | if err != nil { 60 | // golang send signals to implement signal preemption even if we are blocking 61 | // on syscall, it is necessary to distingulish the error 62 | if errors.Is(err, syscall.EINTR) { 63 | continue 64 | } 65 | panic(err) 66 | } 67 | break 68 | } 69 | 70 | // fill up active channels 71 | c := make([]Channel, 0) 72 | if n > 0 { 73 | for i := 0; i < n; i++ { 74 | ch := p.channelMap[int(p.eventList[i].Fd)] 75 | ch.SetRevent(ReactorEvent(p.eventList[i].Events)) 76 | c = append(c, p.channelMap[int(p.eventList[i].Fd)]) 77 | } 78 | } 79 | 80 | // if eventList is full, expand the array 81 | if n == len(p.eventList) { 82 | p.eventList = make([]syscall.EpollEvent, 2*len(p.eventList)) 83 | } 84 | 85 | return c 86 | } 87 | 88 | // UpdateChannel calls epoll_ctl 89 | func (p *poller) UpdateChannel(c Channel) { 90 | // if index == -1, it is a new channel, assign a new index for it 91 | if c.GetIndex() < 0 { 92 | // register fd into epollfd 93 | err := syscall.EpollCtl(p.epollFD, syscall.EPOLL_CTL_ADD, c.GetFD(), &syscall.EpollEvent{ 94 | Events: uint32(c.GetEvent()), 95 | Fd: int32(c.GetFD()), 96 | }) 97 | if err != nil { 98 | panic(err) 99 | } 100 | 101 | // assign an index 102 | p.idxCounter++ 103 | p.channelMap[c.GetFD()] = c 104 | c.SetIndex(p.idxCounter) 105 | } else { 106 | // channel exists, now just modify epollfd 107 | err := syscall.EpollCtl(p.epollFD, syscall.EPOLL_CTL_MOD, c.GetFD(), &syscall.EpollEvent{ 108 | Events: uint32(c.GetEvent()), 109 | Fd: int32(c.GetFD()), 110 | }) 111 | if err != nil { 112 | panic(err) 113 | } 114 | } 115 | } 116 | 117 | // RemoveChannel removes a fd from epollfd 118 | func (p *poller) RemoveChannel(c Channel) { 119 | if c.GetIndex() < 0 { 120 | panic("remove non-exist channel") 121 | } 122 | 123 | err := syscall.EpollCtl(p.epollFD, syscall.EPOLL_CTL_DEL, c.GetFD(), nil) 124 | if err != nil { 125 | panic(err) 126 | } 127 | 128 | delete(p.channelMap, c.GetFD()) 129 | } 130 | 131 | func (p *poller) GetChannelCount() int { 132 | return len(p.channelMap) 133 | } 134 | -------------------------------------------------------------------------------- /pkg/event_loop/timer_queue.go: -------------------------------------------------------------------------------- 1 | package eventloop 2 | 3 | import ( 4 | "container/heap" 5 | "syscall" 6 | "time" 7 | "unsafe" 8 | ) 9 | 10 | // be used for timerfd_settime 11 | type itimerspec struct { 12 | it_interval syscall.Timespec 13 | it_value syscall.Timespec 14 | } 15 | 16 | // timer entry 17 | type timerHeapEntry struct { 18 | // id will be used to cancel timer 19 | timerId int 20 | // first trigger timepoint 21 | TimeStamp time.Time 22 | // callback function 23 | onTimer func(int) 24 | // if interval is 0, only trigger once 25 | interval time.Duration 26 | } 27 | 28 | // implement container.Heap interface 29 | type timerHeap []timerHeapEntry 30 | 31 | func (th *timerHeap) Len() int { 32 | return len(*th) 33 | } 34 | 35 | func (th *timerHeap) Less(i, j int) bool { 36 | if (*th)[i].TimeStamp.Equal((*th)[j].TimeStamp) { 37 | return (*th)[i].timerId < (*th)[j].timerId 38 | } 39 | 40 | return (*th)[i].TimeStamp.Before((*th)[j].TimeStamp) 41 | } 42 | 43 | func (th *timerHeap) Swap(i, j int) { 44 | (*th)[i], (*th)[j] = (*th)[j], (*th)[i] 45 | } 46 | 47 | func (th *timerHeap) Push(x interface{}) { 48 | *th = append(*th, x.(timerHeapEntry)) 49 | } 50 | 51 | func (th *timerHeap) Pop() interface{} { 52 | old := *th 53 | n := len(old) 54 | x := old[n-1] 55 | *th = old[0 : n-1] 56 | return x 57 | } 58 | 59 | type timerQueue struct { 60 | // timerfd's channel 61 | timerChannel Channel 62 | // each timer has an unique index, use counter 63 | timerIdCounter int 64 | // timer array, but uses container.Heap interface to insert 65 | heap timerHeap 66 | // eventloop 67 | loop EventLoop 68 | } 69 | 70 | func newTimerQueue(loop EventLoop) *timerQueue { 71 | // 1 means CLOCK_MONOTONIC, see timerfd_create(2) 72 | timerfd, _, errno := syscall.Syscall(syscall.SYS_TIMERFD_CREATE, 1, syscall.O_NONBLOCK, 0) 73 | if errno != 0 { 74 | panic(errno) 75 | } 76 | err := syscall.SetNonblock(int(timerfd), true) 77 | if err != nil { 78 | panic(err) 79 | } 80 | 81 | ch := NewChannel(int(timerfd)) 82 | ch.SetEvent(ReadableEvent) 83 | tq := timerQueue{ 84 | timerChannel: ch, 85 | timerIdCounter: 0, 86 | heap: make(timerHeap, 0), 87 | loop: loop, 88 | } 89 | heap.Init(&tq.heap) 90 | loop.UpdateChannelInLoopGoroutine(ch) 91 | 92 | // read callback consumes content in timerfd and call getExpired() to execute callbakcs 93 | ch.SetReadCallback(func() { 94 | _, err := syscall.Read(int(timerfd), make([]byte, 8)) 95 | if err != nil { 96 | panic(err) 97 | } 98 | 99 | for _, v := range tq.getExpired() { 100 | v.onTimer(v.timerId) 101 | } 102 | }) 103 | 104 | return &tq 105 | } 106 | 107 | // create a new timer, returns its id 108 | func (tq *timerQueue) AddTimer(triggerAt time.Time, interval time.Duration, f func(timerID int)) int { 109 | tq.timerIdCounter++ 110 | id := tq.timerIdCounter 111 | heap.Push(&tq.heap, timerHeapEntry{ 112 | timerId: id, 113 | TimeStamp: triggerAt, 114 | onTimer: f, 115 | interval: interval, 116 | }) 117 | 118 | // why 1? 119 | // -If new_value->it_value specifies a zero value (i.e., 120 | // both subfields are zero), then the timer is disarmed. 121 | nsec := 1 122 | 123 | // if trigger point alreay passed, nsec is 0, timerfd will be readable right now 124 | now := time.Now() 125 | earliest := tq.heap[0].TimeStamp 126 | if now.Before(earliest) { 127 | interval := earliest.Sub(now) 128 | nsec = int(interval.Nanoseconds()) 129 | } 130 | 131 | sp := itimerspec{ 132 | it_value: syscall.Timespec{ 133 | Sec: int64(nsec / 1000000000), 134 | Nsec: int64(nsec % 1000000000), 135 | }, 136 | // set both to zero means trigger only once 137 | it_interval: syscall.Timespec{Sec: 0, Nsec: 0}, 138 | } 139 | 140 | // 1 means TFD_TIMER_ABSTIME, see timerfd_setime(2) 141 | _, _, errno := syscall.Syscall6(syscall.SYS_TIMERFD_SETTIME, uintptr(tq.timerChannel.GetFD()), 2, uintptr(unsafe.Pointer(&sp)), 0, 0, 0) 142 | if errno != 0 { 143 | panic(errno) 144 | } 145 | 146 | return id 147 | } 148 | 149 | // cancel timer by its'id 150 | // TODO: O(N) but it can have better performance 151 | func (tq *timerQueue) CancelTimer(timerId int) bool { 152 | newHeap := make([]timerHeapEntry, 0) 153 | ok := false 154 | for i, v := range tq.heap { 155 | if v.timerId == timerId { 156 | ok = true 157 | continue 158 | } 159 | newHeap = append(newHeap, tq.heap[i]) 160 | } 161 | tq.heap = newHeap 162 | return ok 163 | } 164 | 165 | // get expired entries 166 | func (tq *timerQueue) getExpired() []timerHeapEntry { 167 | te := make([]timerHeapEntry, 0) 168 | now := time.Now() 169 | for { 170 | if tq.heap.Len() == 0 { 171 | break 172 | } 173 | 174 | minOne := tq.heap[0] 175 | if minOne.TimeStamp.Before(now) { 176 | te = append(te, tq.heap[0]) 177 | heap.Pop(&tq.heap) 178 | // if interval is not 0, reset its timestamp and push it back 179 | if minOne.interval != 0 { 180 | minOne.TimeStamp = minOne.TimeStamp.Add(minOne.interval) 181 | heap.Push(&tq.heap, minOne) 182 | } 183 | } else { 184 | break 185 | } 186 | } 187 | 188 | // reset the timerfd, set it to the earliest one 189 | if tq.heap.Len() != 0 { 190 | nsec := 0 191 | 192 | now := time.Now() 193 | earliest := tq.heap[0].TimeStamp 194 | if now.Before(earliest) { 195 | interval := earliest.Sub(now) 196 | nsec = int(interval.Nanoseconds()) 197 | } 198 | 199 | sp := itimerspec{ 200 | it_value: syscall.Timespec{ 201 | Sec: int64(nsec / 1000000000), 202 | Nsec: int64(nsec % 1000000000), 203 | }, 204 | // set both to zero means trigger only once 205 | it_interval: syscall.Timespec{Sec: 0, Nsec: 0}, 206 | } 207 | 208 | // 1 means TFD_TIMER_ABSTIME, see timerfd_setime(2) 209 | _, _, errno := syscall.Syscall6(syscall.SYS_TIMERFD_SETTIME, uintptr(tq.timerChannel.GetFD()), 2, uintptr(unsafe.Pointer(&sp)), 0, 0, 0) 210 | if errno != 0 { 211 | panic(errno) 212 | } 213 | } 214 | return te 215 | } 216 | -------------------------------------------------------------------------------- /tcp_acceptor.go: -------------------------------------------------------------------------------- 1 | package goreactor 2 | 3 | import ( 4 | "net/netip" 5 | "syscall" 6 | 7 | eventloop "github.com/markity/go-reactor/pkg/event_loop" 8 | ) 9 | 10 | type newConnectionCallback func(socketfd int, peerAddr netip.AddrPort) 11 | 12 | type tcpAcceptor struct { 13 | // event loop 14 | loop eventloop.EventLoop 15 | 16 | // be used to prevent double start 17 | listening bool 18 | 19 | // listen at 20 | listenAddr netip.AddrPort 21 | 22 | // listen socket fd channel 23 | socketChannel eventloop.Channel 24 | 25 | // new connection call back 26 | newConnectionCallback newConnectionCallback 27 | 28 | // syscall.Listen param, see man 2 listen() 29 | // The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow. If a connection request arrives when the 30 | // queue is full, the client may receive an error with an indication of ECONNREFUSED or, if the underlying protocol supports retransmission, the request may be 31 | // ignored so that a later reattempt at connection succeeds. 32 | listenBackup int 33 | } 34 | 35 | func newTCPAcceptor(loop eventloop.EventLoop, listenAddr netip.AddrPort, listenBackup int) *tcpAcceptor { 36 | socketFD, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, 0) 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | c := eventloop.NewChannel(socketFD) 42 | 43 | acc := tcpAcceptor{ 44 | loop: loop, 45 | listenAddr: listenAddr, 46 | listening: false, 47 | socketChannel: c, 48 | newConnectionCallback: defaultNewConnectionCallback, 49 | listenBackup: listenBackup, 50 | } 51 | c.SetReadCallback(acc.HandleRead) 52 | 53 | // 15 means SO_REUSEPORT 54 | err = syscall.SetsockoptInt(socketFD, syscall.SOL_SOCKET, 15, 1) 55 | if err != nil { 56 | panic(err) 57 | } 58 | 59 | return &acc 60 | } 61 | 62 | func (ac *tcpAcceptor) Listen() error { 63 | if ac.listening { 64 | panic("already listening") 65 | } 66 | 67 | err := syscall.Bind(ac.socketChannel.GetFD(), &syscall.SockaddrInet4{ 68 | Addr: ac.listenAddr.Addr().As4(), 69 | Port: int(ac.listenAddr.Port()), 70 | }) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | err = syscall.Listen(ac.socketChannel.GetFD(), ac.listenBackup) 76 | if err != nil { 77 | return err 78 | } 79 | 80 | ac.listening = true 81 | ac.socketChannel.EnableRead() 82 | ac.loop.UpdateChannelInLoopGoroutine(ac.socketChannel) 83 | return nil 84 | } 85 | 86 | func (ac *tcpAcceptor) HandleRead() { 87 | nfd, addr, err := syscall.Accept(ac.socketChannel.GetFD()) 88 | if err != nil { 89 | panic(err) 90 | } 91 | 92 | if ac.newConnectionCallback != nil { 93 | sockaddr := addr.(*syscall.SockaddrInet4) 94 | addrPort := netip.AddrPortFrom(netip.AddrFrom4(sockaddr.Addr), uint16(sockaddr.Port)) 95 | ac.newConnectionCallback(nfd, addrPort) 96 | } 97 | } 98 | 99 | func (ac *tcpAcceptor) SetNewConnectionCallback(cb newConnectionCallback) { 100 | ac.newConnectionCallback = cb 101 | } 102 | 103 | func defaultNewConnectionCallback(socketfd int, peerAddr netip.AddrPort) { 104 | syscall.Close(socketfd) 105 | } 106 | -------------------------------------------------------------------------------- /tcp_conn.go: -------------------------------------------------------------------------------- 1 | package goreactor 2 | 3 | import ( 4 | "net/netip" 5 | "syscall" 6 | 7 | kvcontext "github.com/markity/go-reactor/pkg/context" 8 | eventloop "github.com/markity/go-reactor/pkg/event_loop" 9 | 10 | "github.com/markity/go-reactor/pkg/buffer" 11 | ) 12 | 13 | type tcpConnectionState int 14 | 15 | const ( 16 | Connecting tcpConnectionState = 1 17 | Connected tcpConnectionState = 2 18 | Disconnecting tcpConnectionState = 3 19 | Disconnected tcpConnectionState = 4 20 | ) 21 | 22 | type TCPConnection interface { 23 | SetDisConnectedCallback(f DisConnectedCallbackFunc) 24 | SetHighWaterCallback(f HighWaterCallbackFunc) 25 | SetWriteCompleteCallback(f WriteCompleteCallbackFunc) 26 | Send(bs []byte) 27 | ShutdownWrite() 28 | GetRemoteAddrPort() netip.AddrPort 29 | ForceClose() 30 | SetKeepAlive(b bool) 31 | SetNoDelay(b bool) 32 | GetEventLoop() eventloop.EventLoop 33 | SetContext(key string, value interface{}) 34 | DeleteContext(key string) 35 | GetContext(key string) (interface{}, bool) 36 | MustGetContext(key string) interface{} 37 | GetFD() int 38 | IsConnected() bool 39 | } 40 | 41 | // 能被多个协程share 42 | type tcpConnection struct { 43 | state tcpConnectionState 44 | 45 | loop eventloop.EventLoop 46 | 47 | socketChannel eventloop.Channel 48 | 49 | connectedCallback ConnectedCallbackFunc 50 | disconnectedCallback DisConnectedCallbackFunc 51 | messageCallback MessageCallbackFunc 52 | highWaterCallback HighWaterCallbackFunc 53 | writeCompleteCallback WriteCompleteCallbackFunc 54 | 55 | // 0 means infinite 56 | hignWaterLevel int 57 | 58 | remoteAddrPort netip.AddrPort 59 | 60 | outputBuffer buffer.Buffer 61 | inputBuffer buffer.Buffer 62 | 63 | ctx kvcontext.KVContext 64 | } 65 | 66 | func (tc *tcpConnection) setConnectedCallback(f ConnectedCallbackFunc) { 67 | tc.connectedCallback = f 68 | } 69 | 70 | func (tc *tcpConnection) setMessageCallback(f MessageCallbackFunc) { 71 | tc.messageCallback = f 72 | } 73 | 74 | func (tc *tcpConnection) SetHighWaterCallback(f HighWaterCallbackFunc) { 75 | tc.highWaterCallback = f 76 | } 77 | 78 | // 0 means infinite 79 | func (tc *tcpConnection) SetHighWaterLevel(i int) { 80 | tc.hignWaterLevel = i 81 | } 82 | 83 | func (tc *tcpConnection) SetWriteCompleteCallback(f WriteCompleteCallbackFunc) { 84 | tc.writeCompleteCallback = f 85 | } 86 | 87 | func (tc *tcpConnection) SetDisConnectedCallback(f DisConnectedCallbackFunc) { 88 | tc.disconnectedCallback = f 89 | } 90 | 91 | func newConnection(loop eventloop.EventLoop, sockFD int, remoteAddrPort netip.AddrPort) *tcpConnection { 92 | channel := eventloop.NewChannel(sockFD) 93 | c := &tcpConnection{ 94 | state: Connecting, 95 | loop: loop, 96 | socketChannel: channel, 97 | outputBuffer: buffer.NewBuffer(), 98 | inputBuffer: buffer.NewBuffer(), 99 | remoteAddrPort: remoteAddrPort, 100 | highWaterCallback: defaultHighWaterMarkCallback, 101 | writeCompleteCallback: defaultWriteCompleteCallback, 102 | disconnectedCallback: defaultDisConnectedCallback, 103 | ctx: kvcontext.NewContext(), 104 | } 105 | channel.SetReadCallback(c.handleRead) 106 | channel.SetWriteCallback(c.handleWrite) 107 | channel.SetEvent(eventloop.ReadableEvent | eventloop.WritableEvent) 108 | 109 | return c 110 | } 111 | 112 | func (conn *tcpConnection) Send(bs []byte) { 113 | conn.loop.RunInLoop(func() { 114 | if conn.state == Connected { 115 | conn.outputBuffer.Append(bs) 116 | if conn.hignWaterLevel != 0 && conn.outputBuffer.ReadableBytes() > conn.hignWaterLevel { 117 | conn.highWaterCallback(conn, conn.outputBuffer.ReadableBytes()) 118 | } 119 | if !conn.socketChannel.IsWriting() { 120 | conn.socketChannel.EnableWrite() 121 | conn.loop.UpdateChannelInLoopGoroutine(conn.socketChannel) 122 | } 123 | } 124 | }) 125 | } 126 | 127 | func (conn *tcpConnection) ShutdownWrite() { 128 | conn.loop.RunInLoop(func() { 129 | if conn.state == Connected { 130 | conn.state = Disconnecting 131 | if !conn.socketChannel.IsWriting() { 132 | syscall.Shutdown(conn.socketChannel.GetFD(), syscall.SHUT_WR) 133 | } 134 | } 135 | }) 136 | } 137 | 138 | func (conn *tcpConnection) GetRemoteAddrPort() netip.AddrPort { 139 | return conn.remoteAddrPort 140 | } 141 | 142 | func (conn *tcpConnection) ForceClose() { 143 | conn.loop.RunInLoop(func() { 144 | if conn.state == Disconnecting || conn.state == Connected { 145 | conn.handleClose() 146 | } 147 | }) 148 | } 149 | 150 | func (conn *tcpConnection) SetKeepAlive(b bool) { 151 | conn.loop.RunInLoop(func() { 152 | val := 0 153 | if b { 154 | val = 1 155 | } 156 | syscall.SetsockoptInt(conn.socketChannel.GetFD(), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, val) 157 | }) 158 | } 159 | 160 | func (conn *tcpConnection) SetNoDelay(b bool) { 161 | conn.loop.RunInLoop(func() { 162 | val := 0 163 | if b { 164 | val = 1 165 | } 166 | syscall.SetsockoptInt(conn.socketChannel.GetFD(), syscall.IPPROTO_TCP, syscall.TCP_NODELAY, val) 167 | }) 168 | } 169 | 170 | func (conn *tcpConnection) handleRead() { 171 | n := conn.inputBuffer.ReadFD(conn.socketChannel.GetFD(), conn.GetEventLoop().GetExtraData()) 172 | if n > 0 { 173 | conn.messageCallback(conn, conn.inputBuffer) 174 | } else if n <= 0 { 175 | // n为0意味对面已经close write或close total了, 此时直接关闭连接 176 | conn.handleClose() 177 | } 178 | } 179 | 180 | func (conn *tcpConnection) handleWrite() { 181 | n, _ := syscall.Write(conn.socketChannel.GetFD(), conn.outputBuffer.Peek()[:conn.outputBuffer.ReadableBytes()]) 182 | conn.outputBuffer.Retrieve(n) 183 | if conn.outputBuffer.ReadableBytes() == 0 { 184 | conn.socketChannel.DisableWrite() 185 | conn.loop.UpdateChannelInLoopGoroutine(conn.socketChannel) 186 | 187 | conn.writeCompleteCallback(conn) 188 | if conn.state == Disconnecting { 189 | syscall.Shutdown(conn.socketChannel.GetFD(), syscall.SHUT_WR) 190 | } 191 | } 192 | } 193 | 194 | // 比如对端直接close了socket, 那么此时就进入readhup状态了 195 | func (conn *tcpConnection) handleClose() { 196 | if conn.state != Disconnecting && conn.state != Connected { 197 | panic("checkme") 198 | } 199 | 200 | conn.state = Disconnected 201 | conn.loop.RemoveChannelInLoopGoroutine(conn.socketChannel) 202 | syscall.Close(conn.socketChannel.GetFD()) 203 | conn.disconnectedCallback(conn) 204 | } 205 | 206 | func (conn *tcpConnection) establishConn() { 207 | if conn.state != Connecting { 208 | panic("unexpected") 209 | } 210 | 211 | conn.state = Connected 212 | conn.loop.UpdateChannelInLoopGoroutine(conn.socketChannel) 213 | conn.connectedCallback(conn) 214 | } 215 | 216 | func (conn *tcpConnection) GetEventLoop() eventloop.EventLoop { 217 | return conn.loop 218 | } 219 | 220 | func (conn *tcpConnection) SetContext(key string, value interface{}) { 221 | conn.ctx.Set(key, value) 222 | } 223 | 224 | func (conn *tcpConnection) DeleteContext(key string) { 225 | conn.ctx.Delete(key) 226 | } 227 | 228 | func (conn *tcpConnection) GetContext(key string) (interface{}, bool) { 229 | return conn.ctx.Get(key) 230 | } 231 | 232 | func (ev *tcpConnection) MustGetContext(key string) interface{} { 233 | v, ok := ev.ctx.Get(key) 234 | if !ok { 235 | panic("must get context not exists") 236 | } 237 | return v 238 | } 239 | 240 | func (conn *tcpConnection) GetFD() int { 241 | return conn.socketChannel.GetFD() 242 | } 243 | 244 | func (conn *tcpConnection) IsConnected() bool { 245 | c := make(chan bool, 1) 246 | conn.loop.RunInLoop(func() { 247 | c <- conn.state == Connected 248 | }) 249 | return <-c 250 | } 251 | -------------------------------------------------------------------------------- /tcp_server.go: -------------------------------------------------------------------------------- 1 | package goreactor 2 | 3 | import ( 4 | "net/netip" 5 | 6 | eventloop "github.com/markity/go-reactor/pkg/event_loop" 7 | ) 8 | 9 | type TCPServer interface { 10 | SetConnectionCallback(f ConnectedCallbackFunc) 11 | SetMessageCallback(f MessageCallbackFunc) 12 | Start() error 13 | GetAllLoops() (baseLoop eventloop.EventLoop, others []eventloop.EventLoop) 14 | } 15 | 16 | type tcpServer struct { 17 | loop eventloop.EventLoop 18 | 19 | acceptor *tcpAcceptor 20 | 21 | // only be used to prevent double start 22 | started bool 23 | 24 | connectedCallback ConnectedCallbackFunc 25 | msgCallback MessageCallbackFunc 26 | 27 | evloopPoll *eventloopGoroutinePoll 28 | 29 | loadBalanceStrategy LoadBalanceStrategy 30 | } 31 | 32 | func (server *tcpServer) SetConnectionCallback(f ConnectedCallbackFunc) { 33 | server.connectedCallback = f 34 | } 35 | 36 | func (server *tcpServer) SetMessageCallback(f MessageCallbackFunc) { 37 | server.msgCallback = f 38 | } 39 | 40 | func (server *tcpServer) Start() error { 41 | if server.started { 42 | panic("already started") 43 | } 44 | 45 | err := server.acceptor.Listen() 46 | if err != nil { 47 | return err 48 | } 49 | 50 | server.evloopPoll.start() 51 | 52 | server.started = true 53 | 54 | return nil 55 | } 56 | 57 | func (server *tcpServer) GetAllLoops() (baseLoop eventloop.EventLoop, 58 | others []eventloop.EventLoop) { 59 | cpy := make([]eventloop.EventLoop, len(server.evloopPoll.loops)) 60 | copy(cpy, server.evloopPoll.loops) 61 | return server.loop, cpy 62 | } 63 | 64 | func (server *tcpServer) onNewConnection(socketfd int, peerAddr netip.AddrPort) { 65 | loop := server.evloopPoll.getNext() 66 | 67 | conn := newConnection(loop, socketfd, peerAddr) 68 | conn.setConnectedCallback(server.connectedCallback) 69 | conn.setMessageCallback(server.msgCallback) 70 | 71 | loop.RunInLoop(func() { 72 | conn.establishConn() 73 | }) 74 | 75 | } 76 | 77 | // if numWorkingThread is 0, all channel will run on single loop 78 | func NewTCPServer(loop eventloop.EventLoop, addrPort string, 79 | numWorkingThread int, strategy LoadBalanceStrategy) TCPServer { 80 | listenAt, err := netip.ParseAddrPort(addrPort) 81 | if err != nil { 82 | panic(err) 83 | } 84 | 85 | if numWorkingThread < 0 { 86 | panic(numWorkingThread) 87 | } 88 | 89 | acceptor := newTCPAcceptor(loop, listenAt, 1024) 90 | server := &tcpServer{ 91 | loop: loop, 92 | acceptor: acceptor, 93 | started: false, 94 | connectedCallback: defaultConnectedCallback, 95 | msgCallback: defaultMessageCallback, 96 | evloopPoll: newEventloopGoroutinePoll(loop, numWorkingThread, strategy), 97 | loadBalanceStrategy: strategy, 98 | } 99 | 100 | acceptor.SetNewConnectionCallback(server.onNewConnection) 101 | 102 | return server 103 | } 104 | --------------------------------------------------------------------------------