Files
CoreApp/router/minecraft/servers.go

499 lines
11 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package minecraft
import (
"coreapp/util"
"coreapp/util/ws"
"fmt"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"google.golang.org/protobuf/proto"
"math/rand"
"net/http"
"strings"
"sync"
"time"
)
type ServerType string
const (
ServerTypeUnknown ServerType = ""
ServerTypeLobby ServerType = "LOBBY"
ServerTypeRoom ServerType = "ROOM"
ServerTypeLimbo ServerType = "LIMBO"
ServerTypeSystem ServerType = "SYSTEM"
ServerTypeMega ServerType = "MAGA" // make america great again
)
func (typ ServerType) Prefix() string {
switch typ {
case ServerTypeLobby:
return "L"
case ServerTypeRoom:
return "mini"
case ServerTypeLimbo:
return ""
case ServerTypeSystem:
return "S"
case ServerTypeMega:
return "mega"
case ServerTypeUnknown:
default:
return "U"
}
return "U"
}
type Status string
const (
ServerStatusFree Status = "FREE"
ServerStatusWaiting Status = "WAITING"
ServerStatusStarting Status = "STARTING"
ServerStatusFull Status = "FULL"
ServerStatusPlaying Status = "PLAYING"
ServerStatusEnding Status = "ENDING"
)
type Server struct {
Id string `json:"id"`
Name string `json:"name"`
Group string `json:"group"`
Host string `json:"host"`
Port int `json:"port"`
Type ServerType `json:"type"`
Motd string `json:"motd"`
Players []string `json:"players"`
Status Status `json:"status"`
LastHB time.Time
}
var servers = sync.Map{}
var serverProxyConn *ws.Conn
var serverProxyLastHB time.Time
func ServerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if strings.HasSuffix(c.Request.URL.Path, "ws") || (c.Request.Method == "POST" && !strings.HasSuffix(c.Request.URL.Path, "/register")) {
if c.GetHeader("Authorization") != "NEXTCRAFT" {
c.AbortWithStatusJSON(401, gin.H{
"code": 1000,
"message": "Who are you?",
})
return
}
serverId := c.Query("id")
if serverId == "" {
c.AbortWithStatusJSON(400, gin.H{
"code": -1,
"message": "请提供服务器ID",
})
return
}
if serverId != "PROXY" {
if _, ok := servers.Load(serverId); !ok {
c.AbortWithStatusJSON(404, gin.H{
"code": -1,
"message": "服务器不存在",
})
return
}
}
c.Set("serverId", serverId)
}
}
}
func ServerList(c *gin.Context) {
var serverList []Server
// 遍历 sync.Map
servers.Range(func(key, value interface{}) bool {
server := value.(*Server)
serverList = append(serverList, *server)
return true // 继续遍历
})
c.JSON(200, gin.H{
"code": 0,
"message": "success",
"data": serverList,
})
}
type ServerRegisterData struct {
Name string `json:"name,omitempty"`
Host string `json:"host"`
Port int `json:"port"`
Type ServerType `json:"type"`
Group string `json:"group,omitempty"`
Motd string `json:"motd,omitempty"`
}
func ServerRegister(c *gin.Context) {
data := &ServerRegisterData{}
if err := c.ShouldBindJSON(data); err != nil {
c.JSON(400, gin.H{
"code": -1,
"message": "请提供正确的注册信息",
})
return
}
if data.Name == "" {
data.Name = "AUTO"
}
if data.Type == "" {
c.JSON(400, gin.H{
"code": -1,
"message": "请提供正确的服务器类型{LOBBY, ROOM, SYSTEM, LIMBO, UNKNOWN}",
})
return
}
if data.Host == "" || data.Port == 0 {
c.JSON(400, gin.H{
"code": -1,
"message": "请提供正确的服务器IP和端口",
})
return
}
if data.Group == "" {
data.Group = "default"
}
server := &Server{
Id: generateServerId(data.Type.Prefix()),
Host: data.Host,
Port: data.Port,
Motd: data.Motd,
Type: data.Type,
Group: data.Group,
Players: make([]string, 0),
LastHB: time.Now(),
}
if data.Name == "AUTO" {
server.Name = server.Id
} else {
server.Name = data.Name
}
servers.Store(server.Id, server) // 使用 Store 替代原来的 map 写入
newPkt := &ServerNewPacket{
ServerId: server.Id,
Name: server.Name,
Host: server.Host,
Port: int32(server.Port),
Group: server.Group,
Motd: server.Motd,
}
if serverProxyConn != nil {
buf, err := serverWrapPacket(ServerPacketType_NEW, newPkt)
if err != nil {
panic(err)
}
serverProxyConn.WriteChan <- ws.Binary(buf)
}
c.JSON(200, gin.H{
"code": 0,
"message": "success",
"data": server,
})
}
func ServerUnregister(c *gin.Context) {
serverId := c.GetString("serverId")
deletePkt := &ServerDeletePacket{
ServerId: serverId,
}
if serverProxyConn != nil {
buf, err := serverWrapPacket(ServerPacketType_DELETE, deletePkt)
if err != nil {
panic(err)
}
serverProxyConn.WriteChan <- ws.Binary(buf)
}
servers.Delete(serverId) // 直接删除
c.JSON(200, gin.H{
"code": 0,
"message": "success",
})
}
// ServerMessage 函数用于处理服务器消息
func ServerMessage(c *gin.Context) {
conn, err := serverUpgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": -666,
"message": "WebSocket升级失败",
})
panic(c.Error(err))
return
}
wrapped := ws.Wrap(conn)
go wrapped.ReadLoop()
go wrapped.WriteLoop()
if c.GetString("serverId") == "PROXY" {
serverProxyConn = wrapped
serverProxyLastHB = time.Now()
servers.Range(func(_, value any) bool {
server := value.(*Server)
newPkt := &ServerNewPacket{
ServerId: server.Id,
Name: server.Name,
Host: server.Host,
Port: int32(server.Port),
Group: server.Group,
Motd: server.Motd,
}
buf, err := serverWrapPacket(ServerPacketType_NEW, newPkt)
if err != nil {
panic(err)
}
wrapped.WriteChan <- ws.Binary(buf)
return true
})
go serverProxyReadLoop(wrapped)
} else {
go serverReadLoop(wrapped, c.GetString("serverId"))
}
}
func serverReadLoop(conn *ws.Conn, serverId string) {
defer func() {
fmt.Printf("%s closed\n", serverId)
}()
for {
// 使用 Load 读取,避免并发问题
serverVal, ok := servers.Load(serverId)
if !ok {
err := conn.Close()
if err != nil {
return
}
return
}
server := serverVal.(*Server)
select {
case msg := <-conn.ReadChan:
if msg == nil {
return
}
pkt := &ServerPacket{}
err := proto.Unmarshal(msg, pkt)
if err != nil {
panic(err)
}
switch pkt.GetTyp() {
case ServerPacketType_PING:
// 更新 LastHB由于 Server 本身是结构体指针,可以直接修改)
server.LastHB = time.Now()
resp := &ServerPacket{
Typ: ServerPacketType_PONG,
}
buf, err := proto.Marshal(resp)
if err != nil {
panic(err)
}
conn.WriteChan <- ws.Binary(buf)
case ServerPacketType_CONNECT:
if serverProxyConn != nil {
serverProxyConn.WriteChan <- ws.Binary(msg)
}
case ServerPacketType_JOIN:
var payload ServerPlayerPacket
err := proto.Unmarshal(pkt.GetPayload(), &payload)
if err != nil {
continue
}
server.Players = append(server.Players, payload.Player)
case ServerPacketType_LEAVE:
var payload ServerPlayerPacket
err := proto.Unmarshal(pkt.GetPayload(), &payload)
if err != nil {
continue
}
for i, player := range server.Players {
if player == payload.Player {
server.Players = append(server.Players[:i], server.Players[i+1:]...)
break
}
}
case ServerPacketType_LIST:
var payload ServerListPacket
err := proto.Unmarshal(pkt.GetPayload(), &payload)
if err != nil {
panic(err)
}
reply := &ServerListPacket{
ServerId: payload.ServerId,
}
if s, ok := servers.Load(payload.ServerId); ok {
reply.Players = s.(*Server).Players
buf, err := proto.Marshal(reply)
if err != nil {
panic(err)
}
buf, err = proto.Marshal(&ServerPacket{
Typ: ServerPacketType_LIST,
Payload: buf,
})
if err != nil {
panic(err)
}
conn.WriteChan <- ws.Binary(buf)
return
}
case ServerPacketType_STATUS:
var payload ServerStatusPacket
err := proto.Unmarshal(pkt.GetPayload(), &payload)
if err != nil {
panic(err)
}
switch payload.Status {
case ServerStatus_FREE:
server.Status = ServerStatusFull
break
case ServerStatus_WAITING:
server.Status = ServerStatusWaiting
break
case ServerStatus_STARTING:
server.Status = ServerStatusStarting
break
case ServerStatus_PLAYING:
server.Status = ServerStatusPlaying
break
case ServerStatus_ENDING:
server.Status = ServerStatusEnding
break
case ServerStatus_FULL:
server.Status = ServerStatusFull
break
}
case ServerPacketType_TOTAL:
total := 0
servers.Range(func(_, value any) bool {
total += len(value.(*Server).Players)
return true
})
reply := &ServerTotalPacket{
Total: int32(total),
}
buf, err := proto.Marshal(reply)
if err != nil {
panic(err)
}
buf, err = proto.Marshal(&ServerPacket{
Typ: ServerPacketType_TOTAL,
Payload: buf,
})
if err != nil {
panic(err)
}
conn.WriteChan <- ws.Binary(buf)
}
case <-conn.Context.Done():
return
}
}
}
func serverProxyReadLoop(conn *ws.Conn) {
defer func() {
serverProxyConn = nil
}()
for {
msg := <-conn.ReadChan
if msg == nil {
return
}
pkt := &ServerPacket{}
err := proto.Unmarshal(msg, pkt)
if err != nil {
continue
}
switch pkt.GetTyp() {
case ServerPacketType_PING:
serverProxyLastHB = time.Now()
resp := &ServerPacket{
Typ: ServerPacketType_PONG,
}
buf, err := proto.Marshal(resp)
if err != nil {
panic(err)
}
conn.WriteChan <- ws.Binary(buf)
}
}
}
var serverUpgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
func generateServerId(prefix string) string {
return prefix + util.RandStringNumber(rand.Intn(2)+1) + strings.ToUpper(util.RandStringAlphabet(rand.Intn(2)+1))
}
var serverHBTicker = time.NewTicker(time.Second * 5)
func init() {
go func() {
for range serverHBTicker.C {
// 遍历所有服务器,检查心跳
servers.Range(func(key, value interface{}) bool {
server := value.(*Server)
if time.Since(server.LastHB) > time.Second*10 {
deletePkt := &ServerDeletePacket{
ServerId: server.Id,
}
if serverProxyConn != nil {
buf, err := serverWrapPacket(ServerPacketType_DELETE, deletePkt)
if err != nil {
panic(err)
}
serverProxyConn.WriteChan <- ws.Binary(buf)
}
servers.Delete(key) // 超时则删除
}
return true // 继续遍历
})
if time.Since(serverProxyLastHB) > time.Second*10 && serverProxyConn != nil {
err := serverProxyConn.Close()
if err != nil {
continue
}
}
}
}()
}
func serverWrapPacket(typ ServerPacketType, payload proto.Message) ([]byte, error) {
pkt := &ServerPacket{
Typ: typ,
Payload: nil,
}
if payload != nil {
buf, err := proto.Marshal(payload)
if err != nil {
return nil, err
}
pkt.Payload = buf
}
buf, err := proto.Marshal(pkt)
if err != nil {
return nil, err
}
return buf, nil
}