168 lines
3.3 KiB
Go
168 lines
3.3 KiB
Go
|
package utils
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"context"
|
||
|
"encoding/binary"
|
||
|
"encoding/hex"
|
||
|
"fmt"
|
||
|
"net"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
type GamemodeId int
|
||
|
|
||
|
const (
|
||
|
survival GamemodeId = iota
|
||
|
sandbox
|
||
|
attack
|
||
|
pvp
|
||
|
editor
|
||
|
)
|
||
|
|
||
|
type Gamemode struct {
|
||
|
Name string `json:"name"`
|
||
|
Id GamemodeId `json:"id"`
|
||
|
}
|
||
|
|
||
|
type ServerInfo struct {
|
||
|
Host string `json:"host"`
|
||
|
Port int `json:"port"`
|
||
|
Status string `json:"status"`
|
||
|
Name string `json:"name"`
|
||
|
Maps string `json:"maps"`
|
||
|
Players int `json:"players"`
|
||
|
Version int `json:"version"`
|
||
|
Wave int `json:"wave"`
|
||
|
Vertype string `json:"vertype"`
|
||
|
Gamemode Gamemode `json:"gamemode"`
|
||
|
Description string `json:"description"`
|
||
|
Modename string `json:"modename"`
|
||
|
Limit int `json:"limit"`
|
||
|
Ping int `json:"ping"`
|
||
|
}
|
||
|
|
||
|
type InfoBuffer struct {
|
||
|
*bytes.Reader
|
||
|
}
|
||
|
|
||
|
func (g GamemodeId) Name() string {
|
||
|
switch g {
|
||
|
case survival:
|
||
|
return "生存"
|
||
|
case sandbox:
|
||
|
return "沙盒"
|
||
|
case attack:
|
||
|
return "进攻"
|
||
|
case pvp:
|
||
|
return "PVP"
|
||
|
case editor:
|
||
|
return "编辑"
|
||
|
default:
|
||
|
return "Error"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (r *InfoBuffer) New(b []byte) {
|
||
|
r.Reader = bytes.NewReader(b)
|
||
|
}
|
||
|
|
||
|
func (r *InfoBuffer) readString() string {
|
||
|
l, _ := r.ReadByte()
|
||
|
buf := make([]byte, l)
|
||
|
r.Read(buf)
|
||
|
return string(buf)
|
||
|
}
|
||
|
|
||
|
func (r *InfoBuffer) getInt() int {
|
||
|
var t int32
|
||
|
binary.Read(r, binary.BigEndian, &t)
|
||
|
return int(t)
|
||
|
}
|
||
|
|
||
|
func (r *InfoBuffer) get() byte {
|
||
|
b, _ := r.ReadByte()
|
||
|
return b
|
||
|
}
|
||
|
|
||
|
func connectServer(host string) (buf InfoBuffer, err error) {
|
||
|
socket, err := net.Dial("udp", host)
|
||
|
if err != nil {
|
||
|
return InfoBuffer{}, err
|
||
|
}
|
||
|
defer socket.Close()
|
||
|
sendData, _ := hex.DecodeString("FE01")
|
||
|
_, err = socket.Write(sendData)
|
||
|
if err != nil {
|
||
|
return InfoBuffer{}, err
|
||
|
}
|
||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
||
|
defer cancel()
|
||
|
workDone := make(chan struct{}, 1)
|
||
|
data := make([]byte, 2048)
|
||
|
go func() {
|
||
|
_, err = socket.Read(data)
|
||
|
workDone <- struct{}{}
|
||
|
}()
|
||
|
select {
|
||
|
case <-workDone:
|
||
|
buf.New(data)
|
||
|
case <-ctx.Done():
|
||
|
return InfoBuffer{}, fmt.Errorf("超时:%v", err)
|
||
|
}
|
||
|
return buf, nil
|
||
|
}
|
||
|
func ping(host string) (int64, error) {
|
||
|
startTime := time.Now()
|
||
|
conn, err := net.DialTimeout("tcp", host, time.Second*2)
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
conn.Close()
|
||
|
conn.RemoteAddr()
|
||
|
elapsedTime := time.Since(startTime).Milliseconds()
|
||
|
return elapsedTime, nil
|
||
|
}
|
||
|
func GetServerInfo(host string) (ServerInfo, error) {
|
||
|
var info ServerInfo
|
||
|
ip := strings.Split(host, ":")
|
||
|
if len(ip) == 1 {
|
||
|
info.Host = host
|
||
|
info.Port = 6567
|
||
|
} else {
|
||
|
info.Host = ip[0]
|
||
|
port, err := strconv.Atoi(ip[1])
|
||
|
if err != nil {
|
||
|
return info, err
|
||
|
}
|
||
|
info.Port = port
|
||
|
}
|
||
|
add := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
||
|
d, err := ping(add)
|
||
|
if err != nil {
|
||
|
info.Status = "Offline"
|
||
|
return info, err
|
||
|
}
|
||
|
info.Ping = int(d)
|
||
|
buf, err := connectServer(add)
|
||
|
if err != nil {
|
||
|
info.Status = "Offline"
|
||
|
return info, err
|
||
|
}
|
||
|
info.Status = "Online"
|
||
|
info.Name = buf.readString()
|
||
|
info.Maps = buf.readString()
|
||
|
info.Players = buf.getInt()
|
||
|
info.Wave = buf.getInt()
|
||
|
info.Version = buf.getInt()
|
||
|
info.Vertype = buf.readString()
|
||
|
info.Gamemode.Id = GamemodeId(buf.get())
|
||
|
info.Gamemode.Name = info.Gamemode.Id.Name()
|
||
|
info.Limit = buf.getInt()
|
||
|
info.Description = buf.readString()
|
||
|
info.Modename = buf.readString()
|
||
|
return info, nil
|
||
|
}
|