Browse Source

添加世界管理器,添加玩家上线功能,位置信息同步功能

master
aceld 6 years ago
parent
commit
2fe5b447f3
  1. 38
      zinx_app_demo/mmo_game/api/world_chat.go
  2. 25
      zinx_app_demo/mmo_game/core/aoi.go
  3. 92
      zinx_app_demo/mmo_game/core/player.go
  4. 68
      zinx_app_demo/mmo_game/core/world_manager.go
  5. 165
      zinx_app_demo/mmo_game/pb/msg.pb.go
  6. 22
      zinx_app_demo/mmo_game/pb/msg.proto
  7. 15
      zinx_app_demo/mmo_game/server.go

38
zinx_app_demo/mmo_game/api/world_chat.go

@ -0,0 +1,38 @@
package api
import (
"fmt"
"github.com/golang/protobuf/proto"
"zinx/ziface"
"zinx/zinx_app_demo/mmo_game/core"
"zinx/zinx_app_demo/mmo_game/pb"
"zinx/znet"
)
//世界聊天 路由业务
type WorldChatApi struct {
znet.BaseRouter
}
func (*WorldChatApi) Handle(request ziface.IRequest) {
//1. 将客户端传来的proto协议解码
msg := &pb.Talk{}
err := proto.Unmarshal(request.GetData(), msg)
if err != nil {
fmt.Println("Talk Unmarshal error ", err)
return
}
//2. 得知当前的消息是从哪个玩家传递来的,从连接属性pid中获取
pid, err := request.GetConnection().GetProperty("pid")
if err != nil {
fmt.Println("GetProperty pid error", err)
request.GetConnection().Stop()
return
}
//3. 根据pid得到player对象
player := core.WorldMgrObj.GetPlayerByPid(pid.(int32))
//4. 让player对象发起聊天广播请求
player.Talk(msg.Content)
}

25
zinx_app_demo/mmo_game/core/aoi.go

@ -2,6 +2,15 @@ package core
import "fmt"
const (
AOI_MIN_X int = 85
AOI_MAX_X int = 410
AOI_CNTS_X int = 10
AOI_MIN_Y int = 75
AOI_MAX_Y int = 400
AOI_CNTS_Y int = 20
)
/*
AOI管理模块
*/
@ -38,9 +47,9 @@ func NewAOIManager(minX, maxX, cntsX, minY, maxY, cntsY int) *AOIManager {
//初始化一个格子放在AOI中的map里,key是当前格子的ID
aoiMgr.grids[gid] = NewGrid(gid,
aoiMgr.MinX+ x*aoiMgr.gridWidth(),
aoiMgr.MinX+x*aoiMgr.gridWidth(),
aoiMgr.MinX+(x+1)*aoiMgr.gridWidth(),
aoiMgr.MinY+ y*aoiMgr.gridLength(),
aoiMgr.MinY+y*aoiMgr.gridLength(),
aoiMgr.MinY+(y+1)*aoiMgr.gridLength())
}
}
@ -62,7 +71,7 @@ func (m *AOIManager) gridLength() int {
func (m *AOIManager) String() string {
s := fmt.Sprintf("AOIManagr:\nminX:%d, maxX:%d, cntsX:%d, minY:%d, maxY:%d, cntsY:%d\n Grids in AOI Manager:\n",
m.MinX, m.MaxX, m.CntsX, m.MinY, m.MaxY, m.CntsY)
for _,grid := range m.grids {
for _, grid := range m.grids {
s += fmt.Sprintln(grid)
}
@ -72,12 +81,12 @@ func (m *AOIManager) String() string {
//根据格子的gID得到当前周边的九宫格信息
func (m *AOIManager) GetSurroundGridsByGid(gID int) (grids []*Grid) {
//判断gID是否存在
if _, ok := m.grids[gID]; !ok {
if _, ok := m.grids[gID]; !ok {
return
}
//将当前gid添加到九宫格中
grids = append(grids, m.grids[gID])
grids = append(grids, m.grids[gID])
//根据gid得到当前格子所在的X轴编号
idx := gID % m.CntsX
@ -87,7 +96,7 @@ func (m *AOIManager) GetSurroundGridsByGid(gID int) (grids []*Grid) {
grids = append(grids, m.grids[gID-1])
}
//判断当前的idx右边是否还有格子
if idx < m.CntsX - 1 {
if idx < m.CntsX-1 {
grids = append(grids, m.grids[gID+1])
}
@ -109,7 +118,7 @@ func (m *AOIManager) GetSurroundGridsByGid(gID int) (grids []*Grid) {
grids = append(grids, m.grids[v-m.CntsX])
}
//判断当前的idy下边是否还有格子
if idy < m.CntsY - 1 {
if idy < m.CntsY-1 {
grids = append(grids, m.grids[v+m.CntsX])
}
}
@ -122,7 +131,7 @@ func (m *AOIManager) GetGidByPos(x, y float32) int {
gx := (int(x) - m.MinX) / m.gridWidth()
gy := (int(x) - m.MinY) / m.gridLength()
return gy * m.CntsX + gx
return gy*m.CntsX + gx
}
//通过横纵坐标得到周边九宫格内的全部PlayerIDs

92
zinx_app_demo/mmo_game/core/player.go

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/golang/protobuf/proto"
"math/rand"
"sync"
"zinx/ziface"
"zinx/zinx_app_demo/mmo_game/pb"
)
@ -18,10 +19,22 @@ type Player struct {
V float32 //旋转0-360度
}
/*
Player ID 生成器
*/
var PidGen int32 = 1 //用来生成玩家ID的计数器
var IdLock sync.Mutex //保护PidGen的互斥机制
//创建一个玩家对象
func NewPlayer(conn ziface.IConnection, pid int32) *Player {
func NewPlayer(conn ziface.IConnection) *Player {
//生成一个PID
IdLock.Lock()
id := PidGen
PidGen ++
IdLock.Unlock()
p := &Player{
Pid : pid,
Pid : id,
Conn:conn,
X:float32(160 + rand.Intn(10)),//随机在160坐标点 基于X轴偏移若干坐标
Y:0, //高度为0
@ -46,11 +59,12 @@ func (p *Player) SyncPid() {
//广播玩家自己的出生地点
func (p *Player) BroadCastStartPosition() {
//组建MsgId200 proto数据
msg := &pb.BroadCast{
Pid:p.Pid,
Tp:2,//TP2 代表广播坐标
Data: &pb.BroadCast_P{
&pb.Position{
P:&pb.Position{
X:p.X,
Y:p.Y,
Z:p.Z,
@ -59,18 +73,80 @@ func (p *Player) BroadCastStartPosition() {
},
}
//发送数据给客户端
p.SendMsg(200, msg)
}
//广播玩家的自身地理位置信息
//给当前玩家周边的(九宫格内)玩家广播自己的位置,让他们显示自己
func (p *Player) SyncSurrounding() {
//TODO 根据自己的位子,获取周围九宫格内的玩家PID
//1 根据自己的位置,获取周围九宫格内的玩家pid
pids := WorldMgrObj.AoiMgr.GetPidsByPos(p.X, p.Z)
//2 根据pid得到所有玩家对象
players := make([]*Player, 0, len(pids))
//3 给这些玩家发送MsgID:200消息,让自己出现在对方视野中
for _, pid := range pids {
players = append(players, WorldMgrObj.GetPlayerByPid(int32(pid)))
}
//3.1 组建MsgId200 proto数据
msg := &pb.BroadCast{
Pid:p.Pid,
Tp:2,//TP2 代表广播坐标
Data: &pb.BroadCast_P{
P:&pb.Position{
X:p.X,
Y:p.Y,
Z:p.Z,
V:p.V,
},
},
}
//3.2 每个玩家分别给对应的客户端发送200消息,显示人物
for _, player := range players {
player.SendMsg(200, msg)
}
//4 让周围九宫格内的玩家出现在自己的视野中
//4.1 制作Message SyncPlayers 数据
playersData := make([]*pb.Player, 0, len(players))
for _, player := range players {
p := &pb.Player{
Pid:player.Pid,
P:&pb.Position{
X:player.X,
Y:player.Y,
Z:player.Z,
V:player.V,
},
}
playersData = append(playersData, p)
}
//4.2 封装SyncPlayer protobuf数据
SyncPlayersMsg := &pb.SyncPlayers{
Ps:playersData[:],
}
//TODO 根据获取的PID集合, 获取所有的玩家信息
//4.3 给当前玩家发送需要显示周围的全部玩家数据
p.SendMsg(202, SyncPlayersMsg)
}
//TODO 构建自己的上线地点坐标
//广播玩家聊天
func (p *Player) Talk(content string) {
//1. 组建MsgId200 proto数据
msg := &pb.BroadCast{
Pid:p.Pid,
Tp:1,//TP 1 代表聊天广播
Data: &pb.BroadCast_Content{
Content: content,
},
}
//2. 得到当前世界所有的在线玩家
players := WorldMgrObj.GetAllPlayers()
//TODO
//3. 向所有的玩家发送MsgId:200消息
for _, player := range players {
player.SendMsg(200, msg)
}
}

68
zinx_app_demo/mmo_game/core/world_manager.go

@ -0,0 +1,68 @@
package core
import (
"sync"
)
/*
当前游戏世界的总管理模块
*/
type WorldManager struct {
AoiMgr *AOIManager //当前世界地图的AOI规划管理器
Players map[int32]*Player //当前在线的玩家集合
pLock sync.RWMutex //保护Players的互斥读写机制
}
//提供一个对外的世界管理模块句柄
var WorldMgrObj *WorldManager
//提供WorldManager 初始化方法
func init() {
WorldMgrObj = &WorldManager{
Players: make(map[int32]*Player),
AoiMgr: NewAOIManager(AOI_MIN_X, AOI_MAX_X, AOI_CNTS_X, AOI_MIN_Y, AOI_MAX_Y, AOI_CNTS_Y),
}
}
//提供添加一个玩家的的功能,将玩家添加进玩家信息表Players
func (wm *WorldManager) AddPlayer(player *Player) {
//将player添加到 世界管理器中
wm.pLock.Lock()
wm.Players[player.Pid] = player
wm.pLock.Unlock()
//将player 添加到AOI网络规划中
wm.AoiMgr.AddToGridByPos(int(player.Pid), player.X, player.Z)
}
//从玩家信息表中移除一个玩家
func (wm *WorldManager) RemovePlayerByPid(pid int32) {
wm.pLock.Lock()
delete(wm.Players, pid)
wm.pLock.Unlock()
}
//通过玩家ID 获取对应玩家信息
func (wm *WorldManager) GetPlayerByPid(pid int32) *Player {
wm.pLock.RLock()
defer wm.pLock.RUnlock()
return wm.Players[pid]
}
//获取所有玩家的信息
func (wm *WorldManager) GetAllPlayers() []*Player {
wm.pLock.RLock()
defer wm.pLock.RUnlock()
//创建返回的player集合切片
players := make([]*Player, 0)
//添加切片
for _, v := range wm.Players {
players = append(players, v)
}
//返回
return players
}

165
zinx_app_demo/mmo_game/pb/msg.pb.go

@ -236,28 +236,163 @@ func (*BroadCast) XXX_OneofWrappers() []interface{} {
}
}
//玩家聊天数据
type Talk struct {
Content string `protobuf:"bytes,1,opt,name=Content,proto3" json:"Content,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Talk) Reset() { *m = Talk{} }
func (m *Talk) String() string { return proto.CompactTextString(m) }
func (*Talk) ProtoMessage() {}
func (*Talk) Descriptor() ([]byte, []int) {
return fileDescriptor_c06e4cca6c2cc899, []int{3}
}
func (m *Talk) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Talk.Unmarshal(m, b)
}
func (m *Talk) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Talk.Marshal(b, m, deterministic)
}
func (m *Talk) XXX_Merge(src proto.Message) {
xxx_messageInfo_Talk.Merge(m, src)
}
func (m *Talk) XXX_Size() int {
return xxx_messageInfo_Talk.Size(m)
}
func (m *Talk) XXX_DiscardUnknown() {
xxx_messageInfo_Talk.DiscardUnknown(m)
}
var xxx_messageInfo_Talk proto.InternalMessageInfo
func (m *Talk) GetContent() string {
if m != nil {
return m.Content
}
return ""
}
//玩家信息
type Player struct {
Pid int32 `protobuf:"varint,1,opt,name=Pid,proto3" json:"Pid,omitempty"`
P *Position `protobuf:"bytes,2,opt,name=P,proto3" json:"P,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Player) Reset() { *m = Player{} }
func (m *Player) String() string { return proto.CompactTextString(m) }
func (*Player) ProtoMessage() {}
func (*Player) Descriptor() ([]byte, []int) {
return fileDescriptor_c06e4cca6c2cc899, []int{4}
}
func (m *Player) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Player.Unmarshal(m, b)
}
func (m *Player) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Player.Marshal(b, m, deterministic)
}
func (m *Player) XXX_Merge(src proto.Message) {
xxx_messageInfo_Player.Merge(m, src)
}
func (m *Player) XXX_Size() int {
return xxx_messageInfo_Player.Size(m)
}
func (m *Player) XXX_DiscardUnknown() {
xxx_messageInfo_Player.DiscardUnknown(m)
}
var xxx_messageInfo_Player proto.InternalMessageInfo
func (m *Player) GetPid() int32 {
if m != nil {
return m.Pid
}
return 0
}
func (m *Player) GetP() *Position {
if m != nil {
return m.P
}
return nil
}
//同步玩家显示数据
type SyncPlayers struct {
Ps []*Player `protobuf:"bytes,1,rep,name=ps,proto3" json:"ps,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *SyncPlayers) Reset() { *m = SyncPlayers{} }
func (m *SyncPlayers) String() string { return proto.CompactTextString(m) }
func (*SyncPlayers) ProtoMessage() {}
func (*SyncPlayers) Descriptor() ([]byte, []int) {
return fileDescriptor_c06e4cca6c2cc899, []int{5}
}
func (m *SyncPlayers) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SyncPlayers.Unmarshal(m, b)
}
func (m *SyncPlayers) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_SyncPlayers.Marshal(b, m, deterministic)
}
func (m *SyncPlayers) XXX_Merge(src proto.Message) {
xxx_messageInfo_SyncPlayers.Merge(m, src)
}
func (m *SyncPlayers) XXX_Size() int {
return xxx_messageInfo_SyncPlayers.Size(m)
}
func (m *SyncPlayers) XXX_DiscardUnknown() {
xxx_messageInfo_SyncPlayers.DiscardUnknown(m)
}
var xxx_messageInfo_SyncPlayers proto.InternalMessageInfo
func (m *SyncPlayers) GetPs() []*Player {
if m != nil {
return m.Ps
}
return nil
}
func init() {
proto.RegisterType((*SyncPid)(nil), "pb.SyncPid")
proto.RegisterType((*Position)(nil), "pb.Position")
proto.RegisterType((*BroadCast)(nil), "pb.BroadCast")
proto.RegisterType((*Talk)(nil), "pb.Talk")
proto.RegisterType((*Player)(nil), "pb.Player")
proto.RegisterType((*SyncPlayers)(nil), "pb.SyncPlayers")
}
func init() { proto.RegisterFile("msg.proto", fileDescriptor_c06e4cca6c2cc899) }
var fileDescriptor_c06e4cca6c2cc899 = []byte{
// 218 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0xb1, 0x4e, 0x86, 0x30,
0x10, 0xc7, 0x69, 0xbf, 0xaf, 0x1f, 0x72, 0x12, 0x63, 0x3a, 0x35, 0xea, 0x40, 0x98, 0x9c, 0x18,
0xf4, 0x09, 0x04, 0x07, 0xc6, 0xa6, 0x12, 0x02, 0x6c, 0x2d, 0x18, 0xd3, 0xc1, 0xb6, 0x81, 0x2e,
0x3e, 0x86, 0xaf, 0xe1, 0x53, 0x9a, 0x96, 0x90, 0x98, 0x38, 0xdd, 0xfd, 0xfe, 0x97, 0xfc, 0xee,
0x72, 0x90, 0x7d, 0x6e, 0x1f, 0x95, 0x5b, 0xad, 0xb7, 0x14, 0x3b, 0x55, 0xde, 0x43, 0xfa, 0xf6,
0x65, 0x66, 0xae, 0x17, 0x7a, 0x0b, 0x27, 0xae, 0x17, 0x86, 0x0a, 0xf4, 0x48, 0x44, 0x68, 0xcb,
0x1a, 0xae, 0xb8, 0xdd, 0xb4, 0xd7, 0xd6, 0xd0, 0x1c, 0xd0, 0x10, 0x67, 0x58, 0xa0, 0x21, 0xd0,
0xc8, 0xf0, 0x4e, 0x63, 0xa0, 0x89, 0x9d, 0x76, 0x9a, 0x02, 0xf5, 0xec, 0xbc, 0x53, 0x5f, 0x7e,
0x23, 0xc8, 0xea, 0xd5, 0xca, 0xa5, 0x91, 0x9b, 0xff, 0xbf, 0x83, 0xde, 0x00, 0xee, 0x5c, 0x54,
0x11, 0x81, 0x3b, 0x47, 0xef, 0x20, 0x6d, 0xac, 0xf1, 0xef, 0xc6, 0x47, 0x63, 0xd6, 0x26, 0xe2,
0x08, 0xe8, 0x03, 0x20, 0x1e, 0xcd, 0xd7, 0x4f, 0x79, 0xe5, 0x54, 0x75, 0x1c, 0xd7, 0x26, 0x02,
0x71, 0x5a, 0x00, 0xbc, 0xcc, 0x01, 0x5f, 0xa5, 0x97, 0x8c, 0x04, 0x63, 0x9b, 0x88, 0x3f, 0x59,
0x7d, 0x81, 0x73, 0xac, 0xe4, 0x07, 0x63, 0xae, 0xd4, 0x25, 0xbe, 0xe1, 0xf9, 0x37, 0x00, 0x00,
0xff, 0xff, 0x3b, 0x27, 0xe7, 0x05, 0x13, 0x01, 0x00, 0x00,
// 278 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0xbf, 0x4e, 0xc3, 0x30,
0x10, 0x87, 0x63, 0xe7, 0x4f, 0xc9, 0xa5, 0x42, 0xc8, 0x93, 0x15, 0x18, 0x22, 0x4f, 0x65, 0xc9,
0x50, 0x24, 0x76, 0x52, 0x86, 0x8c, 0x96, 0x89, 0xaa, 0xb6, 0x9b, 0xd3, 0x54, 0x28, 0xa2, 0xc4,
0x56, 0xec, 0xa5, 0x8f, 0xc1, 0x6b, 0xf0, 0x94, 0xc8, 0x8e, 0x0a, 0x48, 0xed, 0x64, 0x7f, 0x77,
0xf6, 0xef, 0x3e, 0xd9, 0x90, 0x7e, 0x9a, 0xf7, 0x52, 0x8f, 0xca, 0x2a, 0x82, 0x75, 0xcb, 0xee,
0x61, 0xf6, 0x76, 0x1a, 0xf6, 0xbc, 0xef, 0xc8, 0x1d, 0x84, 0xbc, 0xef, 0x28, 0x2a, 0xd0, 0x22,
0x16, 0x6e, 0xcb, 0x2a, 0xb8, 0xe1, 0xca, 0xf4, 0xb6, 0x57, 0x03, 0x99, 0x03, 0xda, 0xf8, 0x1e,
0x16, 0x68, 0xe3, 0x68, 0x4b, 0xf1, 0x44, 0x5b, 0x47, 0x3b, 0x1a, 0x4e, 0xb4, 0x73, 0xb4, 0xa6,
0xd1, 0x44, 0x6b, 0xf6, 0x85, 0x20, 0xad, 0x46, 0x25, 0xbb, 0x95, 0x34, 0xf6, 0x72, 0x06, 0xb9,
0x05, 0xdc, 0x68, 0x1f, 0x15, 0x0b, 0xdc, 0x68, 0x92, 0xc3, 0x6c, 0xa5, 0x06, 0x7b, 0x18, 0xac,
0x4f, 0x4c, 0xeb, 0x40, 0x9c, 0x0b, 0xe4, 0x01, 0x10, 0xf7, 0xc9, 0xd9, 0x72, 0x5e, 0xea, 0xb6,
0x3c, 0xcb, 0xd5, 0x81, 0x40, 0x9c, 0x14, 0x00, 0x2f, 0x7b, 0x87, 0xaf, 0xd2, 0x4a, 0x1a, 0xbb,
0xc4, 0x3a, 0x10, 0xff, 0x6a, 0x55, 0x02, 0x91, 0x5b, 0x59, 0x01, 0x51, 0x23, 0x8f, 0x1f, 0x84,
0xfe, 0xcd, 0x72, 0x46, 0xe9, 0xef, 0x24, 0xf6, 0x0c, 0x09, 0x3f, 0xca, 0xd3, 0x61, 0xbc, 0x62,
0x9c, 0x3b, 0x0b, 0x7c, 0x69, 0x21, 0x10, 0x67, 0x8f, 0x90, 0xf9, 0xe7, 0xf4, 0x77, 0x0d, 0xc9,
0x01, 0x6b, 0x43, 0x51, 0x11, 0x2e, 0xb2, 0x25, 0xf8, 0xb3, 0xbe, 0x21, 0xb0, 0x36, 0x55, 0xfc,
0x8d, 0x31, 0x6f, 0xdb, 0xc4, 0xff, 0xc5, 0xd3, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe6, 0xb4,
0xb8, 0x13, 0x98, 0x01, 0x00, 0x00,
}

22
zinx_app_demo/mmo_game/pb/msg.proto

@ -18,10 +18,26 @@ message Position{
//广
message BroadCast{
int32 Pid=1;
int32 Tp=2;
int32 Tp=2; //1- 2-
oneof Data {
string Content=3;
Position P=4;
string Content=3; //
Position P=4; //广
int32 ActionData=5;
}
}
//
message Talk{
string Content=1; //
}
//
message Player{
int32 Pid=1;
Position P=2;
}
//
message SyncPlayers{
repeated Player ps=1;
}

15
zinx_app_demo/mmo_game/server.go

@ -3,21 +3,27 @@ package main
import (
"fmt"
"zinx/ziface"
"zinx/zinx_app_demo/mmo_game/api"
"zinx/zinx_app_demo/mmo_game/core"
"zinx/znet"
)
//当客户端建立连接的时候的hook函数
func OnConnecionAdd(conn ziface.IConnection) {
temp_pid := 1
//创建一个玩家
player := core.NewPlayer(conn, int32(temp_pid))
player := core.NewPlayer(conn)
//同步当前的PlayerID给客户端, 走MsgID:1 消息
player.SyncPid()
//同步当前玩家的初始化坐标信息给客户端,走MsgID:200消息
player.BroadCastStartPosition()
//将当前新上线玩家添加到worldManager中
core.WorldMgrObj.AddPlayer(player)
//将该连接绑定属性Pid
conn.SetProperty("pid", player.Pid)
//同步周边玩家上线信息,与现实周边玩家信息
player.SyncSurrounding()
fmt.Println("=====> Player pidId = ", temp_pid, " arrived ====")
fmt.Println("=====> Player pidId = ", player.Pid, " arrived ====")
}
func main() {
@ -27,6 +33,9 @@ func main() {
//注册客户端连接建立和丢失函数
s.SetOnConnStart(OnConnecionAdd)
//注册路由
s.AddRouter(2, &api.WorldChatApi{})
//启动服务
s.Serve()
}
Loading…
Cancel
Save