33 | 34 | 35 | 36 | 37 |
38 | 39 | 40 |41 | 42 |
43 | 44 |45 | 46 |
47 | 48 | 49 |50 | 51 |
52 | 53 | 54 | 55 | 56 | 89 | 90 | -------------------------------------------------------------------------------- /cmd/example_server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | "time" 10 | 11 | "github.com/byebyebruce/lockstepserver/cmd/example_server/api" 12 | "github.com/byebyebruce/lockstepserver/pkg/log4gox" 13 | "github.com/byebyebruce/lockstepserver/server" 14 | 15 | l4g "github.com/alecthomas/log4go" 16 | ) 17 | 18 | var ( 19 | httpAddress = flag.String("web", ":80", "web listen address") 20 | udpAddress = flag.String("udp", ":10086", "udp listen address(':10086' means localhost:10086)") 21 | debugLog = flag.Bool("log", true, "debug log") 22 | ) 23 | 24 | func main() { 25 | flag.Parse() 26 | 27 | l4g.Close() 28 | l4g.AddFilter("debug logger", l4g.DEBUG, log4gox.NewColorConsoleLogWriter()) 29 | 30 | s, err := server.New(*udpAddress) 31 | if err != nil { 32 | panic(err) 33 | } 34 | _ = api.NewWebAPI(*httpAddress, s.RoomManager()) 35 | 36 | sigs := make(chan os.Signal, 1) 37 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, os.Interrupt) 38 | ticker := time.NewTimer(time.Minute) 39 | defer ticker.Stop() 40 | 41 | l4g.Info("[main] start...") 42 | // 主循环 43 | QUIT: 44 | for { 45 | select { 46 | case sig := <-sigs: 47 | l4g.Info("Signal: %s", sig.String()) 48 | break QUIT 49 | case <-ticker.C: 50 | // todo 51 | fmt.Println("room number ", s.RoomManager().RoomNum()) 52 | } 53 | } 54 | l4g.Info("[main] quiting...") 55 | s.Stop() 56 | } 57 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/byebyebruce/lockstepserver 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/alecthomas/log4go v0.0.0-20180109082532-d146e6b86faa 7 | github.com/golang/protobuf v1.5.0 8 | github.com/xtaci/kcp-go v5.4.20+incompatible 9 | google.golang.org/protobuf v1.26.0 10 | ) 11 | 12 | require ( 13 | github.com/klauspost/cpuid v1.2.3 // indirect 14 | github.com/klauspost/reedsolomon v1.9.3 // indirect 15 | github.com/pkg/errors v0.9.1 // indirect 16 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect 17 | github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect 18 | github.com/tjfoc/gmsm v1.3.0 // indirect 19 | github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 // indirect 20 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 // indirect 21 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a // indirect 22 | golang.org/x/sys v0.0.0-20190412213103-97732733099d // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alecthomas/log4go v0.0.0-20180109082532-d146e6b86faa h1:0zdYOLyuQ3TWIgWNgEH+LnmZNMmkO1ze3wriQt093Mk= 2 | github.com/alecthomas/log4go v0.0.0-20180109082532-d146e6b86faa/go.mod h1:iCVmQ9g4TfaRX5m5jq5sXY7RXYWPv9/PynM/GocbG3w= 3 | github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= 4 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 5 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 6 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 7 | github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs= 8 | github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 9 | github.com/klauspost/reedsolomon v1.9.3 h1:N/VzgeMfHmLc+KHMD1UL/tNkfXAt8FnUqlgXGIduwAY= 10 | github.com/klauspost/reedsolomon v1.9.3/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4= 11 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 12 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 13 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU= 14 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= 15 | github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI= 16 | github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= 17 | github.com/tjfoc/gmsm v1.3.0 h1:qhgkrZru95jFP9NbVPknJvc9vgkMXhOEzkOASKdc0oQ= 18 | github.com/tjfoc/gmsm v1.3.0/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= 19 | github.com/xtaci/kcp-go v5.4.20+incompatible h1:TN1uey3Raw0sTz0Fg8GkfM0uH3YwzhnZWQ1bABv5xAg= 20 | github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= 21 | github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E= 22 | github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0= 23 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 24 | golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 25 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM= 26 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 27 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 28 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= 29 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 30 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 31 | golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= 32 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 33 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 34 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 35 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 36 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 37 | google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= 38 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 39 | -------------------------------------------------------------------------------- /logic/game/game.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/byebyebruce/lockstepserver/pb" 7 | "github.com/byebyebruce/lockstepserver/pkg/network" 8 | "github.com/byebyebruce/lockstepserver/pkg/packet/pb_packet" 9 | "github.com/golang/protobuf/proto" 10 | 11 | l4g "github.com/alecthomas/log4go" 12 | ) 13 | 14 | // GameState 游戏状态 15 | type GameState int 16 | 17 | const ( 18 | k_Ready GameState = 0 // 准备阶段 19 | k_Gaming = 1 // 战斗中阶段 20 | k_Over = 2 // 结束阶段 21 | k_Stop = 3 // 停止 22 | ) 23 | 24 | const ( 25 | MaxReadyTime int64 = 20 // 准备阶段最长时间,如果超过这个时间没人连进来直接关闭游戏 26 | MaxGameFrame uint32 = 30*60*3 + 100 // 每局最大帧数 27 | BroadcastOffsetFrames = 3 // 每隔多少帧广播一次 28 | kMaxFrameDataPerMsg = 60 // 每个消息包最多包含多少个帧数据 29 | kBadNetworkThreshold = 2 // 这个时间段没有收到心跳包认为他网络很差,不再持续给发包(网络层的读写时间设置的比较长,客户端要求的方案) 30 | ) 31 | 32 | type gameListener interface { 33 | OnJoinGame(uint64, uint64) 34 | OnGameStart(uint64) 35 | OnLeaveGame(uint64, uint64) 36 | OnGameOver(uint64) 37 | } 38 | 39 | // Game 一局游戏 40 | type Game struct { 41 | id uint64 42 | startTime int64 43 | randomSeed int32 44 | State GameState 45 | players map[uint64]*Player 46 | logic *lockstep 47 | clientFrameCount uint32 48 | 49 | result map[uint64]uint64 50 | 51 | listener gameListener 52 | 53 | dirty bool 54 | } 55 | 56 | // NewGame 构造游戏 57 | func NewGame(id uint64, players []uint64, randomSeed int32, listener gameListener) *Game { 58 | g := &Game{ 59 | id: id, 60 | players: make(map[uint64]*Player), 61 | logic: newLockstep(), 62 | startTime: time.Now().Unix(), 63 | randomSeed: randomSeed, 64 | listener: listener, 65 | result: make(map[uint64]uint64), 66 | } 67 | 68 | for k, v := range players { 69 | g.players[v] = NewPlayer(v, int32(k+1)) 70 | } 71 | 72 | return g 73 | } 74 | 75 | // JoinGame 加入游戏 76 | func (g *Game) JoinGame(id uint64, conn *network.Conn) bool { 77 | 78 | msg := &pb.S2C_ConnectMsg{ 79 | ErrorCode: pb.ERRORCODE_ERR_Ok.Enum(), 80 | } 81 | 82 | p, ok := g.players[id] 83 | if !ok { 84 | l4g.Error("[game(%d)] player[%d] join room failed", g.id, id) 85 | return false 86 | } 87 | 88 | if k_Ready != g.State && k_Gaming != g.State { 89 | msg.ErrorCode = pb.ERRORCODE_ERR_RoomState.Enum() 90 | p.SendMessage(pb_packet.NewPacket(uint8(pb.ID_MSG_Connect), msg)) 91 | l4g.Error("[game(%d)] player[%d] game is over", g.id, id) 92 | return true 93 | } 94 | 95 | // 把现有的玩家顶掉 96 | if nil != p.client { 97 | // TODO 这里有多线程操作的危险 如果调 p.client.Close() 会把现有刚进来的玩家提调 98 | p.client.PutExtraData(nil) 99 | l4g.Error("[game(%d)] player[%d] replace", g.id, id) 100 | } 101 | 102 | p.Connect(conn) 103 | 104 | p.SendMessage(pb_packet.NewPacket(uint8(pb.ID_MSG_Connect), msg)) 105 | 106 | g.listener.OnJoinGame(g.id, id) 107 | 108 | return true 109 | 110 | } 111 | 112 | // LeaveGame 离开游戏 113 | func (g *Game) LeaveGame(id uint64) bool { 114 | 115 | p, ok := g.players[id] 116 | if !ok { 117 | return false 118 | } 119 | 120 | p.Cleanup() 121 | 122 | g.listener.OnLeaveGame(g.id, id) 123 | 124 | return true 125 | } 126 | 127 | // ProcessMsg 处理消息 128 | func (g *Game) ProcessMsg(id uint64, msg *pb_packet.Packet) { 129 | 130 | player, ok := g.players[id] 131 | if !ok { 132 | l4g.Error("[game(%d)] processMsg player[%d] msg=[%d]", g.id, player.id, msg.GetMessageID()) 133 | return 134 | } 135 | l4g.Info("[game(%d)] processMsg player[%d] msg=[%d]", g.id, player.id, msg.GetMessageID()) 136 | 137 | msgID := pb.ID(msg.GetMessageID()) 138 | 139 | switch msgID { 140 | case pb.ID_MSG_JoinRoom: 141 | msg := &pb.S2C_JoinRoomMsg{ 142 | Roomseatid: proto.Int32(player.idx), 143 | RandomSeed: proto.Int32(g.randomSeed), 144 | } 145 | 146 | for _, v := range g.players { 147 | if player.id == v.id { 148 | continue 149 | } 150 | msg.Others = append(msg.Others, v.id) 151 | msg.Pros = append(msg.Pros, v.loadingProgress) 152 | } 153 | 154 | player.SendMessage(pb_packet.NewPacket(uint8(pb.ID_MSG_JoinRoom), msg)) 155 | 156 | case pb.ID_MSG_Progress: 157 | if g.State > k_Ready { 158 | break 159 | } 160 | m := &pb.C2S_ProgressMsg{} 161 | if err := msg.Unmarshal(m); nil != err { 162 | l4g.Error("[game(%d)] processMsg player[%d] msg=[%d] UnmarshalPB error:[%s]", g.id, player.id, msg.GetMessageID(), err.Error()) 163 | return 164 | } 165 | player.loadingProgress = m.GetPro() 166 | msg := pb_packet.NewPacket(uint8(pb.ID_MSG_Progress), &pb.S2C_ProgressMsg{ 167 | 168 | Id: proto.Uint64(player.id), 169 | Pro: m.Pro, 170 | }) 171 | g.broadcastExclude(msg, player.id) 172 | 173 | case pb.ID_MSG_Heartbeat: 174 | player.SendMessage(pb_packet.NewPacket(uint8(pb.ID_MSG_Heartbeat), nil)) 175 | player.RefreshHeartbeatTime() 176 | case pb.ID_MSG_Ready: 177 | if k_Ready == g.State { 178 | g.doReady(player) 179 | } else if k_Gaming == g.State { 180 | g.doReady(player) 181 | // 重连进来 TODO 对重连进行检查,重连比较耗费 182 | g.doReconnect(player) 183 | l4g.Warn("[game(%d)] doReconnect [%d]", g.id, player.id) 184 | } else { 185 | l4g.Error("[game(%d)] ID_MSG_Ready player[%d] state error:[%d]", g.id, player.id, g.State) 186 | } 187 | 188 | case pb.ID_MSG_Input: 189 | m := &pb.C2S_InputMsg{} 190 | if err := msg.Unmarshal(m); nil != err { 191 | l4g.Error("[game(%d)] processMsg player[%d] msg=[%d] UnmarshalPB error:[%s]", g.id, player.id, msg.GetMessageID(), err.Error()) 192 | return 193 | } 194 | if !g.pushInput(player, m) { 195 | l4g.Warn("[game(%d)] processMsg player[%d] msg=[%d] pushInput failed", g.id, player.id, msg.GetMessageID()) 196 | break 197 | } 198 | 199 | // 下一帧强制广播(客户端要求) 200 | g.dirty = true 201 | case pb.ID_MSG_Result: 202 | m := &pb.C2S_ResultMsg{} 203 | if err := msg.Unmarshal(m); nil != err { 204 | l4g.Error("[game(%d)] processMsg player[%d] msg=[%d] UnmarshalPB error:[%s]", g.id, player.id, msg.GetMessageID(), err.Error()) 205 | return 206 | } 207 | g.result[player.id] = m.GetWinnerID() 208 | l4g.Info("[game(%d)] ID_MSG_Result player[%d] winner=[%d]", g.id, player.id, m.GetWinnerID()) 209 | player.SendMessage(pb_packet.NewPacket(uint8(pb.ID_MSG_Result), nil)) 210 | default: 211 | l4g.Warn("[game(%d)] processMsg unknown message id[%d]", msgID) 212 | } 213 | 214 | } 215 | 216 | // Tick 主逻辑 217 | func (g *Game) Tick(now int64) bool { 218 | 219 | switch g.State { 220 | case k_Ready: 221 | delta := now - g.startTime 222 | if delta < MaxReadyTime { 223 | if g.checkReady() { 224 | g.doStart() 225 | g.State = k_Gaming 226 | } 227 | 228 | } else { 229 | if g.getOnlinePlayerCount() > 0 { 230 | // 大于最大准备时间,只要有在线的,就强制开始 231 | g.doStart() 232 | g.State = k_Gaming 233 | l4g.Warn("[game(%d)] force start game because ready state is timeout ", g.id) 234 | } else { 235 | // 全都没连进来,直接结束 236 | g.State = k_Over 237 | l4g.Error("[game(%d)] game over!! nobody ready", g.id) 238 | } 239 | } 240 | 241 | return true 242 | case k_Gaming: 243 | if g.checkOver() { 244 | g.State = k_Over 245 | l4g.Info("[game(%d)] game over successfully!!", g.id) 246 | return true 247 | } 248 | 249 | if g.isTimeout() { 250 | g.State = k_Over 251 | l4g.Warn("[game(%d)] game timeout", g.id) 252 | return true 253 | } 254 | 255 | g.logic.tick() 256 | g.broadcastFrameData() 257 | 258 | return true 259 | case k_Over: 260 | g.doGameOver() 261 | g.State = k_Stop 262 | l4g.Info("[game(%d)] do game over", g.id) 263 | return true 264 | case k_Stop: 265 | return false 266 | } 267 | 268 | return false 269 | } 270 | 271 | // Result 战斗结果 272 | func (g *Game) Result() map[uint64]uint64 { 273 | return g.result 274 | } 275 | 276 | // Close 关闭游戏 277 | func (g *Game) Close() { 278 | msg := pb_packet.NewPacket(uint8(pb.ID_MSG_Close), nil) 279 | g.broadcast(msg) 280 | } 281 | 282 | // Cleanup 清理游戏 283 | func (g *Game) Cleanup() { 284 | for _, v := range g.players { 285 | v.Cleanup() 286 | } 287 | g.players = make(map[uint64]*Player) 288 | 289 | } 290 | 291 | func (g *Game) doReady(p *Player) { 292 | 293 | if p.isReady == true { 294 | return 295 | } 296 | 297 | p.isReady = true 298 | 299 | msg := pb_packet.NewPacket(uint8(pb.ID_MSG_Ready), nil) 300 | p.SendMessage(msg) 301 | } 302 | 303 | func (g *Game) checkReady() bool { 304 | for _, v := range g.players { 305 | if !v.isReady { 306 | return false 307 | } 308 | } 309 | 310 | return true 311 | } 312 | 313 | func (g *Game) doStart() { 314 | 315 | g.clientFrameCount = 0 316 | g.logic.reset() 317 | for _, v := range g.players { 318 | v.isReady = true 319 | v.loadingProgress = 100 320 | } 321 | g.startTime = time.Now().Unix() 322 | msg := &pb.S2C_StartMsg{ 323 | TimeStamp: proto.Int64(g.startTime), 324 | } 325 | ret := pb_packet.NewPacket(uint8(pb.ID_MSG_Start), msg) 326 | 327 | g.broadcast(ret) 328 | 329 | g.listener.OnGameStart(g.id) 330 | } 331 | 332 | func (g *Game) doGameOver() { 333 | 334 | g.listener.OnGameOver(g.id) 335 | } 336 | 337 | func (g *Game) pushInput(p *Player, msg *pb.C2S_InputMsg) bool { 338 | 339 | cmd := &pb.InputData{ 340 | Id: proto.Uint64(p.id), 341 | Sid: proto.Int32(msg.GetSid()), 342 | X: proto.Int32(msg.GetX()), 343 | Y: proto.Int32(msg.GetY()), 344 | Roomseatid: proto.Int32(p.idx), 345 | } 346 | 347 | return g.logic.pushCmd(cmd) 348 | } 349 | 350 | func (g *Game) doReconnect(p *Player) { 351 | 352 | msg := &pb.S2C_StartMsg{ 353 | TimeStamp: proto.Int64(g.startTime), 354 | } 355 | ret := pb_packet.NewPacket(uint8(pb.ID_MSG_Start), msg) 356 | p.SendMessage(ret) 357 | 358 | framesCount := g.clientFrameCount 359 | var i uint32 = 0 360 | c := 0 361 | frameMsg := &pb.S2C_FrameMsg{} 362 | 363 | for ; i < framesCount; i++ { 364 | 365 | frameData := g.logic.getFrame(i) 366 | if nil == frameData && i != (framesCount-1) { 367 | continue 368 | } 369 | 370 | f := &pb.FrameData{ 371 | FrameID: proto.Uint32(i), 372 | } 373 | 374 | if nil != frameData { 375 | f.Input = frameData.cmds 376 | } 377 | frameMsg.Frames = append(frameMsg.Frames, f) 378 | c++ 379 | 380 | if c >= kMaxFrameDataPerMsg || i == (framesCount-1) { 381 | p.SendMessage(pb_packet.NewPacket(uint8(pb.ID_MSG_Frame), frameMsg)) 382 | c = 0 383 | frameMsg = &pb.S2C_FrameMsg{} 384 | } 385 | } 386 | 387 | p.SetSendFrameCount(g.clientFrameCount) 388 | 389 | } 390 | 391 | func (g *Game) broadcastFrameData() { 392 | 393 | framesCount := g.logic.getFrameCount() 394 | 395 | if !g.dirty && framesCount-g.clientFrameCount < BroadcastOffsetFrames { 396 | return 397 | } 398 | 399 | defer func() { 400 | g.dirty = false 401 | g.clientFrameCount = framesCount 402 | }() 403 | 404 | /* 405 | msg := &pb.S2C_FrameMsg{} 406 | 407 | for i := g.clientFrameCount; i < g.logic.getFrameCount(); i++ { 408 | frameData := g.logic.getFrame(i) 409 | 410 | if nil == frameData && i != (g.logic.getFrameCount()-1) { 411 | continue 412 | } 413 | 414 | f := &pb.FrameData{} 415 | f.FrameID = proto.Uint32(i) 416 | msg.Frames = append(msg.Frames, f) 417 | 418 | if nil != frameData { 419 | f.Input = frameData.cmds 420 | } 421 | 422 | } 423 | if len(msg.Frames) > 0 { 424 | g.broadcast(pb_packet.NewPacket(uint8(pb.ID_MSG_Frame), msg)) 425 | } 426 | */ 427 | now := time.Now().Unix() 428 | 429 | for _, p := range g.players { 430 | 431 | // 掉线的 432 | if !p.IsOnline() { 433 | continue 434 | } 435 | 436 | if !p.isReady { 437 | continue 438 | } 439 | 440 | // 网络不好的 441 | if now-p.GetLastHeartbeatTime() >= kBadNetworkThreshold { 442 | continue 443 | } 444 | 445 | // 获得这个玩家已经发到哪一帧 446 | i := p.GetSendFrameCount() 447 | c := 0 448 | msg := &pb.S2C_FrameMsg{} 449 | for ; i < framesCount; i++ { 450 | frameData := g.logic.getFrame(i) 451 | if nil == frameData && i != (framesCount-1) { 452 | continue 453 | } 454 | 455 | f := &pb.FrameData{ 456 | FrameID: proto.Uint32(i), 457 | } 458 | 459 | if nil != frameData { 460 | f.Input = frameData.cmds 461 | } 462 | msg.Frames = append(msg.Frames, f) 463 | c++ 464 | 465 | // 如果是最后一帧或者达到这个消息包能装下的最大帧数,就发送 466 | if i == (framesCount-1) || c >= kMaxFrameDataPerMsg { 467 | p.SendMessage(pb_packet.NewPacket(uint8(pb.ID_MSG_Frame), msg)) 468 | c = 0 469 | msg = &pb.S2C_FrameMsg{} 470 | } 471 | 472 | } 473 | 474 | p.SetSendFrameCount(framesCount) 475 | 476 | } 477 | 478 | } 479 | 480 | func (g *Game) broadcast(msg network.Packet) { 481 | for _, v := range g.players { 482 | v.SendMessage(msg) 483 | } 484 | } 485 | 486 | func (g *Game) broadcastExclude(msg network.Packet, id uint64) { 487 | for _, v := range g.players { 488 | if v.id == id { 489 | continue 490 | } 491 | v.SendMessage(msg) 492 | } 493 | } 494 | 495 | func (g *Game) getPlayer(id uint64) *Player { 496 | 497 | return g.players[id] 498 | } 499 | 500 | func (g *Game) getPlayerCount() int { 501 | 502 | return len(g.players) 503 | } 504 | 505 | func (g *Game) getOnlinePlayerCount() int { 506 | 507 | i := 0 508 | for _, v := range g.players { 509 | if v.IsOnline() { 510 | i++ 511 | } 512 | } 513 | 514 | return i 515 | } 516 | 517 | func (g *Game) checkOver() bool { 518 | // 只要有人没发结果并且还在线,就不结束 519 | for _, v := range g.players { 520 | if !v.isOnline { 521 | continue 522 | } 523 | 524 | if _, ok := g.result[v.id]; !ok { 525 | return false 526 | } 527 | } 528 | 529 | return true 530 | } 531 | 532 | func (g *Game) isTimeout() bool { 533 | return g.logic.getFrameCount() > MaxGameFrame 534 | 535 | return true 536 | } 537 | -------------------------------------------------------------------------------- /logic/game/lockstep.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "github.com/byebyebruce/lockstepserver/pb" 5 | ) 6 | 7 | type frameData struct { 8 | idx uint32 9 | cmds []*pb.InputData 10 | } 11 | 12 | func newFrameData(index uint32) *frameData { 13 | f := &frameData{ 14 | idx: index, 15 | cmds: make([]*pb.InputData, 0), 16 | } 17 | 18 | return f 19 | } 20 | 21 | type lockstep struct { 22 | frames map[uint32]*frameData 23 | frameCount uint32 24 | } 25 | 26 | func newLockstep() *lockstep { 27 | l := &lockstep{ 28 | frames: make(map[uint32]*frameData), 29 | } 30 | 31 | return l 32 | } 33 | 34 | func (l *lockstep) reset() { 35 | l.frames = make(map[uint32]*frameData) 36 | l.frameCount = 0 37 | } 38 | 39 | func (l *lockstep) getFrameCount() uint32 { 40 | return l.frameCount 41 | } 42 | 43 | func (l *lockstep) pushCmd(cmd *pb.InputData) bool { 44 | f, ok := l.frames[l.frameCount] 45 | if !ok { 46 | f = newFrameData(l.frameCount) 47 | l.frames[l.frameCount] = f 48 | } 49 | 50 | // 检查是否同一帧发来两次操作 51 | for _, v := range f.cmds { 52 | if v.Id == cmd.Id { 53 | return false 54 | } 55 | } 56 | 57 | f.cmds = append(f.cmds, cmd) 58 | 59 | return true 60 | } 61 | 62 | func (l *lockstep) tick() uint32 { 63 | l.frameCount++ 64 | return l.frameCount 65 | } 66 | 67 | func (l *lockstep) getRangeFrames(from, to uint32) []*frameData { 68 | ret := make([]*frameData, 0, to-from) 69 | 70 | for ; from <= to && from <= l.frameCount; from++ { 71 | f, ok := l.frames[from] 72 | if !ok { 73 | continue 74 | } 75 | ret = append(ret, f) 76 | } 77 | 78 | return ret 79 | } 80 | 81 | func (l *lockstep) getFrame(idx uint32) *frameData { 82 | 83 | return l.frames[idx] 84 | } 85 | -------------------------------------------------------------------------------- /logic/game/player.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/byebyebruce/lockstepserver/pkg/network" 7 | ) 8 | 9 | type Player struct { 10 | id uint64 11 | idx int32 12 | isReady bool 13 | isOnline bool 14 | loadingProgress int32 15 | lastHeartbeatTime int64 16 | sendFrameCount uint32 17 | client *network.Conn 18 | } 19 | 20 | func NewPlayer(id uint64, idx int32) *Player { 21 | p := &Player{ 22 | id: id, 23 | idx: idx, 24 | } 25 | 26 | return p 27 | } 28 | 29 | func (p *Player) Connect(conn *network.Conn) { 30 | p.client = conn 31 | p.isOnline = true 32 | p.isReady = false 33 | p.lastHeartbeatTime = time.Now().Unix() 34 | } 35 | 36 | func (p *Player) IsOnline() bool { 37 | return nil != p.client && p.isOnline 38 | } 39 | 40 | func (p *Player) RefreshHeartbeatTime() { 41 | p.lastHeartbeatTime = time.Now().Unix() 42 | } 43 | 44 | func (p *Player) GetLastHeartbeatTime() int64 { 45 | return p.lastHeartbeatTime 46 | } 47 | 48 | func (p *Player) SetSendFrameCount(c uint32) { 49 | p.sendFrameCount = c 50 | } 51 | 52 | func (p *Player) GetSendFrameCount() uint32 { 53 | return p.sendFrameCount 54 | } 55 | 56 | func (p *Player) SendMessage(msg network.Packet) { 57 | 58 | if !p.IsOnline() { 59 | return 60 | } 61 | 62 | if nil != p.client.AsyncWritePacket(msg, 0) { 63 | p.client.Close() 64 | } 65 | } 66 | 67 | func (p *Player) Cleanup() { 68 | 69 | if nil != p.client { 70 | p.client.Close() 71 | } 72 | p.client = nil 73 | p.isReady = false 74 | p.isOnline = false 75 | 76 | } 77 | -------------------------------------------------------------------------------- /logic/manager.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/byebyebruce/lockstepserver/logic/room" 8 | ) 9 | 10 | // RoomManager 房间管理器 11 | type RoomManager struct { 12 | room map[uint64]*room.Room 13 | wg sync.WaitGroup 14 | rw sync.RWMutex 15 | } 16 | 17 | // NewRoomManager 构造 18 | func NewRoomManager() *RoomManager { 19 | m := &RoomManager{ 20 | room: make(map[uint64]*room.Room), 21 | } 22 | return m 23 | } 24 | 25 | // CreateRoom 创建房间 26 | func (m *RoomManager) CreateRoom(id uint64, typeID int32, playerID []uint64, randomSeed int32, logicServer string) (*room.Room, error) { 27 | m.rw.Lock() 28 | defer m.rw.Unlock() 29 | 30 | r, ok := m.room[id] 31 | if ok { 32 | return nil, fmt.Errorf("room id[%d] exists", id) 33 | } 34 | 35 | r = room.NewRoom(id, typeID, playerID, randomSeed, logicServer) 36 | m.room[id] = r 37 | 38 | go func() { 39 | m.wg.Add(1) 40 | defer func() { 41 | m.rw.Lock() 42 | delete(m.room, id) 43 | m.rw.Unlock() 44 | 45 | m.wg.Done() 46 | }() 47 | r.Run() 48 | 49 | }() 50 | 51 | return r, nil 52 | } 53 | 54 | // GetRoom 获得房间 55 | func (m *RoomManager) GetRoom(id uint64) *room.Room { 56 | 57 | m.rw.RLock() 58 | defer m.rw.RUnlock() 59 | 60 | r, _ := m.room[id] 61 | return r 62 | } 63 | 64 | // RoomNum 获得房间数量 65 | func (m *RoomManager) RoomNum() int { 66 | 67 | m.rw.RLock() 68 | defer m.rw.RUnlock() 69 | 70 | return len(m.room) 71 | } 72 | 73 | // Stop 停止 74 | func (m *RoomManager) Stop() { 75 | 76 | m.rw.Lock() 77 | for _, v := range m.room { 78 | v.Stop() 79 | } 80 | m.room = make(map[uint64]*room.Room) 81 | m.rw.Unlock() 82 | 83 | m.wg.Wait() 84 | } 85 | -------------------------------------------------------------------------------- /logic/room/room.go: -------------------------------------------------------------------------------- 1 | package room 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "time" 7 | 8 | "github.com/byebyebruce/lockstepserver/logic/game" 9 | "github.com/byebyebruce/lockstepserver/pkg/network" 10 | "github.com/byebyebruce/lockstepserver/pkg/packet/pb_packet" 11 | 12 | l4g "github.com/alecthomas/log4go" 13 | ) 14 | 15 | const ( 16 | Frequency = 30 // 每分钟心跳频率 17 | TickTimer = time.Second / Frequency // 心跳Timer 18 | TimeoutTime = time.Minute * 5 // 超时时间 19 | ) 20 | 21 | type packet struct { 22 | id uint64 23 | msg network.Packet 24 | } 25 | 26 | // Room 战斗房间 27 | type Room struct { 28 | wg sync.WaitGroup 29 | 30 | roomID uint64 31 | players []uint64 32 | typeID int32 33 | closeFlag int32 34 | timeStamp int64 35 | secretKey string 36 | logicServer string 37 | 38 | exitChan chan struct{} 39 | msgQ chan *packet 40 | inChan chan *network.Conn 41 | outChan chan *network.Conn 42 | 43 | game *game.Game 44 | } 45 | 46 | // NewRoom 构造 47 | func NewRoom(id uint64, typeID int32, players []uint64, randomSeed int32, logicServer string) *Room { 48 | r := &Room{ 49 | roomID: id, 50 | players: players, 51 | typeID: typeID, 52 | exitChan: make(chan struct{}), 53 | msgQ: make(chan *packet, 2048), 54 | outChan: make(chan *network.Conn, 8), 55 | inChan: make(chan *network.Conn, 8), 56 | timeStamp: time.Now().Unix(), 57 | logicServer: logicServer, 58 | secretKey: "test_room", 59 | } 60 | 61 | r.game = game.NewGame(id, players, randomSeed, r) 62 | 63 | return r 64 | } 65 | 66 | // ID room ID 67 | func (r *Room) ID() uint64 { 68 | return r.roomID 69 | } 70 | 71 | // SecretKey secret key 72 | func (r *Room) SecretKey() string { 73 | return r.secretKey 74 | } 75 | 76 | // TimeStamp time stamp 77 | func (r *Room) TimeStamp() int64 { 78 | return r.timeStamp 79 | } 80 | 81 | // IsOver 是否已经结束 82 | func (r *Room) IsOver() bool { 83 | return atomic.LoadInt32(&r.closeFlag) != 0 84 | } 85 | 86 | // HasPlayer 是否有这个player 87 | func (r *Room) HasPlayer(id uint64) bool { 88 | for _, v := range r.players { 89 | if v == id { 90 | return true 91 | } 92 | } 93 | 94 | return false 95 | } 96 | 97 | func (r *Room) OnJoinGame(id, pid uint64) { 98 | l4g.Warn("[room(%d)] onJoinGame %d", id, pid) 99 | } 100 | func (r *Room) OnGameStart(id uint64) { 101 | l4g.Warn("[room(%d)] onGameStart", id) 102 | } 103 | 104 | func (r *Room) OnLeaveGame(id, pid uint64) { 105 | l4g.Warn("[room(%d)] onLeaveGame %d", id, pid) 106 | } 107 | func (r *Room) OnGameOver(id uint64) { 108 | atomic.StoreInt32(&r.closeFlag, 1) 109 | 110 | l4g.Warn("[room(%d)] onGameOver", id) 111 | 112 | r.wg.Add(1) 113 | 114 | go func() { 115 | defer r.wg.Done() 116 | // TODO 117 | // http result 118 | }() 119 | 120 | } 121 | 122 | // OnConnect network.Conn callback 123 | func (r *Room) OnConnect(conn *network.Conn) bool { 124 | 125 | conn.SetCallback(r) // SetCallback只能在OnConnect里调 126 | r.inChan <- conn 127 | l4g.Warn("[room(%d)] OnConnect %d", r.roomID, conn.GetExtraData().(uint64)) 128 | 129 | return true 130 | } 131 | 132 | // OnMessage network.Conn callback 133 | func (r *Room) OnMessage(conn *network.Conn, msg network.Packet) bool { 134 | 135 | id, ok := conn.GetExtraData().(uint64) 136 | if !ok { 137 | l4g.Error("[room] OnMessage error conn don't have id") 138 | return false 139 | } 140 | 141 | p := &packet{ 142 | id: id, 143 | msg: msg, 144 | } 145 | r.msgQ <- p 146 | 147 | return true 148 | } 149 | 150 | // OnClose network.Conn callback 151 | func (r *Room) OnClose(conn *network.Conn) { 152 | r.outChan <- conn 153 | if id, ok := conn.GetExtraData().(uint64); ok { 154 | l4g.Warn("[room(%d)] OnClose %d", r.roomID, id) 155 | } else { 156 | l4g.Warn("[room(%d)] OnClose no id", r.roomID) 157 | } 158 | 159 | } 160 | 161 | // Run 主循环 162 | func (r *Room) Run() { 163 | r.wg.Add(1) 164 | defer r.wg.Done() 165 | defer func() { 166 | /* 167 | err := recover() 168 | if nil != err { 169 | l4g.Error("[room(%d)] Run error:%+v", r.roomID, err) 170 | }*/ 171 | r.game.Cleanup() 172 | l4g.Warn("[room(%d)] quit! total time=[%d]", r.roomID, time.Now().Unix()-r.timeStamp) 173 | }() 174 | 175 | // 心跳 176 | tickerTick := time.NewTicker(TickTimer) 177 | defer tickerTick.Stop() 178 | 179 | // 超时timer 180 | timeoutTimer := time.NewTimer(TimeoutTime) 181 | 182 | l4g.Info("[room(%d)] running...", r.roomID) 183 | 184 | LOOP: 185 | for { 186 | select { 187 | case <-r.exitChan: 188 | l4g.Error("[room(%d)] force exit", r.roomID) 189 | return 190 | case <-timeoutTimer.C: 191 | l4g.Error("[room(%d)] time out", r.roomID) 192 | break LOOP 193 | case msg := <-r.msgQ: 194 | r.game.ProcessMsg(msg.id, msg.msg.(*pb_packet.Packet)) 195 | case <-tickerTick.C: 196 | if !r.game.Tick(time.Now().Unix()) { 197 | l4g.Info("[room(%d)] tick over", r.roomID) 198 | break LOOP 199 | } 200 | case c := <-r.inChan: 201 | id, ok := c.GetExtraData().(uint64) 202 | if ok { 203 | if r.game.JoinGame(id, c) { 204 | l4g.Info("[room(%d)] player[%d] join room ok", r.roomID, id) 205 | } else { 206 | l4g.Error("[room(%d)] player[%d] join room failed", r.roomID, id) 207 | c.Close() 208 | } 209 | } else { 210 | c.Close() 211 | l4g.Error("[room(%d)] inChan don't have id", r.roomID) 212 | } 213 | 214 | case c := <-r.outChan: 215 | if id, ok := c.GetExtraData().(uint64); ok { 216 | r.game.LeaveGame(id) 217 | } else { 218 | c.Close() 219 | l4g.Error("[room(%d)] outChan don't have id", r.roomID) 220 | } 221 | } 222 | } 223 | 224 | r.game.Close() 225 | 226 | for i := 3; i > 0; i-- { 227 | <-time.After(time.Second) 228 | l4g.Info("[room(%d)] quiting %d...", r.roomID, i) 229 | } 230 | } 231 | 232 | // Stop 强制关闭 233 | func (r *Room) Stop() { 234 | close(r.exitChan) 235 | r.wg.Wait() 236 | } 237 | -------------------------------------------------------------------------------- /pb/gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | protoc --go_out=. --go_opt=paths=source_relative *.proto -------------------------------------------------------------------------------- /pb/message.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.26.0 4 | // protoc v3.17.3 5 | // source: message.proto 6 | 7 | package pb 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | //消息ID 24 | type ID int32 25 | 26 | const ( 27 | ID_MSG_BEGIN ID = 0 28 | ID_MSG_Connect ID = 1 //连接(客户端发来第一个消息) 29 | ID_MSG_Heartbeat ID = 2 //心跳(服务端返回Connect成功之后每隔1秒发送一个心跳包) 30 | ID_MSG_JoinRoom ID = 10 //进入 31 | ID_MSG_Progress ID = 20 //进度 32 | ID_MSG_Ready ID = 30 //准备 33 | ID_MSG_Start ID = 40 //开始 34 | ID_MSG_Frame ID = 50 //帧数据 35 | ID_MSG_Input ID = 60 //输入 36 | ID_MSG_Result ID = 70 //结果 37 | ID_MSG_Close ID = 100 //房间关闭 38 | ID_MSG_END ID = 255 39 | ) 40 | 41 | // Enum value maps for ID. 42 | var ( 43 | ID_name = map[int32]string{ 44 | 0: "MSG_BEGIN", 45 | 1: "MSG_Connect", 46 | 2: "MSG_Heartbeat", 47 | 10: "MSG_JoinRoom", 48 | 20: "MSG_Progress", 49 | 30: "MSG_Ready", 50 | 40: "MSG_Start", 51 | 50: "MSG_Frame", 52 | 60: "MSG_Input", 53 | 70: "MSG_Result", 54 | 100: "MSG_Close", 55 | 255: "MSG_END", 56 | } 57 | ID_value = map[string]int32{ 58 | "MSG_BEGIN": 0, 59 | "MSG_Connect": 1, 60 | "MSG_Heartbeat": 2, 61 | "MSG_JoinRoom": 10, 62 | "MSG_Progress": 20, 63 | "MSG_Ready": 30, 64 | "MSG_Start": 40, 65 | "MSG_Frame": 50, 66 | "MSG_Input": 60, 67 | "MSG_Result": 70, 68 | "MSG_Close": 100, 69 | "MSG_END": 255, 70 | } 71 | ) 72 | 73 | func (x ID) Enum() *ID { 74 | p := new(ID) 75 | *p = x 76 | return p 77 | } 78 | 79 | func (x ID) String() string { 80 | return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 81 | } 82 | 83 | func (ID) Descriptor() protoreflect.EnumDescriptor { 84 | return file_message_proto_enumTypes[0].Descriptor() 85 | } 86 | 87 | func (ID) Type() protoreflect.EnumType { 88 | return &file_message_proto_enumTypes[0] 89 | } 90 | 91 | func (x ID) Number() protoreflect.EnumNumber { 92 | return protoreflect.EnumNumber(x) 93 | } 94 | 95 | // Deprecated: Use ID.Descriptor instead. 96 | func (ID) EnumDescriptor() ([]byte, []int) { 97 | return file_message_proto_rawDescGZIP(), []int{0} 98 | } 99 | 100 | //错误码 101 | type ERRORCODE int32 102 | 103 | const ( 104 | ERRORCODE_ERR_Ok ERRORCODE = 0 //OK 105 | ERRORCODE_ERR_NoPlayer ERRORCODE = 1 //没有这个玩家 106 | ERRORCODE_ERR_NoRoom ERRORCODE = 2 //没有房间 107 | ERRORCODE_ERR_RoomState ERRORCODE = 3 //房间状态不正确 108 | ERRORCODE_ERR_Token ERRORCODE = 4 //Token验证失败 109 | ) 110 | 111 | // Enum value maps for ERRORCODE. 112 | var ( 113 | ERRORCODE_name = map[int32]string{ 114 | 0: "ERR_Ok", 115 | 1: "ERR_NoPlayer", 116 | 2: "ERR_NoRoom", 117 | 3: "ERR_RoomState", 118 | 4: "ERR_Token", 119 | } 120 | ERRORCODE_value = map[string]int32{ 121 | "ERR_Ok": 0, 122 | "ERR_NoPlayer": 1, 123 | "ERR_NoRoom": 2, 124 | "ERR_RoomState": 3, 125 | "ERR_Token": 4, 126 | } 127 | ) 128 | 129 | func (x ERRORCODE) Enum() *ERRORCODE { 130 | p := new(ERRORCODE) 131 | *p = x 132 | return p 133 | } 134 | 135 | func (x ERRORCODE) String() string { 136 | return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 137 | } 138 | 139 | func (ERRORCODE) Descriptor() protoreflect.EnumDescriptor { 140 | return file_message_proto_enumTypes[1].Descriptor() 141 | } 142 | 143 | func (ERRORCODE) Type() protoreflect.EnumType { 144 | return &file_message_proto_enumTypes[1] 145 | } 146 | 147 | func (x ERRORCODE) Number() protoreflect.EnumNumber { 148 | return protoreflect.EnumNumber(x) 149 | } 150 | 151 | // Deprecated: Use ERRORCODE.Descriptor instead. 152 | func (ERRORCODE) EnumDescriptor() ([]byte, []int) { 153 | return file_message_proto_rawDescGZIP(), []int{1} 154 | } 155 | 156 | //客户端发来的第一个消息 157 | type C2S_ConnectMsg struct { 158 | state protoimpl.MessageState 159 | sizeCache protoimpl.SizeCache 160 | unknownFields protoimpl.UnknownFields 161 | 162 | PlayerID *uint64 `protobuf:"varint,1,opt,name=playerID,proto3,oneof" json:"playerID,omitempty"` //唯一ID 163 | BattleID *uint64 `protobuf:"varint,2,opt,name=battleID,proto3,oneof" json:"battleID,omitempty"` //战斗ID 164 | Token *string `protobuf:"bytes,10,opt,name=token,proto3,oneof" json:"token,omitempty"` //令牌 165 | } 166 | 167 | func (x *C2S_ConnectMsg) Reset() { 168 | *x = C2S_ConnectMsg{} 169 | if protoimpl.UnsafeEnabled { 170 | mi := &file_message_proto_msgTypes[0] 171 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 172 | ms.StoreMessageInfo(mi) 173 | } 174 | } 175 | 176 | func (x *C2S_ConnectMsg) String() string { 177 | return protoimpl.X.MessageStringOf(x) 178 | } 179 | 180 | func (*C2S_ConnectMsg) ProtoMessage() {} 181 | 182 | func (x *C2S_ConnectMsg) ProtoReflect() protoreflect.Message { 183 | mi := &file_message_proto_msgTypes[0] 184 | if protoimpl.UnsafeEnabled && x != nil { 185 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 186 | if ms.LoadMessageInfo() == nil { 187 | ms.StoreMessageInfo(mi) 188 | } 189 | return ms 190 | } 191 | return mi.MessageOf(x) 192 | } 193 | 194 | // Deprecated: Use C2S_ConnectMsg.ProtoReflect.Descriptor instead. 195 | func (*C2S_ConnectMsg) Descriptor() ([]byte, []int) { 196 | return file_message_proto_rawDescGZIP(), []int{0} 197 | } 198 | 199 | func (x *C2S_ConnectMsg) GetPlayerID() uint64 { 200 | if x != nil && x.PlayerID != nil { 201 | return *x.PlayerID 202 | } 203 | return 0 204 | } 205 | 206 | func (x *C2S_ConnectMsg) GetBattleID() uint64 { 207 | if x != nil && x.BattleID != nil { 208 | return *x.BattleID 209 | } 210 | return 0 211 | } 212 | 213 | func (x *C2S_ConnectMsg) GetToken() string { 214 | if x != nil && x.Token != nil { 215 | return *x.Token 216 | } 217 | return "" 218 | } 219 | 220 | //服务端返回连接结果 221 | type S2C_ConnectMsg struct { 222 | state protoimpl.MessageState 223 | sizeCache protoimpl.SizeCache 224 | unknownFields protoimpl.UnknownFields 225 | 226 | ErrorCode *ERRORCODE `protobuf:"varint,1,opt,name=errorCode,proto3,enum=pb.ERRORCODE,oneof" json:"errorCode,omitempty"` //错误码 227 | } 228 | 229 | func (x *S2C_ConnectMsg) Reset() { 230 | *x = S2C_ConnectMsg{} 231 | if protoimpl.UnsafeEnabled { 232 | mi := &file_message_proto_msgTypes[1] 233 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 234 | ms.StoreMessageInfo(mi) 235 | } 236 | } 237 | 238 | func (x *S2C_ConnectMsg) String() string { 239 | return protoimpl.X.MessageStringOf(x) 240 | } 241 | 242 | func (*S2C_ConnectMsg) ProtoMessage() {} 243 | 244 | func (x *S2C_ConnectMsg) ProtoReflect() protoreflect.Message { 245 | mi := &file_message_proto_msgTypes[1] 246 | if protoimpl.UnsafeEnabled && x != nil { 247 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 248 | if ms.LoadMessageInfo() == nil { 249 | ms.StoreMessageInfo(mi) 250 | } 251 | return ms 252 | } 253 | return mi.MessageOf(x) 254 | } 255 | 256 | // Deprecated: Use S2C_ConnectMsg.ProtoReflect.Descriptor instead. 257 | func (*S2C_ConnectMsg) Descriptor() ([]byte, []int) { 258 | return file_message_proto_rawDescGZIP(), []int{1} 259 | } 260 | 261 | func (x *S2C_ConnectMsg) GetErrorCode() ERRORCODE { 262 | if x != nil && x.ErrorCode != nil { 263 | return *x.ErrorCode 264 | } 265 | return ERRORCODE_ERR_Ok 266 | } 267 | 268 | //服务端返回进入房间消息 269 | type S2C_JoinRoomMsg struct { 270 | state protoimpl.MessageState 271 | sizeCache protoimpl.SizeCache 272 | unknownFields protoimpl.UnknownFields 273 | 274 | Roomseatid *int32 `protobuf:"varint,1,opt,name=roomseatid,proto3,oneof" json:"roomseatid,omitempty"` //自己的位置索引id(1~N) 275 | Others []uint64 `protobuf:"varint,2,rep,packed,name=others,proto3" json:"others,omitempty"` //其他人的id 276 | Pros []int32 `protobuf:"varint,3,rep,packed,name=pros,proto3" json:"pros,omitempty"` //其他人的进度 277 | RandomSeed *int32 `protobuf:"varint,4,opt,name=randomSeed,proto3,oneof" json:"randomSeed,omitempty"` //随机种子 278 | } 279 | 280 | func (x *S2C_JoinRoomMsg) Reset() { 281 | *x = S2C_JoinRoomMsg{} 282 | if protoimpl.UnsafeEnabled { 283 | mi := &file_message_proto_msgTypes[2] 284 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 285 | ms.StoreMessageInfo(mi) 286 | } 287 | } 288 | 289 | func (x *S2C_JoinRoomMsg) String() string { 290 | return protoimpl.X.MessageStringOf(x) 291 | } 292 | 293 | func (*S2C_JoinRoomMsg) ProtoMessage() {} 294 | 295 | func (x *S2C_JoinRoomMsg) ProtoReflect() protoreflect.Message { 296 | mi := &file_message_proto_msgTypes[2] 297 | if protoimpl.UnsafeEnabled && x != nil { 298 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 299 | if ms.LoadMessageInfo() == nil { 300 | ms.StoreMessageInfo(mi) 301 | } 302 | return ms 303 | } 304 | return mi.MessageOf(x) 305 | } 306 | 307 | // Deprecated: Use S2C_JoinRoomMsg.ProtoReflect.Descriptor instead. 308 | func (*S2C_JoinRoomMsg) Descriptor() ([]byte, []int) { 309 | return file_message_proto_rawDescGZIP(), []int{2} 310 | } 311 | 312 | func (x *S2C_JoinRoomMsg) GetRoomseatid() int32 { 313 | if x != nil && x.Roomseatid != nil { 314 | return *x.Roomseatid 315 | } 316 | return 0 317 | } 318 | 319 | func (x *S2C_JoinRoomMsg) GetOthers() []uint64 { 320 | if x != nil { 321 | return x.Others 322 | } 323 | return nil 324 | } 325 | 326 | func (x *S2C_JoinRoomMsg) GetPros() []int32 { 327 | if x != nil { 328 | return x.Pros 329 | } 330 | return nil 331 | } 332 | 333 | func (x *S2C_JoinRoomMsg) GetRandomSeed() int32 { 334 | if x != nil && x.RandomSeed != nil { 335 | return *x.RandomSeed 336 | } 337 | return 0 338 | } 339 | 340 | //服务端广播开始游戏消息 341 | type S2C_StartMsg struct { 342 | state protoimpl.MessageState 343 | sizeCache protoimpl.SizeCache 344 | unknownFields protoimpl.UnknownFields 345 | 346 | TimeStamp *int64 `protobuf:"varint,1,opt,name=timeStamp,proto3,oneof" json:"timeStamp,omitempty"` //同步时间戳 347 | } 348 | 349 | func (x *S2C_StartMsg) Reset() { 350 | *x = S2C_StartMsg{} 351 | if protoimpl.UnsafeEnabled { 352 | mi := &file_message_proto_msgTypes[3] 353 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 354 | ms.StoreMessageInfo(mi) 355 | } 356 | } 357 | 358 | func (x *S2C_StartMsg) String() string { 359 | return protoimpl.X.MessageStringOf(x) 360 | } 361 | 362 | func (*S2C_StartMsg) ProtoMessage() {} 363 | 364 | func (x *S2C_StartMsg) ProtoReflect() protoreflect.Message { 365 | mi := &file_message_proto_msgTypes[3] 366 | if protoimpl.UnsafeEnabled && x != nil { 367 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 368 | if ms.LoadMessageInfo() == nil { 369 | ms.StoreMessageInfo(mi) 370 | } 371 | return ms 372 | } 373 | return mi.MessageOf(x) 374 | } 375 | 376 | // Deprecated: Use S2C_StartMsg.ProtoReflect.Descriptor instead. 377 | func (*S2C_StartMsg) Descriptor() ([]byte, []int) { 378 | return file_message_proto_rawDescGZIP(), []int{3} 379 | } 380 | 381 | func (x *S2C_StartMsg) GetTimeStamp() int64 { 382 | if x != nil && x.TimeStamp != nil { 383 | return *x.TimeStamp 384 | } 385 | return 0 386 | } 387 | 388 | //读条进度 389 | type C2S_ProgressMsg struct { 390 | state protoimpl.MessageState 391 | sizeCache protoimpl.SizeCache 392 | unknownFields protoimpl.UnknownFields 393 | 394 | Pro *int32 `protobuf:"varint,1,opt,name=pro,proto3,oneof" json:"pro,omitempty"` //进度值0~100 395 | } 396 | 397 | func (x *C2S_ProgressMsg) Reset() { 398 | *x = C2S_ProgressMsg{} 399 | if protoimpl.UnsafeEnabled { 400 | mi := &file_message_proto_msgTypes[4] 401 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 402 | ms.StoreMessageInfo(mi) 403 | } 404 | } 405 | 406 | func (x *C2S_ProgressMsg) String() string { 407 | return protoimpl.X.MessageStringOf(x) 408 | } 409 | 410 | func (*C2S_ProgressMsg) ProtoMessage() {} 411 | 412 | func (x *C2S_ProgressMsg) ProtoReflect() protoreflect.Message { 413 | mi := &file_message_proto_msgTypes[4] 414 | if protoimpl.UnsafeEnabled && x != nil { 415 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 416 | if ms.LoadMessageInfo() == nil { 417 | ms.StoreMessageInfo(mi) 418 | } 419 | return ms 420 | } 421 | return mi.MessageOf(x) 422 | } 423 | 424 | // Deprecated: Use C2S_ProgressMsg.ProtoReflect.Descriptor instead. 425 | func (*C2S_ProgressMsg) Descriptor() ([]byte, []int) { 426 | return file_message_proto_rawDescGZIP(), []int{4} 427 | } 428 | 429 | func (x *C2S_ProgressMsg) GetPro() int32 { 430 | if x != nil && x.Pro != nil { 431 | return *x.Pro 432 | } 433 | return 0 434 | } 435 | 436 | //读条进度 437 | type S2C_ProgressMsg struct { 438 | state protoimpl.MessageState 439 | sizeCache protoimpl.SizeCache 440 | unknownFields protoimpl.UnknownFields 441 | 442 | Id *uint64 `protobuf:"varint,1,opt,name=id,proto3,oneof" json:"id,omitempty"` //id 443 | Pro *int32 `protobuf:"varint,2,opt,name=pro,proto3,oneof" json:"pro,omitempty"` //进度值0~100 444 | } 445 | 446 | func (x *S2C_ProgressMsg) Reset() { 447 | *x = S2C_ProgressMsg{} 448 | if protoimpl.UnsafeEnabled { 449 | mi := &file_message_proto_msgTypes[5] 450 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 451 | ms.StoreMessageInfo(mi) 452 | } 453 | } 454 | 455 | func (x *S2C_ProgressMsg) String() string { 456 | return protoimpl.X.MessageStringOf(x) 457 | } 458 | 459 | func (*S2C_ProgressMsg) ProtoMessage() {} 460 | 461 | func (x *S2C_ProgressMsg) ProtoReflect() protoreflect.Message { 462 | mi := &file_message_proto_msgTypes[5] 463 | if protoimpl.UnsafeEnabled && x != nil { 464 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 465 | if ms.LoadMessageInfo() == nil { 466 | ms.StoreMessageInfo(mi) 467 | } 468 | return ms 469 | } 470 | return mi.MessageOf(x) 471 | } 472 | 473 | // Deprecated: Use S2C_ProgressMsg.ProtoReflect.Descriptor instead. 474 | func (*S2C_ProgressMsg) Descriptor() ([]byte, []int) { 475 | return file_message_proto_rawDescGZIP(), []int{5} 476 | } 477 | 478 | func (x *S2C_ProgressMsg) GetId() uint64 { 479 | if x != nil && x.Id != nil { 480 | return *x.Id 481 | } 482 | return 0 483 | } 484 | 485 | func (x *S2C_ProgressMsg) GetPro() int32 { 486 | if x != nil && x.Pro != nil { 487 | return *x.Pro 488 | } 489 | return 0 490 | } 491 | 492 | //操作输入消息 493 | type C2S_InputMsg struct { 494 | state protoimpl.MessageState 495 | sizeCache protoimpl.SizeCache 496 | unknownFields protoimpl.UnknownFields 497 | 498 | Sid *int32 `protobuf:"varint,1,opt,name=sid,proto3,oneof" json:"sid,omitempty"` //操作id 499 | X *int32 `protobuf:"varint,2,opt,name=x,proto3,oneof" json:"x,omitempty"` //操作位置x 500 | Y *int32 `protobuf:"varint,3,opt,name=y,proto3,oneof" json:"y,omitempty"` //操作位置y 501 | FrameID *uint32 `protobuf:"varint,4,opt,name=frameID,proto3,oneof" json:"frameID,omitempty"` //帧ID 502 | } 503 | 504 | func (x *C2S_InputMsg) Reset() { 505 | *x = C2S_InputMsg{} 506 | if protoimpl.UnsafeEnabled { 507 | mi := &file_message_proto_msgTypes[6] 508 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 509 | ms.StoreMessageInfo(mi) 510 | } 511 | } 512 | 513 | func (x *C2S_InputMsg) String() string { 514 | return protoimpl.X.MessageStringOf(x) 515 | } 516 | 517 | func (*C2S_InputMsg) ProtoMessage() {} 518 | 519 | func (x *C2S_InputMsg) ProtoReflect() protoreflect.Message { 520 | mi := &file_message_proto_msgTypes[6] 521 | if protoimpl.UnsafeEnabled && x != nil { 522 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 523 | if ms.LoadMessageInfo() == nil { 524 | ms.StoreMessageInfo(mi) 525 | } 526 | return ms 527 | } 528 | return mi.MessageOf(x) 529 | } 530 | 531 | // Deprecated: Use C2S_InputMsg.ProtoReflect.Descriptor instead. 532 | func (*C2S_InputMsg) Descriptor() ([]byte, []int) { 533 | return file_message_proto_rawDescGZIP(), []int{6} 534 | } 535 | 536 | func (x *C2S_InputMsg) GetSid() int32 { 537 | if x != nil && x.Sid != nil { 538 | return *x.Sid 539 | } 540 | return 0 541 | } 542 | 543 | func (x *C2S_InputMsg) GetX() int32 { 544 | if x != nil && x.X != nil { 545 | return *x.X 546 | } 547 | return 0 548 | } 549 | 550 | func (x *C2S_InputMsg) GetY() int32 { 551 | if x != nil && x.Y != nil { 552 | return *x.Y 553 | } 554 | return 0 555 | } 556 | 557 | func (x *C2S_InputMsg) GetFrameID() uint32 { 558 | if x != nil && x.FrameID != nil { 559 | return *x.FrameID 560 | } 561 | return 0 562 | } 563 | 564 | //帧存储操作输入 565 | type InputData struct { 566 | state protoimpl.MessageState 567 | sizeCache protoimpl.SizeCache 568 | unknownFields protoimpl.UnknownFields 569 | 570 | Id *uint64 `protobuf:"varint,1,opt,name=id,proto3,oneof" json:"id,omitempty"` //id 571 | Sid *int32 `protobuf:"varint,2,opt,name=sid,proto3,oneof" json:"sid,omitempty"` //操作id 572 | X *int32 `protobuf:"varint,3,opt,name=x,proto3,oneof" json:"x,omitempty"` //操作位置x 573 | Y *int32 `protobuf:"varint,4,opt,name=y,proto3,oneof" json:"y,omitempty"` //操作位置y 574 | Roomseatid *int32 `protobuf:"varint,5,opt,name=roomseatid,proto3,oneof" json:"roomseatid,omitempty"` //操作者的位置索引id(1~N) 575 | } 576 | 577 | func (x *InputData) Reset() { 578 | *x = InputData{} 579 | if protoimpl.UnsafeEnabled { 580 | mi := &file_message_proto_msgTypes[7] 581 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 582 | ms.StoreMessageInfo(mi) 583 | } 584 | } 585 | 586 | func (x *InputData) String() string { 587 | return protoimpl.X.MessageStringOf(x) 588 | } 589 | 590 | func (*InputData) ProtoMessage() {} 591 | 592 | func (x *InputData) ProtoReflect() protoreflect.Message { 593 | mi := &file_message_proto_msgTypes[7] 594 | if protoimpl.UnsafeEnabled && x != nil { 595 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 596 | if ms.LoadMessageInfo() == nil { 597 | ms.StoreMessageInfo(mi) 598 | } 599 | return ms 600 | } 601 | return mi.MessageOf(x) 602 | } 603 | 604 | // Deprecated: Use InputData.ProtoReflect.Descriptor instead. 605 | func (*InputData) Descriptor() ([]byte, []int) { 606 | return file_message_proto_rawDescGZIP(), []int{7} 607 | } 608 | 609 | func (x *InputData) GetId() uint64 { 610 | if x != nil && x.Id != nil { 611 | return *x.Id 612 | } 613 | return 0 614 | } 615 | 616 | func (x *InputData) GetSid() int32 { 617 | if x != nil && x.Sid != nil { 618 | return *x.Sid 619 | } 620 | return 0 621 | } 622 | 623 | func (x *InputData) GetX() int32 { 624 | if x != nil && x.X != nil { 625 | return *x.X 626 | } 627 | return 0 628 | } 629 | 630 | func (x *InputData) GetY() int32 { 631 | if x != nil && x.Y != nil { 632 | return *x.Y 633 | } 634 | return 0 635 | } 636 | 637 | func (x *InputData) GetRoomseatid() int32 { 638 | if x != nil && x.Roomseatid != nil { 639 | return *x.Roomseatid 640 | } 641 | return 0 642 | } 643 | 644 | //帧数据 645 | type FrameData struct { 646 | state protoimpl.MessageState 647 | sizeCache protoimpl.SizeCache 648 | unknownFields protoimpl.UnknownFields 649 | 650 | FrameID *uint32 `protobuf:"varint,1,opt,name=frameID,proto3,oneof" json:"frameID,omitempty"` //帧ID 651 | Input []*InputData `protobuf:"bytes,2,rep,name=input,proto3" json:"input,omitempty"` //操作输入 652 | } 653 | 654 | func (x *FrameData) Reset() { 655 | *x = FrameData{} 656 | if protoimpl.UnsafeEnabled { 657 | mi := &file_message_proto_msgTypes[8] 658 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 659 | ms.StoreMessageInfo(mi) 660 | } 661 | } 662 | 663 | func (x *FrameData) String() string { 664 | return protoimpl.X.MessageStringOf(x) 665 | } 666 | 667 | func (*FrameData) ProtoMessage() {} 668 | 669 | func (x *FrameData) ProtoReflect() protoreflect.Message { 670 | mi := &file_message_proto_msgTypes[8] 671 | if protoimpl.UnsafeEnabled && x != nil { 672 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 673 | if ms.LoadMessageInfo() == nil { 674 | ms.StoreMessageInfo(mi) 675 | } 676 | return ms 677 | } 678 | return mi.MessageOf(x) 679 | } 680 | 681 | // Deprecated: Use FrameData.ProtoReflect.Descriptor instead. 682 | func (*FrameData) Descriptor() ([]byte, []int) { 683 | return file_message_proto_rawDescGZIP(), []int{8} 684 | } 685 | 686 | func (x *FrameData) GetFrameID() uint32 { 687 | if x != nil && x.FrameID != nil { 688 | return *x.FrameID 689 | } 690 | return 0 691 | } 692 | 693 | func (x *FrameData) GetInput() []*InputData { 694 | if x != nil { 695 | return x.Input 696 | } 697 | return nil 698 | } 699 | 700 | //广播帧消息 701 | type S2C_FrameMsg struct { 702 | state protoimpl.MessageState 703 | sizeCache protoimpl.SizeCache 704 | unknownFields protoimpl.UnknownFields 705 | 706 | Frames []*FrameData `protobuf:"bytes,1,rep,name=frames,proto3" json:"frames,omitempty"` //帧数据 707 | } 708 | 709 | func (x *S2C_FrameMsg) Reset() { 710 | *x = S2C_FrameMsg{} 711 | if protoimpl.UnsafeEnabled { 712 | mi := &file_message_proto_msgTypes[9] 713 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 714 | ms.StoreMessageInfo(mi) 715 | } 716 | } 717 | 718 | func (x *S2C_FrameMsg) String() string { 719 | return protoimpl.X.MessageStringOf(x) 720 | } 721 | 722 | func (*S2C_FrameMsg) ProtoMessage() {} 723 | 724 | func (x *S2C_FrameMsg) ProtoReflect() protoreflect.Message { 725 | mi := &file_message_proto_msgTypes[9] 726 | if protoimpl.UnsafeEnabled && x != nil { 727 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 728 | if ms.LoadMessageInfo() == nil { 729 | ms.StoreMessageInfo(mi) 730 | } 731 | return ms 732 | } 733 | return mi.MessageOf(x) 734 | } 735 | 736 | // Deprecated: Use S2C_FrameMsg.ProtoReflect.Descriptor instead. 737 | func (*S2C_FrameMsg) Descriptor() ([]byte, []int) { 738 | return file_message_proto_rawDescGZIP(), []int{9} 739 | } 740 | 741 | func (x *S2C_FrameMsg) GetFrames() []*FrameData { 742 | if x != nil { 743 | return x.Frames 744 | } 745 | return nil 746 | } 747 | 748 | //结果消息 749 | type C2S_ResultMsg struct { 750 | state protoimpl.MessageState 751 | sizeCache protoimpl.SizeCache 752 | unknownFields protoimpl.UnknownFields 753 | 754 | WinnerID *uint64 `protobuf:"varint,1,opt,name=winnerID,proto3,oneof" json:"winnerID,omitempty"` //胜利者ID 755 | } 756 | 757 | func (x *C2S_ResultMsg) Reset() { 758 | *x = C2S_ResultMsg{} 759 | if protoimpl.UnsafeEnabled { 760 | mi := &file_message_proto_msgTypes[10] 761 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 762 | ms.StoreMessageInfo(mi) 763 | } 764 | } 765 | 766 | func (x *C2S_ResultMsg) String() string { 767 | return protoimpl.X.MessageStringOf(x) 768 | } 769 | 770 | func (*C2S_ResultMsg) ProtoMessage() {} 771 | 772 | func (x *C2S_ResultMsg) ProtoReflect() protoreflect.Message { 773 | mi := &file_message_proto_msgTypes[10] 774 | if protoimpl.UnsafeEnabled && x != nil { 775 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 776 | if ms.LoadMessageInfo() == nil { 777 | ms.StoreMessageInfo(mi) 778 | } 779 | return ms 780 | } 781 | return mi.MessageOf(x) 782 | } 783 | 784 | // Deprecated: Use C2S_ResultMsg.ProtoReflect.Descriptor instead. 785 | func (*C2S_ResultMsg) Descriptor() ([]byte, []int) { 786 | return file_message_proto_rawDescGZIP(), []int{10} 787 | } 788 | 789 | func (x *C2S_ResultMsg) GetWinnerID() uint64 { 790 | if x != nil && x.WinnerID != nil { 791 | return *x.WinnerID 792 | } 793 | return 0 794 | } 795 | 796 | var File_message_proto protoreflect.FileDescriptor 797 | 798 | var file_message_proto_rawDesc = []byte{ 799 | 0x0a, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 800 | 0x02, 0x70, 0x62, 0x22, 0x91, 0x01, 0x0a, 0x0e, 0x43, 0x32, 0x53, 0x5f, 0x43, 0x6f, 0x6e, 0x6e, 801 | 0x65, 0x63, 0x74, 0x4d, 0x73, 0x67, 0x12, 0x1f, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 802 | 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x79, 803 | 0x65, 0x72, 0x49, 0x44, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x62, 0x61, 0x74, 0x74, 0x6c, 804 | 0x65, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x08, 0x62, 0x61, 0x74, 805 | 0x74, 0x6c, 0x65, 0x49, 0x44, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 806 | 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 807 | 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x44, 808 | 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x44, 0x42, 0x08, 0x0a, 809 | 0x06, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x50, 0x0a, 0x0e, 0x53, 0x32, 0x43, 0x5f, 0x43, 810 | 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x4d, 0x73, 0x67, 0x12, 0x30, 0x0a, 0x09, 0x65, 0x72, 0x72, 811 | 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x70, 812 | 0x62, 0x2e, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x43, 0x4f, 0x44, 0x45, 0x48, 0x00, 0x52, 0x09, 0x65, 813 | 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 814 | 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x22, 0xa5, 0x01, 0x0a, 0x0f, 0x53, 0x32, 815 | 0x43, 0x5f, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x4d, 0x73, 0x67, 0x12, 0x23, 0x0a, 816 | 0x0a, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x65, 0x61, 0x74, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 817 | 0x05, 0x48, 0x00, 0x52, 0x0a, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x65, 0x61, 0x74, 0x69, 0x64, 0x88, 818 | 0x01, 0x01, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 819 | 0x28, 0x04, 0x52, 0x06, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x72, 820 | 0x6f, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x05, 0x52, 0x04, 0x70, 0x72, 0x6f, 0x73, 0x12, 0x23, 821 | 0x0a, 0x0a, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x53, 0x65, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 822 | 0x28, 0x05, 0x48, 0x01, 0x52, 0x0a, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x53, 0x65, 0x65, 0x64, 823 | 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x65, 0x61, 0x74, 824 | 0x69, 0x64, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x53, 0x65, 0x65, 825 | 0x64, 0x22, 0x3f, 0x0a, 0x0c, 0x53, 0x32, 0x43, 0x5f, 0x53, 0x74, 0x61, 0x72, 0x74, 0x4d, 0x73, 826 | 0x67, 0x12, 0x21, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 827 | 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x6d, 828 | 0x70, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 829 | 0x6d, 0x70, 0x22, 0x30, 0x0a, 0x0f, 0x43, 0x32, 0x53, 0x5f, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 830 | 0x73, 0x73, 0x4d, 0x73, 0x67, 0x12, 0x15, 0x0a, 0x03, 0x70, 0x72, 0x6f, 0x18, 0x01, 0x20, 0x01, 831 | 0x28, 0x05, 0x48, 0x00, 0x52, 0x03, 0x70, 0x72, 0x6f, 0x88, 0x01, 0x01, 0x42, 0x06, 0x0a, 0x04, 832 | 0x5f, 0x70, 0x72, 0x6f, 0x22, 0x4c, 0x0a, 0x0f, 0x53, 0x32, 0x43, 0x5f, 0x50, 0x72, 0x6f, 0x67, 833 | 0x72, 0x65, 0x73, 0x73, 0x4d, 0x73, 0x67, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 834 | 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x02, 0x69, 0x64, 0x88, 0x01, 0x01, 0x12, 0x15, 0x0a, 0x03, 835 | 0x70, 0x72, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x01, 0x52, 0x03, 0x70, 0x72, 0x6f, 836 | 0x88, 0x01, 0x01, 0x42, 0x05, 0x0a, 0x03, 0x5f, 0x69, 0x64, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x70, 837 | 0x72, 0x6f, 0x22, 0x8a, 0x01, 0x0a, 0x0c, 0x43, 0x32, 0x53, 0x5f, 0x49, 0x6e, 0x70, 0x75, 0x74, 838 | 0x4d, 0x73, 0x67, 0x12, 0x15, 0x0a, 0x03, 0x73, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 839 | 0x48, 0x00, 0x52, 0x03, 0x73, 0x69, 0x64, 0x88, 0x01, 0x01, 0x12, 0x11, 0x0a, 0x01, 0x78, 0x18, 840 | 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x01, 0x52, 0x01, 0x78, 0x88, 0x01, 0x01, 0x12, 0x11, 0x0a, 841 | 0x01, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x48, 0x02, 0x52, 0x01, 0x79, 0x88, 0x01, 0x01, 842 | 0x12, 0x1d, 0x0a, 0x07, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x44, 0x18, 0x04, 0x20, 0x01, 0x28, 843 | 0x0d, 0x48, 0x03, 0x52, 0x07, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x44, 0x88, 0x01, 0x01, 0x42, 844 | 0x06, 0x0a, 0x04, 0x5f, 0x73, 0x69, 0x64, 0x42, 0x04, 0x0a, 0x02, 0x5f, 0x78, 0x42, 0x04, 0x0a, 845 | 0x02, 0x5f, 0x79, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x44, 0x22, 846 | 0xac, 0x01, 0x0a, 0x09, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x13, 0x0a, 847 | 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x02, 0x69, 0x64, 0x88, 848 | 0x01, 0x01, 0x12, 0x15, 0x0a, 0x03, 0x73, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 849 | 0x01, 0x52, 0x03, 0x73, 0x69, 0x64, 0x88, 0x01, 0x01, 0x12, 0x11, 0x0a, 0x01, 0x78, 0x18, 0x03, 850 | 0x20, 0x01, 0x28, 0x05, 0x48, 0x02, 0x52, 0x01, 0x78, 0x88, 0x01, 0x01, 0x12, 0x11, 0x0a, 0x01, 851 | 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x48, 0x03, 0x52, 0x01, 0x79, 0x88, 0x01, 0x01, 0x12, 852 | 0x23, 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x65, 0x61, 0x74, 0x69, 0x64, 0x18, 0x05, 0x20, 853 | 0x01, 0x28, 0x05, 0x48, 0x04, 0x52, 0x0a, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x65, 0x61, 0x74, 0x69, 854 | 0x64, 0x88, 0x01, 0x01, 0x42, 0x05, 0x0a, 0x03, 0x5f, 0x69, 0x64, 0x42, 0x06, 0x0a, 0x04, 0x5f, 855 | 0x73, 0x69, 0x64, 0x42, 0x04, 0x0a, 0x02, 0x5f, 0x78, 0x42, 0x04, 0x0a, 0x02, 0x5f, 0x79, 0x42, 856 | 0x0d, 0x0a, 0x0b, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x65, 0x61, 0x74, 0x69, 0x64, 0x22, 0x5b, 857 | 0x0a, 0x09, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x07, 0x66, 858 | 0x72, 0x61, 0x6d, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x00, 0x52, 0x07, 859 | 0x66, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x44, 0x88, 0x01, 0x01, 0x12, 0x23, 0x0a, 0x05, 0x69, 0x6e, 860 | 0x70, 0x75, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x62, 0x2e, 0x49, 861 | 0x6e, 0x70, 0x75, 0x74, 0x44, 0x61, 0x74, 0x61, 0x52, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x42, 862 | 0x0a, 0x0a, 0x08, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x44, 0x22, 0x35, 0x0a, 0x0c, 0x53, 863 | 0x32, 0x43, 0x5f, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x4d, 0x73, 0x67, 0x12, 0x25, 0x0a, 0x06, 0x66, 864 | 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x62, 865 | 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x06, 0x66, 0x72, 0x61, 0x6d, 866 | 0x65, 0x73, 0x22, 0x3d, 0x0a, 0x0d, 0x43, 0x32, 0x53, 0x5f, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 867 | 0x4d, 0x73, 0x67, 0x12, 0x1f, 0x0a, 0x08, 0x77, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x49, 0x44, 0x18, 868 | 0x01, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x08, 0x77, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x49, 869 | 0x44, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x77, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x49, 870 | 0x44, 0x2a, 0xc4, 0x01, 0x0a, 0x02, 0x49, 0x44, 0x12, 0x0d, 0x0a, 0x09, 0x4d, 0x53, 0x47, 0x5f, 871 | 0x42, 0x45, 0x47, 0x49, 0x4e, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x53, 0x47, 0x5f, 0x43, 872 | 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x4d, 0x53, 0x47, 0x5f, 873 | 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x4d, 874 | 0x53, 0x47, 0x5f, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x10, 0x0a, 0x12, 0x10, 0x0a, 875 | 0x0c, 0x4d, 0x53, 0x47, 0x5f, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x10, 0x14, 0x12, 876 | 0x0d, 0x0a, 0x09, 0x4d, 0x53, 0x47, 0x5f, 0x52, 0x65, 0x61, 0x64, 0x79, 0x10, 0x1e, 0x12, 0x0d, 877 | 0x0a, 0x09, 0x4d, 0x53, 0x47, 0x5f, 0x53, 0x74, 0x61, 0x72, 0x74, 0x10, 0x28, 0x12, 0x0d, 0x0a, 878 | 0x09, 0x4d, 0x53, 0x47, 0x5f, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x10, 0x32, 0x12, 0x0d, 0x0a, 0x09, 879 | 0x4d, 0x53, 0x47, 0x5f, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x10, 0x3c, 0x12, 0x0e, 0x0a, 0x0a, 0x4d, 880 | 0x53, 0x47, 0x5f, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x10, 0x46, 0x12, 0x0d, 0x0a, 0x09, 0x4d, 881 | 0x53, 0x47, 0x5f, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x10, 0x64, 0x12, 0x0c, 0x0a, 0x07, 0x4d, 0x53, 882 | 0x47, 0x5f, 0x45, 0x4e, 0x44, 0x10, 0xff, 0x01, 0x2a, 0x5b, 0x0a, 0x09, 0x45, 0x52, 0x52, 0x4f, 883 | 0x52, 0x43, 0x4f, 0x44, 0x45, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x52, 0x52, 0x5f, 0x4f, 0x6b, 0x10, 884 | 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x52, 0x52, 0x5f, 0x4e, 0x6f, 0x50, 0x6c, 0x61, 0x79, 0x65, 885 | 0x72, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x52, 0x52, 0x5f, 0x4e, 0x6f, 0x52, 0x6f, 0x6f, 886 | 0x6d, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x52, 0x52, 0x5f, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 887 | 0x74, 0x61, 0x74, 0x65, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x45, 0x52, 0x52, 0x5f, 0x54, 0x6f, 888 | 0x6b, 0x65, 0x6e, 0x10, 0x04, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 889 | 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x79, 0x65, 0x62, 0x79, 0x65, 0x62, 0x72, 0x75, 0x63, 0x65, 0x2f, 890 | 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x74, 0x65, 0x70, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 891 | 0x62, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 892 | } 893 | 894 | var ( 895 | file_message_proto_rawDescOnce sync.Once 896 | file_message_proto_rawDescData = file_message_proto_rawDesc 897 | ) 898 | 899 | func file_message_proto_rawDescGZIP() []byte { 900 | file_message_proto_rawDescOnce.Do(func() { 901 | file_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_message_proto_rawDescData) 902 | }) 903 | return file_message_proto_rawDescData 904 | } 905 | 906 | var file_message_proto_enumTypes = make([]protoimpl.EnumInfo, 2) 907 | var file_message_proto_msgTypes = make([]protoimpl.MessageInfo, 11) 908 | var file_message_proto_goTypes = []interface{}{ 909 | (ID)(0), // 0: pb.ID 910 | (ERRORCODE)(0), // 1: pb.ERRORCODE 911 | (*C2S_ConnectMsg)(nil), // 2: pb.C2S_ConnectMsg 912 | (*S2C_ConnectMsg)(nil), // 3: pb.S2C_ConnectMsg 913 | (*S2C_JoinRoomMsg)(nil), // 4: pb.S2C_JoinRoomMsg 914 | (*S2C_StartMsg)(nil), // 5: pb.S2C_StartMsg 915 | (*C2S_ProgressMsg)(nil), // 6: pb.C2S_ProgressMsg 916 | (*S2C_ProgressMsg)(nil), // 7: pb.S2C_ProgressMsg 917 | (*C2S_InputMsg)(nil), // 8: pb.C2S_InputMsg 918 | (*InputData)(nil), // 9: pb.InputData 919 | (*FrameData)(nil), // 10: pb.FrameData 920 | (*S2C_FrameMsg)(nil), // 11: pb.S2C_FrameMsg 921 | (*C2S_ResultMsg)(nil), // 12: pb.C2S_ResultMsg 922 | } 923 | var file_message_proto_depIdxs = []int32{ 924 | 1, // 0: pb.S2C_ConnectMsg.errorCode:type_name -> pb.ERRORCODE 925 | 9, // 1: pb.FrameData.input:type_name -> pb.InputData 926 | 10, // 2: pb.S2C_FrameMsg.frames:type_name -> pb.FrameData 927 | 3, // [3:3] is the sub-list for method output_type 928 | 3, // [3:3] is the sub-list for method input_type 929 | 3, // [3:3] is the sub-list for extension type_name 930 | 3, // [3:3] is the sub-list for extension extendee 931 | 0, // [0:3] is the sub-list for field type_name 932 | } 933 | 934 | func init() { file_message_proto_init() } 935 | func file_message_proto_init() { 936 | if File_message_proto != nil { 937 | return 938 | } 939 | if !protoimpl.UnsafeEnabled { 940 | file_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 941 | switch v := v.(*C2S_ConnectMsg); i { 942 | case 0: 943 | return &v.state 944 | case 1: 945 | return &v.sizeCache 946 | case 2: 947 | return &v.unknownFields 948 | default: 949 | return nil 950 | } 951 | } 952 | file_message_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 953 | switch v := v.(*S2C_ConnectMsg); i { 954 | case 0: 955 | return &v.state 956 | case 1: 957 | return &v.sizeCache 958 | case 2: 959 | return &v.unknownFields 960 | default: 961 | return nil 962 | } 963 | } 964 | file_message_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 965 | switch v := v.(*S2C_JoinRoomMsg); i { 966 | case 0: 967 | return &v.state 968 | case 1: 969 | return &v.sizeCache 970 | case 2: 971 | return &v.unknownFields 972 | default: 973 | return nil 974 | } 975 | } 976 | file_message_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { 977 | switch v := v.(*S2C_StartMsg); i { 978 | case 0: 979 | return &v.state 980 | case 1: 981 | return &v.sizeCache 982 | case 2: 983 | return &v.unknownFields 984 | default: 985 | return nil 986 | } 987 | } 988 | file_message_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { 989 | switch v := v.(*C2S_ProgressMsg); i { 990 | case 0: 991 | return &v.state 992 | case 1: 993 | return &v.sizeCache 994 | case 2: 995 | return &v.unknownFields 996 | default: 997 | return nil 998 | } 999 | } 1000 | file_message_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { 1001 | switch v := v.(*S2C_ProgressMsg); i { 1002 | case 0: 1003 | return &v.state 1004 | case 1: 1005 | return &v.sizeCache 1006 | case 2: 1007 | return &v.unknownFields 1008 | default: 1009 | return nil 1010 | } 1011 | } 1012 | file_message_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { 1013 | switch v := v.(*C2S_InputMsg); i { 1014 | case 0: 1015 | return &v.state 1016 | case 1: 1017 | return &v.sizeCache 1018 | case 2: 1019 | return &v.unknownFields 1020 | default: 1021 | return nil 1022 | } 1023 | } 1024 | file_message_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { 1025 | switch v := v.(*InputData); i { 1026 | case 0: 1027 | return &v.state 1028 | case 1: 1029 | return &v.sizeCache 1030 | case 2: 1031 | return &v.unknownFields 1032 | default: 1033 | return nil 1034 | } 1035 | } 1036 | file_message_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { 1037 | switch v := v.(*FrameData); i { 1038 | case 0: 1039 | return &v.state 1040 | case 1: 1041 | return &v.sizeCache 1042 | case 2: 1043 | return &v.unknownFields 1044 | default: 1045 | return nil 1046 | } 1047 | } 1048 | file_message_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { 1049 | switch v := v.(*S2C_FrameMsg); i { 1050 | case 0: 1051 | return &v.state 1052 | case 1: 1053 | return &v.sizeCache 1054 | case 2: 1055 | return &v.unknownFields 1056 | default: 1057 | return nil 1058 | } 1059 | } 1060 | file_message_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { 1061 | switch v := v.(*C2S_ResultMsg); i { 1062 | case 0: 1063 | return &v.state 1064 | case 1: 1065 | return &v.sizeCache 1066 | case 2: 1067 | return &v.unknownFields 1068 | default: 1069 | return nil 1070 | } 1071 | } 1072 | } 1073 | file_message_proto_msgTypes[0].OneofWrappers = []interface{}{} 1074 | file_message_proto_msgTypes[1].OneofWrappers = []interface{}{} 1075 | file_message_proto_msgTypes[2].OneofWrappers = []interface{}{} 1076 | file_message_proto_msgTypes[3].OneofWrappers = []interface{}{} 1077 | file_message_proto_msgTypes[4].OneofWrappers = []interface{}{} 1078 | file_message_proto_msgTypes[5].OneofWrappers = []interface{}{} 1079 | file_message_proto_msgTypes[6].OneofWrappers = []interface{}{} 1080 | file_message_proto_msgTypes[7].OneofWrappers = []interface{}{} 1081 | file_message_proto_msgTypes[8].OneofWrappers = []interface{}{} 1082 | file_message_proto_msgTypes[10].OneofWrappers = []interface{}{} 1083 | type x struct{} 1084 | out := protoimpl.TypeBuilder{ 1085 | File: protoimpl.DescBuilder{ 1086 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 1087 | RawDescriptor: file_message_proto_rawDesc, 1088 | NumEnums: 2, 1089 | NumMessages: 11, 1090 | NumExtensions: 0, 1091 | NumServices: 0, 1092 | }, 1093 | GoTypes: file_message_proto_goTypes, 1094 | DependencyIndexes: file_message_proto_depIdxs, 1095 | EnumInfos: file_message_proto_enumTypes, 1096 | MessageInfos: file_message_proto_msgTypes, 1097 | }.Build() 1098 | File_message_proto = out.File 1099 | file_message_proto_rawDesc = nil 1100 | file_message_proto_goTypes = nil 1101 | file_message_proto_depIdxs = nil 1102 | } 1103 | -------------------------------------------------------------------------------- /pb/message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package pb; 4 | option go_package = "github.com/byebyebruce/lockstepserver/pb;pb"; 5 | 6 | 7 | //消息ID 8 | enum ID { 9 | 10 | MSG_BEGIN = 0; 11 | 12 | MSG_Connect = 1; //连接(客户端发来第一个消息) 13 | MSG_Heartbeat = 2; //心跳(服务端返回Connect成功之后每隔1秒发送一个心跳包) 14 | 15 | MSG_JoinRoom = 10; //进入 16 | MSG_Progress = 20; //进度 17 | MSG_Ready = 30; //准备 18 | MSG_Start = 40; //开始 19 | MSG_Frame = 50; //帧数据 20 | MSG_Input = 60; //输入 21 | MSG_Result = 70; //结果 22 | 23 | MSG_Close = 100; //房间关闭 24 | 25 | MSG_END = 255; 26 | } 27 | 28 | //错误码 29 | enum ERRORCODE { 30 | ERR_Ok = 0; //OK 31 | ERR_NoPlayer = 1; //没有这个玩家 32 | ERR_NoRoom = 2; //没有房间 33 | ERR_RoomState = 3; //房间状态不正确 34 | ERR_Token = 4; //Token验证失败 35 | } 36 | 37 | //客户端发来的第一个消息 38 | message C2S_ConnectMsg { 39 | optional uint64 playerID = 1; //唯一ID 40 | optional uint64 battleID = 2; //战斗ID 41 | optional string token = 10; //令牌 42 | } 43 | 44 | //服务端返回连接结果 45 | message S2C_ConnectMsg { 46 | optional ERRORCODE errorCode = 1; //错误码 47 | } 48 | 49 | //服务端返回进入房间消息 50 | message S2C_JoinRoomMsg { 51 | optional int32 roomseatid = 1; //自己的位置索引id(1~N) 52 | repeated uint64 others = 2; //其他人的id 53 | repeated int32 pros = 3; //其他人的进度 54 | optional int32 randomSeed = 4; //随机种子 55 | } 56 | 57 | //服务端广播开始游戏消息 58 | message S2C_StartMsg { 59 | optional int64 timeStamp = 1; //同步时间戳 60 | 61 | } 62 | 63 | //读条进度 64 | message C2S_ProgressMsg { 65 | optional int32 pro = 1; //进度值0~100 66 | } 67 | 68 | //读条进度 69 | message S2C_ProgressMsg { 70 | optional uint64 id = 1; //id 71 | optional int32 pro = 2; //进度值0~100 72 | } 73 | 74 | //操作输入消息 75 | message C2S_InputMsg { 76 | optional int32 sid = 1; //操作id 77 | optional int32 x = 2; //操作位置x 78 | optional int32 y = 3; //操作位置y 79 | optional uint32 frameID = 4; //帧ID 80 | } 81 | 82 | //帧存储操作输入 83 | message InputData { 84 | optional uint64 id = 1; //id 85 | optional int32 sid = 2; //操作id 86 | optional int32 x = 3; //操作位置x 87 | optional int32 y = 4; //操作位置y 88 | optional int32 roomseatid = 5; //操作者的位置索引id(1~N) 89 | } 90 | 91 | //帧数据 92 | message FrameData { 93 | optional uint32 frameID = 1; //帧ID 94 | repeated InputData input = 2; //操作输入 95 | } 96 | 97 | //广播帧消息 98 | message S2C_FrameMsg { 99 | repeated FrameData frames = 1; //帧数据 100 | } 101 | 102 | //结果消息 103 | message C2S_ResultMsg { 104 | optional uint64 winnerID = 1; //胜利者ID 105 | } 106 | 107 | -------------------------------------------------------------------------------- /pkg/ipx/ip.go: -------------------------------------------------------------------------------- 1 | package ipx 2 | 3 | import ( 4 | "io/ioutil" 5 | "net" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | // GetExternalIP 获取公网IP 11 | func GetExternalIP() string { 12 | resp, err := http.Get("http://myexternalip.com/raw") 13 | if err != nil { 14 | return "" 15 | } 16 | defer resp.Body.Close() 17 | content, _ := ioutil.ReadAll(resp.Body) 18 | return strings.TrimSpace(string(content)) 19 | } 20 | 21 | // GetLocalIP 获得内网IP 22 | func GetLocalIP() string { 23 | addrs, err := net.InterfaceAddrs() 24 | 25 | if err != nil { 26 | return "" 27 | } 28 | 29 | for _, address := range addrs { 30 | 31 | // 检查ip地址判断是否回环地址 32 | if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 33 | if ipnet.IP.To4() != nil { 34 | return ipnet.IP.String() 35 | } 36 | 37 | } 38 | } 39 | return "" 40 | } 41 | -------------------------------------------------------------------------------- /pkg/kcp_server/example_test.go: -------------------------------------------------------------------------------- 1 | package kcp_server 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | "sync/atomic" 7 | "testing" 8 | "time" 9 | 10 | "github.com/byebyebruce/lockstepserver/pkg/network" 11 | 12 | "github.com/xtaci/kcp-go" 13 | ) 14 | 15 | const ( 16 | latency = time.Millisecond * 10 17 | ) 18 | 19 | type testCallback struct { 20 | numConn uint32 21 | numMsg uint32 22 | numDiscon uint32 23 | } 24 | 25 | func (t *testCallback) OnMessage(conn *network.Conn, msg network.Packet) bool { 26 | 27 | atomic.AddUint32(&t.numMsg, 1) 28 | 29 | // fmt.Println("OnMessage", conn.GetExtraData(), string(msg.(*network.DefaultPacket).GetBody())) 30 | conn.AsyncWritePacket(network.NewDefaultPacket([]byte("pong")), time.Second*1) 31 | return true 32 | } 33 | 34 | func (t *testCallback) OnConnect(conn *network.Conn) bool { 35 | id := atomic.AddUint32(&t.numConn, 1) 36 | conn.PutExtraData(id) 37 | // fmt.Println("OnConnect", conn.GetExtraData()) 38 | return true 39 | } 40 | 41 | func (t *testCallback) OnClose(conn *network.Conn) { 42 | atomic.AddUint32(&t.numDiscon, 1) 43 | 44 | // fmt.Println("OnDisconnect", conn.GetExtraData()) 45 | } 46 | 47 | func Test_KCPServer(t *testing.T) { 48 | 49 | l, err := kcp.Listen(":10086") 50 | if nil != err { 51 | panic(err) 52 | } 53 | 54 | config := &network.Config{ 55 | PacketReceiveChanLimit: 1024, 56 | PacketSendChanLimit: 1024, 57 | ConnReadTimeout: latency, 58 | ConnWriteTimeout: latency, 59 | } 60 | 61 | callback := &testCallback{} 62 | server := network.NewServer(config, callback, &network.DefaultProtocol{}) 63 | 64 | go server.Start(l, func(conn net.Conn, i *network.Server) *network.Conn { 65 | kcpConn := conn.(*kcp.UDPSession) 66 | kcpConn.SetNoDelay(1, 10, 2, 1) 67 | kcpConn.SetStreamMode(true) 68 | kcpConn.SetWindowSize(4096, 4096) 69 | kcpConn.SetReadBuffer(4 * 1024 * 1024) 70 | kcpConn.SetWriteBuffer(4 * 1024 * 1024) 71 | kcpConn.SetACKNoDelay(true) 72 | 73 | return network.NewConn(conn, server) 74 | }) 75 | defer server.Stop() 76 | 77 | time.Sleep(time.Second) 78 | 79 | wg := sync.WaitGroup{} 80 | const max_con = 100 81 | for i := 0; i < max_con; i++ { 82 | wg.Add(1) 83 | time.Sleep(time.Nanosecond) 84 | go func() { 85 | defer wg.Done() 86 | 87 | c, e := kcp.Dial("127.0.0.1:10086") 88 | if nil != e { 89 | t.FailNow() 90 | } 91 | defer c.Close() 92 | 93 | c.Write(network.NewDefaultPacket([]byte("ping")).Serialize()) 94 | b := make([]byte, 1024) 95 | c.SetReadDeadline(time.Now().Add(latency)) 96 | if _, e := c.Read(b); nil != e { 97 | t.Fatalf("error:%s", e.Error()) 98 | } 99 | 100 | // time.Sleep(time.Second) 101 | }() 102 | } 103 | 104 | wg.Wait() 105 | time.Sleep(time.Second * 2) 106 | 107 | n := atomic.LoadUint32(&callback.numConn) 108 | if n != max_con { 109 | t.Errorf("numConn[%d] should be [%d]", n, max_con) 110 | } 111 | 112 | n = atomic.LoadUint32(&callback.numMsg) 113 | if n != max_con { 114 | t.Errorf("numMsg[%d] should be [%d]", n, max_con) 115 | } 116 | 117 | n = atomic.LoadUint32(&callback.numDiscon) 118 | if n != max_con { 119 | t.Errorf("numDiscon[%d] should be [%d]", n, max_con) 120 | } 121 | } 122 | 123 | func Benchmark_KCPServer(b *testing.B) { 124 | 125 | l, err := kcp.Listen(":10086") 126 | if nil != err { 127 | panic(err) 128 | } 129 | 130 | config := &network.Config{ 131 | PacketReceiveChanLimit: 1024, 132 | PacketSendChanLimit: 1024, 133 | } 134 | 135 | callback := &testCallback{} 136 | server := network.NewServer(config, &testCallback{}, &network.DefaultProtocol{}) 137 | 138 | go server.Start(l, func(conn net.Conn, i *network.Server) *network.Conn { 139 | kcpConn := conn.(*kcp.UDPSession) 140 | kcpConn.SetNoDelay(1, 10, 2, 1) 141 | kcpConn.SetStreamMode(true) 142 | kcpConn.SetWindowSize(4096, 4096) 143 | kcpConn.SetReadBuffer(4 * 1024 * 1024) 144 | kcpConn.SetWriteBuffer(4 * 1024 * 1024) 145 | kcpConn.SetACKNoDelay(true) 146 | 147 | return network.NewConn(conn, server) 148 | }) 149 | 150 | time.Sleep(time.Millisecond * 100) 151 | 152 | wg := sync.WaitGroup{} 153 | var max_con uint32 = 0 154 | c, e := kcp.Dial("127.0.0.1:10086") 155 | if nil != e { 156 | b.FailNow() 157 | } 158 | 159 | go func() { 160 | for { 161 | buf := make([]byte, 1024) 162 | c.SetReadDeadline(time.Now().Add(time.Second * 2)) 163 | _, er := c.Read(buf) 164 | if nil != er { 165 | // b.FailNow() 166 | return 167 | } 168 | wg.Done() 169 | } 170 | 171 | }() 172 | 173 | for i := 0; i < b.N; i++ { 174 | max_con++ 175 | 176 | wg.Add(1) 177 | go func() { 178 | 179 | c.Write(network.NewDefaultPacket([]byte("ping")).Serialize()) 180 | 181 | // time.Sleep(time.Second) 182 | }() 183 | } 184 | 185 | wg.Wait() 186 | // time.Sleep(time.Second * 2) 187 | server.Stop() 188 | 189 | n := atomic.LoadUint32(&callback.numMsg) 190 | b.Logf("numMsg[%d]", n) 191 | if n != callback.numMsg { 192 | b.Errorf("numMsg[%d] should be [%d]", n, max_con) 193 | } 194 | /* 195 | n = atomic.LoadUint32(&numConn) 196 | b.Logf("numConn[%d]", n) 197 | if n != max_con { 198 | b.Errorf("numConn[%d] should be [%d]", n, max_con) 199 | } 200 | 201 | 202 | 203 | n = atomic.LoadUint32(&numDiscon) 204 | b.Logf("numDiscon[%d]", n) 205 | if n != numDiscon { 206 | b.Errorf("numDiscon[%d] should be [%d]", n, max_con) 207 | } 208 | */ 209 | } 210 | 211 | func Test_TCPServer(t *testing.T) { 212 | 213 | l, err := net.Listen("tcp", ":10086") 214 | if nil != err { 215 | panic(err) 216 | } 217 | 218 | config := &network.Config{ 219 | PacketReceiveChanLimit: 1024, 220 | PacketSendChanLimit: 1024, 221 | ConnReadTimeout: time.Millisecond * 50, 222 | ConnWriteTimeout: time.Millisecond * 50, 223 | } 224 | 225 | callback := &testCallback{} 226 | server := network.NewServer(config, callback, &network.DefaultProtocol{}) 227 | 228 | go server.Start(l, func(conn net.Conn, i *network.Server) *network.Conn { 229 | return network.NewConn(conn, server) 230 | }) 231 | 232 | time.Sleep(time.Second) 233 | 234 | wg := sync.WaitGroup{} 235 | const max_con = 100 236 | for i := 0; i < max_con; i++ { 237 | wg.Add(1) 238 | time.Sleep(time.Nanosecond) 239 | go func() { 240 | defer wg.Done() 241 | c, e := net.Dial("tcp", "127.0.0.1:10086") 242 | if nil != e { 243 | t.FailNow() 244 | } 245 | defer c.Close() 246 | c.Write(network.NewDefaultPacket([]byte("ping")).Serialize()) 247 | b := make([]byte, 1024) 248 | c.SetReadDeadline(time.Now().Add(time.Second * 2)) 249 | c.Read(b) 250 | // time.Sleep(time.Second) 251 | }() 252 | } 253 | 254 | wg.Wait() 255 | // time.Sleep(max_sleep) 256 | server.Stop() 257 | 258 | n := atomic.LoadUint32(&callback.numConn) 259 | if n != max_con { 260 | t.Errorf("numConn[%d] should be [%d]", n, max_con) 261 | } 262 | 263 | n = atomic.LoadUint32(&callback.numMsg) 264 | if n != max_con { 265 | t.Errorf("numMsg[%d] should be [%d]", n, max_con) 266 | } 267 | 268 | n = atomic.LoadUint32(&callback.numDiscon) 269 | if n != max_con { 270 | t.Errorf("numDiscon[%d] should be [%d]", n, max_con) 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /pkg/kcp_server/server.go: -------------------------------------------------------------------------------- 1 | package kcp_server 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | "github.com/byebyebruce/lockstepserver/pkg/network" 8 | "github.com/xtaci/kcp-go" 9 | ) 10 | 11 | func ListenAndServe(addr string, callback network.ConnCallback, protocol network.Protocol) (*network.Server, error) { 12 | dupConfig := &network.Config{ 13 | PacketReceiveChanLimit: 1024, 14 | PacketSendChanLimit: 1024, 15 | ConnReadTimeout: time.Second * 5, 16 | ConnWriteTimeout: time.Second * 5, 17 | } 18 | 19 | l, err := kcp.Listen(addr) 20 | if nil != err { 21 | return nil, err 22 | } 23 | 24 | server := network.NewServer(dupConfig, callback, protocol) 25 | go server.Start(l, func(conn net.Conn, i *network.Server) *network.Conn { 26 | 27 | // 普通模式 28 | // setKCPConfig(32, 32, 0, 40, 0, 0, 100, 1400) 29 | 30 | // 极速模式 31 | // setKCPConfig(32, 32, 1, 10, 2, 1, 30, 1400) 32 | 33 | // 普通模式:ikcp_nodelay(kcp, 0, 40, 0, 0); 极速模式: ikcp_nodelay(kcp, 1, 10, 2, 1); 34 | 35 | kcpConn := conn.(*kcp.UDPSession) 36 | kcpConn.SetNoDelay(1, 10, 2, 1) 37 | kcpConn.SetStreamMode(true) 38 | kcpConn.SetWindowSize(4096, 4096) 39 | kcpConn.SetReadBuffer(4 * 1024 * 1024) 40 | kcpConn.SetWriteBuffer(4 * 1024 * 1024) 41 | kcpConn.SetACKNoDelay(true) 42 | 43 | return network.NewConn(conn, server) 44 | }) 45 | 46 | return server, nil 47 | } 48 | -------------------------------------------------------------------------------- /pkg/log4gox/color_logger.go: -------------------------------------------------------------------------------- 1 | package log4gox 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | 8 | l4g "github.com/alecthomas/log4go" 9 | ) 10 | 11 | var stdout io.Writer = os.Stdout 12 | 13 | /* 14 | 前景色 背景色 颜色 15 | --------------------------------------- 16 | 30 40 黑色 17 | 31 41 红色 18 | 32 42 绿色 19 | 33 43 黃色 20 | 34 44 蓝色 21 | 35 45 紫红色 22 | 36 46 青蓝色 23 | 37 47 白色 24 | */ 25 | var ( 26 | levelColor = [...]int{30, 30, 32, 37, 37, 33, 31, 34} 27 | levelStrings = [...]string{"FNST", "FINE", "DEBG", "TRAC", "INFO", "WARN", "EROR", "CRIT"} 28 | ) 29 | 30 | /* 31 | fmt.Printf("%c[%dm(1 2 3 4)%c[0m ", 0x1B, 30, 0x1B) 32 | 33 | for b := 40; b <= 47; b++ { // 背景色彩 = 40-47 34 | for f := 30; f <= 37; f++ { // 前景色彩 = 30-37 35 | for d := range []int{0, 1, 4, 5, 7, 8} { // 显示方式 = 0,1,4,5,7,8 36 | fmt.Fprintf(os.Stderr, " %c[%d;%d;%dm%s(1 2 3 4 )%c[0m ", 0x1B, d, b, f, "", 0x1B) 37 | } 38 | fmt.Println("") 39 | } 40 | fmt.Println("") 41 | } 42 | */ 43 | 44 | const ( 45 | colorSymbol = 0x1B 46 | ) 47 | 48 | // This is the standard writer that prints to standard output. 49 | type ConsoleLogWriter chan *l4g.LogRecord 50 | 51 | // This creates a new ConsoleLogWriter 52 | func NewColorConsoleLogWriter() ConsoleLogWriter { 53 | records := make(ConsoleLogWriter, l4g.LogBufferLength) 54 | go records.run(stdout) 55 | return records 56 | } 57 | 58 | func (w ConsoleLogWriter) run(out io.Writer) { 59 | var timestr string 60 | var timestrAt int64 61 | 62 | for rec := range w { 63 | if at := rec.Created.UnixNano() / 1e9; at != timestrAt { 64 | timestr, timestrAt = rec.Created.Format("01/02/06 15:04:05"), at 65 | } 66 | fmt.Fprintf(out, "%c[%dm[%s] [%s] (%s) %s\n%c[0m", 67 | colorSymbol, 68 | levelColor[rec.Level], 69 | timestr, 70 | levelStrings[rec.Level], 71 | rec.Source, 72 | rec.Message, 73 | colorSymbol) 74 | } 75 | } 76 | 77 | // This is the ConsoleLogWriter's output method. This will block if the output 78 | // buffer is full. 79 | func (w ConsoleLogWriter) LogWrite(rec *l4g.LogRecord) { 80 | w <- rec 81 | } 82 | 83 | // Close stops the logger from sending messages to standard output. Attempts to 84 | // send log messages to this logger after a Close have undefined behavior. 85 | func (w ConsoleLogWriter) Close() { 86 | close(w) 87 | } 88 | -------------------------------------------------------------------------------- /pkg/network/conn.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "sync" 7 | "sync/atomic" 8 | "time" 9 | ) 10 | 11 | // Error type 12 | var ( 13 | ErrConnClosing = errors.New("use of closed network connection") 14 | ErrWriteBlocking = errors.New("write packet was blocking") 15 | ErrReadBlocking = errors.New("read packet was blocking") 16 | ) 17 | 18 | // Conn exposes a set of callbacks for the various events that occur on a connection 19 | type Conn struct { 20 | srv *Server 21 | conn net.Conn // the raw connection 22 | extraData interface{} // to save extra data 23 | closeOnce sync.Once // close the conn, once, per instance 24 | closeFlag int32 // close flag 25 | closeChan chan struct{} // close chanel 26 | packetSendChan chan Packet // packet send chanel 27 | packetReceiveChan chan Packet // packeet receive chanel 28 | callback ConnCallback // callback 29 | } 30 | 31 | // ConnCallback is an interface of methods that are used as callbacks on a connection 32 | type ConnCallback interface { 33 | // OnConnect is called when the connection was accepted, 34 | // If the return value of false is closed 35 | OnConnect(*Conn) bool 36 | 37 | // OnMessage is called when the connection receives a packet, 38 | // If the return value of false is closed 39 | OnMessage(*Conn, Packet) bool 40 | 41 | // OnClose is called when the connection closed 42 | OnClose(*Conn) 43 | } 44 | 45 | // NewConn returns a wrapper of raw conn 46 | func NewConn(conn net.Conn, srv *Server) *Conn { 47 | return &Conn{ 48 | srv: srv, 49 | callback: srv.callback, 50 | conn: conn, 51 | closeChan: make(chan struct{}), 52 | packetSendChan: make(chan Packet, srv.config.PacketSendChanLimit), 53 | packetReceiveChan: make(chan Packet, srv.config.PacketReceiveChanLimit), 54 | } 55 | } 56 | 57 | // GetExtraData gets the extra data from the Conn 58 | func (c *Conn) GetExtraData() interface{} { 59 | return c.extraData 60 | } 61 | 62 | // PutExtraData puts the extra data with the Conn 63 | func (c *Conn) PutExtraData(data interface{}) { 64 | c.extraData = data 65 | } 66 | 67 | // GetRawConn returns the raw net.TCPConn from the Conn 68 | func (c *Conn) GetRawConn() net.Conn { 69 | return c.conn 70 | } 71 | 72 | // Close closes the connection 73 | func (c *Conn) Close() { 74 | c.closeOnce.Do(func() { 75 | atomic.StoreInt32(&c.closeFlag, 1) 76 | close(c.closeChan) 77 | close(c.packetSendChan) 78 | close(c.packetReceiveChan) 79 | c.conn.Close() 80 | c.callback.OnClose(c) 81 | }) 82 | } 83 | 84 | // IsClosed indicates whether or not the connection is closed 85 | func (c *Conn) IsClosed() bool { 86 | return atomic.LoadInt32(&c.closeFlag) == 1 87 | } 88 | 89 | func (c *Conn) SetCallback(callback ConnCallback) { 90 | c.callback = callback 91 | } 92 | 93 | // AsyncWritePacket async writes a packet, this method will never block 94 | func (c *Conn) AsyncWritePacket(p Packet, timeout time.Duration) (err error) { 95 | if c.IsClosed() { 96 | return ErrConnClosing 97 | } 98 | 99 | defer func() { 100 | if e := recover(); e != nil { 101 | err = ErrConnClosing 102 | } 103 | }() 104 | 105 | if timeout == 0 { 106 | select { 107 | case c.packetSendChan <- p: 108 | return nil 109 | 110 | default: 111 | return ErrWriteBlocking 112 | } 113 | 114 | } else { 115 | select { 116 | case c.packetSendChan <- p: 117 | return nil 118 | 119 | case <-c.closeChan: 120 | return ErrConnClosing 121 | 122 | case <-time.After(timeout): 123 | return ErrWriteBlocking 124 | } 125 | } 126 | } 127 | 128 | // Do it 129 | func (c *Conn) Do() { 130 | if !c.callback.OnConnect(c) { 131 | return 132 | } 133 | 134 | asyncDo(c.handleLoop, c.srv.waitGroup) 135 | asyncDo(c.readLoop, c.srv.waitGroup) 136 | asyncDo(c.writeLoop, c.srv.waitGroup) 137 | } 138 | 139 | func (c *Conn) readLoop() { 140 | defer func() { 141 | recover() 142 | c.Close() 143 | }() 144 | 145 | for { 146 | select { 147 | case <-c.srv.exitChan: 148 | return 149 | 150 | case <-c.closeChan: 151 | return 152 | 153 | default: 154 | } 155 | 156 | c.conn.SetReadDeadline(time.Now().Add(c.srv.config.ConnReadTimeout)) 157 | p, err := c.srv.protocol.ReadPacket(c.conn) 158 | if err != nil { 159 | return 160 | } 161 | 162 | c.packetReceiveChan <- p 163 | } 164 | } 165 | 166 | func (c *Conn) writeLoop() { 167 | defer func() { 168 | recover() 169 | c.Close() 170 | }() 171 | 172 | for { 173 | select { 174 | case <-c.srv.exitChan: 175 | return 176 | 177 | case <-c.closeChan: 178 | return 179 | 180 | case p := <-c.packetSendChan: 181 | if c.IsClosed() { 182 | return 183 | } 184 | c.conn.SetWriteDeadline(time.Now().Add(c.srv.config.ConnWriteTimeout)) 185 | if _, err := c.conn.Write(p.Serialize()); err != nil { 186 | return 187 | } 188 | } 189 | } 190 | } 191 | 192 | func (c *Conn) handleLoop() { 193 | defer func() { 194 | recover() 195 | c.Close() 196 | }() 197 | 198 | for { 199 | select { 200 | case <-c.srv.exitChan: 201 | return 202 | 203 | case <-c.closeChan: 204 | return 205 | 206 | case p := <-c.packetReceiveChan: 207 | if c.IsClosed() { 208 | return 209 | } 210 | if !c.callback.OnMessage(c, p) { 211 | return 212 | } 213 | } 214 | } 215 | } 216 | 217 | func asyncDo(fn func(), wg *sync.WaitGroup) { 218 | wg.Add(1) 219 | go func() { 220 | fn() 221 | wg.Done() 222 | }() 223 | } 224 | -------------------------------------------------------------------------------- /pkg/network/protocol.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | ) 8 | 9 | type Packet interface { 10 | Serialize() []byte 11 | } 12 | 13 | type Protocol interface { 14 | ReadPacket(conn io.Reader) (Packet, error) 15 | } 16 | 17 | type DefaultPacket struct { 18 | buff []byte 19 | } 20 | 21 | func (this *DefaultPacket) Serialize() []byte { 22 | return this.buff 23 | } 24 | 25 | func (this *DefaultPacket) GetBody() []byte { 26 | return this.buff[4:] 27 | } 28 | 29 | func NewDefaultPacket(buff []byte) *DefaultPacket { 30 | p := &DefaultPacket{} 31 | 32 | p.buff = make([]byte, 4+len(buff)) 33 | binary.BigEndian.PutUint32(p.buff[0:4], uint32(len(buff))) 34 | copy(p.buff[4:], buff) 35 | 36 | return p 37 | } 38 | 39 | type DefaultProtocol struct { 40 | } 41 | 42 | func (this *DefaultProtocol) ReadPacket(r io.Reader) (Packet, error) { 43 | var ( 44 | lengthBytes []byte = make([]byte, 4) 45 | length uint32 46 | ) 47 | 48 | // read length 49 | if _, err := io.ReadFull(r, lengthBytes); err != nil { 50 | return nil, err 51 | } 52 | if length = binary.BigEndian.Uint32(lengthBytes); length > 1024 { 53 | return nil, errors.New("the size of packet is larger than the limit") 54 | } 55 | 56 | buff := make([]byte, length) 57 | 58 | // read body ( buff = lengthBytes + body ) 59 | if _, err := io.ReadFull(r, buff); err != nil { 60 | return nil, err 61 | } 62 | 63 | return NewDefaultPacket(buff), nil 64 | } 65 | -------------------------------------------------------------------------------- /pkg/network/server.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | type Config struct { 10 | PacketSendChanLimit uint32 // the limit of packet send channel 11 | PacketReceiveChanLimit uint32 // the limit of packet receive channel 12 | ConnReadTimeout time.Duration // read timeout 13 | ConnWriteTimeout time.Duration // write timeout 14 | } 15 | 16 | type Server struct { 17 | config *Config // server configuration 18 | callback ConnCallback // message callbacks in connection 19 | protocol Protocol // customize packet protocol 20 | exitChan chan struct{} // notify all goroutines to shutdown 21 | waitGroup *sync.WaitGroup // wait for all goroutines 22 | closeOnce sync.Once 23 | listener net.Listener 24 | } 25 | 26 | // NewServer creates a server 27 | func NewServer(config *Config, callback ConnCallback, protocol Protocol) *Server { 28 | return &Server{ 29 | config: config, 30 | callback: callback, 31 | protocol: protocol, 32 | exitChan: make(chan struct{}), 33 | waitGroup: &sync.WaitGroup{}, 34 | } 35 | } 36 | 37 | type ConnectionCreator func(net.Conn, *Server) *Conn 38 | 39 | // Start starts service 40 | func (s *Server) Start(listener net.Listener, create ConnectionCreator) { 41 | s.listener = listener 42 | s.waitGroup.Add(1) 43 | defer func() { 44 | s.waitGroup.Done() 45 | }() 46 | 47 | for { 48 | select { 49 | case <-s.exitChan: 50 | return 51 | 52 | default: 53 | } 54 | 55 | conn, err := listener.Accept() 56 | if err != nil { 57 | continue 58 | } 59 | 60 | s.waitGroup.Add(1) 61 | go func() { 62 | create(conn, s).Do() 63 | s.waitGroup.Done() 64 | }() 65 | } 66 | } 67 | 68 | // Stop stops service 69 | func (s *Server) Stop() { 70 | s.closeOnce.Do(func() { 71 | close(s.exitChan) 72 | s.listener.Close() 73 | }) 74 | 75 | s.waitGroup.Wait() 76 | } 77 | -------------------------------------------------------------------------------- /pkg/packet/pb_packet/pb_packet.go: -------------------------------------------------------------------------------- 1 | package pb_packet 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | 8 | l4g "github.com/alecthomas/log4go" 9 | "github.com/byebyebruce/lockstepserver/pkg/network" 10 | "github.com/golang/protobuf/proto" 11 | ) 12 | 13 | const ( 14 | DataLen = 2 15 | MessageIDLen = 1 16 | 17 | MinPacketLen = DataLen + MessageIDLen 18 | MaxPacketLen = (2 << 8) * DataLen 19 | MaxMessageID = (2 << 8) * MessageIDLen 20 | ) 21 | 22 | /* 23 | 24 | s->c 25 | 26 | |--totalDataLen(uint16)--|--msgIDLen(uint8)--|--------------data--------------| 27 | |-------------2----------|---------1---------|---------(totalDataLen-2-1)-----| 28 | 29 | */ 30 | 31 | // Packet 服务端发往客户端的消息 32 | type Packet struct { 33 | id uint8 34 | data []byte 35 | } 36 | 37 | func (p *Packet) GetMessageID() uint8 { 38 | return p.id 39 | } 40 | 41 | func (p *Packet) GetData() []byte { 42 | return p.data 43 | } 44 | 45 | func (p *Packet) Serialize() []byte { 46 | buff := make([]byte, MinPacketLen, MinPacketLen) 47 | 48 | dataLen := len(p.data) 49 | binary.BigEndian.PutUint16(buff, uint16(dataLen)) 50 | 51 | buff[DataLen] = p.id 52 | return append(buff, p.data...) 53 | } 54 | 55 | func (p *Packet) Unmarshal(m interface{}) error { 56 | return proto.Unmarshal(p.data, m.(proto.Message)) 57 | } 58 | 59 | func NewPacket(id uint8, msg interface{}) *Packet { 60 | 61 | p := &Packet{ 62 | id: id, 63 | } 64 | 65 | switch v := msg.(type) { 66 | case []byte: 67 | p.data = v 68 | case proto.Message: 69 | if mdata, err := proto.Marshal(v); err == nil { 70 | p.data = mdata 71 | } else { 72 | l4g.Error("[NewPacket] proto marshal msg: %d error: %v", 73 | id, err) 74 | return nil 75 | } 76 | case nil: 77 | default: 78 | l4g.Error("[NewPacket] error msg type msg: %d", id) 79 | return nil 80 | } 81 | 82 | return p 83 | } 84 | 85 | type MsgProtocol struct { 86 | } 87 | 88 | func (p *MsgProtocol) ReadPacket(r io.Reader) (network.Packet, error) /*Packet*/ { 89 | 90 | buff := make([]byte, MinPacketLen, MinPacketLen) 91 | 92 | // data length 93 | if _, err := io.ReadFull(r, buff); err != nil { 94 | return nil, err 95 | } 96 | dataLen := binary.BigEndian.Uint16(buff) 97 | 98 | if dataLen > MaxPacketLen { 99 | return nil, errors.New("data max") 100 | } 101 | 102 | // id 103 | msg := &Packet{ 104 | id: buff[DataLen], 105 | } 106 | 107 | // data 108 | if dataLen > 0 { 109 | msg.data = make([]byte, dataLen, dataLen) 110 | if _, err := io.ReadFull(r, msg.data); err != nil { 111 | return nil, err 112 | } 113 | } 114 | 115 | return msg, nil 116 | } 117 | -------------------------------------------------------------------------------- /pkg/packet/pb_packet/protocol_test.go: -------------------------------------------------------------------------------- 1 | package pb_packet 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/json" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/byebyebruce/lockstepserver/pkg/packet/pb_packet/testdata" 11 | "github.com/golang/protobuf/proto" 12 | ) 13 | 14 | func Test_SCPacket(t *testing.T) { 15 | 16 | var sID int32 = 19234333 17 | msg := &testdata.TestMsg{ 18 | Sid: proto.Int32(sID), 19 | X: proto.Int32(10), 20 | Y: proto.Int32(20), 21 | } 22 | raw, _ := proto.Marshal(msg) 23 | p := NewPacket(uint8(testdata.ID_MSG_Test), msg) 24 | if nil == p { 25 | t.Fail() 26 | } 27 | 28 | buff := p.Serialize() 29 | 30 | dataLen := binary.BigEndian.Uint16(buff[0:]) 31 | if dataLen != uint16(len(raw)) { 32 | t.Error("dataLen != uint16(len(raw))") 33 | } 34 | 35 | if MinPacketLen+dataLen != MinPacketLen+uint16(len(raw)) { 36 | t.Error("MinPacketLen+dataLen != MinPacketLen+uint16(len(raw))") 37 | } 38 | 39 | id := buff[DataLen] 40 | if p.id != id { 41 | t.Error("uint8(ID_C2S_Connect) != id") 42 | } 43 | 44 | msg1 := &testdata.TestMsg{} 45 | if err := proto.Unmarshal(buff[MinPacketLen:], msg1); nil != err { 46 | t.Error(err) 47 | } 48 | 49 | if msg.GetSid() != msg1.GetSid() || msg.GetX() != msg1.GetX() || msg.GetY() != msg1.GetY() { 50 | t.Error("msg.Sid != data1.Sid || msg.X != data1.X || msg.Y != data1.Y") 51 | } 52 | } 53 | 54 | func Benchmark_SCPacket(b *testing.B) { 55 | 56 | var sID int32 = 19234333 57 | msg := &testdata.TestMsg{ 58 | Sid: proto.Int32(sID), 59 | X: proto.Int32(10), 60 | Y: proto.Int32(20), 61 | } 62 | 63 | for i := 0; i < b.N; i++ { 64 | NewPacket(uint8(testdata.ID_MSG_Test), msg) 65 | } 66 | 67 | } 68 | 69 | func Test_Packet(t *testing.T) { 70 | var sID int32 = 19234333 71 | msg := &testdata.TestMsg{ 72 | Sid: proto.Int32(sID), 73 | X: proto.Int32(10), 74 | Y: proto.Int32(20000), 75 | } 76 | 77 | temp, _ := proto.Marshal(msg) 78 | 79 | p := &Packet{ 80 | id: uint8(testdata.ID_MSG_Test), 81 | data: temp, 82 | } 83 | 84 | b := p.Serialize() 85 | 86 | r := strings.NewReader(string(b)) 87 | 88 | proto := &MsgProtocol{} 89 | 90 | ret, err := proto.ReadPacket(r) 91 | if nil != err { 92 | t.Error(err) 93 | } 94 | 95 | packet, _ := ret.(*Packet) 96 | if packet.GetMessageID() != p.id { 97 | t.Error("packet.GetMessageID() != uint8(ID_MSG_Input)") 98 | } 99 | 100 | if len(packet.data) != len(p.data) { 101 | t.Error("len(packet.data)!=len(p.data)") 102 | } 103 | 104 | msg1 := &testdata.TestMsg{} 105 | err = packet.Unmarshal(msg1) 106 | if nil != err { 107 | t.Error(err) 108 | } 109 | if msg.GetSid() != msg1.GetSid() || msg.GetX() != msg1.GetX() || msg.GetY() != msg1.GetY() { 110 | t.Error("msg.Sid != data1.Sid || msg.X != data1.X || msg.Y != data1.Y") 111 | } 112 | } 113 | 114 | func Benchmark_Packet(b *testing.B) { 115 | var sID int32 = 19234333 116 | msg := &testdata.TestMsg{ 117 | Sid: proto.Int32(sID), 118 | X: proto.Int32(10), 119 | Y: proto.Int32(20000), 120 | } 121 | 122 | temp, _ := json.Marshal(msg) 123 | 124 | p := &Packet{ 125 | id: uint8(testdata.ID_MSG_Test), 126 | data: temp, 127 | } 128 | 129 | buf := p.Serialize() 130 | 131 | // strings.NewReader(string(b)) 132 | 133 | proto := &MsgProtocol{} 134 | 135 | r := bytes.NewBuffer(nil) 136 | 137 | for i := 0; i < b.N; i++ { 138 | r.Write(buf) 139 | if _, err := proto.ReadPacket(r); nil != err { 140 | b.Error(err) 141 | } 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /pkg/packet/pb_packet/testdata/testdata.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.26.0 4 | // protoc v3.17.3 5 | // source: testdata.proto 6 | 7 | package testdata 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | //消息ID 24 | type ID int32 25 | 26 | const ( 27 | ID_MSG_BEGIN ID = 0 28 | ID_MSG_Test ID = 60 29 | ID_MSG_END ID = 255 30 | ) 31 | 32 | // Enum value maps for ID. 33 | var ( 34 | ID_name = map[int32]string{ 35 | 0: "MSG_BEGIN", 36 | 60: "MSG_Test", 37 | 255: "MSG_END", 38 | } 39 | ID_value = map[string]int32{ 40 | "MSG_BEGIN": 0, 41 | "MSG_Test": 60, 42 | "MSG_END": 255, 43 | } 44 | ) 45 | 46 | func (x ID) Enum() *ID { 47 | p := new(ID) 48 | *p = x 49 | return p 50 | } 51 | 52 | func (x ID) String() string { 53 | return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 54 | } 55 | 56 | func (ID) Descriptor() protoreflect.EnumDescriptor { 57 | return file_testdata_proto_enumTypes[0].Descriptor() 58 | } 59 | 60 | func (ID) Type() protoreflect.EnumType { 61 | return &file_testdata_proto_enumTypes[0] 62 | } 63 | 64 | func (x ID) Number() protoreflect.EnumNumber { 65 | return protoreflect.EnumNumber(x) 66 | } 67 | 68 | // Deprecated: Use ID.Descriptor instead. 69 | func (ID) EnumDescriptor() ([]byte, []int) { 70 | return file_testdata_proto_rawDescGZIP(), []int{0} 71 | } 72 | 73 | // TestMsg 74 | type TestMsg struct { 75 | state protoimpl.MessageState 76 | sizeCache protoimpl.SizeCache 77 | unknownFields protoimpl.UnknownFields 78 | 79 | Sid *int32 `protobuf:"varint,1,opt,name=sid,proto3,oneof" json:"sid,omitempty"` //操作id 80 | X *int32 `protobuf:"varint,2,opt,name=x,proto3,oneof" json:"x,omitempty"` //操作位置x 81 | Y *int32 `protobuf:"varint,3,opt,name=y,proto3,oneof" json:"y,omitempty"` //操作位置y 82 | FrameID *uint32 `protobuf:"varint,4,opt,name=frameID,proto3,oneof" json:"frameID,omitempty"` //帧ID 83 | } 84 | 85 | func (x *TestMsg) Reset() { 86 | *x = TestMsg{} 87 | if protoimpl.UnsafeEnabled { 88 | mi := &file_testdata_proto_msgTypes[0] 89 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 90 | ms.StoreMessageInfo(mi) 91 | } 92 | } 93 | 94 | func (x *TestMsg) String() string { 95 | return protoimpl.X.MessageStringOf(x) 96 | } 97 | 98 | func (*TestMsg) ProtoMessage() {} 99 | 100 | func (x *TestMsg) ProtoReflect() protoreflect.Message { 101 | mi := &file_testdata_proto_msgTypes[0] 102 | if protoimpl.UnsafeEnabled && x != nil { 103 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 104 | if ms.LoadMessageInfo() == nil { 105 | ms.StoreMessageInfo(mi) 106 | } 107 | return ms 108 | } 109 | return mi.MessageOf(x) 110 | } 111 | 112 | // Deprecated: Use TestMsg.ProtoReflect.Descriptor instead. 113 | func (*TestMsg) Descriptor() ([]byte, []int) { 114 | return file_testdata_proto_rawDescGZIP(), []int{0} 115 | } 116 | 117 | func (x *TestMsg) GetSid() int32 { 118 | if x != nil && x.Sid != nil { 119 | return *x.Sid 120 | } 121 | return 0 122 | } 123 | 124 | func (x *TestMsg) GetX() int32 { 125 | if x != nil && x.X != nil { 126 | return *x.X 127 | } 128 | return 0 129 | } 130 | 131 | func (x *TestMsg) GetY() int32 { 132 | if x != nil && x.Y != nil { 133 | return *x.Y 134 | } 135 | return 0 136 | } 137 | 138 | func (x *TestMsg) GetFrameID() uint32 { 139 | if x != nil && x.FrameID != nil { 140 | return *x.FrameID 141 | } 142 | return 0 143 | } 144 | 145 | var File_testdata_proto protoreflect.FileDescriptor 146 | 147 | var file_testdata_proto_rawDesc = []byte{ 148 | 0x0a, 0x0e, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 149 | 0x12, 0x08, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x22, 0x85, 0x01, 0x0a, 0x07, 0x54, 150 | 0x65, 0x73, 0x74, 0x4d, 0x73, 0x67, 0x12, 0x15, 0x0a, 0x03, 0x73, 0x69, 0x64, 0x18, 0x01, 0x20, 151 | 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x03, 0x73, 0x69, 0x64, 0x88, 0x01, 0x01, 0x12, 0x11, 0x0a, 152 | 0x01, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x01, 0x52, 0x01, 0x78, 0x88, 0x01, 0x01, 153 | 0x12, 0x11, 0x0a, 0x01, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x48, 0x02, 0x52, 0x01, 0x79, 154 | 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x44, 0x18, 0x04, 155 | 0x20, 0x01, 0x28, 0x0d, 0x48, 0x03, 0x52, 0x07, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x44, 0x88, 156 | 0x01, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x73, 0x69, 0x64, 0x42, 0x04, 0x0a, 0x02, 0x5f, 0x78, 157 | 0x42, 0x04, 0x0a, 0x02, 0x5f, 0x79, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 158 | 0x49, 0x44, 0x2a, 0x2f, 0x0a, 0x02, 0x49, 0x44, 0x12, 0x0d, 0x0a, 0x09, 0x4d, 0x53, 0x47, 0x5f, 159 | 0x42, 0x45, 0x47, 0x49, 0x4e, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x4d, 0x53, 0x47, 0x5f, 0x54, 160 | 0x65, 0x73, 0x74, 0x10, 0x3c, 0x12, 0x0c, 0x0a, 0x07, 0x4d, 0x53, 0x47, 0x5f, 0x45, 0x4e, 0x44, 161 | 0x10, 0xff, 0x01, 0x42, 0x4e, 0x5a, 0x4c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 162 | 0x6d, 0x2f, 0x62, 0x79, 0x65, 0x62, 0x79, 0x65, 0x62, 0x72, 0x75, 0x63, 0x65, 0x2f, 0x6c, 0x6f, 163 | 0x63, 0x6b, 0x73, 0x74, 0x65, 0x70, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x6b, 0x67, 164 | 0x2f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x2f, 0x70, 0x62, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 165 | 0x74, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x3b, 0x74, 0x65, 0x73, 0x74, 0x64, 166 | 0x61, 0x74, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 167 | } 168 | 169 | var ( 170 | file_testdata_proto_rawDescOnce sync.Once 171 | file_testdata_proto_rawDescData = file_testdata_proto_rawDesc 172 | ) 173 | 174 | func file_testdata_proto_rawDescGZIP() []byte { 175 | file_testdata_proto_rawDescOnce.Do(func() { 176 | file_testdata_proto_rawDescData = protoimpl.X.CompressGZIP(file_testdata_proto_rawDescData) 177 | }) 178 | return file_testdata_proto_rawDescData 179 | } 180 | 181 | var file_testdata_proto_enumTypes = make([]protoimpl.EnumInfo, 1) 182 | var file_testdata_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 183 | var file_testdata_proto_goTypes = []interface{}{ 184 | (ID)(0), // 0: testdata.ID 185 | (*TestMsg)(nil), // 1: testdata.TestMsg 186 | } 187 | var file_testdata_proto_depIdxs = []int32{ 188 | 0, // [0:0] is the sub-list for method output_type 189 | 0, // [0:0] is the sub-list for method input_type 190 | 0, // [0:0] is the sub-list for extension type_name 191 | 0, // [0:0] is the sub-list for extension extendee 192 | 0, // [0:0] is the sub-list for field type_name 193 | } 194 | 195 | func init() { file_testdata_proto_init() } 196 | func file_testdata_proto_init() { 197 | if File_testdata_proto != nil { 198 | return 199 | } 200 | if !protoimpl.UnsafeEnabled { 201 | file_testdata_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 202 | switch v := v.(*TestMsg); i { 203 | case 0: 204 | return &v.state 205 | case 1: 206 | return &v.sizeCache 207 | case 2: 208 | return &v.unknownFields 209 | default: 210 | return nil 211 | } 212 | } 213 | } 214 | file_testdata_proto_msgTypes[0].OneofWrappers = []interface{}{} 215 | type x struct{} 216 | out := protoimpl.TypeBuilder{ 217 | File: protoimpl.DescBuilder{ 218 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 219 | RawDescriptor: file_testdata_proto_rawDesc, 220 | NumEnums: 1, 221 | NumMessages: 1, 222 | NumExtensions: 0, 223 | NumServices: 0, 224 | }, 225 | GoTypes: file_testdata_proto_goTypes, 226 | DependencyIndexes: file_testdata_proto_depIdxs, 227 | EnumInfos: file_testdata_proto_enumTypes, 228 | MessageInfos: file_testdata_proto_msgTypes, 229 | }.Build() 230 | File_testdata_proto = out.File 231 | file_testdata_proto_rawDesc = nil 232 | file_testdata_proto_goTypes = nil 233 | file_testdata_proto_depIdxs = nil 234 | } 235 | -------------------------------------------------------------------------------- /pkg/packet/pb_packet/testdata/testdata.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package testdata; 4 | option go_package = "github.com/byebyebruce/lockstepserver/pkg/packet/pb_packet/testdata;testdata"; 5 | 6 | 7 | //消息ID 8 | enum ID { 9 | 10 | MSG_BEGIN = 0; 11 | 12 | 13 | MSG_Test = 60; 14 | 15 | MSG_END = 255; 16 | } 17 | 18 | 19 | // TestMsg 20 | message TestMsg { 21 | optional int32 sid = 1; //操作id 22 | optional int32 x = 2; //操作位置x 23 | optional int32 y = 3; //操作位置y 24 | optional uint32 frameID = 4; //帧ID 25 | } 26 | -------------------------------------------------------------------------------- /server/router.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "sync/atomic" 5 | "time" 6 | 7 | "github.com/byebyebruce/lockstepserver/pb" 8 | "github.com/byebyebruce/lockstepserver/pkg/network" 9 | "github.com/byebyebruce/lockstepserver/pkg/packet/pb_packet" 10 | 11 | l4g "github.com/alecthomas/log4go" 12 | ) 13 | 14 | // TODO 15 | func verifyToken(secret string) string { 16 | return secret 17 | } 18 | 19 | // OnConnect 链接进来 20 | func (r *LockStepServer) OnConnect(conn *network.Conn) bool { 21 | count := atomic.AddInt64(&r.totalConn, 1) 22 | l4g.Debug("[router] OnConnect [%s] totalConn=%d", conn.GetRawConn().RemoteAddr().String(), count) 23 | // TODO 可以做一些check,不合法返回false 24 | return true 25 | } 26 | 27 | // OnMessage 消息处理 28 | func (r *LockStepServer) OnMessage(conn *network.Conn, p network.Packet) bool { 29 | 30 | msg := p.(*pb_packet.Packet) 31 | 32 | l4g.Info("[router] OnMessage [%s] msg=[%d] len=[%d]", conn.GetRawConn().RemoteAddr().String(), msg.GetMessageID(), len(msg.GetData())) 33 | 34 | switch pb.ID(msg.GetMessageID()) { 35 | case pb.ID_MSG_Connect: 36 | 37 | rec := &pb.C2S_ConnectMsg{} 38 | if err := msg.Unmarshal(rec); nil != err { 39 | l4g.Error("[router] msg.Unmarshal error=[%s]", err.Error()) 40 | return false 41 | } 42 | 43 | // player id 44 | playerID := rec.GetPlayerID() 45 | // room id 46 | roomID := rec.GetBattleID() 47 | // token 48 | token := rec.GetToken() 49 | 50 | ret := &pb.S2C_ConnectMsg{ 51 | ErrorCode: pb.ERRORCODE_ERR_Ok.Enum(), 52 | } 53 | 54 | room := r.roomMgr.GetRoom(roomID) 55 | if nil == room { 56 | ret.ErrorCode = pb.ERRORCODE_ERR_NoRoom.Enum() 57 | conn.AsyncWritePacket(pb_packet.NewPacket(uint8(pb.ID_MSG_Connect), ret), time.Millisecond) 58 | l4g.Error("[router] no room player=[%d] room=[%d] token=[%s]", playerID, roomID, token) 59 | return true 60 | } 61 | 62 | if room.IsOver() { 63 | ret.ErrorCode = pb.ERRORCODE_ERR_RoomState.Enum() 64 | conn.AsyncWritePacket(pb_packet.NewPacket(uint8(pb.ID_MSG_Connect), ret), time.Millisecond) 65 | l4g.Error("[router] room is over player=[%d] room==[%d] token=[%s]", playerID, roomID, token) 66 | return true 67 | } 68 | 69 | if !room.HasPlayer(playerID) { 70 | ret.ErrorCode = pb.ERRORCODE_ERR_NoPlayer.Enum() 71 | conn.AsyncWritePacket(pb_packet.NewPacket(uint8(pb.ID_MSG_Connect), ret), time.Millisecond) 72 | l4g.Error("[router] !room.HasPlayer(playerID) player=[%d] room==[%d] token=[%s]", playerID, roomID, token) 73 | return true 74 | } 75 | 76 | // 验证token 77 | if token != verifyToken(token) { 78 | ret.ErrorCode = pb.ERRORCODE_ERR_Token.Enum() 79 | conn.AsyncWritePacket(pb_packet.NewPacket(uint8(pb.ID_MSG_Connect), ret), time.Millisecond) 80 | l4g.Error("[router] verifyToken failed player=[%d] room==[%d] token=[%s]", playerID, roomID, token) 81 | return true 82 | } 83 | 84 | conn.PutExtraData(playerID) 85 | 86 | // 这里只是先给加上身份标识,不能直接返回Connect成功,又后面Game返回 87 | // conn.AsyncWritePacket(pb_packet.NewPacket(uint8(pb.ID_MSG_Connect), ret), time.Millisecond) 88 | return room.OnConnect(conn) 89 | 90 | case pb.ID_MSG_Heartbeat: 91 | conn.AsyncWritePacket(pb_packet.NewPacket(uint8(pb.ID_MSG_Heartbeat), nil), time.Millisecond) 92 | return true 93 | 94 | case pb.ID_MSG_END: 95 | // 正式版不会提供这个消息 96 | conn.AsyncWritePacket(pb_packet.NewPacket(uint8(pb.ID_MSG_END), msg.GetData()), time.Millisecond) 97 | return true 98 | } 99 | 100 | return false 101 | 102 | } 103 | 104 | // OnClose 链接断开 105 | func (r *LockStepServer) OnClose(conn *network.Conn) { 106 | count := atomic.AddInt64(&r.totalConn, -1) 107 | 108 | l4g.Info("[router] OnClose: total=%d", count) 109 | } 110 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/byebyebruce/lockstepserver/logic" 5 | "github.com/byebyebruce/lockstepserver/pkg/kcp_server" 6 | "github.com/byebyebruce/lockstepserver/pkg/network" 7 | "github.com/byebyebruce/lockstepserver/pkg/packet/pb_packet" 8 | ) 9 | 10 | // LockStepServer 帧同步服务器 11 | type LockStepServer struct { 12 | roomMgr *logic.RoomManager 13 | udpServer *network.Server 14 | totalConn int64 15 | } 16 | 17 | // New 构造 18 | func New(address string) (*LockStepServer, error) { 19 | s := &LockStepServer{ 20 | roomMgr: logic.NewRoomManager(), 21 | } 22 | networkServer, err := kcp_server.ListenAndServe(address, s, &pb_packet.MsgProtocol{}) 23 | if err != nil { 24 | return nil, err 25 | } 26 | s.udpServer = networkServer 27 | return s, nil 28 | } 29 | 30 | // RoomManager 获取房间管理器 31 | func (r *LockStepServer) RoomManager() *logic.RoomManager { 32 | return r.roomMgr 33 | } 34 | 35 | // Stop 停止服务 36 | func (r *LockStepServer) Stop() { 37 | r.roomMgr.Stop() 38 | r.udpServer.Stop() 39 | } 40 | --------------------------------------------------------------------------------