Go語言併發編程——模擬遊戲服務器

寫在前面: 我是「虐貓人薛定諤i」,一個不滿足於現狀,有夢想,有追求的00後
\quad
本博客主要記錄和分享自己畢生所學的知識,歡迎關注,第一時間獲取更新。
\quad
不忘初心,方得始終。
\quad

❤❤❤❤❤❤❤❤❤❤


在這裏插入圖片描述

目錄結構

在這裏插入圖片描述

設計思路

本次我們將實現如下功能:
1、玩家登錄
2、玩家退出
3、顯示在線玩家
4、向玩家廣播信息
首先,要有一個玩家類,具體實現如下:

type Player struct {
	Name string
	Level int
	Exp int
	Room int

	mq chan *Message // 等待收取的消息
}

其次,要有客戶端和服務端(具體的實現可以參考下面的源代碼),對於消息的廣播,我們爲每一個玩家都創建一個獨立的goroutine,用來監聽消息,一旦接受到消息,就將消息輸出到控制檯上。

代碼

player.go

package cg

import "fmt"

type Player struct {
	Name string
	Level int
	Exp int
	Room int

	mq chan *Message // 等待收取的消息
}

func NewPlayer() *Player {
	m := make(chan *Message, 1024)
	player := &Player{"", 0, 0, 0, m}

	go func(p *Player) {
		for {
			msg := <- p.mq
			fmt.Println(p.Name, "receive message:", msg.Content)
		}
	}(player)
	return player
}

center.go

package cg

import (
	"encoding/json"
	"errors"
	"fmt"
	"sync"

	"example.com/m/src/ipc"
)


type Message struct {
	From string
	To string
	Content string
}

type CenterServer struct {
	servers map[string] ipc.Server
	players[] *Player
	mutex sync.RWMutex
}

func NewCenterServer() *CenterServer {
	servers := make(map[string] ipc.Server)
	players := make([]*Player, 0)
	return &CenterServer{servers: servers, players: players}
}

func (server *CenterServer)addPlayer(params string) error {
	player := NewPlayer()
	err := json.Unmarshal([]byte(params), &player)
	if err != nil {
		return err
	}
	server.mutex.Lock()
	defer server.mutex.Unlock()
	for _, item := range server.players { // 判斷是否重複登陸了
		if item.Name == player.Name {
			fmt.Println("The user already exists")
			return nil
		}
	}
	server.players = append(server.players, player)
	return nil
}

func (server *CenterServer)removePlayer(params string)  error{
	server.mutex.Lock()
	defer server.mutex.Unlock()

	for i, v := range server.players {
		if v.Name == params {
			if len(server.players) == 1 {
				server.players = make([]*Player, 0)
			} else if i == len(server.players) - 1 {
				server.players = server.players[:i - 1]
			} else if i == 0 {
				server.players = server.players[1:]
			} else {
				server.players = append(server.players[:i - 1], server.players[:i +1 ]...)
			}
			return nil
		}
	}
	return errors.New("player not found")
}

func (server *CenterServer)listPlayer()(players string, err error) {
	server.mutex.RLock()
	defer server.mutex.RUnlock()

	if len(server.players) > 0 {
		b, _ := json.Marshal(server.players)
		players = string(b)
	} else {
		err = errors.New("no player online")
	}
	return
}

func (server *CenterServer)broadcast(params string) error {
	var message Message
	err := json.Unmarshal([]byte(params), &message)
	if err != nil {
		return err
	}
	server.mutex.Lock()
	defer server.mutex.Unlock()

	if len(server.players) > 0 {
		for _, player := range server.players {
			player.mq <- &message
		}
	} else {
		err = errors.New("no player online")
	}
	return err
}

func (server *CenterServer)Handle(method, params string) *ipc.Response {
	switch method {
	case "addplayer":
		err := server.addPlayer(params)
		if err != nil {
			return &ipc.Response{Code:err.Error()}
		}
	case "removeplayer":
		err := server.removePlayer(params)
		if err != nil {
			return &ipc.Response{Code:err.Error()}
		}
	case "listplayer":
		players, err := server.listPlayer()
		if err != nil {
			return &ipc.Response{Code:err.Error()}
		}
		return &ipc.Response{Code: "200", Body: players}
	case "broadcast":
		err := server.broadcast(params)
		if err != nil {
			return &ipc.Response{Code:err.Error()}
		}
		return &ipc.Response{Code: "200"}
	default:
		return &ipc.Response{Code:"404", Body: method + ":" + params}
	}
	return &ipc.Response{Code: "200"}
}

func (server *CenterServer)Name() string {
	return "CenterServer"
}

centerclient.go

package cg

import (
	"encoding/json"
	"errors"
	"example.com/m/src/ipc"
)

type CenterClient struct {
	*ipc.IClient
}

func (client *CenterClient)AddPlayer(player *Player) error {
	b, err := json.Marshal(*player)
	if err != nil {
		return err
	}
	resp, err := client.Call("addplayer", string(b))
	if err == nil && resp.Code == "200" {
		return nil
	}
	return err
}

func (client *CenterClient)RemovePlayer(name string) error {
	ret, _ := client.Call("removeplayer", name)
	if ret.Code == "200" {
		return nil
	}
	return errors.New(ret.Code)
}

func (client *CenterClient)ListPlayer(params string)(ps []*Player, err error) {
	resp, _ := client.Call("listplayer", params)
	if resp.Code != "200" {
		err = errors.New(resp.Code)
		return
	}
	err = json.Unmarshal([]byte(resp.Body), &ps)
	return
}

func (client *CenterClient)Broadcast(message string) error {
	m := &Message{Content:message}
	b, err := json.Marshal(m)
	if err != nil {
		return err
	}
	resp, _ := client.Call("broadcast", string(b))
	if resp.Code == "200" {
		return nil
	}
	return errors.New(resp.Code)
}

client.go

package ipc

import "encoding/json"

type IClient struct {
	conn chan string
}

func NewIpcClient(server *IServer) *IClient {
	c := server.Connect()
	return &IClient{c}
}

func (client *IClient)Call(method, params string)(resp *Response, err error) {
	req := &Request{method, params}
	var b []byte
	b, err = json.Marshal(req)
	if err != nil {
		return
	}
	client.conn <- string(b)
	str := <- client.conn // 等待返回值

	var resp1 Response
	err = json.Unmarshal([]byte(str), &resp1)
	resp = &resp1
	return
}

func (client *IClient)Close() {
	client.conn <- "CLOSE"
}

server.go

package ipc

import (
	"encoding/json"
	"fmt"
)

type Request struct {
	Method string
	Params string
}

type Response struct {
	Code string
	Body string
}

type Server interface {
	Name() string
	Handle(method, params string) *Response
}

type IServer struct {
	Server
}

func NewIpcServer(server Server) *IServer {
	return &IServer{server}
}

func (server *IServer)Connect() chan string {
	session := make(chan string, 0)
	go func(c chan string) {
		for {
			request := <- c
			if request == "CLOSE" { // 關閉連接
				break
			}
			var req Request
			err := json.Unmarshal([]byte(request), &req)
			if err != nil {
				fmt.Println("Invalid request format:", request)
			}
			resp := server.Handle(req.Method, req.Params)
			b, err := json.Marshal(resp)
			c <- string(b) // 返回結果
		}
		fmt.Println("Session closed")
	}(session)
	fmt.Println("A new session has been created successfully")
	return session
}

cgss.go

package main

import (
	"bufio"
	"example.com/m/src/cg"
	"example.com/m/src/ipc"
	"fmt"
	"os"
	"strconv"
	"strings"
)

var centerClient *cg.CenterClient

func startCenterService() error {
	server := ipc.NewIpcServer(&cg.CenterServer{})
	client := ipc.NewIpcClient(server)
	centerClient = &cg.CenterClient{IClient: client}

	return nil
}

func Help(args []string) int {
	fmt.Println(`
		Commands:
			login <username><level><exp>
			logout <username>
			send <message>
			listplayer
			quit(q)
			help(h)
	`)
	return 0
}

func Quit(args []string) int {
	return 1
}

func Logout(args []string) int {
	if len(args) != 2 {
		fmt.Println("USAGE: logout <username>")
		return 0
	}
	_ = centerClient.RemovePlayer(args[1])
	return 0
}

func Login(args []string) int {
	if len(args) != 4 {
		fmt.Println("USAGE: login <username><level><exp>")
		return 0
	}
	level, err := strconv.Atoi(args[2])
	if err != nil {
		fmt.Println("invalid parameter: <level> should be an integer")
		return 0
	}

	exp, err := strconv.Atoi(args[3])
	if err != nil {
		fmt.Println("invalid parameter: <exp> should be an integer")
		return 0
	}
	player := cg.NewPlayer()
	player.Name = args[1]
	player.Level = level
	player.Exp = exp

	err = centerClient.AddPlayer(player)
	if err != nil {
		fmt.Println("failed adding player", err)
	}
	return 0
}

func ListPlayer(args []string) int {
	ps, err := centerClient.ListPlayer("")
	if err != nil {
		fmt.Println("failed", err)
	} else {
		for i, v := range ps {
			fmt.Println(i + 1, ":", v)
		}
	}
	return 0
}

func Send(args []string) int {
	message := strings.Join(args[1:], " ")
	err := centerClient.Broadcast(message)
	if err != nil {
		fmt.Println("failed", err)
	}
	return 0
}

func GetCommandHandlers() map[string]func(args []string) int {
	return map[string]func([]string) int {
		"help" : Help,
		"h" : Help,
		"quit" : Quit,
		"q" : Quit,
		"login" : Login,
		"logout" : Logout,
		"listplayer" : ListPlayer,
		"send" : Send,
	}
}

func main() {
	fmt.Println("Casual Game Server Solution")
	_ = startCenterService()
	Help(nil)
	r := bufio.NewReader(os.Stdin)
	handlers := GetCommandHandlers()
	for {
		fmt.Print("Command->")
		b, _, _ := r.ReadLine()
		line := string(b)

		tokens := strings.Split(line, " ")
		if handler, ok := handlers[tokens[0]]; ok {
			ret := handler(tokens)
			if ret != 0 {
				break
			}
		} else {
			if tokens[0] == "" {
				continue
			}
			fmt.Println("Unknown command:", tokens[0])
		}
	}
}

在這裏插入圖片描述

效果展示

在這裏插入圖片描述

總結

只是實現了消息的廣播,玩家之間的對話系統沒有實現,不能進行玩家之間的私聊

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章